@indielayer/ui 1.0.0-alpha.0 → 1.0.0-alpha.5

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -72
  3. package/lib/components/avatar/Avatar.vue.d.ts +2 -2
  4. package/lib/components/badge/Badge.vue.d.ts +2 -2
  5. package/lib/components/button/Button.vue.d.ts +2 -2
  6. package/lib/components/button/ButtonGroup.vue.d.ts +2 -2
  7. package/lib/components/checkbox/Checkbox.vue.d.ts +4 -3
  8. package/lib/components/drawer/Drawer.vue.d.ts +2 -2
  9. package/lib/components/icon/Icon.vue.d.ts +7 -3
  10. package/lib/components/index.d.ts +2 -2
  11. package/lib/components/input/Input.vue.d.ts +3 -2
  12. package/lib/components/menu/Menu.vue.d.ts +2 -2
  13. package/lib/components/menu/MenuItem.vue.d.ts +3 -3
  14. package/lib/components/notifications/Notifications.vue.d.ts +2 -2
  15. package/lib/components/pagination/Pagination.vue.d.ts +3 -2
  16. package/lib/components/pagination/PaginationItem.vue.d.ts +2 -2
  17. package/lib/components/radio/Radio.vue.d.ts +2 -2
  18. package/lib/components/select/Select.vue.d.ts +3 -2
  19. package/lib/components/slider/Slider.vue.d.ts +2 -2
  20. package/lib/components/spacer/Spacer.vue.d.ts +1 -1
  21. package/lib/components/spinner/Spinner.vue.d.ts +2 -2
  22. package/lib/components/{tabs → tab}/Tab.vue.d.ts +2 -2
  23. package/lib/components/{tabs/Tabs.vue.d.ts → tab/TabGroup.vue.d.ts} +0 -0
  24. package/lib/components/table/TableBody.vue.d.ts +1 -1
  25. package/lib/components/table/TableHead.vue.d.ts +1 -1
  26. package/lib/components/tag/Tag.vue.d.ts +2 -2
  27. package/lib/components/textarea/Textarea.vue.d.ts +3 -11
  28. package/lib/components/toggle/Toggle.vue.d.ts +2 -2
  29. package/lib/composables/keys.d.ts +1 -0
  30. package/lib/create.d.ts +12 -0
  31. package/lib/index.cjs.js +2 -2
  32. package/lib/index.d.ts +2 -0
  33. package/lib/index.es.js +271 -130
  34. package/lib/install.d.ts +4 -6
  35. package/lib/nuxt.js +15 -16
  36. package/lib/nuxt.plugin.js +8 -0
  37. package/lib/style.css +1 -1
  38. package/lib/version.d.ts +1 -1
  39. package/package.json +21 -15
  40. package/src/components/alert/Alert.vue +164 -0
  41. package/src/components/avatar/Avatar.vue +137 -0
  42. package/src/components/badge/Badge.vue +107 -0
  43. package/src/components/breadcrumbs/Breadcrumbs.vue +60 -0
  44. package/src/components/button/Button.vue +433 -0
  45. package/src/components/button/ButtonGroup.vue +73 -0
  46. package/src/components/card/Card.vue +25 -0
  47. package/src/components/checkbox/Checkbox.vue +205 -0
  48. package/src/components/collapse/Collapse.vue +181 -0
  49. package/src/components/container/Container.vue +23 -0
  50. package/src/components/divider/Divider.vue +52 -0
  51. package/src/components/drawer/Drawer.vue +244 -0
  52. package/src/components/form/Form.vue +111 -0
  53. package/src/components/icon/Icon.vue +123 -0
  54. package/src/components/image/Image.vue +36 -0
  55. package/src/components/index.ts +45 -0
  56. package/src/components/input/Input.vue +199 -0
  57. package/src/components/link/Link.vue +110 -0
  58. package/src/components/menu/Menu.vue +118 -0
  59. package/src/components/menu/MenuItem.vue +277 -0
  60. package/src/components/modal/Modal.vue +175 -0
  61. package/src/components/notifications/Notifications.vue +318 -0
  62. package/src/components/pagination/Pagination.vue +181 -0
  63. package/src/components/pagination/PaginationItem.vue +58 -0
  64. package/src/components/popover/Popover.vue +194 -0
  65. package/src/components/popover/PopoverContainer.vue +23 -0
  66. package/src/components/progress/Progress.vue +86 -0
  67. package/src/components/radio/Radio.vue +220 -0
  68. package/src/components/scroll/Scroll.vue +143 -0
  69. package/src/components/select/Select.vue +408 -0
  70. package/src/components/skeleton/Skeleton.vue +23 -0
  71. package/src/components/slider/Slider.vue +240 -0
  72. package/src/components/spacer/Spacer.vue +11 -0
  73. package/src/components/spinner/Spinner.vue +45 -0
  74. package/src/components/tab/Tab.vue +100 -0
  75. package/src/components/tab/TabGroup.vue +151 -0
  76. package/src/components/table/Table.vue +172 -0
  77. package/src/components/table/TableBody.vue +13 -0
  78. package/src/components/table/TableCell.vue +78 -0
  79. package/src/components/table/TableHead.vue +15 -0
  80. package/src/components/table/TableHeader.vue +94 -0
  81. package/src/components/table/TableRow.vue +43 -0
  82. package/src/components/tag/Tag.vue +98 -0
  83. package/src/components/textarea/Textarea.vue +156 -0
  84. package/src/components/toggle/Toggle.vue +144 -0
  85. package/src/components/tooltip/Tooltip.vue +26 -0
  86. package/src/composables/colors-utils.ts +378 -0
  87. package/src/composables/colors.ts +82 -0
  88. package/src/composables/common.ts +20 -0
  89. package/src/composables/css.ts +45 -0
  90. package/src/composables/index.ts +7 -0
  91. package/src/composables/inputtable.ts +128 -0
  92. package/src/composables/interactive.ts +16 -0
  93. package/src/composables/keys.ts +8 -0
  94. package/src/composables/notification.ts +10 -0
  95. package/src/create.ts +38 -0
  96. package/src/exports/nuxt.js +32 -0
  97. package/src/exports/nuxt.plugin.js +8 -0
  98. package/src/exports/tailwind.preset.js +55 -0
  99. package/src/index.ts +8 -0
  100. package/src/install.ts +8 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/version.ts +1 -0
  103. package/volar.d.ts +1 -1
