@nuxt/ui 3.0.0-alpha.5 → 3.0.0-alpha.7
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.
- package/README.md +4 -2
- package/dist/module.d.mts +19 -5
- package/dist/module.d.ts +19 -5
- package/dist/module.json +2 -1
- package/dist/module.mjs +514 -338
- package/dist/runtime/components/Accordion.vue +13 -5
- package/dist/runtime/components/Alert.vue +2 -2
- package/dist/runtime/components/Breadcrumb.vue +16 -8
- package/dist/runtime/components/Button.vue +4 -2
- package/dist/runtime/components/Carousel.vue +306 -0
- package/dist/runtime/components/Checkbox.vue +3 -6
- package/dist/runtime/components/CommandPalette.vue +27 -18
- package/dist/runtime/components/ContextMenu.vue +33 -9
- package/dist/runtime/components/ContextMenuContent.vue +36 -8
- package/dist/runtime/components/DropdownMenu.vue +33 -9
- package/dist/runtime/components/DropdownMenuContent.vue +38 -8
- package/dist/runtime/components/Form.vue +6 -2
- package/dist/runtime/components/Input.vue +6 -4
- package/dist/runtime/components/InputMenu.vue +40 -24
- package/dist/runtime/components/LinkBase.vue +4 -2
- package/dist/runtime/components/Modal.vue +2 -2
- package/dist/runtime/components/NavigationMenu.vue +80 -43
- package/dist/runtime/components/Pagination.vue +2 -2
- package/dist/runtime/components/RadioGroup.vue +22 -3
- package/dist/runtime/components/Select.vue +22 -10
- package/dist/runtime/components/SelectMenu.vue +43 -31
- package/dist/runtime/components/Slideover.vue +2 -2
- package/dist/runtime/components/Table.vue +232 -0
- package/dist/runtime/components/Tabs.vue +15 -7
- package/dist/runtime/components/Toast.vue +2 -2
- package/dist/runtime/components/Toaster.vue +1 -1
- package/dist/runtime/composables/defineShortcuts.js +1 -1
- package/dist/runtime/composables/useComponentIcons.d.ts +3 -0
- package/dist/runtime/composables/useModal.d.ts +1 -1
- package/dist/runtime/composables/useSlideover.d.ts +1 -1
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/keyframes.css +1 -0
- package/dist/runtime/plugins/colors.js +11 -7
- package/dist/runtime/types/form.d.ts +3 -1
- package/dist/runtime/types/index.d.ts +2 -0
- package/dist/runtime/types/index.js +2 -0
- package/dist/runtime/utils/form.d.ts +7 -1
- package/dist/runtime/utils/form.js +25 -1
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +3 -0
- package/dist/runtime/utils/link.d.ts +1 -1
- package/package.json +30 -14
- package/dist/runtime/types/component.d.ts +0 -17
- package/dist/runtime/types/component.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
3
|
-
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps,
|
|
3
|
+
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps } from 'radix-vue'
|
|
4
4
|
import type { AppConfig } from '@nuxt/schema'
|
|
5
5
|
import _appConfig from '#build/app.config'
|
|
6
6
|
import theme from '#build/ui/select-menu'
|
|
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof t
|
|
|
12
12
|
|
|
13
13
|
const selectMenu = tv({ extend: tv(theme), ...(appConfig.ui?.selectMenu || {}) })
|
|
14
14
|
|
|
15
|
-
export interface SelectMenuItem
|
|
15
|
+
export interface SelectMenuItem {
|
|
16
16
|
label?: string
|
|
17
17
|
icon?: string
|
|
18
18
|
avatar?: AvatarProps
|
|
@@ -22,7 +22,8 @@ export interface SelectMenuItem extends Pick<ComboboxItemProps, 'disabled'> {
|
|
|
22
22
|
* @defaultValue 'item'
|
|
23
23
|
*/
|
|
24
24
|
type?: 'label' | 'separator' | 'item'
|
|
25
|
-
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
onSelect?(e?: Event): void
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
type SelectMenuVariants = VariantProps<typeof selectMenu>
|
|
@@ -77,6 +78,11 @@ export interface SelectMenuProps<T> extends Pick<ComboboxRootProps<T>, 'modelVal
|
|
|
77
78
|
* @defaultValue undefined
|
|
78
79
|
*/
|
|
79
80
|
valueKey?: keyof T
|
|
81
|
+
/**
|
|
82
|
+
* When `items` is an array of objects, select the field to use as the label.
|
|
83
|
+
* @defaultValue 'label'
|
|
84
|
+
*/
|
|
85
|
+
labelKey?: keyof T
|
|
80
86
|
items?: T[] | T[][]
|
|
81
87
|
/** Highlight the ring color like a focus state. */
|
|
82
88
|
highlight?: boolean
|
|
@@ -108,22 +114,24 @@ export interface SelectMenuSlots<T> {
|
|
|
108
114
|
import { computed, toRef } from 'vue'
|
|
109
115
|
import { ComboboxRoot, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, useForwardPropsEmits } from 'radix-vue'
|
|
110
116
|
import { defu } from 'defu'
|
|
117
|
+
import isEqual from 'fast-deep-equal'
|
|
111
118
|
import { reactivePick } from '@vueuse/core'
|
|
112
119
|
import { useAppConfig } from '#imports'
|
|
113
120
|
import { useButtonGroup } from '../composables/useButtonGroup'
|
|
114
121
|
import { useComponentIcons } from '../composables/useComponentIcons'
|
|
115
122
|
import { useFormField } from '../composables/useFormField'
|
|
123
|
+
import { get, escapeRegExp } from '../utils'
|
|
116
124
|
import UIcon from './Icon.vue'
|
|
117
125
|
import UAvatar from './Avatar.vue'
|
|
118
126
|
import UChip from './Chip.vue'
|
|
119
|
-
import { get } from '../utils'
|
|
120
127
|
|
|
121
128
|
const props = withDefaults(defineProps<SelectMenuProps<T>>(), {
|
|
122
129
|
search: true,
|
|
123
130
|
portal: true,
|
|
124
131
|
autofocusDelay: 0,
|
|
125
132
|
searchInput: () => ({ placeholder: 'Search...' }),
|
|
126
|
-
filter: () => ['label']
|
|
133
|
+
filter: () => ['label'],
|
|
134
|
+
labelKey: 'label' as keyof T
|
|
127
135
|
})
|
|
128
136
|
const emits = defineEmits<SelectMenuEmits<T>>()
|
|
129
137
|
const slots = defineSlots<SelectMenuSlots<T>>()
|
|
@@ -145,21 +153,19 @@ const ui = computed(() => selectMenu({
|
|
|
145
153
|
size: selectSize?.value,
|
|
146
154
|
loading: props.loading,
|
|
147
155
|
highlight: highlight.value,
|
|
148
|
-
leading: isLeading.value || !!slots.leading,
|
|
156
|
+
leading: isLeading.value || !!props.avatar || !!slots.leading,
|
|
149
157
|
trailing: isTrailing.value || !!slots.trailing,
|
|
150
158
|
buttonGroup: orientation.value
|
|
151
159
|
}))
|
|
152
160
|
|
|
153
|
-
function displayValue(
|
|
154
|
-
if (multiple && Array.isArray(
|
|
155
|
-
return
|
|
161
|
+
function displayValue(value: T): string {
|
|
162
|
+
if (props.multiple && Array.isArray(value)) {
|
|
163
|
+
return value.map(v => displayValue(v)).join(', ')
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
|
|
159
|
-
return val.label
|
|
160
|
-
}
|
|
166
|
+
const item = items.value.find(item => props.valueKey ? isEqual(get(item as Record<string, any>, props.valueKey as string), value) : isEqual(item, value))
|
|
161
167
|
|
|
162
|
-
return
|
|
168
|
+
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
function filterFunction(items: ArrayOrWrapped<AcceptableValue>, searchTerm: string): ArrayOrWrapped<AcceptableValue> {
|
|
@@ -167,22 +173,25 @@ function filterFunction(items: ArrayOrWrapped<AcceptableValue>, searchTerm: stri
|
|
|
167
173
|
return items
|
|
168
174
|
}
|
|
169
175
|
|
|
170
|
-
const fields = Array.isArray(props.filter) ? props.filter : [
|
|
176
|
+
const fields = Array.isArray(props.filter) ? props.filter : [props.labelKey]
|
|
177
|
+
const escapedSearchTerm = escapeRegExp(searchTerm)
|
|
171
178
|
|
|
172
179
|
return items.filter((item) => {
|
|
173
180
|
if (typeof item !== 'object') {
|
|
174
|
-
return String(item).search(new RegExp(
|
|
181
|
+
return String(item).search(new RegExp(escapedSearchTerm, 'i')) !== -1
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
return fields.some((field) => {
|
|
178
|
-
const child = get(item, field)
|
|
185
|
+
const child = get(item, field as string)
|
|
179
186
|
|
|
180
|
-
return child !== null && child !== undefined && String(child).search(new RegExp(
|
|
187
|
+
return child !== null && child !== undefined && String(child).search(new RegExp(escapedSearchTerm, 'i')) !== -1
|
|
181
188
|
})
|
|
182
189
|
}) as ArrayOrWrapped<T>
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as SelectMenuItem[][] : [])
|
|
193
|
+
// eslint-disable-next-line vue/no-dupe-keys
|
|
194
|
+
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
|
186
195
|
|
|
187
196
|
function onUpdate(value: any) {
|
|
188
197
|
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
|
@@ -220,19 +229,22 @@ function onUpdateOpen(value: boolean) {
|
|
|
220
229
|
>
|
|
221
230
|
<ComboboxAnchor as-child>
|
|
222
231
|
<ComboboxTrigger :class="ui.base({ class: [props.class, props.ui?.base] })" tabindex="0">
|
|
223
|
-
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
|
232
|
+
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
|
224
233
|
<slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
|
|
225
|
-
<UIcon v-if="leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
234
|
+
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
235
|
+
<UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
226
236
|
</slot>
|
|
227
237
|
</span>
|
|
228
238
|
|
|
229
239
|
<slot :model-value="(modelValue as T)" :open="open">
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
<template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
|
|
241
|
+
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
|
242
|
+
{{ displayedModelValue }}
|
|
243
|
+
</span>
|
|
244
|
+
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
|
|
245
|
+
{{ placeholder ?? ' ' }}
|
|
246
|
+
</span>
|
|
247
|
+
</template>
|
|
236
248
|
</slot>
|
|
237
249
|
|
|
238
250
|
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
|
@@ -263,7 +275,7 @@ function onUpdateOpen(value: boolean) {
|
|
|
263
275
|
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
|
264
276
|
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
|
265
277
|
<ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
|
266
|
-
{{ item.
|
|
278
|
+
{{ get(item, props.labelKey as string) }}
|
|
267
279
|
</ComboboxLabel>
|
|
268
280
|
|
|
269
281
|
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
|
|
@@ -272,13 +284,13 @@ function onUpdateOpen(value: boolean) {
|
|
|
272
284
|
v-else
|
|
273
285
|
:class="ui.item({ class: props.ui?.item })"
|
|
274
286
|
:disabled="item.disabled"
|
|
275
|
-
:value="valueKey && typeof item === 'object' ? (item
|
|
276
|
-
@select="item.
|
|
287
|
+
:value="valueKey && typeof item === 'object' ? get(item, props.valueKey as string) : item"
|
|
288
|
+
@select="item.onSelect"
|
|
277
289
|
>
|
|
278
290
|
<slot name="item" :item="(item as T)" :index="index">
|
|
279
291
|
<slot name="item-leading" :item="(item as T)" :index="index">
|
|
280
|
-
<
|
|
281
|
-
<
|
|
292
|
+
<UIcon v-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
|
|
293
|
+
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
282
294
|
<UChip
|
|
283
295
|
v-else-if="item.chip"
|
|
284
296
|
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
|
|
@@ -291,7 +303,7 @@ function onUpdateOpen(value: boolean) {
|
|
|
291
303
|
|
|
292
304
|
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
|
293
305
|
<slot name="item-label" :item="(item as T)" :index="index">
|
|
294
|
-
{{
|
|
306
|
+
{{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
|
|
295
307
|
</slot>
|
|
296
308
|
</span>
|
|
297
309
|
|
|
@@ -35,7 +35,7 @@ export interface SlideoverProps extends DialogRootProps {
|
|
|
35
35
|
portal?: boolean
|
|
36
36
|
/**
|
|
37
37
|
* Display a close button to dismiss the slideover.
|
|
38
|
-
* `{ size: 'md', color: '
|
|
38
|
+
* `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
|
|
39
39
|
* @defaultValue true
|
|
40
40
|
*/
|
|
41
41
|
close?: ButtonProps | boolean
|
|
@@ -137,7 +137,7 @@ const ui = computed(() => slideover({
|
|
|
137
137
|
v-if="close"
|
|
138
138
|
:icon="closeIcon || appConfig.ui.icons.close"
|
|
139
139
|
size="md"
|
|
140
|
-
color="
|
|
140
|
+
color="neutral"
|
|
141
141
|
variant="ghost"
|
|
142
142
|
aria-label="Close"
|
|
143
143
|
v-bind="typeof close === 'object' ? close : undefined"
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { tv, type VariantProps } from 'tailwind-variants'
|
|
5
|
+
import type { AppConfig } from '@nuxt/schema'
|
|
6
|
+
import type {
|
|
7
|
+
Row,
|
|
8
|
+
ColumnDef,
|
|
9
|
+
ColumnFiltersState,
|
|
10
|
+
ColumnPinningState,
|
|
11
|
+
RowSelectionState,
|
|
12
|
+
SortingState,
|
|
13
|
+
ExpandedState,
|
|
14
|
+
VisibilityState,
|
|
15
|
+
GlobalFilterOptions,
|
|
16
|
+
ColumnFiltersOptions,
|
|
17
|
+
ColumnPinningOptions,
|
|
18
|
+
VisibilityOptions,
|
|
19
|
+
ExpandedOptions,
|
|
20
|
+
SortingOptions,
|
|
21
|
+
RowSelectionOptions,
|
|
22
|
+
Updater
|
|
23
|
+
} from '@tanstack/vue-table'
|
|
24
|
+
import _appConfig from '#build/app.config'
|
|
25
|
+
import theme from '#build/ui/table'
|
|
26
|
+
|
|
27
|
+
const appConfig = _appConfig as AppConfig & { ui: { table: Partial<typeof theme> } }
|
|
28
|
+
|
|
29
|
+
const table = tv({ extend: tv(theme), ...(appConfig.ui?.table || {}) })
|
|
30
|
+
|
|
31
|
+
type TableVariants = VariantProps<typeof table>
|
|
32
|
+
|
|
33
|
+
export type TableColumn<T> = ColumnDef<T>
|
|
34
|
+
|
|
35
|
+
export interface TableData {
|
|
36
|
+
[key: string]: any
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TableProps<T> {
|
|
40
|
+
data?: T[]
|
|
41
|
+
columns?: TableColumn<T>[]
|
|
42
|
+
/**
|
|
43
|
+
* Whether the table should have a sticky header.
|
|
44
|
+
* @defaultValue false
|
|
45
|
+
*/
|
|
46
|
+
sticky?: boolean
|
|
47
|
+
/** Whether the table should be in loading state. */
|
|
48
|
+
loading?: boolean
|
|
49
|
+
loadingColor?: TableVariants['loadingColor']
|
|
50
|
+
loadingAnimation?: TableVariants['loadingAnimation']
|
|
51
|
+
/**
|
|
52
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/global-filtering#table-options)
|
|
53
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/global-filtering)
|
|
54
|
+
*/
|
|
55
|
+
globalFilterOptions?: Omit<GlobalFilterOptions<T>, 'onGlobalFilterChange'>
|
|
56
|
+
/**
|
|
57
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-filtering#table-options)
|
|
58
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-filtering)
|
|
59
|
+
*/
|
|
60
|
+
columnFiltersOptions?: Omit<ColumnFiltersOptions<T>, 'getFilteredRowModel' | 'onColumnFiltersChange'>
|
|
61
|
+
/**
|
|
62
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-pinning#table-options)
|
|
63
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-pinning)
|
|
64
|
+
*/
|
|
65
|
+
columnPinningOptions?: Omit<ColumnPinningOptions, 'onColumnPinningChange'>
|
|
66
|
+
/**
|
|
67
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-visibility#table-options)
|
|
68
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-visibility)
|
|
69
|
+
*/
|
|
70
|
+
visibilityOptions?: Omit<VisibilityOptions, 'onColumnVisibilityChange'>
|
|
71
|
+
/**
|
|
72
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/sorting#table-options)
|
|
73
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
|
|
74
|
+
*/
|
|
75
|
+
sortingOptions?: Omit<SortingOptions<T>, 'getSortedRowModel' | 'onSortingChange'>
|
|
76
|
+
/**
|
|
77
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/expanding#table-options)
|
|
78
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/expanding)
|
|
79
|
+
*/
|
|
80
|
+
expandedOptions?: Omit<ExpandedOptions<T>, 'getExpandedRowModel' | 'onExpandedChange'>
|
|
81
|
+
/**
|
|
82
|
+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#table-options)
|
|
83
|
+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
|
|
84
|
+
*/
|
|
85
|
+
rowSelectionOptions?: Omit<RowSelectionOptions<T>, 'onRowSelectionChange'>
|
|
86
|
+
class?: any
|
|
87
|
+
ui?: Partial<typeof table.slots>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TableSlots<T> {
|
|
91
|
+
expanded(props: { row: Row<T> }): any
|
|
92
|
+
empty(props?: {}): any
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<script setup lang="ts" generic="T extends TableData">
|
|
98
|
+
import { computed } from 'vue'
|
|
99
|
+
import {
|
|
100
|
+
FlexRender,
|
|
101
|
+
getCoreRowModel,
|
|
102
|
+
getFilteredRowModel,
|
|
103
|
+
getSortedRowModel,
|
|
104
|
+
getExpandedRowModel,
|
|
105
|
+
useVueTable
|
|
106
|
+
} from '@tanstack/vue-table'
|
|
107
|
+
import { upperFirst } from 'scule'
|
|
108
|
+
|
|
109
|
+
const props = defineProps<TableProps<T>>()
|
|
110
|
+
defineSlots<TableSlots<T>>()
|
|
111
|
+
|
|
112
|
+
const data = computed(() => props.data ?? [])
|
|
113
|
+
const columns = computed<TableColumn<T>[]>(() => props.columns ?? Object.keys(data.value[0] ?? {}).map((accessorKey: string) => ({ accessorKey, header: upperFirst(accessorKey) })))
|
|
114
|
+
|
|
115
|
+
const ui = computed(() => table({
|
|
116
|
+
sticky: props.sticky,
|
|
117
|
+
loading: props.loading,
|
|
118
|
+
loadingColor: props.loadingColor,
|
|
119
|
+
loadingAnimation: props.loadingAnimation
|
|
120
|
+
}))
|
|
121
|
+
|
|
122
|
+
const globalFilterState = defineModel<string>('globalFilter', { default: undefined })
|
|
123
|
+
const columnFiltersState = defineModel<ColumnFiltersState>('columnFilters', { default: [] })
|
|
124
|
+
const columnVisibilityState = defineModel<VisibilityState>('columnVisibility', { default: {} })
|
|
125
|
+
const columnPinningState = defineModel<ColumnPinningState>('columnPinning', { default: {} })
|
|
126
|
+
const rowSelectionState = defineModel<RowSelectionState>('rowSelection', { default: {} })
|
|
127
|
+
const sortingState = defineModel<SortingState>('sorting', { default: [] })
|
|
128
|
+
const expandedState = defineModel<ExpandedState>('expanded', { default: {} })
|
|
129
|
+
|
|
130
|
+
const tableApi = useVueTable({
|
|
131
|
+
data,
|
|
132
|
+
columns: columns.value,
|
|
133
|
+
getCoreRowModel: getCoreRowModel(),
|
|
134
|
+
...(props.globalFilterOptions || {}),
|
|
135
|
+
onGlobalFilterChange: updaterOrValue => valueUpdater(updaterOrValue, globalFilterState),
|
|
136
|
+
...(props.columnFiltersOptions || {}),
|
|
137
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
138
|
+
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFiltersState),
|
|
139
|
+
...(props.visibilityOptions || {}),
|
|
140
|
+
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibilityState),
|
|
141
|
+
...(props.columnPinningOptions || {}),
|
|
142
|
+
onColumnPinningChange: updaterOrValue => valueUpdater(updaterOrValue, columnPinningState),
|
|
143
|
+
...(props.rowSelectionOptions || {}),
|
|
144
|
+
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelectionState),
|
|
145
|
+
...(props.sortingOptions || {}),
|
|
146
|
+
getSortedRowModel: getSortedRowModel(),
|
|
147
|
+
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sortingState),
|
|
148
|
+
...(props.expandedOptions || {}),
|
|
149
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
150
|
+
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expandedState),
|
|
151
|
+
state: {
|
|
152
|
+
get globalFilter() {
|
|
153
|
+
return globalFilterState.value
|
|
154
|
+
},
|
|
155
|
+
get columnFilters() {
|
|
156
|
+
return columnFiltersState.value
|
|
157
|
+
},
|
|
158
|
+
get columnVisibility() {
|
|
159
|
+
return columnVisibilityState.value
|
|
160
|
+
},
|
|
161
|
+
get columnPinning() {
|
|
162
|
+
return columnPinningState.value
|
|
163
|
+
},
|
|
164
|
+
get expanded() {
|
|
165
|
+
return expandedState.value
|
|
166
|
+
},
|
|
167
|
+
get rowSelection() {
|
|
168
|
+
return rowSelectionState.value
|
|
169
|
+
},
|
|
170
|
+
get sorting() {
|
|
171
|
+
return sortingState.value
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
|
|
177
|
+
ref.value = typeof updaterOrValue === 'function' ? updaterOrValue(ref.value) : updaterOrValue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
defineExpose({
|
|
181
|
+
tableApi
|
|
182
|
+
})
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<template>
|
|
186
|
+
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
|
187
|
+
<table :class="ui.base({ class: [props.ui?.base] })">
|
|
188
|
+
<thead :class="ui.thead({ class: [props.ui?.thead] })">
|
|
189
|
+
<tr v-for="headerGroup in tableApi.getHeaderGroups()" :key="headerGroup.id" :class="ui.tr({ class: [props.ui?.tr] })">
|
|
190
|
+
<th
|
|
191
|
+
v-for="header in headerGroup.headers"
|
|
192
|
+
:key="header.id"
|
|
193
|
+
:data-pinned="header.column.getIsPinned()"
|
|
194
|
+
:class="ui.th({ class: [props.ui?.th], pinned: !!header.column.getIsPinned() })"
|
|
195
|
+
>
|
|
196
|
+
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
|
|
197
|
+
</th>
|
|
198
|
+
</tr>
|
|
199
|
+
</thead>
|
|
200
|
+
|
|
201
|
+
<tbody :class="ui.tbody({ class: [props.ui?.tbody] })">
|
|
202
|
+
<template v-if="tableApi.getRowModel().rows?.length">
|
|
203
|
+
<template v-for="row in tableApi.getRowModel().rows" :key="row.id">
|
|
204
|
+
<tr :data-selected="row.getIsSelected()" :data-expanded="row.getIsExpanded()" :class="ui.tr({ class: [props.ui?.tr] })">
|
|
205
|
+
<td
|
|
206
|
+
v-for="cell in row.getVisibleCells()"
|
|
207
|
+
:key="cell.id"
|
|
208
|
+
:data-pinned="cell.column.getIsPinned()"
|
|
209
|
+
:class="ui.td({ class: [props.ui?.td], pinned: !!cell.column.getIsPinned() })"
|
|
210
|
+
>
|
|
211
|
+
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
|
212
|
+
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
<tr v-if="row.getIsExpanded()" :class="ui.tr({ class: [props.ui?.tr] })">
|
|
215
|
+
<td :colspan="row.getAllCells().length" :class="ui.td({ class: [props.ui?.td] })">
|
|
216
|
+
<slot name="expanded" :row="row" />
|
|
217
|
+
</td>
|
|
218
|
+
</tr>
|
|
219
|
+
</template>
|
|
220
|
+
</template>
|
|
221
|
+
|
|
222
|
+
<tr v-else :class="ui.tr({ class: [props.ui?.tr] })">
|
|
223
|
+
<td :colspan="columns?.length" :class="ui.empty({ class: props.ui?.empty })">
|
|
224
|
+
<slot name="empty">
|
|
225
|
+
No results
|
|
226
|
+
</slot>
|
|
227
|
+
</td>
|
|
228
|
+
</tr>
|
|
229
|
+
</tbody>
|
|
230
|
+
</table>
|
|
231
|
+
</div>
|
|
232
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
4
|
-
import type { TabsRootProps, TabsRootEmits, TabsContentProps
|
|
4
|
+
import type { TabsRootProps, TabsRootEmits, TabsContentProps } from 'radix-vue'
|
|
5
5
|
import type { AppConfig } from '@nuxt/schema'
|
|
6
6
|
import _appConfig from '#build/app.config'
|
|
7
7
|
import theme from '#build/ui/tabs'
|
|
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { tabs: Partial<typeof theme>
|
|
|
12
12
|
|
|
13
13
|
const tabs = tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {}) })
|
|
14
14
|
|
|
15
|
-
export interface TabsItem
|
|
15
|
+
export interface TabsItem {
|
|
16
16
|
label?: string
|
|
17
17
|
icon?: string
|
|
18
18
|
avatar?: AvatarProps
|
|
@@ -20,6 +20,7 @@ export interface TabsItem extends Pick<TabsTriggerProps, 'disabled'> {
|
|
|
20
20
|
content?: string
|
|
21
21
|
/** A unique value for the tab item. Defaults to the index. */
|
|
22
22
|
value?: string | number
|
|
23
|
+
disabled?: boolean
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
type TabsVariants = VariantProps<typeof tabs>
|
|
@@ -44,6 +45,11 @@ export interface TabsProps<T> extends Pick<TabsRootProps<string | number>, 'defa
|
|
|
44
45
|
* @defaultValue true
|
|
45
46
|
*/
|
|
46
47
|
content?: boolean | Omit<TabsContentProps, 'as' | 'asChild' | 'value'>
|
|
48
|
+
/**
|
|
49
|
+
* The key used to get the label from the item.
|
|
50
|
+
* @defaultValue 'label'
|
|
51
|
+
*/
|
|
52
|
+
labelKey?: string
|
|
47
53
|
class?: any
|
|
48
54
|
ui?: PartialString<typeof tabs.slots>
|
|
49
55
|
}
|
|
@@ -66,11 +72,13 @@ import { computed, toRef } from 'vue'
|
|
|
66
72
|
import { defu } from 'defu'
|
|
67
73
|
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'radix-vue'
|
|
68
74
|
import { reactivePick } from '@vueuse/core'
|
|
75
|
+
import { get } from '../utils'
|
|
69
76
|
|
|
70
77
|
const props = withDefaults(defineProps<TabsProps<T>>(), {
|
|
71
78
|
content: true,
|
|
72
79
|
defaultValue: '0',
|
|
73
|
-
orientation: 'horizontal'
|
|
80
|
+
orientation: 'horizontal',
|
|
81
|
+
labelKey: 'label'
|
|
74
82
|
})
|
|
75
83
|
const emits = defineEmits<TabsEmits>()
|
|
76
84
|
const slots = defineSlots<TabsSlots<T>>()
|
|
@@ -93,12 +101,12 @@ const ui = computed(() => tabs({
|
|
|
93
101
|
|
|
94
102
|
<TabsTrigger v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :disabled="item.disabled" :class="ui.trigger({ class: props.ui?.trigger })">
|
|
95
103
|
<slot name="leading" :item="item" :index="index">
|
|
96
|
-
<
|
|
97
|
-
<
|
|
104
|
+
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
105
|
+
<UAvatar v-else-if="item.avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
|
|
98
106
|
</slot>
|
|
99
107
|
|
|
100
|
-
<span v-if="item.
|
|
101
|
-
<slot :item="item" :index="index">{{ item.
|
|
108
|
+
<span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: props.ui?.label })">
|
|
109
|
+
<slot :item="item" :index="index">{{ get(item, props.labelKey as string) }}</slot>
|
|
102
110
|
</span>
|
|
103
111
|
|
|
104
112
|
<slot name="trailing" :item="item" :index="index" />
|
|
@@ -31,7 +31,7 @@ export interface ToastProps extends Pick<ToastRootProps, 'defaultOpen' | 'open'
|
|
|
31
31
|
actions?: ButtonProps[]
|
|
32
32
|
/**
|
|
33
33
|
* Display a close button to dismiss the toast.
|
|
34
|
-
* `{ size: 'md', color: '
|
|
34
|
+
* `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
|
|
35
35
|
* @defaultValue true
|
|
36
36
|
*/
|
|
37
37
|
close?: ButtonProps | boolean
|
|
@@ -145,7 +145,7 @@ defineExpose({
|
|
|
145
145
|
v-if="close"
|
|
146
146
|
:icon="closeIcon || appConfig.ui.icons.close"
|
|
147
147
|
size="md"
|
|
148
|
-
color="
|
|
148
|
+
color="neutral"
|
|
149
149
|
variant="link"
|
|
150
150
|
aria-label="Close"
|
|
151
151
|
v-bind="typeof close === 'object' ? close : undefined"
|
|
@@ -32,8 +32,8 @@ import { ref, computed } from 'vue'
|
|
|
32
32
|
import { ToastProvider, ToastViewport, useForwardProps } from 'radix-vue'
|
|
33
33
|
import { reactivePick } from '@vueuse/core'
|
|
34
34
|
import { useToast } from '../composables/useToast'
|
|
35
|
-
import UToast from './Toast.vue'
|
|
36
35
|
import { omit } from '../utils'
|
|
36
|
+
import UToast from './Toast.vue'
|
|
37
37
|
|
|
38
38
|
const props = withDefaults(defineProps<ToasterProps>(), {
|
|
39
39
|
expand: true,
|
|
@@ -9,7 +9,7 @@ export function extractShortcuts(items) {
|
|
|
9
9
|
items2.forEach((item) => {
|
|
10
10
|
if (item.kbds?.length && (item.select || item.click)) {
|
|
11
11
|
const shortcutKey = item.kbds.join("_");
|
|
12
|
-
shortcuts[shortcutKey] = item.
|
|
12
|
+
shortcuts[shortcutKey] = item.onSelect || item.onClick;
|
|
13
13
|
}
|
|
14
14
|
if (item.children) {
|
|
15
15
|
traverse(item.children.flat());
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { AvatarProps } from '../types';
|
|
2
3
|
export interface UseComponentIconsProps {
|
|
3
4
|
/** Display an icon based on the `leading` and `trailing` props. */
|
|
4
5
|
icon?: string;
|
|
6
|
+
/** Display an avatar on the left side. */
|
|
7
|
+
avatar?: AvatarProps;
|
|
5
8
|
/** When `true`, the icon will be displayed on the left side. */
|
|
6
9
|
leading?: boolean;
|
|
7
10
|
/** Display an icon on the left side. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ShallowRef, Component, InjectionKey } from 'vue';
|
|
2
|
+
import type { ComponentProps } from 'vue-component-type-helpers';
|
|
2
3
|
import type { ModalProps } from '../types';
|
|
3
|
-
import type { ComponentProps } from '../types/component';
|
|
4
4
|
export interface ModalState {
|
|
5
5
|
component: Component | string;
|
|
6
6
|
props: ModalProps;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ShallowRef, Component, InjectionKey } from 'vue';
|
|
2
|
+
import type { ComponentProps } from 'vue-component-type-helpers';
|
|
2
3
|
import type { SlideoverProps } from '../types';
|
|
3
|
-
import type { ComponentProps } from '../types/component';
|
|
4
4
|
export interface SlideoverState {
|
|
5
5
|
component: Component | string;
|
|
6
6
|
props: SlideoverProps;
|
package/dist/runtime/index.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@theme{--
|
|
1
|
+
@import "./keyframes.css";@theme default{--spacing-4_5:1.125rem}@variant dark (&:where(.dark, .dark *));@layer base{body{@apply antialiased text-[var(--ui-text)] bg-[var(--ui-bg)]}:root{color-scheme:light;--ui-text-dimmed:var(--ui-color-neutral-400);--ui-text-muted:var(--ui-color-neutral-500);--ui-text-toned:var(--ui-color-neutral-600);--ui-text:var(--ui-color-neutral-700);--ui-text-highlighted:var(--ui-color-neutral-900);--ui-bg:var(--color-white);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-200);--ui-bg-inverted:var(--ui-color-neutral-900);--ui-border:var(--ui-color-neutral-200);--ui-border-accented:var(--ui-color-neutral-300);--ui-border-inverted:var(--ui-color-neutral-900);--ui-radius:var(--radius)}.dark{color-scheme:dark;--ui-text-dimmed:var(--ui-color-neutral-500);--ui-text-muted:var(--ui-color-neutral-400);--ui-text-toned:var(--ui-color-neutral-300);--ui-text:var(--ui-color-neutral-200);--ui-text-highlighted:var(--color-white);--ui-bg:var(--ui-color-neutral-900);--ui-bg-elevated:var(--ui-color-neutral-800);--ui-bg-accented:var(--ui-color-neutral-700);--ui-bg-inverted:var(--color-white);--ui-border:var(--ui-color-neutral-800);--ui-border-accented:var(--ui-color-neutral-700);--ui-border-inverted:var(--color-white)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}@keyframes collapsible-up{0%{height:var(--radix-collapsible-content-height)}to{height:0}}@keyframes collapsible-down{0%{height:0}to{height:var(--radix-collapsible-content-height)}}@keyframes toast-collapsed-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--before) - var(--height))*var(--gap))) scale(var(--scale))}}@keyframes toast-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--offset) - var(--height))*var(--translate-factor)))}}@keyframes toast-slide-left{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(-100%) translateY(var(--translate))}}@keyframes toast-slide-right{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(100%) translateY(var(--translate))}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slide-in-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes slide-out-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes slide-in-from-right{0%{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes slide-out-to-right{0%{transform:translateX(0)}to{transform:translateX(100%)}}@keyframes slide-in-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slide-out-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes slide-in-from-left{0%{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes slide-out-to-left{0%{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes slide-in-from-top-and-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-top-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes slide-in-from-right-and-fade{0%{opacity:0;transform:translateX(4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-right-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(4px)}}@keyframes slide-in-from-bottom-and-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-bottom-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(4px)}}@keyframes slide-in-from-left-and-fade{0%{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-left-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-4px)}}@keyframes enter-from-right{0%{opacity:0;transform:translateX(200px)}to{opacity:1;transform:translateX(0)}}@keyframes enter-from-left{0%{opacity:0;transform:translateX(-200px)}to{opacity:1;transform:translateX(0)}}@keyframes exit-to-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(200px)}}@keyframes exit-to-left{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-200px)}}@keyframes carousel{0%,to{width:50%}0%{transform:translateX(-100%)}to{transform:translateX(200%)}}@keyframes carousel-vertical{0%,to{height:50%}0%{transform:translateY(-100%)}to{transform:translateY(200%)}}@keyframes carousel-inverse{0%,to{width:50%}0%{transform:translateX(200%)}to{transform:translateX(-100%)}}@keyframes carousel-inverse-vertical{0%,to{height:50%}0%{transform:translateY(200%)}to{transform:translateY(-100%)}}@keyframes swing{0%,to{width:50%}0%,to{transform:translateX(-25%)}50%{transform:translateX(125%)}}@keyframes swing-vertical{0%,to{height:50%}0%,to{transform:translateY(-25%)}50%{transform:translateY(125%)}}@keyframes elastic{0%,to{margin-left:25%;width:50%}50%{margin-left:5%;width:90%}}@keyframes elastic-vertical{0%,to{height:50%;margin-top:25%}50%{height:90%;margin-top:5%}}
|
|
@@ -4,18 +4,22 @@ export default defineNuxtPlugin(() => {
|
|
|
4
4
|
const appConfig = useAppConfig();
|
|
5
5
|
const nuxtApp = useNuxtApp();
|
|
6
6
|
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
|
7
|
-
function
|
|
8
|
-
return `${shades.map((shade) => `--color-${key}-${shade}: var(--color-${value}-${shade});`).join("\n ")}`;
|
|
7
|
+
function generateShades(key, value) {
|
|
8
|
+
return `${shades.map((shade) => `--ui-color-${key}-${shade}: var(--color-${value}-${shade});`).join("\n ")}`;
|
|
9
|
+
}
|
|
10
|
+
function generateColor(key, shade) {
|
|
11
|
+
return `--ui-${key}: var(--ui-color-${key}-${shade});`;
|
|
9
12
|
}
|
|
10
13
|
const root = computed(() => {
|
|
14
|
+
const { neutral, ...colors } = appConfig.ui.colors;
|
|
11
15
|
return `:root {
|
|
12
|
-
${Object.entries(appConfig.ui.colors).map(([key, value]) =>
|
|
13
|
-
|
|
16
|
+
${Object.entries(appConfig.ui.colors).map(([key, value]) => generateShades(key, value)).join("\n ")}
|
|
17
|
+
|
|
18
|
+
${Object.keys(colors).map((key) => generateColor(key, 500)).join("\n ")}
|
|
14
19
|
}
|
|
15
20
|
.dark {
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
`;
|
|
21
|
+
${Object.keys(colors).map((key) => generateColor(key, 400)).join("\n ")}
|
|
22
|
+
}`;
|
|
19
23
|
});
|
|
20
24
|
const headData = {
|
|
21
25
|
style: [{
|