@geckou/ui-vue 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +253 -0
  3. package/dist/assets/scss/functions.scss +6 -0
  4. package/dist/assets/scss/mixin.scss +33 -0
  5. package/dist/components/ArticleList/Card/Artistic.vue +310 -0
  6. package/dist/components/ArticleList/Card/Entertainment.vue +244 -0
  7. package/dist/components/ArticleList/Card/Gallery.vue +254 -0
  8. package/dist/components/ArticleList/Card/Grid.vue +247 -0
  9. package/dist/components/ArticleList/Card/News.vue +150 -0
  10. package/dist/components/ArticleList/Card/Rounded.vue +154 -0
  11. package/dist/components/ArticleList/Card/Row.vue +167 -0
  12. package/dist/components/ArticleList/Card/Simple.vue +232 -0
  13. package/dist/components/ArticleList/Card/Standard.vue +187 -0
  14. package/dist/components/ArticleList/Card/Tile.vue +171 -0
  15. package/dist/components/ArticleList/GenericArticleList.vue +143 -0
  16. package/dist/components/ArticleList/List/Artistic.vue +24 -0
  17. package/dist/components/ArticleList/List/Entertainment.vue +24 -0
  18. package/dist/components/ArticleList/List/Gallery.vue +24 -0
  19. package/dist/components/ArticleList/List/Grid.vue +216 -0
  20. package/dist/components/ArticleList/List/News.vue +24 -0
  21. package/dist/components/ArticleList/List/Rounded.vue +24 -0
  22. package/dist/components/ArticleList/List/Row.vue +24 -0
  23. package/dist/components/ArticleList/List/Simple.vue +24 -0
  24. package/dist/components/ArticleList/List/Standard.vue +24 -0
  25. package/dist/components/ArticleList/List/Tile.vue +24 -0
  26. package/dist/components/ArticleList/Parts/AuthorInfo.vue +100 -0
  27. package/dist/components/ArticleList/Parts/CardContainer.vue +23 -0
  28. package/dist/components/ArticleList/Parts/CardHeading.vue +43 -0
  29. package/dist/components/ArticleList/Parts/CategoryList.vue +41 -0
  30. package/dist/components/ArticleList/Parts/ExcerptText.vue +43 -0
  31. package/dist/components/ArticleList/Parts/MetadataList.vue +120 -0
  32. package/dist/components/ArticleList/Parts/NoImage.vue +36 -0
  33. package/dist/components/ArticleList/Parts/PostedDate.vue +41 -0
  34. package/dist/components/ArticleList/Parts/TagList.vue +41 -0
  35. package/dist/components/ArticleList/Parts/ThumbnailImage.vue +85 -0
  36. package/dist/components/BasicButton.vue +143 -0
  37. package/dist/components/CheckBox.vue +140 -0
  38. package/dist/components/CheckBoxes.vue +100 -0
  39. package/dist/components/CheckButton.vue +127 -0
  40. package/dist/components/DatePicker.vue +234 -0
  41. package/dist/components/DateRangePicker.vue +85 -0
  42. package/dist/components/DateSelector.vue +273 -0
  43. package/dist/components/DropdownUi.vue +118 -0
  44. package/dist/components/ErrorMessage.vue +61 -0
  45. package/dist/components/Icon/CalendarIcon.vue +8 -0
  46. package/dist/components/Icon/CheckIcon.vue +8 -0
  47. package/dist/components/Icon/CloseIcon.vue +8 -0
  48. package/dist/components/Icon/Folder.vue +8 -0
  49. package/dist/components/Icon/Image.vue +14 -0
  50. package/dist/components/Icon/KeyboardArrowDownIcon.vue +8 -0
  51. package/dist/components/Icon/Tag.vue +8 -0
  52. package/dist/components/InputBox.vue +134 -0
  53. package/dist/components/InputGroup.vue +41 -0
  54. package/dist/components/LabeledCheckbox.vue +82 -0
  55. package/dist/components/LabeledFieldset.vue +41 -0
  56. package/dist/components/LoadingSpinner.vue +56 -0
  57. package/dist/components/ModalBox.vue +192 -0
  58. package/dist/components/PopupBox.vue +93 -0
  59. package/dist/components/RadioButtons.vue +163 -0
  60. package/dist/components/SelectBox.vue +166 -0
  61. package/dist/components/SlideDownUi.vue +123 -0
  62. package/dist/components/TabUI.vue +123 -0
  63. package/dist/components/TextArea.vue +115 -0
  64. package/dist/components/TextBox.vue +111 -0
  65. package/dist/components/TextButton.vue +47 -0
  66. package/dist/components/ToggleButton.vue +179 -0
  67. package/dist/const/index.ts +2 -0
  68. package/dist/const/list-theme.ts +62 -0
  69. package/dist/index.ts +126 -0
  70. package/dist/scripts/form-validation-manager.ts +63 -0
  71. package/dist/scripts/utils.ts +36 -0
  72. package/dist/types/custom.d.ts +1 -0
  73. package/dist/types/index.d.ts +93 -0
  74. package/dist/types/vue-shim.d.ts +5 -0
  75. package/package.json +44 -0
