Skip to content

Commit

Permalink
Merge pull request #109 from traP-jp/update-textbox
Browse files Browse the repository at this point in the history
Textboxのデザイン更新に対応
  • Loading branch information
ZOI-dayo authored Jan 13, 2025
2 parents f7985c4 + 4283ac0 commit 0ac57d2
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 120 deletions.
52 changes: 15 additions & 37 deletions src/components/Controls/Textbox/EmailTextbox.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,28 @@
<script setup lang="ts">
import MaterialIcon from '@/components/MaterialIcon.vue'
import TextboxLabel from '@/components/Controls/Textbox/TextboxLabel.vue'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import isEmail from 'validator/lib/isEmail'
import PlainTextbox from '@/components/Controls/Textbox/PlainTextbox.vue'
defineOptions({
inheritAttrs: false
})
const { errorMessage = '', label = '' } = defineProps<{
disabled?: boolean
const props = defineProps<{
errorMessage?: string
isRequired?: boolean
label?: string
placeholder?: string
autocomplete?: string
}>()
const value = defineModel<string>()
const isEmailError = computed(() => (value.value?.length ?? 0) > 0 && !isEmail(value.value ?? ''))
const isError = computed(() => errorMessage != '' || isEmailError.value)
const shownErrorMessage = ref<string | undefined>()
const updateError = () => {
shownErrorMessage.value = isEmailError.value
? 'メールアドレスの形式が正しくありません。'
: props.errorMessage
}
</script>

<template>
<div class="flex flex-col gap-2">
<TextboxLabel v-if="label != ''" :is-required="isRequired" :label="label" />
<input
v-model.lazy="value"
v-bind="$attrs"
:autocomplete="autocomplete"
:class="[
{
'border-border-secondary outline-text-primary focus:border-text-primary': !isError
},
{ 'border-status-error outline outline-1 outline-status-error': isError },
'fontstyle-ui-body w-full rounded border bg-background-primary py-1 pl-4 text-text-primary placeholder:text-text-tertiary focus:outline focus:outline-1 disabled:bg-background-secondary'
]"
:disabled="disabled"
:placeholder="placeholder"
type="email"
/>
<div v-if="isError" class="flex items-start gap-2 pl-1 text-status-error">
<MaterialIcon icon="error" size="1.25rem" />
<span class="fontstyle-ui-control min-w-0 break-words">{{
isEmailError ? 'メールアドレスの形式が正しくありません' : errorMessage
}}</span>
</div>
</div>
<PlainTextbox
v-bind="$attrs"
v-model="value"
:error-message="shownErrorMessage"
@blur="updateError"
/>
</template>

<style scoped></style>
57 changes: 11 additions & 46 deletions src/components/Controls/Textbox/PasswordTextbox.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,20 @@
<script setup lang="ts">
import MaterialIcon from '@/components/MaterialIcon.vue'
import TextboxLabel from '@/components/Controls/Textbox/TextboxLabel.vue'
import PlainTextbox from '@/components/Controls/Textbox/PlainTextbox.vue'
import { computed, ref } from 'vue'
import type { Icon } from '@/components/MaterialIcon.vue'
defineOptions({
inheritAttrs: false
})
const { errorMessage = '', label = '' } = defineProps<{
disabled?: boolean
errorMessage?: string
isRequired?: boolean
label?: string
placeholder?: string
autocomplete?: string
}>()
const value = defineModel<string>()
const isError = computed(() => errorMessage != '')
const passwordShown = ref<boolean>(false)
const visible = ref<boolean>(false)
const rightIcon = computed<Icon>(() => (visible.value ? 'visibility_off' : 'visibility'))
const toggleVisibility = () => (visible.value = !visible.value)
</script>

<template>
<div class="flex flex-col gap-2">
<TextboxLabel v-if="label != ''" :is-required="isRequired" :label="label" />
<span class="relative">
<input
v-model="value"
v-bind="$attrs"
:class="[
{
'border-border-secondary outline-text-primary focus:border-text-primary': !isError
},
{ 'border-status-error outline outline-1 outline-status-error': isError },
'fontstyle-ui-body w-full rounded border bg-background-primary py-1 pl-4 pr-11.5 text-text-primary placeholder:text-text-tertiary focus:outline focus:outline-1 disabled:bg-background-secondary'
]"
:disabled="disabled"
:placeholder="placeholder"
:type="passwordShown ? 'text' : 'password'"
:autocomplete="autocomplete"
/>
<span
class="absolute right-4 top-1.75 select-none"
@click="() => (passwordShown = !passwordShown)"
>
<MaterialIcon :icon="passwordShown ? 'visibility_off' : 'visibility'" size="1.25rem" />
</span>
</span>
<div v-if="isError" class="flex items-start gap-2 pl-1 text-status-error">
<MaterialIcon icon="error" size="1.25rem" />
<span class="fontstyle-ui-control min-w-0 break-words">{{ errorMessage }}</span>
</div>
</div>
<PlainTextbox
displays-length
:right-icon="rightIcon"
:type="visible ? 'text' : 'password'"
@click-right="toggleVisibility"
/>
</template>

