@nexxtmove/ui 1.33.0 → 1.35.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.
- package/dist/components/atoms/FloatingPanel/FloatingPanel.vue.d.ts +48 -7
- package/dist/components/atoms/PickerItem/PickerItem.vue.d.ts +40 -0
- package/dist/components/atoms/PickerPreview/PickerPreview.vue.d.ts +29 -0
- package/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +4 -1
- package/dist/components/editor/molecules/EditorRadius/EditorRadius.vue.d.ts +2 -2
- package/dist/components/molecules/PickerGroup/PickerGroup.vue.d.ts +32 -0
- package/dist/components/molecules/PickerList/PickerList.vue.d.ts +107 -0
- package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +8 -1
- package/dist/components/organisms/PickerPanel/PickerPanel.vue.d.ts +93 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2387 -1612
- package/dist/nuxt.js +5 -0
- package/package.json +1 -1
- package/src/components/atoms/FloatingPanel/FloatingPanel.vue +280 -20
- package/src/components/atoms/PickerItem/PickerItem.vue +113 -0
- package/src/components/atoms/PickerPreview/PickerPreview.vue +99 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.vue +8 -1
- package/src/components/molecules/PickerGroup/PickerGroup.vue +107 -0
- package/src/components/molecules/PickerList/PickerList.vue +649 -0
- package/src/components/organisms/ColorPicker/ColorPicker.vue +86 -10
- package/src/components/organisms/PickerPanel/PickerPanel.vue +267 -0
- package/src/components.json +5 -0
- package/src/index.ts +9 -0
|
@@ -19,6 +19,7 @@ import NexxtFloatingPanel from '../../atoms/FloatingPanel/FloatingPanel.vue'
|
|
|
19
19
|
import NexxtIcon from '../../atoms/Icon/Icon.vue'
|
|
20
20
|
|
|
21
21
|
export type ColorFormat = 'hex' | 'hsl' | 'rgb'
|
|
22
|
+
export type ColorSuggestions = Partial<Record<ColorFormat, string[]>>
|
|
22
23
|
|
|
23
24
|
export interface NexxtColorPickerProps {
|
|
24
25
|
label?: string
|
|
@@ -30,6 +31,10 @@ export interface NexxtColorPickerProps {
|
|
|
30
31
|
formats?: ColorFormat[]
|
|
31
32
|
fullWidth?: boolean
|
|
32
33
|
matchWidth?: boolean
|
|
34
|
+
/** Existing colours grouped by format; only suggestions for the active format are shown. */
|
|
35
|
+
suggestions?: ColorSuggestions
|
|
36
|
+
/** Accessible and visible label for the suggestions. */
|
|
37
|
+
suggestionsLabel?: string
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
defineOptions({ name: 'NexxtColorPicker' })
|
|
@@ -42,22 +47,62 @@ const props = withDefaults(defineProps<NexxtColorPickerProps>(), {
|
|
|
42
47
|
formats: () => ['hex'],
|
|
43
48
|
fullWidth: false,
|
|
44
49
|
matchWidth: false,
|
|
50
|
+
suggestions: () => ({}),
|
|
51
|
+
suggestionsLabel: 'Gebruikte kleuren',
|
|
45
52
|
})
|
|
46
53
|
|
|
47
54
|
const model = defineModel<string>({ default: '' })
|
|
48
55
|
|
|
49
|
-
const
|
|
50
|
-
|
|
56
|
+
const withoutAlpha = (color: Color) =>
|
|
57
|
+
color.alpha < 1 ? setChannelValue(color, 'alpha', 100) : color
|
|
58
|
+
|
|
59
|
+
const normalizeModelColor = (value: string) => {
|
|
60
|
+
const color = normalizeColor(value)
|
|
61
|
+
|
|
62
|
+
return props.showAlpha ? color : withoutAlpha(color)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const normalizeInputColor = (value: string) => {
|
|
66
|
+
const color = normalizeColor(value)
|
|
67
|
+
|
|
68
|
+
if (!props.showAlpha && color.alpha < 1) {
|
|
69
|
+
throw new Error('Transparantie is niet beschikbaar')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return color
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
const activeFormatIndex = ref(0)
|
|
76
|
+
const activeFormat = computed<ColorFormat>(() => props.formats[activeFormatIndex.value] ?? 'hex')
|
|
77
|
+
const fmt = (color: Color, format: ColorFormat) => {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
return colorToString(color, format as any)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const colorObj = ref<Color>(normalizeModelColor(model.value || '#3b82f6'))
|
|
83
|
+
const hexInputError = ref('')
|
|
52
84
|
const channelInputErrors = ref<Partial<Record<ColorChannel, string>>>({})
|
|
53
85
|
|
|
54
86
|
const hexColor = computed(() => colorToString(colorObj.value, 'hex'))
|
|
55
|
-
const activeFormat = computed<ColorFormat>(() => props.formats[activeFormatIndex.value] ?? 'hex')
|
|
56
87
|
const displayValue = computed(() => (model.value ? fmt(colorObj.value, activeFormat.value) : ''))
|
|
88
|
+
const colorSuggestions = computed(() => {
|
|
89
|
+
const values = new Set<string>()
|
|
57
90
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
props.suggestions[activeFormat.value]?.forEach((suggestion) => {
|
|
92
|
+
try {
|
|
93
|
+
const color = normalizeInputColor(suggestion)
|
|
94
|
+
values.add(fmt(color, activeFormat.value))
|
|
95
|
+
} catch {
|
|
96
|
+
// Consumers can derive suggestions from stored classes; ignore malformed or unsupported values safely.
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return [...values]
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const selectSuggestion = (suggestion: string) => {
|
|
104
|
+
colorObj.value = normalizeInputColor(suggestion)
|
|
105
|
+
model.value = fmt(colorObj.value, activeFormat.value)
|
|
61
106
|
}
|
|
62
107
|
|
|
63
108
|
const cycleFormat = (direction: 1 | -1) => {
|
|
@@ -67,14 +112,15 @@ const cycleFormat = (direction: 1 | -1) => {
|
|
|
67
112
|
}
|
|
68
113
|
|
|
69
114
|
const handleColorUpdate = (newColor: Color) => {
|
|
70
|
-
|
|
71
|
-
|
|
115
|
+
const updated = props.showAlpha ? newColor : withoutAlpha(newColor)
|
|
116
|
+
colorObj.value = updated
|
|
117
|
+
model.value = fmt(updated, activeFormat.value)
|
|
72
118
|
}
|
|
73
119
|
|
|
74
120
|
const handleHexInput = (event: Event) => {
|
|
75
121
|
const val = (event.target as HTMLInputElement).value
|
|
76
122
|
try {
|
|
77
|
-
colorObj.value =
|
|
123
|
+
colorObj.value = normalizeInputColor(val)
|
|
78
124
|
model.value = fmt(colorObj.value, activeFormat.value)
|
|
79
125
|
hexInputError.value = ''
|
|
80
126
|
} catch (e) {
|
|
@@ -103,12 +149,23 @@ watch(
|
|
|
103
149
|
(val) => {
|
|
104
150
|
if (!val) return
|
|
105
151
|
try {
|
|
106
|
-
colorObj.value =
|
|
152
|
+
colorObj.value = normalizeModelColor(val)
|
|
107
153
|
} catch {
|
|
108
154
|
// Ignore invalid color strings
|
|
109
155
|
}
|
|
110
156
|
},
|
|
111
157
|
)
|
|
158
|
+
|
|
159
|
+
watch(
|
|
160
|
+
() => props.showAlpha,
|
|
161
|
+
(showAlpha) => {
|
|
162
|
+
if (!showAlpha && colorObj.value.alpha < 1) {
|
|
163
|
+
const updated = withoutAlpha(colorObj.value)
|
|
164
|
+
colorObj.value = updated
|
|
165
|
+
model.value = fmt(updated, activeFormat.value)
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
)
|
|
112
169
|
</script>
|
|
113
170
|
|
|
114
171
|
<template>
|
|
@@ -386,6 +443,25 @@ watch(
|
|
|
386
443
|
</ColorFieldRoot>
|
|
387
444
|
</div>
|
|
388
445
|
</div>
|
|
446
|
+
<div v-if="colorSuggestions.length" class="flex flex-col gap-1">
|
|
447
|
+
<span class="extra-small-normal text-gray-500">{{ suggestionsLabel }}</span>
|
|
448
|
+
<div class="flex flex-wrap gap-1.5">
|
|
449
|
+
<button
|
|
450
|
+
v-for="suggestion in colorSuggestions"
|
|
451
|
+
:key="suggestion"
|
|
452
|
+
type="button"
|
|
453
|
+
class="rounded-md p-0.5 ring-gray-200 transition-shadow hover:ring-2 hover:ring-cornflower-blue-400 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none"
|
|
454
|
+
:aria-label="`Kies kleur ${suggestion}`"
|
|
455
|
+
@click="selectSuggestion(suggestion)"
|
|
456
|
+
>
|
|
457
|
+
<ColorSwatch
|
|
458
|
+
:color="suggestion"
|
|
459
|
+
class="size-6 rounded-sm shadow-[inset_0_0_0_1px_rgba(0,0,0,0.1)]"
|
|
460
|
+
:style="{ backgroundColor: 'var(--reka-color-swatch-color)' }"
|
|
461
|
+
/>
|
|
462
|
+
</button>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
389
465
|
</div>
|
|
390
466
|
</template>
|
|
391
467
|
</NexxtFloatingPanel>
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'
|
|
3
|
+
import { useResizeObserver } from '@vueuse/core'
|
|
4
|
+
import NexxtPickerPreview from '../../atoms/PickerPreview/PickerPreview.vue'
|
|
5
|
+
import NexxtInputField from '../../molecules/InputField/InputField.vue'
|
|
6
|
+
import NexxtPickerList, {
|
|
7
|
+
type NexxtPickerGroupData,
|
|
8
|
+
type NexxtPickerItemData,
|
|
9
|
+
} from '../../molecules/PickerList/PickerList.vue'
|
|
10
|
+
|
|
11
|
+
interface NexxtPickerPanelProps {
|
|
12
|
+
/** All items; the search field filters them. */
|
|
13
|
+
items: NexxtPickerItemData[]
|
|
14
|
+
/** Groups in display order. Ungrouped items come first. */
|
|
15
|
+
groups?: NexxtPickerGroupData[]
|
|
16
|
+
/** Render the search field. Without it the listbox takes focus on mount. */
|
|
17
|
+
searchable?: boolean
|
|
18
|
+
/** Placeholder of the search field. */
|
|
19
|
+
searchPlaceholder?: string
|
|
20
|
+
/** Accessible name of the search field. Required for an accessible name; the placeholder is only a fallback. */
|
|
21
|
+
searchLabel?: string
|
|
22
|
+
/** Accessible name of the panel (`role="group"`). Required for an accessible name. */
|
|
23
|
+
ariaLabel?: string
|
|
24
|
+
/** Accessible name of the listbox. Required for an accessible name. */
|
|
25
|
+
listLabel?: string
|
|
26
|
+
/** Panel width; a number is in pixels. The columns never shrink with it. */
|
|
27
|
+
width?: number | string
|
|
28
|
+
/** Panel height; a number is in pixels. Clamped to `calc(100dvh - 5rem)`, and only the list gives way. */
|
|
29
|
+
height?: number | string
|
|
30
|
+
/** Below this height of the column row (the panel minus the header), in pixels, the preview column is left out and the list takes the full width. */
|
|
31
|
+
hidePreviewBelow?: number
|
|
32
|
+
/** Whether group headings fold their group. */
|
|
33
|
+
collapsible?: boolean
|
|
34
|
+
/** Heading of the empty state, shown when the search has no results. */
|
|
35
|
+
emptyTitle?: string
|
|
36
|
+
/** Line under the empty heading. A function receives the trimmed search term. */
|
|
37
|
+
emptyDescription?: string | ((search: string) => string)
|
|
38
|
+
/** Label of the button in the empty state that clears the search. Without it there is no button. */
|
|
39
|
+
clearLabel?: string
|
|
40
|
+
/** Text in the preview frame while nothing is highlighted; the `preview-empty` slot replaces it. */
|
|
41
|
+
previewEmptyText?: string
|
|
42
|
+
/** Text announced 500ms after the search changes, e.g. `(n) => \`${n} resultaten\``. */
|
|
43
|
+
resultsAnnouncement?: (count: number) => string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
defineOptions({ name: 'NexxtPickerPanel' })
|
|
47
|
+
|
|
48
|
+
const props = withDefaults(defineProps<NexxtPickerPanelProps>(), {
|
|
49
|
+
groups: () => [],
|
|
50
|
+
searchable: true,
|
|
51
|
+
width: 640,
|
|
52
|
+
height: 420,
|
|
53
|
+
hidePreviewBelow: 340,
|
|
54
|
+
collapsible: true,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/** Id of the highlighted item; the list keeps it on a visible item. Drives the preview. */
|
|
58
|
+
const highlighted = defineModel<string | null>('highlighted', { default: null })
|
|
59
|
+
|
|
60
|
+
/** The search term. Bind it to reset or prefill the search; unbound, the panel keeps its own. */
|
|
61
|
+
const search = defineModel<string>('search', { default: '' })
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits<{
|
|
64
|
+
/** Enter, Space or a click on an enabled item. The panel never closes itself. */
|
|
65
|
+
select: [item: NexxtPickerItemData]
|
|
66
|
+
/** The highlighted item changed; `null` when nothing is visible. */
|
|
67
|
+
highlight: [item: NexxtPickerItemData | null]
|
|
68
|
+
/**
|
|
69
|
+
* Escape in an empty search field, or in the listbox. A surrounding `NexxtFloatingPanel`
|
|
70
|
+
* closes on the same key, so keep the handler idempotent.
|
|
71
|
+
*/
|
|
72
|
+
close: []
|
|
73
|
+
}>()
|
|
74
|
+
|
|
75
|
+
defineSlots<{
|
|
76
|
+
/** Above both columns, e.g. `NexxtTabs`. */
|
|
77
|
+
header?(): unknown
|
|
78
|
+
/** Content of the preview frame for the highlighted item. */
|
|
79
|
+
preview?(props: { item: NexxtPickerItemData }): unknown
|
|
80
|
+
/** Replaces `previewEmptyText` in the preview frame. */
|
|
81
|
+
'preview-empty'?(): unknown
|
|
82
|
+
/** Content of an option. */
|
|
83
|
+
item?(props: { item: NexxtPickerItemData; highlighted: boolean }): unknown
|
|
84
|
+
/** Replaces the content of the empty search state. */
|
|
85
|
+
empty?(props: { search: string; clear: () => void }): unknown
|
|
86
|
+
}>()
|
|
87
|
+
|
|
88
|
+
const inputField = useTemplateRef<InstanceType<typeof NexxtInputField>>('inputField')
|
|
89
|
+
const list = useTemplateRef<InstanceType<typeof NexxtPickerList>>('list')
|
|
90
|
+
const row = useTemplateRef<HTMLElement>('row')
|
|
91
|
+
|
|
92
|
+
const PAGE_SIZE = 8
|
|
93
|
+
|
|
94
|
+
const toLength = (value: number | string) => (typeof value === 'number' ? `${value}px` : value)
|
|
95
|
+
|
|
96
|
+
const panelStyle = computed(() => {
|
|
97
|
+
const height = `min(${toLength(props.height)}, calc(100dvh - 5rem))`
|
|
98
|
+
return { width: toLength(props.width), height, maxHeight: height }
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Layout height of the column row. Not `getBoundingClientRect()`: a FloatingPanel scales its
|
|
102
|
+
// content in, which would read 5% short. 0 means unknown (jsdom, a hidden tab): show the preview.
|
|
103
|
+
const rowHeight = ref(0)
|
|
104
|
+
|
|
105
|
+
useResizeObserver(row, ([entry]) => {
|
|
106
|
+
rowHeight.value =
|
|
107
|
+
entry.borderBoxSize?.[0]?.blockSize ?? (entry.target as HTMLElement).offsetHeight
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const showPreview = computed(
|
|
111
|
+
() => rowHeight.value === 0 || rowHeight.value >= props.hidePreviewBelow,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const highlightedItem = computed(
|
|
115
|
+
() => props.items.find((item) => item.id === highlighted.value) ?? null,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
/** Focuses the search field, or the listbox when the panel is not searchable. */
|
|
119
|
+
const focus = () => {
|
|
120
|
+
if (props.searchable) inputField.value?.inputEl?.focus({ preventScroll: true })
|
|
121
|
+
else list.value?.focus()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
onMounted(async () => {
|
|
125
|
+
// Measured before the first paint, so the preview never flashes in and out
|
|
126
|
+
if (row.value) rowHeight.value = row.value.offsetHeight
|
|
127
|
+
|
|
128
|
+
// One tick for the list's template ref to land, so the field already carries
|
|
129
|
+
// `aria-controls` and `aria-activedescendant` when it receives focus. `preventScroll`: a
|
|
130
|
+
// FloatingPanel is still in its enter transition here.
|
|
131
|
+
await nextTick()
|
|
132
|
+
focus()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const onSearchKeydown = (event: KeyboardEvent) => {
|
|
136
|
+
if (event.isComposing) return
|
|
137
|
+
|
|
138
|
+
switch (event.key) {
|
|
139
|
+
case 'ArrowDown':
|
|
140
|
+
list.value?.move(1)
|
|
141
|
+
break
|
|
142
|
+
case 'ArrowUp':
|
|
143
|
+
list.value?.move(-1)
|
|
144
|
+
break
|
|
145
|
+
case 'PageDown':
|
|
146
|
+
list.value?.move(PAGE_SIZE)
|
|
147
|
+
break
|
|
148
|
+
case 'PageUp':
|
|
149
|
+
list.value?.move(-PAGE_SIZE)
|
|
150
|
+
break
|
|
151
|
+
case 'Enter':
|
|
152
|
+
// Always prevented: the field may sit in a form
|
|
153
|
+
list.value?.selectHighlighted()
|
|
154
|
+
break
|
|
155
|
+
case 'Escape':
|
|
156
|
+
if (search.value === '') {
|
|
157
|
+
// Left to propagate: a surrounding FloatingPanel closes on it too
|
|
158
|
+
emit('close')
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
search.value = ''
|
|
162
|
+
// A FloatingPanel listens on `document` and would close on the first press already
|
|
163
|
+
event.stopPropagation()
|
|
164
|
+
break
|
|
165
|
+
// Home, End, ← and → stay cursor keys in a text field
|
|
166
|
+
default:
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
event.preventDefault()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Focus moves in the same handler, before the clear button leaves the DOM with the empty state
|
|
174
|
+
const onClear = () => {
|
|
175
|
+
search.value = ''
|
|
176
|
+
focus()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
defineExpose({ focus })
|
|
180
|
+
|
|
181
|
+
const COLUMN_CLASSES = {
|
|
182
|
+
withPreview: 'flex w-66 shrink-0 flex-col border-r border-gray-200',
|
|
183
|
+
full: 'flex w-full flex-col',
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<div
|
|
189
|
+
role="group"
|
|
190
|
+
:aria-label="ariaLabel"
|
|
191
|
+
class="flex flex-col overflow-hidden"
|
|
192
|
+
:style="panelStyle"
|
|
193
|
+
>
|
|
194
|
+
<div v-if="$slots.header" class="shrink-0 border-b border-gray-200 p-3">
|
|
195
|
+
<slot name="header" />
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div ref="row" class="flex min-h-0 flex-1">
|
|
199
|
+
<div :class="showPreview ? COLUMN_CLASSES.withPreview : COLUMN_CLASSES.full">
|
|
200
|
+
<!-- `text-gray-700` colours the magnifying glass; the input sets its own text colour.
|
|
201
|
+
`[&_input]:min-w-0` lifts the input's own `min-w-60` inside the panel only; with it
|
|
202
|
+
the field overflows the 240px column and a long term loses its caret. -->
|
|
203
|
+
<div v-if="searchable" class="shrink-0 border-b border-gray-200 p-3 text-gray-700">
|
|
204
|
+
<NexxtInputField
|
|
205
|
+
ref="inputField"
|
|
206
|
+
v-model="search"
|
|
207
|
+
type="text"
|
|
208
|
+
role="combobox"
|
|
209
|
+
aria-expanded="true"
|
|
210
|
+
aria-autocomplete="list"
|
|
211
|
+
autocomplete="off"
|
|
212
|
+
spellcheck="false"
|
|
213
|
+
left-icon="magnifying-glass"
|
|
214
|
+
class="w-full [&_input]:min-w-0"
|
|
215
|
+
:placeholder="searchPlaceholder"
|
|
216
|
+
:aria-label="searchLabel"
|
|
217
|
+
:aria-controls="list?.listboxId"
|
|
218
|
+
:aria-activedescendant="list?.activeDescendantId"
|
|
219
|
+
@keydown="onSearchKeydown"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<NexxtPickerList
|
|
224
|
+
ref="list"
|
|
225
|
+
v-model:highlighted="highlighted"
|
|
226
|
+
class="min-h-0 flex-1"
|
|
227
|
+
:items="items"
|
|
228
|
+
:groups="groups"
|
|
229
|
+
:search="search"
|
|
230
|
+
:collapsible="collapsible"
|
|
231
|
+
:aria-label="listLabel"
|
|
232
|
+
:empty-title="emptyTitle"
|
|
233
|
+
:empty-description="emptyDescription"
|
|
234
|
+
:clear-label="clearLabel"
|
|
235
|
+
:results-announcement="resultsAnnouncement"
|
|
236
|
+
@select="emit('select', $event)"
|
|
237
|
+
@highlight="emit('highlight', $event)"
|
|
238
|
+
@close="emit('close')"
|
|
239
|
+
@clear="onClear"
|
|
240
|
+
>
|
|
241
|
+
<template v-if="$slots.item" #item="scope">
|
|
242
|
+
<slot name="item" v-bind="scope" />
|
|
243
|
+
</template>
|
|
244
|
+
<template v-if="$slots.empty" #empty="scope">
|
|
245
|
+
<slot name="empty" v-bind="scope" />
|
|
246
|
+
</template>
|
|
247
|
+
</NexxtPickerList>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div v-if="showPreview" class="min-w-0 flex-1 overflow-hidden p-4">
|
|
251
|
+
<NexxtPickerPreview
|
|
252
|
+
:content-key="highlighted ?? undefined"
|
|
253
|
+
:title="highlightedItem?.label"
|
|
254
|
+
:description="highlightedItem?.description"
|
|
255
|
+
:empty-text="previewEmptyText"
|
|
256
|
+
>
|
|
257
|
+
<template v-if="highlightedItem && $slots.preview" #default>
|
|
258
|
+
<slot name="preview" :item="highlightedItem" />
|
|
259
|
+
</template>
|
|
260
|
+
<template v-if="$slots['preview-empty']" #empty>
|
|
261
|
+
<slot name="preview-empty" />
|
|
262
|
+
</template>
|
|
263
|
+
</NexxtPickerPreview>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
</template>
|
package/src/components.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"NexxtFloatingPanel": "components/atoms/FloatingPanel/FloatingPanel.vue",
|
|
10
10
|
"NexxtIcon": "components/atoms/Icon/Icon.vue",
|
|
11
11
|
"NexxtLoader": "components/atoms/Loader/Loader.vue",
|
|
12
|
+
"NexxtPickerItem": "components/atoms/PickerItem/PickerItem.vue",
|
|
13
|
+
"NexxtPickerPreview": "components/atoms/PickerPreview/PickerPreview.vue",
|
|
12
14
|
"NexxtPresenceIndicator": "components/atoms/PresenceIndicator/PresenceIndicator.vue",
|
|
13
15
|
"NexxtPropertyListing": "components/atoms/PropertyListing/PropertyListing.vue",
|
|
14
16
|
"NexxtSelectableOption": "components/atoms/SelectableOption/SelectableOption.vue",
|
|
@@ -30,6 +32,8 @@
|
|
|
30
32
|
"NexxtPageMockup": "components/molecules/PageMockup/PageMockup.vue",
|
|
31
33
|
"NexxtPageTypeTile": "components/molecules/PageTypeTile/PageTypeTile.vue",
|
|
32
34
|
"NexxtPagination": "components/molecules/Pagination/Pagination.vue",
|
|
35
|
+
"NexxtPickerGroup": "components/molecules/PickerGroup/PickerGroup.vue",
|
|
36
|
+
"NexxtPickerList": "components/molecules/PickerList/PickerList.vue",
|
|
33
37
|
"NexxtPlaceholderInput": "components/molecules/PlaceholderInput/PlaceholderInput.vue",
|
|
34
38
|
"NexxtPropertyCard": "components/molecules/PropertyCard/PropertyCard.vue",
|
|
35
39
|
"NexxtSelect": "components/molecules/Select/Select.vue",
|
|
@@ -57,6 +61,7 @@
|
|
|
57
61
|
"NexxtDropdownButton": "components/organisms/DropdownButton/DropdownButton.vue",
|
|
58
62
|
"NexxtHeader": "components/organisms/Header/Header.vue",
|
|
59
63
|
"NexxtModal": "components/organisms/Modal/Modal.vue",
|
|
64
|
+
"NexxtPickerPanel": "components/organisms/PickerPanel/PickerPanel.vue",
|
|
60
65
|
"NexxtProgressBar": "components/organisms/ProgressBar/ProgressBar.vue",
|
|
61
66
|
"NexxtReferenceCard": "components/organisms/ReferenceCard/ReferenceCard.vue",
|
|
62
67
|
"NexxtSocialMediaTemplate": "components/organisms/SocialMediaTemplate/SocialMediaTemplate.vue",
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,10 @@ export type {
|
|
|
18
18
|
NexxtPageCardSlots,
|
|
19
19
|
} from './components/molecules/PageCard/PageCard.vue'
|
|
20
20
|
export type { NexxtPageMockupType } from './components/molecules/PageMockup/PageMockup.vue'
|
|
21
|
+
export type {
|
|
22
|
+
NexxtPickerGroupData,
|
|
23
|
+
NexxtPickerItemData,
|
|
24
|
+
} from './components/molecules/PickerList/PickerList.vue'
|
|
21
25
|
export type {
|
|
22
26
|
NexxtTreeNode,
|
|
23
27
|
NexxtTreeProps,
|
|
@@ -127,6 +131,11 @@ export { default as NexxtPageContainer } from './components/molecules/PageContai
|
|
|
127
131
|
export { default as NexxtPageMockup } from './components/molecules/PageMockup/PageMockup.vue'
|
|
128
132
|
export { default as NexxtPageTypeTile } from './components/molecules/PageTypeTile/PageTypeTile.vue'
|
|
129
133
|
export { default as NexxtPagination } from './components/molecules/Pagination/Pagination.vue'
|
|
134
|
+
export { default as NexxtPickerGroup } from './components/molecules/PickerGroup/PickerGroup.vue'
|
|
135
|
+
export { default as NexxtPickerItem } from './components/atoms/PickerItem/PickerItem.vue'
|
|
136
|
+
export { default as NexxtPickerList } from './components/molecules/PickerList/PickerList.vue'
|
|
137
|
+
export { default as NexxtPickerPanel } from './components/organisms/PickerPanel/PickerPanel.vue'
|
|
138
|
+
export { default as NexxtPickerPreview } from './components/atoms/PickerPreview/PickerPreview.vue'
|
|
130
139
|
export { default as NexxtPlaceholderInput } from './components/molecules/PlaceholderInput/PlaceholderInput.vue'
|
|
131
140
|
export { default as NexxtPresenceIndicator } from './components/atoms/PresenceIndicator/PresenceIndicator.vue'
|
|
132
141
|
export { default as NexxtProgressBar } from './components/organisms/ProgressBar/ProgressBar.vue'
|