@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,82 @@
1
+ <script setup lang="ts">
2
+ import type { CheckBoxStyleForEachStatus } from '../types'
3
+ import { computed } from 'vue'
4
+ import CheckBox from '../components/CheckBox.vue'
5
+ import { COLOR } from '../const'
6
+
7
+ const emit = defineEmits<{ (e: 'update:modelValue', newValue: boolean): void }>()
8
+
9
+ const props = withDefaults(defineProps<{
10
+ name: string
11
+ label: string
12
+ modelValue?: boolean
13
+ isDisabled?: boolean
14
+ cssStyle?: CheckBoxStyleForEachStatus
15
+ isDisableAnimation?: boolean
16
+ }>(), {
17
+ cssStyle: undefined,
18
+ })
19
+
20
+ const isChecked = computed<boolean>({
21
+ get: () => props.modelValue ?? false,
22
+ set: (newValue: boolean) => emit('update:modelValue', newValue),
23
+ })
24
+
25
+ const currentCssStyle = computed(() => {
26
+ const cssStyle = props.isDisabled ? props.cssStyle?.disabled : props.cssStyle?.default
27
+
28
+ return {
29
+ ...{
30
+ textColor : props.isDisabled ? COLOR.darkGray : COLOR.black,
31
+ backgroundColor: props.isDisabled ? COLOR.darkGray : COLOR.blue,
32
+ },
33
+ ...(cssStyle ?? {}),
34
+ }
35
+ })
36
+ </script>
37
+
38
+ <template>
39
+ <label :class="$style.labeled_check_box">
40
+ <CheckBox
41
+ v-model="isChecked"
42
+ :name="name"
43
+ :isDisabled="isDisabled"
44
+ :cssStyle="cssStyle"
45
+ :isDisableAnimation="isDisableAnimation"
46
+ style="pointer-events: none;"
47
+ />
48
+ <span
49
+ :class="$style.label"
50
+ :style="{
51
+ '--text-color': currentCssStyle?.textColor,
52
+ '--checked-color': currentCssStyle?.backgroundColor,
53
+ }"
54
+ >
55
+ {{ label }}
56
+ </span>
57
+ </label>
58
+ </template>
59
+
60
+ <style lang="scss" module>
61
+ :is(.labeled_check_box) {
62
+ display : inline-flex;
63
+ align-items: center;
64
+ gap : .5em;
65
+ cursor : pointer;
66
+
67
+ &:has(input:disabled) {
68
+ cursor: auto;
69
+ }
70
+
71
+ .label {
72
+ color: var(--checked-color);
73
+ }
74
+
75
+ &:has(input:checked) {
76
+ .label {
77
+ color: var(--text-color);
78
+ }
79
+ }
80
+ }
81
+
82
+ </style>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ isDisabled?: boolean }>()
3
+ </script>
4
+
5
+ <template>
6
+ <div :class="$style.fieldset">
7
+ <fieldset :disabled="isDisabled">
8
+ <legend
9
+ v-if="$slots.label"
10
+ :class="$style.legend"
11
+ >
12
+ <slot name="label" />
13
+ </legend>
14
+ <div :class="$style.input">
15
+ <slot />
16
+ </div>
17
+ </fieldset>
18
+ </div>
19
+ </template>
20
+
21
+ <style lang="scss" module>
22
+ :is(.fieldset) {
23
+ display: grid;
24
+ gap: .5rem;
25
+
26
+ > fieldset {
27
+ display: contents;
28
+ }
29
+ }
30
+
31
+ :is(.legend) {
32
+ display: flex;
33
+ align-items: center;
34
+ }
35
+
36
+ :is(.input) {
37
+ > * {
38
+ inline-size: 100%;
39
+ }
40
+ }
41
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <svg
3
+ viewBox="0 0 24 24"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ >
6
+ <g>
7
+ <circle
8
+ cx="12"
9
+ cy="2.5"
10
+ r="1.5"
11
+ opacity=".14"
12
+ /><circle
13
+ cx="16.75"
14
+ cy="3.77"
15
+ r="1.5"
16
+ opacity=".29"
17
+ /><circle
18
+ cx="20.23"
19
+ cy="7.25"
20
+ r="1.5"
21
+ opacity=".43"
22
+ />
23
+ <circle
24
+ cx="21.50"
25
+ cy="12.00"
26
+ r="1.5"
27
+ opacity=".57"
28
+ />
29
+ <circle
30
+ cx="20.23"
31
+ cy="16.75"
32
+ r="1.5"
33
+ opacity=".71"
34
+ />
35
+ <circle
36
+ cx="16.75"
37
+ cy="20.23"
38
+ r="1.5"
39
+ opacity=".86"
40
+ />
41
+ <circle
42
+ cx="12"
43
+ cy="21.5"
44
+ r="1.5"
45
+ />
46
+ <animateTransform
47
+ attributeName="transform"
48
+ type="rotate"
49
+ calcMode="discrete"
50
+ dur="0.75s"
51
+ values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12;360 12 12"
52
+ repeatCount="indefinite"
53
+ />
54
+ </g>
55
+ </svg>
56
+ </template>
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ // スクロールロックはページ全体で 1 つの状態として扱う
3
+ let lockCount = 0
4
+ let previousOverflow = ''
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import { onMounted, onBeforeUnmount, watch } from 'vue'
9
+ import IconClose from '../components/Icon/CloseIcon.vue'
10
+
11
+ const props = defineProps<{
12
+ isShown: boolean,
13
+ size?: 'small' | 'medium' | 'large',
14
+ }>()
15
+
16
+ const emit = defineEmits<{ (e: 'closeModal', state: boolean): void }>()
17
+ const bodyElement = document.querySelector('body') as HTMLElement
18
+ // 複数のモーダルが重なっても解除順で壊れないよう、ロック数をカウントする
19
+ let isLocked = false
20
+
21
+ const toggleScrollLock = (shouldLock: boolean) => {
22
+ if (!bodyElement || isLocked === shouldLock) return
23
+
24
+ isLocked = shouldLock
25
+ lockCount += shouldLock ? 1 : -1
26
+
27
+ if (lockCount === 1 && shouldLock) {
28
+ previousOverflow = bodyElement.style.overflow
29
+ bodyElement.style.overflow = 'hidden'
30
+ } else if (lockCount <= 0) {
31
+ lockCount = 0
32
+ bodyElement.style.overflow = previousOverflow
33
+ }
34
+ }
35
+
36
+ const closeModal = () => {
37
+ toggleScrollLock(false)
38
+ emit('closeModal', false)
39
+ }
40
+
41
+ watch(() => props.isShown, newVal => {
42
+ if (newVal) toggleScrollLock(true)
43
+ else closeModal()
44
+ })
45
+
46
+ onMounted(() => toggleScrollLock(props.isShown))
47
+ onBeforeUnmount(() => closeModal())
48
+ </script>
49
+
50
+ <template>
51
+ <div
52
+ :class="[$style.overlay, {[$style.display]: isShown}]"
53
+ @click.self="closeModal"
54
+ >
55
+ <div :class="[$style.container, $style[size ?? 'medium'],]">
56
+ <header
57
+ v-if="$slots.header"
58
+ :class="$style.header"
59
+ >
60
+ <slot name="header" />
61
+ </header>
62
+ <div :class="$style.contents">
63
+ <slot />
64
+ </div>
65
+ <footer
66
+ v-if="$slots.footer"
67
+ :class="$style.footer"
68
+ >
69
+ <slot name="footer" />
70
+ </footer>
71
+ <button
72
+ type="button"
73
+ :class="$style.close_button"
74
+ @click="closeModal"
75
+ >
76
+ <IconClose />
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </template>
81
+
82
+ <style lang="scss" module>
83
+ @use '../assets/scss/mixin' as *;
84
+
85
+ .container {
86
+ display : flex;
87
+ flex-direction : column;
88
+ inline-size : 100%;
89
+ max-inline-size : var(--desktop-lower-width);
90
+ max-block-size : 100%;
91
+ background-color: var(--white);
92
+ border-radius : var(--radius-small);
93
+ cursor : auto;
94
+ filter : drop-shadow(0 0 6px var(--shadow-color));
95
+ position : relative;
96
+
97
+ &.small {
98
+ max-inline-size: var(--mobile-lower-width);
99
+ }
100
+
101
+ &.large {
102
+ max-inline-size: var(--contents-max-width);
103
+ }
104
+ }
105
+
106
+ .header {
107
+ padding: var(--sp-medium) var(--sp-large);
108
+ border-block-end: 1px solid var(--light-border-color);
109
+
110
+ > h2 {
111
+ font-size : var(--fs-large);
112
+ font-weight: bold;
113
+ }
114
+
115
+ @include media('mobile') {
116
+ padding: var(--sp-medium);
117
+ }
118
+ }
119
+
120
+ .contents {
121
+ flex : 1 1 auto;
122
+ overflow: auto;
123
+ padding : var(--sp-large);
124
+
125
+ @include media('mobile') {
126
+ padding: var(--sp-medium);
127
+ }
128
+ }
129
+
130
+ .footer {
131
+ display : flex;
132
+ justify-content : center;
133
+ gap : var(--sp-large);
134
+ padding : var(--sp-medium) var(--sp-large);
135
+ border-block-start: 1px solid var(--light-border-color);
136
+
137
+ @include media('mobile') {
138
+ gap : var(--sp-medium);
139
+ padding: var(--sp-medium);
140
+
141
+ > * {
142
+ flex: 1 1 auto;
143
+ }
144
+ }
145
+ }
146
+
147
+ .close_button {
148
+ inline-size: var(--icon-medium);
149
+ block-size : var(--icon-medium);
150
+ color : var(--white);
151
+ line-height: 1;
152
+ cursor : pointer;
153
+ position : absolute;
154
+ bottom : 100%;
155
+ left : 100%;
156
+
157
+ > * {
158
+ @include icon();
159
+ }
160
+ }
161
+
162
+ .overlay {
163
+ display : flex;
164
+ justify-content : center;
165
+ align-items : center;
166
+ inline-size : 100dvw;
167
+ block-size : 100dvh;
168
+ padding : var(--sp-larger);
169
+ background : var(--overlay-color);
170
+ background-blend-mode: multiply;
171
+ backdrop-filter : blur(4px);
172
+ opacity : 0;
173
+ transition : opacity .1s linear;
174
+ pointer-events : none;
175
+ position : fixed;
176
+ z-index : var(--z-index-overlay);
177
+ top : 0;
178
+ left : 0;
179
+ cursor : pointer;
180
+ overflow : hidden;
181
+
182
+ @include media('mobile') {
183
+ padding-inline: var(--sp-large);
184
+ }
185
+
186
+ &.display {
187
+ opacity : 1;
188
+ pointer-events: auto;
189
+ z-index : calc(var(--z-index-nav) + 1);
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ import type { Ref } from 'vue'
3
+ import { ref } from 'vue'
4
+ withDefaults(defineProps<{
5
+ position?: {
6
+ x: 'left' | 'right' | 'center'
7
+ y: 'top' | 'bottom' | 'center'
8
+ },
9
+ }>(), {
10
+ position: () => ({ x: 'right', y: 'top' }),
11
+ })
12
+
13
+ const isShown: Ref<boolean> = ref(false)
14
+
15
+ const showPopup = () => {
16
+ isShown.value = true
17
+
18
+ setTimeout(() => {
19
+ isShown.value = false
20
+ }, 3000)
21
+ }
22
+
23
+ defineExpose({
24
+ showPopup,
25
+ })
26
+ </script>
27
+
28
+ <template>
29
+ <teleport to="body">
30
+ <div
31
+ :class="[
32
+ $style.popup,
33
+ $style[`x-${props.position.x}`],
34
+ $style[`y-${props.position.y}`],
35
+ { [$style.show]: isShown },
36
+ ]"
37
+ >
38
+ <slot />
39
+ </div>
40
+ </teleport>
41
+ </template>
42
+
43
+ <style lang="scss" module>
44
+ .x {
45
+ &-left {
46
+ left: max(calc((100vw - var(--contents-max-width)) / 2), var(--sp-medium));
47
+ }
48
+
49
+ &-right {
50
+ right: max(calc((100vw - var(--contents-max-width)) / 2), var(--sp-medium));
51
+ }
52
+
53
+ &-center {
54
+ inset: 0;
55
+ margin: auto;
56
+ }
57
+ }
58
+
59
+ .y {
60
+ &-top {
61
+ top: calc(var(--global-header-height) - var(--sp-small));
62
+ }
63
+
64
+ &-bottom {
65
+ bottom: var(--sp-medium);
66
+ }
67
+
68
+ &-center {
69
+ inset : 0;
70
+ margin: auto;
71
+ }
72
+ }
73
+
74
+ .popup {
75
+ position : fixed;
76
+ background-color : var(--white);
77
+ border : 1px solid var(--primary-color);
78
+ padding : var(--sp-medium);
79
+ border-radius : var(--radius-small);
80
+ inline-size : max-content;
81
+ max-inline-size : calc(var(--lower-mobile-size) / 2);
82
+ block-size : max-content;
83
+ opacity : 0;
84
+ transition : transform, opacity;
85
+ transition-duration: var(--transition-duration);
86
+ pointer-events : none;
87
+ z-index : var(--z-index-overlay);
88
+
89
+ &.show {
90
+ opacity: 1;
91
+ }
92
+ }
93
+ </style>
@@ -0,0 +1,163 @@
1
+ <script lang="ts">
2
+ // name を省略したときの既定値に使う連番。
3
+ // <script setup> の中はインスタンスごとに実行されるため、モジュールスコープはこちらに置く
4
+ let groupSequence = 0
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import type {
9
+ Option,
10
+ RadioButtonStyleForEachStatus,
11
+ } from '../types'
12
+ import {
13
+ ref,
14
+ watch,
15
+ computed,
16
+ } from 'vue'
17
+ import { COLOR } from '../const'
18
+
19
+ type SelectValue = string | number
20
+
21
+ const emit = defineEmits<{(e: 'update:modelValue', newValue: SelectValue): void}>()
22
+
23
+ const props = withDefaults(defineProps<{
24
+ modelValue: SelectValue
25
+ options: Array<Option>
26
+ name?: string
27
+ isDisabled? : boolean
28
+ isRequired?: boolean
29
+ cssStyle?: RadioButtonStyleForEachStatus
30
+ isDisableAnimation?: boolean
31
+ }>(), {
32
+ name : undefined,
33
+ cssStyle: undefined,
34
+ })
35
+
36
+ // name を省略したときの一意な既定値。
37
+ // vue 3.5 の useId は peerDependencies(^3.0.0)を満たさないため使わない
38
+ const groupName = props.name ?? `radio_group_${++groupSequence}`
39
+
40
+ const errorMessages = ref<Array<string>>([])
41
+
42
+ const selectedValue = computed({
43
+ get: () => props.modelValue ?? '',
44
+ set: (newValue: SelectValue) => emit('update:modelValue', newValue),
45
+ })
46
+
47
+ const currentCssStyle = computed(() => {
48
+ const cssStyle = props.isDisabled ? props.cssStyle?.disabled : props.cssStyle?.default
49
+
50
+ return {
51
+ ...{
52
+ textColor : props.isDisabled ? COLOR.darkGray : COLOR.black,
53
+ backgroundColor: props.isDisabled ? COLOR.lightGray : COLOR.white,
54
+ border : {
55
+ color: props.isDisabled ? COLOR.darkGray : COLOR.blue,
56
+ size : '1px',
57
+ },
58
+ },
59
+ ...(cssStyle ?? {}),
60
+ }
61
+ })
62
+
63
+ const validateValue = () => {
64
+ errorMessages.value = []
65
+ if (!selectedValue.value && props.isRequired) errorMessages.value.push('必須項目です')
66
+ }
67
+
68
+ watch(() => selectedValue.value, () => validateValue(), { immediate: !!props.modelValue })
69
+ </script>
70
+
71
+ <template>
72
+ <div :class="$style.radios">
73
+ <label
74
+ v-for="option in options"
75
+ :key="option.value"
76
+ :class="$style.radio"
77
+ :style="{
78
+ '--border-color': currentCssStyle?.border?.color,
79
+ '--border-size' : currentCssStyle?.border?.size,
80
+ '--background-color': currentCssStyle?.backgroundColor,
81
+ '--duration': isDisableAnimation ? '0s' : '.3s',
82
+ }"
83
+ >
84
+ <input
85
+ v-model="selectedValue"
86
+ type="radio"
87
+ :name="groupName"
88
+ :value="option.value"
89
+ :disabled="isDisabled"
90
+ :required="isRequired"
91
+ :checked="option.value === selectedValue"
92
+ >
93
+ <span>
94
+ {{ option.label }}
95
+ </span>
96
+ </label>
97
+ </div>
98
+ </template>
99
+
100
+ <style lang="scss" module>
101
+ @keyframes pop {
102
+ 0% {
103
+ scale : 1;
104
+ }
105
+
106
+ 10% {
107
+ scale : .8;
108
+ }
109
+
110
+ 50% {
111
+ scale : 1.2;
112
+ }
113
+
114
+ 100% {
115
+ scale: 1;
116
+ }
117
+ }
118
+
119
+ :is(.radios) {
120
+ display : flex;
121
+ align-items: center;
122
+ flex-wrap : wrap;
123
+ gap : 1rem;
124
+ }
125
+
126
+ :is(.radio) {
127
+ display : grid;
128
+ grid-template-columns: auto 1fr;
129
+ align-items : center;
130
+ gap : .5rem;
131
+ color : var(--border-color);
132
+ cursor : pointer;
133
+
134
+ &::before {
135
+ content : '';
136
+ display : inline-block;
137
+ aspect-ratio : 1 / 1;
138
+ width : 1rem;
139
+ box-shadow : 0 0 0 1px var(--border-color) inset;
140
+ background-color: var(--background-color);
141
+ border-radius : 50%;
142
+ transition : all var(--duration) linear;
143
+ }
144
+
145
+ &:has(input:checked) {
146
+ color: var(--text-color);
147
+
148
+ &::before {
149
+ box-shadow : 0 0 0 2px var(--background-color) inset, 0 0 0 1px var(--border-color);
150
+ background-color: var(--border-color);
151
+ animation : pop var(--duration) ease-out;
152
+ }
153
+ }
154
+
155
+ &:has(input:disabled) {
156
+ cursor: not-allowed !important;
157
+ }
158
+
159
+ > input {
160
+ display: none;
161
+ }
162
+ }
163
+ </style>