@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,45 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+ import { useCommon } from '../../composables/common'
4
+
5
+ import XIcon from '../../components/icon/Icon.vue'
6
+
7
+ export default defineComponent({
8
+ name: 'XSpinner',
9
+
10
+ components: {
11
+ XIcon,
12
+ },
13
+
14
+ props: {
15
+ ...useCommon.props(),
16
+ icon: String,
17
+ },
18
+
19
+ setup() {
20
+ const defaultIcon = `<g fill="none" fill-rule="evenodd">
21
+ <g transform="translate(1 1)" stroke-width="2">
22
+ <circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
23
+ <path d="M36 18c0-9.94-8.06-18-18-18">
24
+ <animateTransform
25
+ attributeName="transform"
26
+ type="rotate"
27
+ from="0 18 18"
28
+ to="360 18 18"
29
+ dur="0.5s"
30
+ repeatCount="indefinite"
31
+ />
32
+ </path>
33
+ </g>
34
+ </g>`
35
+
36
+ return {
37
+ defaultIcon,
38
+ }
39
+ },
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <x-icon :icon="icon || defaultIcon" :size="size" view-box="0 0 38 38"/>
45
+ </template>
@@ -0,0 +1,100 @@
1
+ <script lang="ts">
2
+ import { defineComponent, inject, reactive, computed, toRefs, ref, onMounted } from 'vue'
3
+ import { injectTabKey } from '../../composables/keys'
4
+ import { useCommon } from '../../composables/common'
5
+
6
+ export default defineComponent({
7
+ name: 'XTab',
8
+
9
+ props: {
10
+ ...useCommon.props(),
11
+ value: {
12
+ type: [String, Number],
13
+ required: true,
14
+ },
15
+ tag: {
16
+ type: String,
17
+ default: 'div',
18
+ },
19
+ to: String,
20
+ label: String,
21
+ icon: String,
22
+ disabled: Boolean,
23
+ },
24
+
25
+ setup(props) {
26
+ const cLabel = computed(() => props.label || props.value)
27
+ const teleportTo = ref(null)
28
+
29
+ const tabs = inject(injectTabKey, {
30
+ tabsContentRef: ref(null),
31
+ activateTab: () => {},
32
+ state: reactive({
33
+ active: null,
34
+ variant: 'line',
35
+ grow: false,
36
+ }),
37
+ })
38
+
39
+ onMounted(() => {
40
+ teleportTo.value = tabs.tabsContentRef.value
41
+ })
42
+
43
+ const state = reactive({
44
+ selected: computed(() => tabs.state.active === props.value),
45
+ })
46
+
47
+ function onClickTab() {
48
+ tabs.activateTab(props.value)
49
+ }
50
+
51
+ return {
52
+ ...toRefs(state),
53
+ variant: tabs.state.variant,
54
+ grow: tabs.state.grow,
55
+ cLabel,
56
+ tabs,
57
+ teleportTo,
58
+ onClickTab,
59
+ }
60
+ },
61
+ })
62
+ </script>
63
+
64
+ <template>
65
+ <li :data-value="value" class="shrink-0 font-medium" :class="{ 'flex-1': grow }">
66
+ <component
67
+ :is="to ? 'router-link' : tag"
68
+ :to="to"
69
+ class="py-2 transition-colors duration-150 ease-in-out whitespace-nowrap text-center"
70
+ :class="[
71
+ {
72
+ 'px-8': variant === 'block',
73
+ 'text-[color:var(--x-tabs-text)] dark:text-[color:var(--x-dark-tabs-text)]': selected,
74
+ 'cursor-pointer': !disabled,
75
+ 'cursor-not-allowed': disabled,
76
+ 'cursor-not-allowed text-gray-500': disabled && !selected,
77
+ },
78
+ ]"
79
+ :aria-disabled="disabled ? 'true' : undefined"
80
+ :aria-selected="selected ? 'true' : 'false'"
81
+ @click="onClickTab"
82
+ >
83
+ <slot
84
+ name="tab"
85
+ :label="label"
86
+ :value="value"
87
+ :size="size"
88
+ :icon="icon"
89
+ >
90
+ <div class="flex items-center justify-center">
91
+ <x-icon v-if="icon" :icon="icon" :size="size" class="mr-1.5 shrink-0" />
92
+ <span>{{ cLabel }}</span>
93
+ </div>
94
+ </slot>
95
+ <Teleport v-if="selected && teleportTo" :to="teleportTo">
96
+ <slot></slot>
97
+ </Teleport>
98
+ </component>
99
+ </li>
100
+ </template>
@@ -0,0 +1,151 @@
1
+ <script lang="ts">
2
+ import { defineComponent, reactive, computed, provide, type PropType, ref, watch, onMounted } from 'vue'
3
+ import { injectTabKey } from '../../composables/keys'
4
+ import { useCSS } from '../../composables/css'
5
+ import { useColors } from '../../composables/colors'
6
+
7
+ import { useResizeObserver, useThrottleFn } from '@vueuse/core'
8
+
9
+ import XScroll from '../../components/scroll/Scroll.vue'
10
+
11
+ export default defineComponent({
12
+ name: 'XTabGroup',
13
+
14
+ components: {
15
+ XScroll,
16
+ },
17
+
18
+ props: {
19
+ ...useColors.props('primary'),
20
+ modelValue: [String, Number],
21
+ variant: {
22
+ type: String as PropType<'line' | 'block'>,
23
+ default: 'line',
24
+ },
25
+ ghost: Boolean,
26
+ grow: Boolean,
27
+ },
28
+
29
+ emits: ['update:modelValue'],
30
+
31
+ setup(props, { emit }) {
32
+ const scrollRef = ref()
33
+ const wrapperRef = ref<HTMLElement>()
34
+ const trackerRef = ref<HTMLElement>()
35
+ const tabsRef = ref<HTMLElement>()
36
+ const tabsContentRef = ref<HTMLElement>()
37
+
38
+ const state = reactive({
39
+ active: computed(() => props.modelValue),
40
+ variant: computed(() => props.variant),
41
+ ghost: computed(() => props.ghost),
42
+ grow: computed(() => props.grow),
43
+ })
44
+
45
+ function activateTab(tab: string | number) {
46
+ emit('update:modelValue', tab)
47
+ }
48
+
49
+ provide(injectTabKey, {
50
+ tabsContentRef,
51
+ activateTab,
52
+ state,
53
+ })
54
+
55
+ const updateTracker = useThrottleFn((value: string | number | undefined) => {
56
+ if (typeof value === 'undefined') return
57
+
58
+ const tabEl = tabsRef.value?.querySelector(`[data-value="${value}"]`) as HTMLElement
59
+
60
+ if (!tabEl || !trackerRef.value) return
61
+
62
+ trackerRef.value.style.left = `${tabEl.offsetLeft}px`
63
+ trackerRef.value.style.width = `${tabEl.offsetWidth}px`
64
+
65
+ if (!tabsRef.value || !scrollRef.value) return
66
+
67
+ // scrollIntoView only updates one at a time
68
+ const center = tabEl.offsetLeft - (tabsRef.value.getBoundingClientRect().width - tabEl.getBoundingClientRect().width) / 2
69
+
70
+ if (scrollRef.value.scrollEl) scrollRef.value.scrollEl.scrollTo({ left: center, behavior: 'smooth' })
71
+ }, 100)
72
+
73
+ useResizeObserver(wrapperRef, () => { updateTracker(props.modelValue) })
74
+
75
+ watch(() => props.modelValue, (value) => {
76
+ updateTracker(value)
77
+ })
78
+
79
+ onMounted(() => {
80
+ updateTracker(props.modelValue)
81
+ })
82
+
83
+ const css = useCSS('tabs')
84
+ const colors = useColors()
85
+ const gray = colors.getPalette('gray')
86
+ const style = computed(() => {
87
+ const color = colors.getPalette(props.color)
88
+
89
+ return css.variables({
90
+ text: color[600],
91
+ bg: props.ghost ? color[50] : '#fff',
92
+ dark: {
93
+ text: color[400],
94
+ bg: props.ghost ? color[900] : gray[700],
95
+ },
96
+ })
97
+ })
98
+
99
+ return {
100
+ scrollRef,
101
+ wrapperRef,
102
+ trackerRef,
103
+ tabsRef,
104
+ tabsContentRef,
105
+ style,
106
+ }
107
+ },
108
+ })
109
+ </script>
110
+
111
+ <template>
112
+ <div>
113
+ <div
114
+ ref="wrapperRef"
115
+ class="relative"
116
+ :style="style"
117
+ >
118
+ <x-scroll
119
+ ref="scrollRef"
120
+ :scrollbar="false"
121
+ horizontal
122
+ mousewheel
123
+ :class="{
124
+ 'rounded-md': variant === 'block',
125
+ 'bg-gray-50 dark:bg-gray-800 p-1': variant === 'block' && !ghost
126
+ }"
127
+ >
128
+ <ul
129
+ ref="tabsRef"
130
+ class="flex relative min-w-full w-fit"
131
+ :class="{
132
+ 'border-b border-gray-200 dark:border-gray-700': variant === 'line',
133
+ 'space-x-8': variant === 'line' && !grow,
134
+ 'z-[1]': variant === 'block'
135
+ }"
136
+ >
137
+ <slot></slot>
138
+ </ul>
139
+ <div
140
+ ref="trackerRef"
141
+ class="absolute transition-all duration-150"
142
+ :class="{
143
+ 'h-[2px] -mt-[2px] bg-[color:var(--x-tabs-text)] dark:bg-[color:var(--x-dark-tabs-text)]': variant === 'line',
144
+ 'rounded-md h-full top-0 bg-[color:var(--x-tabs-bg)] dark:bg-[color:var(--x-dark-tabs-bg)]': variant === 'block'
145
+ }"
146
+ ></div>
147
+ </x-scroll>
148
+ </div>
149
+ <div ref="tabsContentRef"></div>
150
+ </div>
151
+ </template>
@@ -0,0 +1,172 @@
1
+ <script lang="ts">
2
+ import { defineComponent, type PropType } from 'vue'
3
+
4
+ import TableHead from './TableHead.vue'
5
+ import TableHeader, { type Sort, type Align } from './TableHeader.vue'
6
+ import TableBody from './TableBody.vue'
7
+ import TableRow from './TableRow.vue'
8
+ import TableCell from './TableCell.vue'
9
+ import XSpinner from '../spinner/Spinner.vue'
10
+
11
+ export type Header = {
12
+ sortable?: boolean,
13
+ sort?: string[],
14
+ align?: Align,
15
+ value: string,
16
+ text: string,
17
+ }
18
+
19
+ export default defineComponent({
20
+ name: 'XTable',
21
+
22
+ components: {
23
+ TableHead,
24
+ TableHeader,
25
+ TableBody,
26
+ TableRow,
27
+ TableCell,
28
+ XSpinner,
29
+ },
30
+
31
+ props: {
32
+ headers: {
33
+ type: Array as PropType<Array<Header>>,
34
+ default: () => [],
35
+ },
36
+ items: {
37
+ type: Array,
38
+ default: () => [],
39
+ },
40
+ sort: {
41
+ type: Array as PropType<Array<string>>,
42
+ default: () => [],
43
+ },
44
+ loading: Boolean,
45
+ dense: Boolean,
46
+ fixed: Boolean,
47
+ striped: Boolean,
48
+ pointer: Boolean,
49
+ scrollable: {
50
+ type: Boolean,
51
+ default: true,
52
+ },
53
+ stickyHeader: {
54
+ type: Boolean,
55
+ default: true,
56
+ },
57
+ },
58
+
59
+ emits: ['update:sort', 'click-row'],
60
+
61
+ setup(props, { emit }) {
62
+ function getSort(headerValue: string, sort: string[]): Sort {
63
+ for (let i = 0; i < sort.length; i++) {
64
+ const { 0: value, 1: order } = sort[i].split(',')
65
+
66
+ if (headerValue === value) {
67
+ if (parseInt(order) > 0) return 1
68
+ else return -1
69
+ }
70
+ }
71
+
72
+ return undefined
73
+ }
74
+
75
+ function sortHeader(header: Header) {
76
+ // update sort array
77
+ const sort = props.sort.slice(0)
78
+ let exists = false
79
+
80
+ for (let i = 0; i < sort.length; i++) {
81
+ const { 0: value, 1: order } = sort[i].split(',')
82
+
83
+ if (value === header.value) {
84
+ exists = true
85
+
86
+ if (order === '-1') {
87
+ // update position to 1
88
+ sort.splice(i, 1, `${header.value},1`)
89
+ break
90
+ } else if (order === '1') {
91
+ // delete position
92
+ sort.splice(i, 1)
93
+ break
94
+ }
95
+ }
96
+ }
97
+
98
+ if (!exists) sort.push(`${header.value},-1`)
99
+
100
+ emit('update:sort', sort)
101
+ }
102
+
103
+ function getValue(item: any, path: string | string[]) {
104
+ if (!path) return ''
105
+ const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
106
+ const result = pathArray?.reduce((prevObj: any, key: string) => prevObj && prevObj[key], item)
107
+
108
+ return result ?? ''
109
+ }
110
+
111
+ return {
112
+ getSort,
113
+ getValue,
114
+ sortHeader,
115
+ }
116
+ },
117
+ })
118
+ </script>
119
+
120
+ <template>
121
+ <table
122
+ class="w-full relative"
123
+ :class="[
124
+ {
125
+ 'overflow-x-scroll sm:overflow-x-auto whitespace-wrap sm:whitespace-normal block sm:table': scrollable,
126
+ 'relative': stickyHeader,
127
+ 'table-fixed': fixed,
128
+ }
129
+ ]"
130
+ >
131
+ <table-head>
132
+ <table-header
133
+ v-for="(header, index) in headers"
134
+ :key="index"
135
+ :sticky-header="stickyHeader"
136
+ :text-align="header.align"
137
+ :sort="getSort(header.value, sort)"
138
+ :sortable="header.sortable"
139
+ @click="header.sortable ? sortHeader(header) : null"
140
+ >
141
+ {{ header.text }}
142
+ </table-header>
143
+ </table-head>
144
+ <table-body>
145
+ <table-row
146
+ v-for="(item, index) in items"
147
+ :key="index"
148
+ :pointer="pointer"
149
+ :striped="striped"
150
+ @click="$emit('click-row', item)"
151
+ >
152
+ <table-cell
153
+ v-for="(header, index2) in headers"
154
+ :key="index2"
155
+ :text-align="header.align"
156
+ :dense="dense"
157
+ :fixed="fixed"
158
+ >
159
+ <slot :name="`item-${header.value}`" :item="item">
160
+ {{ getValue(item, header.value) }}
161
+ </slot>
162
+ </table-cell>
163
+ </table-row>
164
+ </table-body>
165
+ <div
166
+ v-if="loading"
167
+ class="absolute inset-0 flex items-center justify-center z-40 bg-gray-300 dark:bg-gray-600 rounded opacity-50"
168
+ >
169
+ <x-spinner size="lg"/>
170
+ </div>
171
+ </table>
172
+ </template>
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'XTableBody',
6
+ })
7
+ </script>
8
+
9
+ <template>
10
+ <tbody>
11
+ <slot></slot>
12
+ </tbody>
13
+ </template>
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ const validators = {
5
+ textAlign: [
6
+ null,
7
+ 'left',
8
+ 'center',
9
+ 'right',
10
+ 'justify',
11
+ ],
12
+ verticalAlign: [
13
+ null,
14
+ 'baseline',
15
+ 'bottom',
16
+ 'middle',
17
+ 'text-bottom',
18
+ 'text-top',
19
+ 'top',
20
+ ],
21
+ }
22
+
23
+ export default defineComponent({
24
+ name: 'XTableCell',
25
+
26
+ validators,
27
+
28
+ props: {
29
+ textAlign: {
30
+ type: String,
31
+ validator: (value: string) => validators.textAlign.includes(value),
32
+ },
33
+ truncate: Boolean,
34
+ dense: Boolean,
35
+ fixed: Boolean,
36
+ verticalAlign: {
37
+ type: String,
38
+ default: 'middle',
39
+ validator: (value: string) => validators.verticalAlign.includes(value),
40
+ },
41
+ },
42
+
43
+ setup(props) {
44
+ if (props.truncate && !props.fixed) {
45
+ console.warn('Table must have "fixed" property set to true when using TableCell "truncate" property')
46
+ }
47
+ },
48
+ })
49
+ </script>
50
+
51
+ <template>
52
+ <td
53
+ class="last:pr-0 px-3"
54
+ :class="[
55
+ {
56
+ // density
57
+ 'py-2': dense,
58
+ 'py-4': !dense,
59
+ // text-align
60
+ 'text-left': textAlign === 'left',
61
+ 'text-center': textAlign === 'center',
62
+ 'text-right': textAlign === 'right',
63
+ 'text-justify': textAlign === 'justify',
64
+ // vertical-align
65
+ 'align-baseline': verticalAlign === 'baseline',
66
+ 'align-bottom': verticalAlign === 'bottom',
67
+ 'align-middle': verticalAlign === 'middle',
68
+ 'align-text-bottom': verticalAlign === 'text-bottom',
69
+ 'align-text-top': verticalAlign === 'text-top',
70
+ 'align-top': verticalAlign === 'top',
71
+ // truncate
72
+ 'truncate': truncate && fixed,
73
+ },
74
+ ]"
75
+ >
76
+ <slot></slot>
77
+ </td>
78
+ </template>
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'XTableHead',
6
+ })
7
+ </script>
8
+
9
+ <template>
10
+ <thead class="align-bottom">
11
+ <tr class="text-sm text-gray-600 dark:text-gray-400 border-b">
12
+ <slot></slot>
13
+ </tr>
14
+ </thead>
15
+ </template>
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ import { defineComponent, type PropType } from 'vue'
3
+
4
+ export type Sort = 1 | -1 | undefined
5
+ export type Align = 'left' | 'center' | 'right' | 'justify' | undefined
6
+
7
+ const validators = {
8
+ sort: [1,-1],
9
+ textAlign: ['left','center','right','justify'],
10
+ }
11
+
12
+ export default defineComponent({
13
+ name: 'XTableHeader',
14
+
15
+ validators,
16
+
17
+ props: {
18
+ sort: {
19
+ type: Number as PropType<Sort>,
20
+ validator: (value: number) => validators.sort.includes(value),
21
+ },
22
+ sortable: Boolean,
23
+ textAlign: {
24
+ type: String as PropType<Align>,
25
+ default: 'left',
26
+ validator: (value: string) => validators.textAlign.includes(value),
27
+ },
28
+ stickyHeader: Boolean,
29
+ },
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <th
35
+ class="py-2 font-semibold tracking-widest uppercase text-xs px-3"
36
+ :class="[
37
+ {
38
+ // sort
39
+ 'cursor-pointer hover:text-gray-800 dark:hover:text-gray-300 transition-colors duration-150 ease-in-out': sortable,
40
+ // stickyHeader
41
+ 'sticky top-0': stickyHeader,
42
+ // textAlign
43
+ 'text-left': textAlign === 'left',
44
+ 'text-right': textAlign === 'right',
45
+ 'text-center': textAlign === 'center',
46
+ 'text-justify': textAlign === 'justify',
47
+ },
48
+ ]"
49
+ >
50
+ <div
51
+ v-if="sortable"
52
+ class="relative inline-block"
53
+ >
54
+ <slot></slot>
55
+
56
+ <svg
57
+ v-if="sort && [1, -1].includes(sort)"
58
+ class="absolute stroke-2 w-3 h-3 ml-0.5 -right-3 top-0.5"
59
+ width="24"
60
+ height="24"
61
+ viewBox="0 0 24 24"
62
+ stroke="currentColor"
63
+ stroke-linejoin="round"
64
+ stroke-linecap="round"
65
+ fill="none"
66
+ role="presentation"
67
+ >
68
+ <template v-if="sort === -1">
69
+ <line
70
+ x1="12"
71
+ y1="5"
72
+ x2="12"
73
+ y2="19"
74
+ />
75
+ <polyline points="19 12 12 19 5 12" />
76
+ </template>
77
+
78
+ <template v-if="sort === 1">
79
+ <line
80
+ x1="12"
81
+ y1="19"
82
+ x2="12"
83
+ y2="5"
84
+ />
85
+ <polyline points="5 12 12 5 19 12" />
86
+ </template>
87
+ </svg>
88
+ </div>
89
+
90
+ <template v-else>
91
+ <slot></slot>
92
+ </template>
93
+ </th>
94
+ </template>