@@ -0,0 +1,166 @@
1
+ <script setup lang="ts">
2
+ import type {
3
+ Option,
4
+ InputBoxStyleForEachStatus,
5
+ } from '../types'
6
+ import {
7
+ ref,
8
+ computed,
9
+ watch,
10
+ } from 'vue'
11
+ import InputBox from '../components/InputBox.vue'
12
+ import ErrorMessage from '../components/ErrorMessage.vue'
13
+ import KeyboardArrowDownIcon from '../components/Icon/KeyboardArrowDownIcon.vue'
14
+
15
+ type SelectValue = string | number
16
+
17
+ const emit = defineEmits<{ (e: 'update:modelValue', newValue: SelectValue): void }>()
18
+
19
+ const props = withDefaults(defineProps<{
20
+ options : Array<Option | Record<string, Option[]>>
21
+ name: string
22
+ modelValue?: SelectValue
23
+ cssStyle?: InputBoxStyleForEachStatus | undefined
24
+ placeholder?: string
25
+ canOmitSelect?: boolean
26
+ isDisabled? : boolean
27
+ isRequired?: boolean
28
+ }>(), {
29
+ modelValue : undefined,
30
+ cssStyle : undefined,
31
+ placeholder: '選択してください',
32
+ })
33
+
34
+ const errorMessages = ref<Array<string>>([])
35
+
36
+ const selectedValue = computed<SelectValue>({
37
+ get: () => props.modelValue ?? '',
38
+ set: (newValue: SelectValue) => emit('update:modelValue', newValue),
39
+ })
40
+
41
+ const isOption = (obj: unknown): obj is Option => {
42
+ return (
43
+ typeof obj === 'object' &&
44
+ obj !== null &&
45
+ 'label' in obj && typeof obj.label === 'string' &&
46
+ 'value' in obj
47
+ )
48
+ }
49
+
50
+ const validateValue = () => {
51
+ errorMessages.value = []
52
+ if (!selectedValue.value && props.isRequired) errorMessages.value.push('必須項目です')
53
+ }
54
+
55
+ watch(() => selectedValue.value, () => validateValue(), { immediate: !!props.modelValue, flush: 'post' })
56
+ </script>
57
+
58
+ <template>
59
+ <InputBox
60
+ :cssStyle="cssStyle"
61
+ :class="$style.select_box"
62
+ :isDisabled="isDisabled"
63
+ >
64
+ <select
65
+ v-model="selectedValue"
66
+ :disabled="isDisabled"
67
+ :required="isRequired"
68
+ :class="$style.select"
69
+ @blur="validateValue()"
70
+ >
71
+ <option
72
+ v-if="canOmitSelect"
73
+ value=""
74
+ >
75
+ {{ placeholder || '選択しない' }}
76
+ </option>
77
+ <option
78
+ v-else
79
+ disabled
80
+ selected
81
+ value=""
82
+ >
83
+ {{ placeholder || '選択してください' }}
84
+ </option>
85
+ <option
86
+ v-if="['0', 'NA'].includes(selectedValue.toString()) && isDisabled"
87
+ disabled
88
+ selected
89
+ :value="selectedValue"
90
+ >
91
+ {{ placeholder || '選択してください' }}
92
+ </option>
93
+ <template
94
+ v-for="option in options"
95
+ :key="isOption(option) ? option.value : Object.keys(option)[0]"
96
+ >
97
+ <option
98
+ v-if="isOption(option)"
99
+ :value="option.value"
100
+ :disabled="option.isDisabled"
101
+ >
102
+ {{ option.label }}
103
+ </option>
104
+ <template v-else>
105
+ <optgroup
106
+ v-for="(opt, key) in option"
107
+ :key="key"
108
+ :label="key"
109
+ >
110
+ <option
111
+ v-for="o in opt"
112
+ :key="o.value"
113
+ :value="o.value"
114
+ :disabled="o.isDisabled"
115
+ >
116
+ {{ o.label }}
117
+ </option>
118
+ </optgroup>
119
+ <hr>
120
+ </template>
121
+ </template>
122
+ </select>
123
+ <div :class="$style.arrow_container">
124
+ <slot name="arrow" />
125
+ <KeyboardArrowDownIcon
126
+ v-if="!$slots.arrow"
127
+ :class="$style.arrow"
128
+ />
129
+ </div>
130
+ <ErrorMessage
131
+ :errorMessages="errorMessages"
132
+ :cssStyle="{
133
+ textColor: cssStyle?.error?.backgroundColor,
134
+ backgroundColor: cssStyle?.error?.textColor,
135
+ }"
136
+ />
137
+ </InputBox>
138
+ </template>
139
+
140
+ <style lang="scss" module>
141
+ :is(.select_box) {
142
+ display: inline-flex;
143
+
144
+ > select {
145
+ flex : 1 1 auto;
146
+ padding-inline-end: 2rem;
147
+ cursor : pointer;
148
+ }
149
+ }
150
+
151
+ :is(.arrow_container) {
152
+ display : flex;
153
+ align-items : center;
154
+ position : absolute;
155
+ margin : auto;
156
+ inset-inline-end: .5rem;
157
+ inset-block : 0;
158
+ pointer-events : none;
159
+
160
+ > * {
161
+ color : currentColor;
162
+ fill : currentColor;
163
+ block-size: 1rem;
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import type { Ref } from 'vue'
3
+ import { ref, onMounted, onUpdated, watch } from 'vue'
4
+ import IconChevronDown from '../components/Icon/KeyboardArrowDownIcon.vue'
5
+ const props = withDefaults(defineProps<{
6
+ isOpened?: boolean | null
7
+ isHiddenArrow?: boolean
8
+ isDisabled?: boolean
9
+ isDisableClickOutside?: boolean
10
+ duration?: number
11
+ }>(), {
12
+ isOpened: null,
13
+ duration: .3,
14
+ })
15
+
16
+ const isOpenedContents = ref(props.isOpened || false)
17
+ const contents: Ref<HTMLElement | null> = ref(null)
18
+ const contentsHeight = ref(0)
19
+ const toggleBox = () => isOpenedContents.value = !isOpenedContents.value
20
+
21
+ const closeDropDown = () => {
22
+ if (!props.isDisableClickOutside) isOpenedContents.value = false
23
+ }
24
+
25
+ const updateContentsHeight = () => {
26
+ const contentsValue = contents.value
27
+ if (contentsValue) {
28
+ contentsHeight.value = contentsValue.clientHeight
29
+ }
30
+ }
31
+
32
+ onMounted(()=> updateContentsHeight())
33
+ onUpdated(() => updateContentsHeight())
34
+ watch(() => props.isOpened, newValue => {
35
+ if (typeof newValue === 'boolean') isOpenedContents.value = newValue
36
+ })
37
+ defineExpose({ isOpenedContents })
38
+ </script>
39
+
40
+ <template>
41
+ <div
42
+ v-click-outside="closeDropDown"
43
+ :class="{ [$style.opened]: isOpenedContents }"
44
+ :style="{ '--accordion-toggle-duration': `${props.duration}s` }"
45
+ >
46
+ <button
47
+ :class="$style.trigger"
48
+ :disabled="isDisabled"
49
+ type="button"
50
+ @click.prevent="toggleBox"
51
+ >
52
+ <div class="width:100% text-align:left">
53
+ <slot name="trigger" />
54
+ </div>
55
+ <IconChevronDown
56
+ v-if="!isHiddenArrow"
57
+ :class="$style.icon"
58
+ />
59
+ </button>
60
+ <div
61
+ :style="{ height: isOpenedContents ? `${contentsHeight}px` : 0 }"
62
+ :class="$style.contents"
63
+ >
64
+ <div
65
+ ref="contents"
66
+ :class="$style.container"
67
+ >
68
+ <slot />
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </template>
73
+
74
+ <style lang="scss" module>
75
+ @use '../assets/scss/mixin' as *;
76
+
77
+ @keyframes overflow {
78
+ 0% {
79
+ overflow: hidden;
80
+ }
81
+
82
+ 99% {
83
+ overflow: hidden;
84
+ }
85
+
86
+ 100% {
87
+ overflow: visible;
88
+ }
89
+ }
90
+
91
+ .trigger {
92
+ display : grid;
93
+ grid-template-columns: 1fr auto;
94
+ align-items : center;
95
+ inline-size : 100%;
96
+ justify-items : start;
97
+ cursor : pointer;
98
+ position : relative;
99
+ color : var(--link-color);
100
+ }
101
+
102
+ .icon {
103
+ @include icon($color: var(--link-color));
104
+ flex : 0 0 auto;
105
+ transition: all .1s;
106
+ }
107
+
108
+ .contents {
109
+ transition: height var(--accordion-toggle-duration);
110
+ overflow : hidden;
111
+ }
112
+
113
+ .opened {
114
+ .icon {
115
+ transform: rotate(180deg);
116
+ }
117
+
118
+ .contents {
119
+ animation : overflow var(--accordion-toggle-duration);
120
+ animation-fill-mode: forwards;
121
+ }
122
+ }
123
+ </style>
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { COLOR } from '../const'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ tabs: {
7
+ key: string
8
+ label: string
9
+ }[]
10
+ color?: {
11
+ active: string
12
+ background: string
13
+ text: string
14
+ }
15
+ cssStyle?: {
16
+ textColor: string
17
+ backgroundColor: string
18
+ border: {
19
+ color: string
20
+ size: string
21
+ radius: string
22
+ }
23
+ }
24
+ type?: 'tab' | 'button' | 'border'
25
+ initialIndex?: number
26
+ }>(), {
27
+ color : undefined,
28
+ cssStyle : undefined,
29
+ type : 'tab',
30
+ initialIndex: 0,
31
+ })
32
+
33
+ const activeTab = ref(props.tabs[props.initialIndex].key)
34
+ const tabRefs = ref<HTMLButtonElement[]>([])
35
+ const changeTabs = (key: string) => activeTab.value = key
36
+
37
+ const activateTab = (index: number) => {
38
+ activeTab.value = props.tabs[index].key
39
+ tabRefs.value[index]?.focus()
40
+ }
41
+
42
+ // 以前は window 全体に keydown を張っていたため、フォーカス位置と無関係にタブが
43
+ // 切り替わり、1 画面に複数設置すると互いに競合した。
44
+ // タブリストにフォーカスがあるときだけ矢印キーで移動する(WAI-ARIA Tabs パターン)
45
+ const handleKeydown = (event: KeyboardEvent) => {
46
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
47
+
48
+ const currentIndex = props.tabs.findIndex(tab => tab.key === activeTab.value)
49
+ if (currentIndex === -1) return
50
+
51
+ event.preventDefault()
52
+ const lastIndex = props.tabs.length - 1
53
+
54
+ if (event.key === 'ArrowLeft') {
55
+ activateTab(currentIndex > 0 ? currentIndex - 1 : lastIndex)
56
+ } else {
57
+ activateTab(currentIndex < lastIndex ? currentIndex + 1 : 0)
58
+ }
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <div>
64
+ <div
65
+ :class="$style.tabs"
66
+ :style="{
67
+ '--active-color': color?.active || COLOR.blue,
68
+ '--background-color': color?.background || 'transparent',
69
+ }"
70
+ role="tablist"
71
+ @keydown="handleKeydown"
72
+ >
73
+ <button
74
+ v-for="(tab, index) in tabs"
75
+ :id="tab.key"
76
+ :key="tab.key"
77
+ :ref="el => { if (el) tabRefs[index] = el as HTMLButtonElement }"
78
+ role="tab"
79
+ :aria-controls="`${tab.key}_${index}`"
80
+ :aria-selected="activeTab === tab.key"
81
+ :tabindex="activeTab === tab.key ? 0 : -1"
82
+ :class="{ [$style.active]: activeTab === tab.key }"
83
+ @click="changeTabs(tab.key)"
84
+ >
85
+ <slot :name="tab.key" />
86
+ <template v-if="!$slots[tab.key]">
87
+ {{ tab.label }}
88
+ </template>
89
+ </button>
90
+ </div>
91
+ <div
92
+ v-for="(tab, index) in tabs"
93
+ v-show="activeTab === tab.key"
94
+ :id="`${tab.key}_${index}`"
95
+ :key="`${tab.key}_panel`"
96
+ role="tabpanel"
97
+ :aria-labelledby="tab.key"
98
+ >
99
+ <slot :name="`${tab.key}Contents`" />
100
+ </div>
101
+ </div>
102
+ </template>
103
+
104
+ <style lang="scss" module>
105
+ .tabs {
106
+ display : flex;
107
+ background-color: var(--background-color);
108
+
109
+ > button {
110
+ &[role="tab"] {
111
+ border : none;
112
+ background-color: transparent;
113
+ font-size : 1rem;
114
+ padding : .5rem 1rem;
115
+ cursor : pointer;
116
+
117
+ &.active {
118
+ cursor: auto;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ </style>
@@ -0,0 +1,115 @@
1
+ <script lang="ts" setup>
2
+ import type {
3
+ Validates,
4
+ InputBoxStyleForEachStatus,
5
+ } from '../types'
6
+ import {
7
+ ref,
8
+ computed,
9
+ watch,
10
+ nextTick,
11
+ onMounted,
12
+ } from 'vue'
13
+ import { isEmptyValue, validateInputValue } from '@geckou/ui-core'
14
+ import InputBox from '../components/InputBox.vue'
15
+ import ErrorMessage from '../components/ErrorMessage.vue'
16
+
17
+ type InputValue = string | null
18
+
19
+ const emit = defineEmits<{ (e: 'update:modelValue', newValue: InputValue): void }>()
20
+
21
+ const props = withDefaults(defineProps<{
22
+ name: string
23
+ modelValue?: InputValue
24
+ cssStyle?: InputBoxStyleForEachStatus | undefined
25
+ placeholder?: string
26
+ isDisabled? : boolean
27
+ isRequired?: boolean
28
+ rows?: number
29
+ maxLength?: number
30
+ autocomplete?: string
31
+ validates?: Validates
32
+ autoAdjustHeight?: boolean
33
+ }>(), {
34
+ modelValue : undefined,
35
+ cssStyle : undefined,
36
+ isRequired : false,
37
+ placeholder : '入力してください',
38
+ rows : undefined,
39
+ maxLength : 100,
40
+ isDisabled : false,
41
+ autocomplete: 'off',
42
+ validates : () => [],
43
+ })
44
+
45
+ const inputValue = computed<InputValue>({
46
+ get: () => props.modelValue ?? '',
47
+ set: (newValue: InputValue) => emit('update:modelValue', newValue),
48
+ })
49
+
50
+ const errorMessages = ref<string[]>([])
51
+ const textareaRef = ref<HTMLTextAreaElement | null>(null)
52
+
53
+ const validateValue = () => {
54
+ errorMessages.value = validateInputValue(inputValue.value, {
55
+ isRequired: props.isRequired,
56
+ validates : props.validates,
57
+ })
58
+ }
59
+
60
+ const adjustTextareaHeight = () => {
61
+ if (!props.autoAdjustHeight) return
62
+
63
+ nextTick(() => {
64
+ if (textareaRef.value) {
65
+ textareaRef.value.style.height = 'auto'
66
+ textareaRef.value.style.height = `calc(${textareaRef.value.scrollHeight}px - 2rem)`
67
+ }
68
+ })
69
+ }
70
+
71
+ onMounted(adjustTextareaHeight)
72
+
73
+ watch(inputValue, () => {
74
+ validateValue()
75
+ adjustTextareaHeight()
76
+ },
77
+ // 数値 0 も初期値として扱うため truthy 判定は使わない
78
+ { immediate: !isEmptyValue(props.modelValue), flush: 'post' })
79
+ </script>
80
+
81
+ <template>
82
+ <InputBox
83
+ :cssStyle="cssStyle"
84
+ :class="$style.text_area"
85
+ :isErrored="!!errorMessages.length"
86
+ :isDisabled="isDisabled"
87
+ >
88
+ <textarea
89
+ ref="textareaRef"
90
+ v-model="inputValue"
91
+ :name="name"
92
+ :required="isRequired"
93
+ :placeholder="placeholder"
94
+ :disabled="isDisabled"
95
+ :autocomplete="autocomplete"
96
+ :rows="rows"
97
+ :maxlength="maxLength"
98
+ :aria-invalid="!!errorMessages.length ? 'true' : undefined"
99
+ @blur="validateValue()"
100
+ />
101
+ <ErrorMessage
102
+ :errorMessages="errorMessages"
103
+ :cssStyle="{
104
+ textColor: cssStyle?.error?.backgroundColor,
105
+ backgroundColor: cssStyle?.error?.textColor,
106
+ }"
107
+ />
108
+ </InputBox>
109
+ </template>
110
+
111
+ <style module>
112
+ :is(.text_area) {
113
+ min-block-size: 4em;
114
+ }
115
+ </style>
@@ -0,0 +1,111 @@
1
+ <script lang="ts" setup>
2
+ import type {
3
+ Validates,
4
+ InputBoxStyleForEachStatus,
5
+ } from '../types'
6
+ import {
7
+ ref,
8
+ computed,
9
+ watch,
10
+ } from 'vue'
11
+ import {
12
+ convertFullWidthToHalfWidth,
13
+ isEmptyValue,
14
+ validateInputValue,
15
+ } from '@geckou/ui-core'
16
+ import InputBox from '../components/InputBox.vue'
17
+ import ErrorMessage from '../components/ErrorMessage.vue'
18
+
19
+ type InputValue = string | number
20
+
21
+ const emit = defineEmits<{ (e: 'update:modelValue', newValue: InputValue): void }>()
22
+
23
+ const props = withDefaults(defineProps<{
24
+ name: string
25
+ modelValue?: InputValue
26
+ cssStyle?: InputBoxStyleForEachStatus | undefined
27
+ inputType? : string
28
+ placeholder?: string
29
+ isDisabled? : boolean
30
+ isRequired?: boolean
31
+ maxLength?: number
32
+ autocomplete?: string
33
+ validates?: Validates
34
+ }>(), {
35
+ modelValue : undefined,
36
+ cssStyle : undefined,
37
+ inputType : 'text',
38
+ placeholder : '入力してください',
39
+ maxLength : 30,
40
+ autocomplete: 'off',
41
+ validates : () => [],
42
+ })
43
+
44
+ const errorMessages = ref<string[]>([])
45
+
46
+ const inputValue = computed<InputValue>({
47
+ get: () => props.modelValue ?? '',
48
+ set: (newValue: InputValue) => {
49
+ const convertedValue = typeof newValue === 'number'
50
+ ? newValue
51
+ : convertFullWidthToHalfWidth(String(newValue))
52
+
53
+ emit('update:modelValue', convertedValue)
54
+ },
55
+ })
56
+
57
+ const validateValue = () => {
58
+ errorMessages.value = validateInputValue(inputValue.value, {
59
+ isRequired: props.isRequired,
60
+ validates : props.validates,
61
+ })
62
+ }
63
+
64
+ // 初期値があるときはマウント時にも検証する。
65
+ // 数値 0 も初期値として扱うため truthy 判定は使わない
66
+ watch(inputValue, () => validateValue(), {
67
+ immediate: !isEmptyValue(props.modelValue),
68
+ flush : 'post',
69
+ })
70
+ </script>
71
+
72
+ <template>
73
+ <InputBox
74
+ :cssStyle="cssStyle"
75
+ :class="$style.text_box"
76
+ :isErrored="!!errorMessages.length"
77
+ :isDisabled="isDisabled"
78
+ >
79
+ <slot name="before" />
80
+ <input
81
+ v-model="inputValue"
82
+ :type="inputType"
83
+ :name="name"
84
+ :required="isRequired"
85
+ :placeholder="placeholder"
86
+ :disabled="isDisabled"
87
+ :autocomplete="autocomplete"
88
+ :maxlength="maxLength"
89
+ :aria-invalid="!!errorMessages.length ? 'true' : undefined"
90
+ @blur="validateValue()"
91
+ >
92
+ <slot name="after" />
93
+ <ErrorMessage
94
+ :errorMessages="errorMessages"
95
+ :cssStyle="{
96
+ textColor: cssStyle?.error?.backgroundColor,
97
+ backgroundColor: cssStyle?.error?.textColor,
98
+ }"
99
+ />
100
+ </InputBox>
101
+ </template>
102
+
103
+ <style lang="scss" module>
104
+ :is(.text_box) {
105
+ display: inline-flex;
106
+
107
+ > input {
108
+ flex: 1 1 auto;
109
+ }
110
+ }
111
+ </style>
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { COLOR } from '../const'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ text: string
7
+ variant?: 'default' | 'caution'
8
+ isDisabled?: boolean
9
+ }>(), {
10
+ variant : 'default',
11
+ isDisabled: false,
12
+ })
13
+
14
+ const textColor = computed(() => props.variant === 'caution' ? COLOR.red : 'var(--link-color)')
15
+ </script>
16
+
17
+ <template>
18
+ <button
19
+ type="button"
20
+ :class="$style.text_button"
21
+ :disabled="isDisabled"
22
+ :style="{ '--text-button-color': textColor }"
23
+ >
24
+ {{ text }}
25
+ </button>
26
+ </template>
27
+
28
+ <style lang="scss" module>
29
+ .text_button {
30
+ padding : 0;
31
+ border : none;
32
+ background-color: transparent;
33
+ color : var(--text-button-color);
34
+ font-size : inherit;
35
+ line-height : 1;
36
+ cursor : pointer;
37
+
38
+ &:hover:not(:disabled) {
39
+ text-decoration: underline;
40
+ }
41
+
42
+ &:disabled {
43
+ color : var(--disable-text-color);
44
+ cursor: not-allowed;
45
+ }
46
+ }
47
+ </style>