<style scoped></style>
97 changes: 76 additions & 21 deletions src/components/Controls/Textbox/PlainTextbox.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,97 @@
<script setup lang="ts">
import MaterialIcon from '@/components/MaterialIcon.vue'
import TextboxLabel from '@/components/Controls/Textbox/TextboxLabel.vue'
import { computed } from 'vue'
import MaterialIcon, { type Icon } from '@/components/MaterialIcon.vue'
import { computed, onMounted, ref, useTemplateRef } from 'vue'
defineOptions({
inheritAttrs: false
})
const {
errorMessage = '',
label = '',
isRequired
leftIcon,
rightIcon,
supportingText
} = defineProps<{
disabled?: boolean
displaysLength?: boolean
errorMessage?: string
id?: string
label?: string
isRequired?: boolean
leftIcon?: Icon
required?: boolean
rightIcon?: Icon
supportingText?: string
}>()
const emit = defineEmits(['clickRight', 'focusin', 'blur'])
const value = defineModel<string>()
const isError = computed(() => errorMessage != '')
const displaysError = computed(() => errorMessage != '')
const displaysLeftIcon = computed(() => leftIcon != null)
const displaysRightIcon = computed(() => rightIcon != null)
const displaysSupportingText = computed(() => supportingText != null)
const input = useTemplateRef('input')
const isFocused = ref<boolean>(false)
const onFocusin = (): void => {
isFocused.value = true
emit('focusin')
}
const onBlur = (): void => {
isFocused.value = false
emit('blur')
}
const onClickInnerBorder = (e: MouseEvent) => {
e.stopPropagation()
e.preventDefault()
if (!isFocused.value) input.value?.focus()
}
</script>

<template>
<div class="flex flex-col gap-2">
<TextboxLabel v-if="label != ''" :is-required="isRequired" :label="label" :for="$attrs.id" />
<input
v-model="value"
v-bind="$attrs"
<div class="flex flex-col gap-1">
<span v-if="label != ''" class="flex items-center gap-2">
<label class="fontstyle-ui-control text-text-primary" :for="id">{{ label }}</label>
<span v-if="required" class="fontstyle-ui-caption-strong text-status-error">必須</span>
</span>
<span
class="flex rounded border bg-background-primary px-2 py-1"
:class="[
{
'border-border-secondary outline-text-primary focus:border-text-primary': !isError
},
{ 'border-status-error outline outline-1 outline-status-error': isError },
'fontstyle-ui-body w-full rounded border bg-background-primary py-1 pl-4 text-text-primary placeholder:text-text-tertiary focus:outline focus:outline-1 disabled:bg-background-secondary'
{ 'outline outline-1': isFocused },
{ 'border-border-secondary outline-text-primary': !displaysError },
{ 'border-status-error outline outline-1 outline-status-error': displaysError },
{ 'border-text-primary': isFocused && !displaysError },
{ 'bg-background-secondary': disabled }
]"
/>
<div v-if="isError" class="flex items-start gap-2 pl-1 text-status-error">
<MaterialIcon icon="error" size="1.25rem" />
<span class="fontstyle-ui-control min-w-0 break-words">{{ errorMessage }}</span>
</div>
@mousedown="onClickInnerBorder"
>
<MaterialIcon v-if="displaysLeftIcon" :icon="leftIcon!" size="1.25rem" />
<input
v-bind="$attrs"
:id="id"
ref="input"
v-model="value"
:disabled="disabled"
class="fontstyle-ui-body h-5 w-full min-w-0 bg-transparent px-2 text-text-primary outline-none placeholder:text-text-tertiary"
@focusin="onFocusin"
@blur="onBlur"
/>
<span class="inline-flex items-center gap-2">
<span v-if="displaysLength" class="fontstyle-ui-caption text-text-secondary">{{
value?.length ?? 0
}}</span>
<MaterialIcon
v-if="displaysRightIcon"
:icon="rightIcon!"
size="1.25rem"
@click="emit('clickRight')"
/>
</span>
</span>
<span v-if="displaysSupportingText" class="fontstyle-ui-caption text-text-secondary">{{
supportingText
}}</span>
</div>
<div v-if="displaysError" class="mt-2 flex items-start gap-2 text-status-error">
<MaterialIcon icon="error" size="1.25rem" is-filled />
<span class="fontstyle-ui-control min-w-0 break-words">{{ errorMessage }}</span>
</div>
</template>

Expand Down
15 changes: 0 additions & 15 deletions src/components/Controls/Textbox/TextboxLabel.vue

This file was deleted.

3 changes: 2 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default {
'text-inv-primary': '#f0f0f0',
'status-error': '#e02a2a',
red: '#ff0000',
white: '#ffffff'
white: '#ffffff',
transparent: 'transparent',
},
fontFamily: {
primary: ['Open Sans', 'Noto Sans JP', 'sans-serif']
Expand Down

0 comments on commit 0ac57d2

Please sign in to comment.