@@ -0,0 +1,194 @@
1
+ <script lang="ts">
2
+ import { computed, defineComponent, ref, useCssModule, watch, type PropType } from 'vue'
3
+ import { onClickOutside } from '@vueuse/core'
4
+
5
+ const validators = {
6
+ align: ['bottom','center','left','right','top'],
7
+ position: ['bottom','left','right','top'],
8
+ }
9
+
10
+ export default defineComponent({
11
+ name: 'XPopover',
12
+
13
+ validators,
14
+
15
+ props: {
16
+ align: {
17
+ type: String as PropType<'bottom' | 'center' | 'left' | 'right' | 'top'>,
18
+ default: 'center',
19
+ validator: (value: string) => validators.align.includes(value),
20
+ },
21
+ position: {
22
+ type: String as PropType<'bottom' | 'left' | 'right' | 'top'>,
23
+ default: 'bottom',
24
+ validator: (value: string) => validators.position.includes(value),
25
+ },
26
+ dismissOnClick: {
27
+ type: Boolean,
28
+ default: true,
29
+ },
30
+ disabled: Boolean,
31
+ hover: Boolean,
32
+ block: Boolean,
33
+ },
34
+
35
+ emits: ['open', 'close', 'toggle'],
36
+
37
+ expose: ['open', 'close', 'toggle', 'isOpen'],
38
+
39
+ setup(props, { emit }) {
40
+ const elRef = ref<HTMLElement>()
41
+ const isOpen = ref(false)
42
+
43
+ let stopClickOutside: undefined | (()=> void) = undefined
44
+
45
+ watch(isOpen, (newValue, oldValue) => {
46
+ if (props.hover) return
47
+
48
+ if (stopClickOutside) {
49
+ stopClickOutside()
50
+ stopClickOutside = undefined
51
+ }
52
+
53
+ if (newValue) {
54
+ setTimeout(() => {
55
+ stopClickOutside = onClickOutside(elRef, close)
56
+ })
57
+ }
58
+ })
59
+
60
+ const $style = useCssModule()
61
+ const classes = computed(() => {
62
+ const c = []
63
+
64
+ c.push({
65
+ // align-left
66
+ 'left-0 right-auto': props.align === 'left' && ['bottom', 'top'].includes(props.position),
67
+ // align-center
68
+ 'left-1/2 right-auto -translate-x-1/2': props.align === 'center' && ['bottom', 'top'].includes(props.position),
69
+ // align-right
70
+ 'right-0 left-auto': props.align === 'right' && ['bottom', 'top'].includes(props.position),
71
+ // align-top
72
+ 'top-0 bottom-auto': props.align === 'top' && ['left', 'right'].includes(props.position),
73
+ // align-middle
74
+ '-translate-y-1/2 top-1/2 bottom-auto': props.align === 'center' && ['left', 'right'].includes(props.position),
75
+ // align-bottom
76
+ 'bottom-0': props.align === 'bottom' && ['left', 'right'].includes(props.position),
77
+ })
78
+
79
+ if (props.position === 'top') c.push(`bottom-full pb-2 ${$style.popoverTop}`)
80
+ if (props.position === 'right') c.push(`left-full pl-2 ${$style.popoverRight}`)
81
+ if (props.position === 'bottom') c.push(`top-full bottom-0 ${$style.popoverBottom}`)
82
+ if (props.position === 'left') c.push(`right-full left-auto pr-2 ${$style.popoverLeft}`)
83
+
84
+ if (props.block) c.push('min-w-full')
85
+
86
+ return c
87
+ })
88
+
89
+ function close() {
90
+ if (props.disabled) return
91
+ isOpen.value = false
92
+ emit('close')
93
+ }
94
+
95
+ function open() {
96
+ if (props.disabled) return
97
+ isOpen.value = true
98
+ emit('open')
99
+ }
100
+
101
+ function toggle() {
102
+ if (props.disabled) return
103
+ isOpen.value = !isOpen.value
104
+ emit('toggle', isOpen.value)
105
+ }
106
+
107
+ return {
108
+ elRef,
109
+ isOpen,
110
+ classes,
111
+ close,
112
+ open,
113
+ toggle,
114
+ }
115
+ },
116
+ })
117
+ </script>
118
+
119
+ <template>
120
+ <div
121
+ ref="elRef"
122
+ class="inline-block relative"
123
+ :class="[
124
+ $style.popover,
125
+ [hover ? $style.hover : ''],
126
+ [isOpen ? $style['is-open'] : ''],
127
+ { 'w-full': block }
128
+ ]"
129
+ >
130
+ <div class="flex" @click="!hover ? toggle() : null">
131
+ <slot></slot>
132
+ </div>
133
+
134
+ <div
135
+ class="absolute w-fit bottom-0 left-0 right-0 sm:p-0 transform transition-transform z-40 max-w-xs"
136
+ :class="[
137
+ $style.popoverContent,
138
+ classes
139
+ ]"
140
+ @click="dismissOnClick ? close() : null"
141
+ >
142
+ <slot name="content"></slot>
143
+ </div>
144
+ </div>
145
+ </template>
146
+
147
+ <style lang="postcss" module>
148
+ .popover {
149
+ .popoverContent {
150
+ visibility: hidden;
151
+ transition-duration: .1s;
152
+ transition-timing-function: cubic-bezier(.4,0,1,1);
153
+ }
154
+ .popoverTop {
155
+ --tw-translate-y: 0.5rem;
156
+ }
157
+ .popoverRight {
158
+ --tw-translate-x: -0.5rem;
159
+ }
160
+ .popoverBottom {
161
+ --tw-translate-y: -0.25rem;
162
+ }
163
+ .popoverLeft {
164
+ --tw-translate-x: 0.5rem;
165
+ }
166
+
167
+ &.hover:hover .popoverContent,
168
+ &.is-open .popoverContent {
169
+ visibility: visible;
170
+ transition-duration: .15s;
171
+ transition-timing-function: cubic-bezier(0,0,.2,1);
172
+ }
173
+
174
+ &.hover:hover .popoverTop,
175
+ &.is-open .popoverTop {
176
+ --tw-translate-y: 0.25rem;
177
+ }
178
+
179
+ &.hover:hover .popoverRight,
180
+ &.is-open .popoverRight {
181
+ --tw-translate-x: 0px;
182
+ }
183
+
184
+ &.hover:hover .popoverBottom,
185
+ &.is-open .popoverBottom {
186
+ --tw-translate-y: 0.25rem;
187
+ }
188
+
189
+ &.hover:hover .popoverLeft,
190
+ &.is-open .popoverLeft {
191
+ --tw-translate-x: 0px;
192
+ }
193
+ }
194
+ </style>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'XPopoverContainer',
6
+
7
+ props: {
8
+ tag: {
9
+ default: 'div',
10
+ type: String,
11
+ },
12
+ },
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <component
18
+ :is="tag"
19
+ class="block w-full bg-white dark:bg-gray-700 shadow-lg rounded-md border border-gray-200 dark:border-gray-800"
20
+ >
21
+ <slot></slot>
22
+ </component>
23
+ </template>
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ import { computed, defineComponent, type PropType } from 'vue'
3
+ import { useCSS } from '../../composables/css'
4
+ import { useColors } from '../../composables/colors'
5
+
6
+ // const validators = {
7
+ // variant: ['bar','score'],
8
+ // }
9
+
10
+ export default defineComponent({
11
+ name: 'XProgress',
12
+
13
+ // validators,
14
+
15
+ props: {
16
+ ...useColors.props('primary'),
17
+ percentage: {
18
+ type: Number,
19
+ default: 0,
20
+ validator: (value: number) => value >= 0 && value <= 100,
21
+ },
22
+ // variant: {
23
+ // type: String as PropType<'bar' | 'score'>,
24
+ // default: 'bar',
25
+ // validator: (value: string) => validators.variant.includes(value),
26
+ // },
27
+ gradient: Boolean,
28
+ animate: {
29
+ type: Boolean,
30
+ default: true,
31
+ },
32
+ thick: Boolean,
33
+ },
34
+
35
+ setup(props) {
36
+ const css = useCSS('progress')
37
+ const colors = useColors()
38
+ const style = computed(() => {
39
+ const color = colors.getPalette(props.color)
40
+ const vars = []
41
+
42
+ vars.push(css.variables({
43
+ bg: color[500],
44
+ }))
45
+
46
+ if (props.gradient) vars.push({
47
+ '--tw-gradient-stops': `${color[100]}, ${color[800]}`,
48
+ })
49
+
50
+ return vars
51
+ })
52
+
53
+ return {
54
+ style,
55
+ }
56
+ },
57
+ })
58
+ </script>
59
+
60
+ <template>
61
+ <div
62
+ class="relative rounded bg-gray-100 dark:bg-gray-700 overflow-hidden pointer-events-none"
63
+ :class="[thick ? 'h-1.5' : 'h-1']"
64
+ :style="style"
65
+ >
66
+ <div
67
+ class="h-full"
68
+ :class="[
69
+ gradient ? 'bg-gradient-to-r' : 'bg-[color:var(--x-progress-bg)]',
70
+ {
71
+ 'duration-150 transition-[width]': animate
72
+ }
73
+ ]"
74
+ :style="[!gradient ? {width: `${percentage}%`} : '']"
75
+ >
76
+ </div>
77
+ <div
78
+ v-if="gradient"
79
+ class="absolute h-full bg-gray-100 dark:bg-gray-700 right-0 top-0"
80
+ :class="{ 'duration-150 transition-[width]': animate }"
81
+ :style="{
82
+ width: `${100 - percentage}%`
83
+ }"
84
+ ></div>
85
+ </div>
86
+ </template>
@@ -0,0 +1,220 @@
1
+ <script lang="ts">
2
+ import { defineComponent, computed, ref } from 'vue'
3
+ import { useCSS } from '../../composables/css'
4
+ import { useCommon } from '../../composables/common'
5
+ import { useColors } from '../../composables/colors'
6
+ import { useInteractive } from '../../composables/interactive'
7
+ import { useInputtable } from '../../composables/inputtable'
8
+
9
+ import XSpinner from '../../components/spinner/Spinner.vue'
10
+
11
+ export default defineComponent({
12
+ name: 'XRadio',
13
+
14
+ components: {
15
+ XSpinner,
16
+ },
17
+
18
+ validators: {
19
+ ...useCommon.validators(),
20
+ },
21
+
22
+ props: {
23
+ ...useCommon.props(),
24
+ ...useColors.props('primary'),
25
+ ...useInteractive.props(),
26
+ ...useInputtable.props(),
27
+ value: [String, Number, Boolean, Function, Object, Array],
28
+ label: String,
29
+ glow: Boolean,
30
+ },
31
+
32
+ emits: useInputtable.emits(false),
33
+
34
+ setup(props, { attrs, emit }) {
35
+ const elRef = ref<HTMLElement>()
36
+
37
+ const selected = computed({
38
+ get() {
39
+ return props.value === props.modelValue
40
+ },
41
+ set() {
42
+ emit('update:modelValue', props.value)
43
+ },
44
+ })
45
+
46
+ const css = useCSS()
47
+ const colors = useColors()
48
+ const gray = colors.getPalette('gray')
49
+
50
+ const styles = computed(() => {
51
+ const color = colors.getPalette(props.color)
52
+ const vars = []
53
+
54
+ if (props.loading) {
55
+ return css.variables({
56
+ bg: 'transparent',
57
+ border: 'transparent',
58
+ dark: {
59
+ bg: 'transparent',
60
+ border: 'transparent',
61
+ },
62
+ })
63
+ }
64
+
65
+ if (props.disabled) {
66
+ if (selected.value) {
67
+ vars.push(css.variables({
68
+ bg: 'transparent',
69
+ border: gray[200],
70
+ circle: gray[200],
71
+ dark: {
72
+ bg: 'transparent',
73
+ border: gray[700],
74
+ circle: gray[700],
75
+ },
76
+ }))
77
+ } else {
78
+ vars.push(css.variables({
79
+ bg: gray[200],
80
+ border: gray[200],
81
+ dark: {
82
+ bg: gray[700],
83
+ border: gray[700],
84
+ },
85
+ }))
86
+ }
87
+ } else {
88
+ if (selected.value) {
89
+ vars.push(css.variables({
90
+ bg: 'transparent',
91
+ border: color[500],
92
+ circle: color[500],
93
+ dark: {
94
+ bg: 'transparent',
95
+ border: color[500],
96
+ circle: color[500],
97
+ },
98
+ }))
99
+ } else {
100
+ vars.push(css.variables({
101
+ bg: '#fff',
102
+ border: props.glow ? color[300] : gray[300],
103
+ dark: {
104
+ bg: gray[900],
105
+ border: props.glow ? color[300] : gray[300],
106
+ },
107
+ }))
108
+ }
109
+
110
+ if (props.glow) {
111
+ vars.push(css.get('glow', colors.getColorOpacity(color[500], 0.5)))
112
+ }
113
+ }
114
+
115
+ return vars
116
+ })
117
+
118
+ const interactive = useInteractive(elRef)
119
+
120
+ return {
121
+ ...interactive,
122
+ ...useInputtable(props, { focus: interactive.focus, emit, withListeners: false }),
123
+ selected,
124
+ styles,
125
+ elRef,
126
+ }
127
+ },
128
+ })
129
+ </script>
130
+
131
+ <template>
132
+ <label
133
+ ref="elRef"
134
+ class="inline-block mb-1 relative cursor-pointer focus:outline-none"
135
+ :aria-selected="selected ? 'true' : 'false'"
136
+ :aria-disabled="(disabled || loading) ? 'true' : undefined"
137
+ tabindex="0"
138
+ @keypress.prevent.stop.space="$emit('update:modelValue', value)"
139
+ >
140
+ <div
141
+ class="flex items-center"
142
+ :class="{ 'cursor-not-allowed': disabled }"
143
+ >
144
+ <input
145
+ v-model="selected"
146
+ :name="name"
147
+ :required="required"
148
+ :disabled="disabled || loading"
149
+ type="radio"
150
+ class="invisible absolute"
151
+ />
152
+ <div
153
+ class="rounded-full flex justify-center items-center flex-shrink-0 border-2
154
+ border-[color:var(--x-border)]
155
+ bg-[color:var(--x-bg)]
156
+ dark:border-[color:var(--x-dark-border)]
157
+ dark:bg-[color:var(--x-dark-bg)]
158
+ "
159
+ :style="styles"
160
+ :class="[
161
+ [glow && !disabled && !loading ? $style['radio--glow'] : ''],
162
+ {
163
+ 'h-4 w-4': size === 'sm' || size === 'xs',
164
+ 'h-5 w-5': !size || !['xs', 'sm', 'xl'].includes(size),
165
+ 'h-6 w-6': size === 'xl',
166
+ },
167
+ ]"
168
+ >
169
+ <x-spinner v-if="loading" :size="size" class="absolute" />
170
+ <svg
171
+ v-else
172
+ class="fill-current text-[color:var(--x-circle)] dark:text-[color:var(--x-dark-circle)] w-full"
173
+ :class="{
174
+ 'opacity-0': !selected,
175
+ 'h-2': size === 'sm' || size === 'xs',
176
+ 'h-[0.6rem]': !size || !['xs', 'sm', 'lg', 'xl'].includes(size),
177
+ 'h-2.5': size === 'lg',
178
+ 'h-3': size === 'xl',
179
+ }"
180
+ viewBox="0 0 20 20"
181
+ >
182
+ <circle cx="10" cy="10" r="10"/>
183
+ </svg>
184
+ </div>
185
+ <span
186
+ v-if="label"
187
+ class="font-medium text-gray-800 dark:text-gray-200 pl-2"
188
+ :class="{
189
+ 'text-xs': size === 'xs',
190
+ 'text-sm': size === 'sm',
191
+ 'text-lg': size === 'lg',
192
+ 'text-xl': size === 'xl',
193
+ }"
194
+ v-text="label"
195
+ ></span>
196
+ </div>
197
+
198
+ <div
199
+ v-if="$slots.default"
200
+ :class="{
201
+ 'text-xs pl-6': size === 'xs',
202
+ 'text-sm pl-6': size === 'sm',
203
+ 'pl-7': !size || !['xs', 'sm', 'lg', 'xl'].includes(size),
204
+ 'text-lg pl-7': size === 'lg',
205
+ 'text-lg pl-8': size === 'xl',
206
+ }"
207
+ >
208
+ <slot></slot>
209
+ </div>
210
+ <p v-if="errorInternal" class="text-sm text-red-500 mt-1" v-text="errorInternal"></p>
211
+ </label>
212
+ </template>
213
+
214
+ <style lang="postcss" module scoped>
215
+ .radio {
216
+ &--glow {
217
+ box-shadow: 0 0 #000, 0 0 #000, 0 10px 15px -3px var(--x-glow),0 4px 6px -4px var(--x-glow);
218
+ }
219
+ }
220
+ </style>
@@ -0,0 +1,143 @@
1
+ <script lang="ts">
2
+ import { defineComponent, ref, toRefs } from 'vue'
3
+ import { useScroll, useResizeObserver, useEventListener } from '@vueuse/core'
4
+
5
+ export default defineComponent({
6
+ name: 'XScroll',
7
+
8
+ props: {
9
+ shadow: Boolean,
10
+ horizontal: Boolean,
11
+ mousewheel: Boolean,
12
+ scrollbar: {
13
+ type: Boolean,
14
+ default: true,
15
+ },
16
+ },
17
+
18
+ setup(props) {
19
+ const scrollEl = ref<HTMLElement | null>(null)
20
+
21
+ const { x, y, isScrolling, arrivedState, directions } = useScroll(scrollEl)
22
+ const { left, right, top, bottom } = toRefs(arrivedState)
23
+
24
+ useResizeObserver(scrollEl, triggerScroll)
25
+
26
+ if (props.horizontal && props.mousewheel)
27
+ useEventListener(scrollEl, 'wheel', (e: WheelEvent) => {
28
+ if (!scrollEl.value) return
29
+
30
+ e.preventDefault()
31
+ scrollEl.value.scrollLeft += e.deltaY + e.deltaX
32
+ })
33
+
34
+ function triggerScroll() {
35
+ scrollEl.value?.dispatchEvent(new CustomEvent('scroll'))
36
+ }
37
+
38
+ return {
39
+ scrollEl,
40
+ left,
41
+ right,
42
+ top,
43
+ bottom,
44
+ }
45
+ },
46
+ })
47
+ </script>
48
+
49
+ <template>
50
+ <div
51
+ class="flex relative overflow-hidden"
52
+ :class="[
53
+ $style.scrollWrap,
54
+ horizontal ? [
55
+ $style.horizontal,
56
+ {
57
+ [$style['shadow-left']]: !left,
58
+ [$style['shadow-right']]: !right,
59
+ }
60
+ ] : [
61
+ $style.vertical,
62
+ {
63
+ [$style['shadow-top']]: !top,
64
+ [$style['shadow-bottom']]: !bottom,
65
+ }
66
+ ]
67
+ ]"
68
+ >
69
+ <div
70
+ ref="scrollEl"
71
+ class="relative overflow-auto w-full"
72
+ :class="[
73
+ [horizontal ? 'overflow-x-auto' : 'overflow-y-auto'],
74
+ {
75
+ [$style.hideScroll]: !scrollbar,
76
+ }
77
+ ]"
78
+ >
79
+ <slot></slot>
80
+ </div>
81
+ </div>
82
+ </template>
83
+
84
+ <style lang="postcss" scoped module>
85
+ .scrollWrap {
86
+ &:before, &:after {
87
+ content: "";
88
+ pointer-events: none;
89
+ position: absolute;
90
+ z-index: 1;
91
+ transition: box-shadow .2s;
92
+ }
93
+
94
+ &.horizontal:before, &.horizontal:after {
95
+ top: 0;
96
+ bottom: 0;
97
+ width: 20px;
98
+ }
99
+
100
+ &.vertical:before, &.vertical:after {
101
+ right: 0;
102
+ left: 0;
103
+ height: 20px;
104
+ }
105
+
106
+ &.horizontal:before {
107
+ left: 0;
108
+ }
109
+ &.horizontal:after {
110
+ right: 0;
111
+ }
112
+ &.vertical:before {
113
+ top: 0;
114
+ }
115
+ &.vertical:after {
116
+ bottom: 0;
117
+ }
118
+
119
+ &.shadow-left:before {
120
+ box-shadow: inset 12px 0 10px -10px rgb(0 0 0 / 7%);
121
+ }
122
+
123
+ &.shadow-right:after {
124
+ box-shadow: inset -12px 0 10px -10px rgb(0 0 0 / 7%);
125
+ }
126
+
127
+ &.shadow-top:before {
128
+ box-shadow: inset 0 12px 10px -10px rgb(0 0 0 / 7%);
129
+ }
130
+
131
+ &.shadow-bottom:after {
132
+ box-shadow: inset 0 -12px 10px -10px rgb(0 0 0 / 7%);
133
+ }
134
+ }
135
+
136
+ .hideScroll {
137
+ -ms-overflow-style: auto;
138
+ scrollbar-width: none;
139
+ &::-webkit-scrollbar {
140
+ display: none;
141
+ }
142
+ }
143
+ </style>