@sparklingopinions/sparkling-ui 0.4.2 → 0.5.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/Board/Board.js.map +1 -1
- package/dist/components/Board/Board.vue.d.ts +6 -2
- package/dist/components/Board/Board.vue_vue_type_script_setup_true_lang.js +142 -74
- package/dist/components/Board/Board.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/Filters/FilterChip.js.map +1 -1
- package/dist/components/Filters/FilterChip.vue.d.ts +4 -1
- package/dist/components/Filters/FilterChip.vue_vue_type_script_setup_true_lang.js +29 -24
- package/dist/components/Filters/FilterChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/Filters/SortMenu.js.map +1 -1
- package/dist/components/Filters/SortMenu.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/Filters/SortMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/Filters/index.d.ts +1 -1
- package/dist/components/Filters/types.d.ts +8 -0
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Table/Table.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/Table/Table.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/Table/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.js","names":["_sfc_main","Board_vue_vue_type_script_setup_true_lang_default","Board_default"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n 'card-click': [item: T]\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\nfunction onDragStart(event: DragEvent, item: T, column: string, index: number) {\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', String(idOf(item)))\n }\n dragged.value = { id: idOf(item), column, index }\n}\n\nfunction onDragEnd() {\n dragged.value = null\n target.value = null\n}\n\nfunction overColumn(event: DragEvent, column: BoardColumn<T>) {\n if (!dragged.value) return\n event.preventDefault()\n if (target.value?.column !== column.id) target.value = { column: column.id, index: column.items.length }\n}\n\n// The card's top half drops before it, the bottom half after.\nfunction overCard(event: DragEvent, column: string, index: number) {\n if (!dragged.value) return\n event.preventDefault()\n event.stopPropagation()\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n const at = event.clientY < rect.top + rect.height / 2 ? index : index + 1\n if (target.value?.column !== column || target.value.index !== at) target.value = { column, index: at }\n}\n\nfunction onLeave(event: DragEvent) {\n if (!(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node | null)) target.value = null\n}\n\nfunction onDrop(event: DragEvent, column: BoardColumn<T>) {\n event.preventDefault()\n const from = dragged.value\n if (from) {\n let index = target.value?.column === column.id ? target.value.index : column.items.length\n if (from.column === column.id) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, column.id, index)\n } else {\n emit('move', from.id, column.id, index)\n }\n }\n onDragEnd()\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n emit('card-click', item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-sunken px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n @dragleave=\"onLeave\"\n @dragover=\"overColumn($event, column)\"\n @drop=\"onDrop($event, column)\"\n >\n <template v-if=\"loading\">\n <div\n v-for=\"i in 3\"\n :key=\"i\"\n aria-hidden=\"true\"\n class=\"rounded-xl border border-sui-line-subtle bg-sui-surface p-4\"\n >\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative cursor-pointer rounded-xl border border-sui-line-subtle bg-sui-surface p-4 outline-hidden transition-[border-color,opacity] duration-150 hover:border-sui-line focus-visible:sui-ring\"\n :class=\"[\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n draggable=\"true\"\n role=\"button\"\n tabindex=\"0\"\n @click=\"emit('card-click', item)\"\n @dragend=\"onDragEnd\"\n @dragover=\"overCard($event, column.id, index)\"\n @dragstart=\"onDragStart($event, item, column.id, index)\"\n @keydown=\"onCardKeydown($event, item)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-9 w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Board.js","names":["_sfc_main","Board_vue_vue_type_script_setup_true_lang_default","Board_default"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-line-subtle bg-sui-surface p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-sunken px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div\n v-for=\"i in 3\"\n :key=\"i\"\n aria-hidden=\"true\"\n class=\"rounded-xl border border-sui-line-subtle bg-sui-surface p-4\"\n >\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-9 w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -10,6 +10,11 @@ export interface BoardProps<T> {
|
|
|
10
10
|
loading?: boolean;
|
|
11
11
|
/** The columns' heading level, to fit the page's outline. */
|
|
12
12
|
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
13
|
+
/** Draws each card's border, padding and surface. False when the `card` slot draws its own. */
|
|
14
|
+
cardFrame?: boolean;
|
|
15
|
+
/** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its
|
|
16
|
+
* own link or buttons. */
|
|
17
|
+
onCardClick?: (item: T) => void;
|
|
13
18
|
/** Adds a dashed "New" button at the foot of each column, shown on hover (`@new`). */
|
|
14
19
|
onNew?: (column: BoardColumn<T>) => void;
|
|
15
20
|
newLabel?: string;
|
|
@@ -17,7 +22,6 @@ export interface BoardProps<T> {
|
|
|
17
22
|
}
|
|
18
23
|
declare const __VLS_export: <T extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
19
24
|
props: import("vue").PublicProps & __VLS_PrettifyLocal<BoardProps<T> & {
|
|
20
|
-
"onCard-click"?: ((item: T) => any) | undefined;
|
|
21
25
|
onMove?: ((id: unknown, column: string, index: number) => any) | undefined;
|
|
22
26
|
}> & (typeof globalThis extends {
|
|
23
27
|
__VLS_PROPS_FALLBACK: infer P;
|
|
@@ -35,7 +39,7 @@ declare const __VLS_export: <T extends Record<string, any>>(__VLS_props: NonNull
|
|
|
35
39
|
column: BoardColumn<T>;
|
|
36
40
|
}) => unknown;
|
|
37
41
|
};
|
|
38
|
-
emit: (
|
|
42
|
+
emit: (evt: "move", id: unknown, column: string, index: number) => void;
|
|
39
43
|
}>) => import("vue").VNode & {
|
|
40
44
|
__ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
|
|
41
45
|
};
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import e from "../Icon/Icon.js";
|
|
2
2
|
import t from "../Skeleton/Skeleton.js";
|
|
3
|
-
import { Fragment as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, normalizeClass as u,
|
|
3
|
+
import { Fragment as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, normalizeClass as u, onBeforeUnmount as d, openBlock as f, ref as p, renderList as m, renderSlot as h, resolveDynamicComponent as g, toDisplayString as _, unref as v, useId as y, withCtx as b, withModifiers as ee } from "vue";
|
|
4
4
|
//#region src/components/Board/Board.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
-
var
|
|
5
|
+
var te = ["aria-labelledby"], ne = { class: "flex items-center justify-between px-1 pb-2.5" }, re = { class: "ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-sunken px-2 text-xs text-sui-ink-muted tabular-nums" }, x = ["data-board-column"], S = {
|
|
6
6
|
key: 1,
|
|
7
7
|
class: "m-0 px-1 py-3 text-center text-13 text-sui-ink-muted"
|
|
8
|
-
},
|
|
8
|
+
}, C = {
|
|
9
9
|
key: 2,
|
|
10
10
|
class: "m-0 flex list-none flex-col gap-2.5 p-0"
|
|
11
|
-
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
11
|
+
}, w = [
|
|
12
|
+
"data-board-index",
|
|
13
|
+
"role",
|
|
14
|
+
"tabindex",
|
|
15
|
+
"onClickCapture",
|
|
16
|
+
"onKeydown",
|
|
17
|
+
"onPointerdown"
|
|
18
|
+
], T = { class: "m-0 text-sm font-semibold text-sui-ink" }, E = ["onClick"], D = 4, O = 8, k = 250, A = 48, j = "border border-sui-line-subtle bg-sui-surface p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line", M = /*@__PURE__*/ l({
|
|
17
19
|
__name: "Board",
|
|
18
20
|
props: {
|
|
19
21
|
columns: {},
|
|
@@ -25,6 +27,14 @@ var b = ["aria-labelledby"], x = { class: "flex items-center justify-between px-
|
|
|
25
27
|
},
|
|
26
28
|
loading: { type: Boolean },
|
|
27
29
|
headingLevel: { default: 3 },
|
|
30
|
+
cardFrame: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: !0
|
|
33
|
+
},
|
|
34
|
+
onCardClick: {
|
|
35
|
+
type: Function,
|
|
36
|
+
default: void 0
|
|
37
|
+
},
|
|
28
38
|
onNew: {
|
|
29
39
|
type: Function,
|
|
30
40
|
default: void 0
|
|
@@ -32,104 +42,162 @@ var b = ["aria-labelledby"], x = { class: "flex items-center justify-between px-
|
|
|
32
42
|
newLabel: { default: "New" },
|
|
33
43
|
emptyLabel: { default: "No cards" }
|
|
34
44
|
},
|
|
35
|
-
emits: ["
|
|
36
|
-
setup(l, { emit:
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
emits: ["move"],
|
|
46
|
+
setup(l, { emit: M }) {
|
|
47
|
+
let N = l, P = M, F = y(), I = (e) => e[N.rowKey ?? "id"], L = p(), R = p(null), z = p(null), B = null, V, H = null, U = {
|
|
48
|
+
x: 0,
|
|
49
|
+
y: 0
|
|
50
|
+
}, W = !1;
|
|
51
|
+
function G(e, t, n, r) {
|
|
52
|
+
if (e.button !== 0 || e.target.closest("input, textarea, select, [contenteditable]")) return;
|
|
53
|
+
let i = e.pointerType === "touch";
|
|
54
|
+
B = {
|
|
55
|
+
item: t,
|
|
41
56
|
column: n,
|
|
42
|
-
index: r
|
|
57
|
+
index: r,
|
|
58
|
+
x: e.clientX,
|
|
59
|
+
y: e.clientY,
|
|
60
|
+
card: e.currentTarget,
|
|
61
|
+
touch: i
|
|
62
|
+
}, i && (V = window.setTimeout(() => B && K(), k)), window.addEventListener("pointermove", q), window.addEventListener("pointerup", X), window.addEventListener("pointercancel", $), window.addEventListener("touchmove", Z, { passive: !1 }), window.addEventListener("keydown", Q);
|
|
63
|
+
}
|
|
64
|
+
function K() {
|
|
65
|
+
if (!B) return;
|
|
66
|
+
let e = B.card.getBoundingClientRect();
|
|
67
|
+
U = {
|
|
68
|
+
x: B.x - e.left,
|
|
69
|
+
y: B.y - e.top
|
|
70
|
+
}, H = B.card.cloneNode(!0), H.removeAttribute("id"), H.setAttribute("aria-hidden", "true"), H.classList.add("pointer-events-none", "fixed", "top-0", "left-0", "z-[1300]", "shadow-lg", "opacity-95"), H.style.width = `${e.width}px`, document.body.appendChild(H), J(B.x, B.y), R.value = {
|
|
71
|
+
id: I(B.item),
|
|
72
|
+
column: B.column,
|
|
73
|
+
index: B.index
|
|
43
74
|
};
|
|
44
75
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
76
|
+
function q(e) {
|
|
77
|
+
if (B) {
|
|
78
|
+
if (!R.value) {
|
|
79
|
+
let t = Math.hypot(e.clientX - B.x, e.clientY - B.y);
|
|
80
|
+
if (B.touch) return t > O ? $() : void 0;
|
|
81
|
+
if (t < D) return;
|
|
82
|
+
K();
|
|
83
|
+
}
|
|
84
|
+
e.preventDefault(), J(e.clientX, e.clientY), Y(e.clientX, e.clientY), ie(e.clientX, e.clientY);
|
|
85
|
+
}
|
|
47
86
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
column: t.id,
|
|
51
|
-
index: t.items.length
|
|
52
|
-
}));
|
|
87
|
+
function J(e, t) {
|
|
88
|
+
H && (H.style.transform = `translate(${e - U.x}px, ${t - U.y}px) rotate(1.5deg)`);
|
|
53
89
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
function Y(e, t) {
|
|
91
|
+
let n = document.elementFromPoint(e, t), r = n?.closest("[data-board-column]");
|
|
92
|
+
if (!r || !L.value?.contains(r)) {
|
|
93
|
+
z.value = null;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let i = r.dataset.boardColumn, a = n.closest("[data-board-index]"), o = N.columns.find((e) => e.id === i)?.items.length ?? 0;
|
|
97
|
+
if (a && r.contains(a)) {
|
|
98
|
+
let e = a.getBoundingClientRect();
|
|
99
|
+
o = Number(a.dataset.boardIndex) + (t < e.top + e.height / 2 ? 0 : 1);
|
|
100
|
+
}
|
|
101
|
+
(z.value?.column !== i || z.value.index !== o) && (z.value = {
|
|
102
|
+
column: i,
|
|
103
|
+
index: o
|
|
61
104
|
});
|
|
62
105
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
function V(e, t) {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
let n = P.value;
|
|
106
|
+
function ie(e, t) {
|
|
107
|
+
let n = document.elementFromPoint(e, t)?.closest("[data-board-column]");
|
|
69
108
|
if (n) {
|
|
70
|
-
let e =
|
|
71
|
-
|
|
109
|
+
let e = n.getBoundingClientRect();
|
|
110
|
+
t < e.top + A ? n.scrollTop -= 12 : t > e.bottom - A && (n.scrollTop += 12);
|
|
111
|
+
}
|
|
112
|
+
let r = L.value?.getBoundingClientRect();
|
|
113
|
+
r && L.value && (e < r.left + A ? L.value.scrollLeft -= 12 : e > r.right - A && (L.value.scrollLeft += 12));
|
|
114
|
+
}
|
|
115
|
+
function X() {
|
|
116
|
+
let e = R.value, t = z.value;
|
|
117
|
+
if (e && t) {
|
|
118
|
+
let n = t.index;
|
|
119
|
+
e.column === t.column ? (n > e.index && n--, N.sortable && n !== e.index && P("move", e.id, t.column, n)) : P("move", e.id, t.column, n);
|
|
72
120
|
}
|
|
73
|
-
|
|
121
|
+
$();
|
|
122
|
+
}
|
|
123
|
+
function Z(e) {
|
|
124
|
+
R.value && e.preventDefault();
|
|
125
|
+
}
|
|
126
|
+
function Q(e) {
|
|
127
|
+
e.key === "Escape" && R.value && (e.preventDefault(), $());
|
|
128
|
+
}
|
|
129
|
+
function $() {
|
|
130
|
+
window.clearTimeout(V), window.removeEventListener("pointermove", q), window.removeEventListener("pointerup", X), window.removeEventListener("pointercancel", $), window.removeEventListener("touchmove", Z), window.removeEventListener("keydown", Q), H?.remove(), H = null, R.value && (W = !0, window.setTimeout(() => W = !1)), B = null, R.value = null, z.value = null;
|
|
131
|
+
}
|
|
132
|
+
d($);
|
|
133
|
+
function ae(e, t) {
|
|
134
|
+
W ? (e.preventDefault(), e.stopPropagation()) : N.onCardClick?.(t);
|
|
74
135
|
}
|
|
75
|
-
function
|
|
76
|
-
e.target !== e.currentTarget || e.key !== "Enter" && e.key !== " " || (e.preventDefault(),
|
|
136
|
+
function oe(e, t) {
|
|
137
|
+
!N.onCardClick || e.target !== e.currentTarget || e.key !== "Enter" && e.key !== " " || (e.preventDefault(), N.onCardClick(t));
|
|
77
138
|
}
|
|
78
|
-
let
|
|
79
|
-
return (
|
|
80
|
-
|
|
81
|
-
|
|
139
|
+
let se = (e, t, n) => z.value?.column === e && z.value.index === t && R.value?.id !== I(n);
|
|
140
|
+
return (d, p) => (f(), a("div", {
|
|
141
|
+
ref_key: "root",
|
|
142
|
+
ref: L,
|
|
143
|
+
class: u(["flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']", l.fill && "h-full min-h-0 flex-1"])
|
|
144
|
+
}, [(f(!0), a(n, null, m(l.columns, (y) => (f(), a("section", {
|
|
145
|
+
key: y.id,
|
|
146
|
+
"aria-labelledby": `${v(F)}-${y.id}`,
|
|
82
147
|
class: "flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col"
|
|
83
|
-
}, [o("header",
|
|
84
|
-
id: `${
|
|
148
|
+
}, [o("header", ne, [(f(), r(g(`h${l.headingLevel}`), {
|
|
149
|
+
id: `${v(F)}-${y.id}`,
|
|
85
150
|
class: "m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink"
|
|
86
151
|
}, {
|
|
87
|
-
default:
|
|
152
|
+
default: b(() => [y.icon ? (f(), r(e, {
|
|
88
153
|
key: 0,
|
|
89
|
-
color:
|
|
90
|
-
name:
|
|
91
|
-
}, null, 8, ["color", "name"])) : i("", !0), s(" " +
|
|
154
|
+
color: y.iconColor ?? "ink-subtle",
|
|
155
|
+
name: y.icon
|
|
156
|
+
}, null, 8, ["color", "name"])) : i("", !0), s(" " + _(y.name), 1)]),
|
|
92
157
|
_: 2
|
|
93
|
-
}, 1032, ["id"])), o("span",
|
|
94
|
-
class: u(["group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
onDrop: (e) => V(e, k)
|
|
98
|
-
}, [l.loading ? (d(), a(n, { key: 0 }, p(3, (e) => o("div", {
|
|
158
|
+
}, 1032, ["id"])), o("span", re, [h(d.$slots, "count", { column: y }, () => [s(_(y.items.length), 1)])])]), o("div", {
|
|
159
|
+
class: u(["group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150", z.value?.column === y.id && "ring-2 ring-sui-accent-line ring-inset"]),
|
|
160
|
+
"data-board-column": y.id
|
|
161
|
+
}, [l.loading ? (f(), a(n, { key: 0 }, m(3, (e) => o("div", {
|
|
99
162
|
key: e,
|
|
100
163
|
"aria-hidden": "true",
|
|
101
164
|
class: "rounded-xl border border-sui-line-subtle bg-sui-surface p-4"
|
|
102
165
|
}, [c(t, { class: "mb-3" }), c(t, {
|
|
103
166
|
height: "0.75rem",
|
|
104
167
|
width: "70%"
|
|
105
|
-
})])), 64)) :
|
|
106
|
-
class: u(["relative
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
})])), 64)) : y.items.length === 0 && !R.value ? (f(), a("p", S, _(l.emptyLabel), 1)) : (f(), a("ul", C, [(f(!0), a(n, null, m(y.items, (e, t) => (f(), a("li", { key: I(e) }, [o("div", {
|
|
169
|
+
class: u(["relative rounded-xl outline-hidden transition-opacity duration-150 select-none", [
|
|
170
|
+
l.cardFrame && j,
|
|
171
|
+
l.onCardClick && "cursor-pointer focus-visible:sui-ring",
|
|
172
|
+
R.value?.id === I(e) && "opacity-40",
|
|
173
|
+
se(y.id, t, e) && "shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]"
|
|
174
|
+
]]),
|
|
175
|
+
"data-board-index": t,
|
|
176
|
+
role: l.onCardClick ? "button" : void 0,
|
|
177
|
+
tabindex: l.onCardClick ? 0 : void 0,
|
|
178
|
+
onClickCapture: (t) => ae(t, e),
|
|
179
|
+
onContextmenu: p[0] ||= (e) => R.value && e.preventDefault(),
|
|
180
|
+
onDragstart: p[1] ||= ee(() => {}, ["prevent"]),
|
|
181
|
+
onKeydown: (t) => oe(t, e),
|
|
182
|
+
onPointerdown: (n) => G(n, e, y.id, t)
|
|
183
|
+
}, [h(d.$slots, "card", {
|
|
184
|
+
column: y,
|
|
117
185
|
item: e
|
|
118
|
-
}, () => [o("p",
|
|
186
|
+
}, () => [o("p", T, _(e.name), 1)])], 42, w)]))), 128))])), l.onNew && !l.loading ? (f(), a("button", {
|
|
119
187
|
key: 3,
|
|
120
188
|
class: "flex h-9 w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring",
|
|
121
189
|
type: "button",
|
|
122
|
-
onClick: (e) => l.onNew(
|
|
123
|
-
}, [
|
|
190
|
+
onClick: (e) => l.onNew(y)
|
|
191
|
+
}, [p[2] ||= o("svg", {
|
|
124
192
|
"aria-hidden": "true",
|
|
125
193
|
class: "size-3.5 fill-none stroke-current",
|
|
126
194
|
"stroke-linecap": "round",
|
|
127
195
|
"stroke-width": "1.5",
|
|
128
196
|
viewBox: "0 0 14 14"
|
|
129
|
-
}, [o("path", { d: "M7 2.5v9M2.5 7h9" })], -1), s(" " +
|
|
197
|
+
}, [o("path", { d: "M7 2.5v9M2.5 7h9" })], -1), s(" " + _(l.newLabel), 1)], 8, E)) : i("", !0)], 10, x)], 8, te))), 128))], 2));
|
|
130
198
|
}
|
|
131
199
|
});
|
|
132
200
|
//#endregion
|
|
133
|
-
export {
|
|
201
|
+
export { M as default };
|
|
134
202
|
|
|
135
203
|
//# sourceMappingURL=Board.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_withCtx","Icon","_createCommentVNode","_createTextVNode","_toDisplayString","_renderSlot","_createVNode","Skeleton","Icon_default","Skeleton_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","Board_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","id","useId","idOf","item","dragged","ref","target","onDragStart","event","onDragEnd","overColumn","column","overCard","rect","at","index","onLeave","onDrop","from","onCardKeydown","isTarget","openBlock","createElementBlock","normalizeClass","Fragment","renderList","unref","createElementVNode","createBlock","resolveDynamicComponent","withCtx","createCommentVNode","createTextVNode","toDisplayString","renderSlot","_ctx","$event","i","createVNode","_cache"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n 'card-click': [item: T]\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\nfunction onDragStart(event: DragEvent, item: T, column: string, index: number) {\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', String(idOf(item)))\n }\n dragged.value = { id: idOf(item), column, index }\n}\n\nfunction onDragEnd() {\n dragged.value = null\n target.value = null\n}\n\nfunction overColumn(event: DragEvent, column: BoardColumn<T>) {\n if (!dragged.value) return\n event.preventDefault()\n if (target.value?.column !== column.id) target.value = { column: column.id, index: column.items.length }\n}\n\n// The card's top half drops before it, the bottom half after.\nfunction overCard(event: DragEvent, column: string, index: number) {\n if (!dragged.value) return\n event.preventDefault()\n event.stopPropagation()\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n const at = event.clientY < rect.top + rect.height / 2 ? index : index + 1\n if (target.value?.column !== column || target.value.index !== at) target.value = { column, index: at }\n}\n\nfunction onLeave(event: DragEvent) {\n if (!(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node | null)) target.value = null\n}\n\nfunction onDrop(event: DragEvent, column: BoardColumn<T>) {\n event.preventDefault()\n const from = dragged.value\n if (from) {\n let index = target.value?.column === column.id ? target.value.index : column.items.length\n if (from.column === column.id) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, column.id, index)\n } else {\n emit('move', from.id, column.id, index)\n }\n }\n onDragEnd()\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n emit('card-click', item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-sunken px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n @dragleave=\"onLeave\"\n @dragover=\"overColumn($event, column)\"\n @drop=\"onDrop($event, column)\"\n >\n <template v-if=\"loading\">\n <div\n v-for=\"i in 3\"\n :key=\"i\"\n aria-hidden=\"true\"\n class=\"rounded-xl border border-sui-line-subtle bg-sui-surface p-4\"\n >\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative cursor-pointer rounded-xl border border-sui-line-subtle bg-sui-surface p-4 outline-hidden transition-[border-color,opacity] duration-150 hover:border-sui-line focus-visible:sui-ring\"\n :class=\"[\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n draggable=\"true\"\n role=\"button\"\n tabindex=\"0\"\n @click=\"emit('card-click', item)\"\n @dragend=\"onDragEnd\"\n @dragover=\"overCard($event, column.id, index)\"\n @dragstart=\"onDragStart($event, item, column.id, index)\"\n @keydown=\"onCardKeydown($event, item)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-9 w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Board.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_withCtx","Icon","_createCommentVNode","_createTextVNode","_toDisplayString","_renderSlot","_createVNode","Skeleton","_withModifiers","Icon_default","Skeleton_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","MOUSE_SLOP","TOUCH_SLOP","TOUCH_DELAY","EDGE","frame","Board_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","id","useId","idOf","item","root","ref","dragged","target","pending","pressTimer","ghost","grab","justDragged","onPointerDown","event","touch","start","onPointerMove","onPointerUp","stop","holdScroll","onEscape","rect","follow","moved","aim","scrollNear","x","y","under","body","column","card","index","c","board","from","to","clickCard","onCardKeydown","isTarget","openBlock","createElementBlock","normalizeClass","Fragment","renderList","unref","createElementVNode","createBlock","resolveDynamicComponent","withCtx","createCommentVNode","createTextVNode","toDisplayString","renderSlot","_ctx","i","createVNode","$event","_cache","withModifiers"],"sources":["../../../src/components/Board/Board.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { onBeforeUnmount, ref, useId } from 'vue'\n\nimport Icon from '../Icon/Icon.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { BoardColumn } from './types'\n\nexport interface BoardProps<T> {\n columns: BoardColumn<T>[]\n /** The item field that identifies it. */\n rowKey?: keyof T & string\n /** Cards also reorder within a column, for records that carry a position. */\n sortable?: boolean\n /** Columns run to the bottom of the parent and scroll inside it; give the parent a height. False sizes them to their cards. */\n fill?: boolean\n loading?: boolean\n /** The columns' heading level, to fit the page's outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n /** Draws each card's border, padding and surface. False when the `card` slot draws its own. */\n cardFrame?: boolean\n /** Makes a card a button that opens on click, Enter and Space (`@card-click`). Without it, the card slot carries its\n * own link or buttons. */\n onCardClick?: (item: T) => void\n /** Adds a dashed \"New\" button at the foot of each column, shown on hover (`@new`). */\n onNew?: (column: BoardColumn<T>) => void\n newLabel?: string\n emptyLabel?: string\n}\n\nconst props = withDefaults(defineProps<BoardProps<T>>(), {\n rowKey: 'id' as never,\n fill: true,\n headingLevel: 3,\n cardFrame: true,\n onCardClick: undefined,\n onNew: undefined,\n newLabel: 'New',\n emptyLabel: 'No cards'\n})\n\nconst emit = defineEmits<{\n /** A card dropped into a column, at its new index there. Show it moved at once; put it back if saving fails. */\n move: [id: unknown, column: string, index: number]\n}>()\n\ndefineSlots<{\n /** A card's content; `item.name` as a title by default. */\n card?: (props: { item: T; column: BoardColumn<T> }) => unknown\n /** Replaces a column's count, e.g. its summed deal value. */\n count?: (props: { column: BoardColumn<T> }) => unknown\n}>()\n\nconst id = useId()\nconst idOf = (item: T): unknown => item[props.rowKey ?? 'id']\nconst root = ref<HTMLElement>()\n\nconst dragged = ref<{ id: unknown; column: string; index: number } | null>(null)\nconst target = ref<{ column: string; index: number } | null>(null)\n\n// Dragging runs on pointer events, so it works with a mouse, a pen and a finger. A mouse drags once it moves a few\n// pixels, so a click still clicks; a finger after a short press, so a swipe still scrolls.\nconst MOUSE_SLOP = 4\nconst TOUCH_SLOP = 8\nconst TOUCH_DELAY = 250\nconst EDGE = 48\n\nlet pending: {\n item: T\n column: string\n index: number\n x: number\n y: number\n card: HTMLElement\n touch: boolean\n} | null = null\nlet pressTimer: number | undefined\nlet ghost: HTMLElement | null = null\nlet grab = { x: 0, y: 0 }\nlet justDragged = false\n\nfunction onPointerDown(event: PointerEvent, item: T, column: string, index: number) {\n if (event.button !== 0 || (event.target as HTMLElement).closest('input, textarea, select, [contenteditable]')) return\n const touch = event.pointerType === 'touch'\n pending = { item, column, index, x: event.clientX, y: event.clientY, card: event.currentTarget as HTMLElement, touch }\n if (touch) pressTimer = window.setTimeout(() => pending && start(), TOUCH_DELAY)\n window.addEventListener('pointermove', onPointerMove)\n window.addEventListener('pointerup', onPointerUp)\n window.addEventListener('pointercancel', stop)\n window.addEventListener('touchmove', holdScroll, { passive: false })\n window.addEventListener('keydown', onEscape)\n}\n\nfunction start() {\n if (!pending) return\n const rect = pending.card.getBoundingClientRect()\n grab = { x: pending.x - rect.left, y: pending.y - rect.top }\n ghost = pending.card.cloneNode(true) as HTMLElement\n ghost.removeAttribute('id')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.classList.add('pointer-events-none', 'fixed', 'top-0', 'left-0', 'z-[1300]', 'shadow-lg', 'opacity-95')\n ghost.style.width = `${rect.width}px`\n document.body.appendChild(ghost)\n follow(pending.x, pending.y)\n dragged.value = { id: idOf(pending.item), column: pending.column, index: pending.index }\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (!pending) return\n if (!dragged.value) {\n const moved = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n // A finger that moves before the press is long enough is scrolling.\n if (pending.touch) return moved > TOUCH_SLOP ? stop() : undefined\n if (moved < MOUSE_SLOP) return\n start()\n }\n event.preventDefault()\n follow(event.clientX, event.clientY)\n aim(event.clientX, event.clientY)\n scrollNear(event.clientX, event.clientY)\n}\n\nfunction follow(x: number, y: number) {\n if (ghost) ghost.style.transform = `translate(${x - grab.x}px, ${y - grab.y}px) rotate(1.5deg)`\n}\n\n// What's under the pointer: a card's top half drops before it, the bottom half after; an empty spot, at the end.\nfunction aim(x: number, y: number) {\n const under = document.elementFromPoint(x, y)\n const body = under?.closest<HTMLElement>('[data-board-column]')\n if (!body || !root.value?.contains(body)) {\n target.value = null\n return\n }\n const column = body.dataset.boardColumn!\n const card = under!.closest<HTMLElement>('[data-board-index]')\n let index = props.columns.find((c) => c.id === column)?.items.length ?? 0\n if (card && body.contains(card)) {\n const rect = card.getBoundingClientRect()\n index = Number(card.dataset.boardIndex) + (y < rect.top + rect.height / 2 ? 0 : 1)\n }\n if (target.value?.column !== column || target.value.index !== index) target.value = { column, index }\n}\n\n// Near a column's top or bottom, or the board's sides, it scrolls that way.\nfunction scrollNear(x: number, y: number) {\n const body = document.elementFromPoint(x, y)?.closest<HTMLElement>('[data-board-column]')\n if (body) {\n const rect = body.getBoundingClientRect()\n if (y < rect.top + EDGE) body.scrollTop -= 12\n else if (y > rect.bottom - EDGE) body.scrollTop += 12\n }\n const board = root.value?.getBoundingClientRect()\n if (board && root.value) {\n if (x < board.left + EDGE) root.value.scrollLeft -= 12\n else if (x > board.right - EDGE) root.value.scrollLeft += 12\n }\n}\n\nfunction onPointerUp() {\n const from = dragged.value\n const to = target.value\n if (from && to) {\n let index = to.index\n if (from.column === to.column) {\n // Within a column the dragged card leaves its old slot first.\n if (index > from.index) index--\n if (props.sortable && index !== from.index) emit('move', from.id, to.column, index)\n } else {\n emit('move', from.id, to.column, index)\n }\n }\n stop()\n}\n\nfunction holdScroll(event: TouchEvent) {\n if (dragged.value) event.preventDefault()\n}\n\nfunction onEscape(event: KeyboardEvent) {\n if (event.key === 'Escape' && dragged.value) {\n event.preventDefault()\n stop()\n }\n}\n\nfunction stop() {\n window.clearTimeout(pressTimer)\n window.removeEventListener('pointermove', onPointerMove)\n window.removeEventListener('pointerup', onPointerUp)\n window.removeEventListener('pointercancel', stop)\n window.removeEventListener('touchmove', holdScroll)\n window.removeEventListener('keydown', onEscape)\n ghost?.remove()\n ghost = null\n // The click that ends a drag isn't a click on the card.\n if (dragged.value) {\n justDragged = true\n window.setTimeout(() => (justDragged = false))\n }\n pending = null\n dragged.value = null\n target.value = null\n}\n\nonBeforeUnmount(stop)\n\nfunction clickCard(event: MouseEvent, item: T) {\n if (justDragged) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n props.onCardClick?.(item)\n}\n\nfunction onCardKeydown(event: KeyboardEvent, item: T) {\n if (!props.onCardClick || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n props.onCardClick(item)\n}\n\nconst isTarget = (column: string, index: number, item: T) =>\n target.value?.column === column && target.value.index === index && dragged.value?.id !== idOf(item)\n\nconst frame =\n 'border border-sui-line-subtle bg-sui-surface p-4 transition-[border-color,opacity] duration-150 hover:border-sui-line'\n</script>\n\n<!-- A board: cards in columns by a field (a stage, a status), dragged between columns. -->\n<template>\n <div\n ref=\"root\"\n class=\"flex items-stretch gap-5 overflow-x-auto pb-2 after:w-1 after:shrink-0 after:content-['']\"\n :class=\"fill && 'h-full min-h-0 flex-1'\"\n >\n <section\n v-for=\"column in columns\"\n :key=\"column.id\"\n :aria-labelledby=\"`${id}-${column.id}`\"\n class=\"flex min-h-0 w-full max-w-xs min-w-[16rem] shrink-0 flex-col\"\n >\n <header class=\"flex items-center justify-between px-1 pb-2.5\">\n <component\n :is=\"`h${headingLevel}`\"\n :id=\"`${id}-${column.id}`\"\n class=\"m-0 flex items-center gap-2 text-sm font-semibold text-sui-ink\"\n >\n <Icon v-if=\"column.icon\" :color=\"column.iconColor ?? 'ink-subtle'\" :name=\"column.icon\" />\n {{ column.name }}\n </component>\n <span\n class=\"ml-2 inline-flex h-5 items-center justify-center rounded-full bg-sui-sunken px-2 text-xs text-sui-ink-muted tabular-nums\"\n >\n <slot :column=\"column\" name=\"count\">{{ column.items.length }}</slot>\n </span>\n </header>\n <div\n class=\"group/body flex min-h-[120px] flex-1 flex-col gap-2.5 overflow-y-auto rounded-xl bg-sui-sunken p-2.5 transition-shadow duration-150\"\n :class=\"target?.column === column.id && 'ring-2 ring-sui-accent-line ring-inset'\"\n :data-board-column=\"column.id\"\n >\n <template v-if=\"loading\">\n <div\n v-for=\"i in 3\"\n :key=\"i\"\n aria-hidden=\"true\"\n class=\"rounded-xl border border-sui-line-subtle bg-sui-surface p-4\"\n >\n <Skeleton class=\"mb-3\" />\n <Skeleton height=\"0.75rem\" width=\"70%\" />\n </div>\n </template>\n <p\n v-else-if=\"column.items.length === 0 && !dragged\"\n class=\"m-0 px-1 py-3 text-center text-13 text-sui-ink-muted\"\n >\n {{ emptyLabel }}\n </p>\n <ul v-else class=\"m-0 flex list-none flex-col gap-2.5 p-0\">\n <li v-for=\"(item, index) in column.items\" :key=\"idOf(item) as PropertyKey\">\n <div\n class=\"relative rounded-xl outline-hidden transition-opacity duration-150 select-none\"\n :class=\"[\n cardFrame && frame,\n onCardClick && 'cursor-pointer focus-visible:sui-ring',\n dragged?.id === idOf(item) && 'opacity-40',\n isTarget(column.id, index, item) && 'shadow-[0_-6px_0_-4px_rgb(var(--sui-accent-solid))]'\n ]\"\n :data-board-index=\"index\"\n :role=\"onCardClick ? 'button' : undefined\"\n :tabindex=\"onCardClick ? 0 : undefined\"\n @click.capture=\"clickCard($event, item)\"\n @contextmenu=\"dragged && $event.preventDefault()\"\n @dragstart.prevent\n @keydown=\"onCardKeydown($event, item)\"\n @pointerdown=\"onPointerDown($event, item, column.id, index)\"\n >\n <slot :column=\"column\" :item=\"item\" name=\"card\">\n <p class=\"m-0 text-sm font-semibold text-sui-ink\">{{ item.name }}</p>\n </slot>\n </div>\n </li>\n </ul>\n <button\n v-if=\"onNew && !loading\"\n class=\"flex h-9 w-full shrink-0 items-center justify-center gap-1.5 rounded-lg border border-dashed border-sui-line-strong text-xs font-medium text-sui-ink-muted opacity-0 outline-hidden transition-all duration-150 group-hover/body:opacity-100 hover:border-sui-ink-muted hover:bg-sui-surface/60 hover:text-sui-ink-body focus-visible:opacity-100 focus-visible:sui-ring\"\n type=\"button\"\n @click=\"onNew(column)\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 14 14\"\n >\n <path d=\"M7 2.5v9M2.5 7h9\" />\n </svg>\n {{ newLabel }}\n </button>\n </div>\n </section>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterChip.js","names":["_sfc_main","FilterChip_vue_vue_type_script_setup_true_lang_default","FilterChip_default"],"sources":["../../../src/components/Filters/FilterChip.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, useAttrs } from 'vue'\n\nimport {\n DropdownMenuContent,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuRoot,\n DropdownMenuTrigger,\n PopoverContent,\n PopoverPortal,\n PopoverRoot,\n PopoverTrigger,\n RadioGroupItem,\n RadioGroupRoot\n} from 'reka-ui'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Icon from '../Icon/Icon.vue'\nimport Input from '../Input/Input.vue'\nimport SegmentedControl from '../SegmentedControl/SegmentedControl.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport { datePresets as defaultDatePresets, describeFilter, filterOperators } from './defaults'\nimport type { FilterFieldType, FilterOperator, FilterOption, FilterValue } from './types'\n\nexport interface FilterChipProps {\n /** A filter, read as a sentence; `add` is the quiet \"+ Filter\" that starts one (FilterMenu includes it). */\n kind?: 'filter' | 'add'\n field?: string\n /** Picks the editor: a checklist for enum, person and company, an input for text and number, presets for date, Yes / No for boolean. */\n type?: FilterFieldType\n /** An app icon for the field, instead of the type's glyph. */\n icon?: string\n /** The operators offered; English ones by type by default. */\n operators?: FilterOperator[]\n /** The choices for enum, person and company. */\n options?: FilterOption[]\n /** The live result count in the panel's footer, e.g. \"120 people\". */\n status?: string\n /** The plural noun once more than two values are picked: \"3 stages\". */\n valueLabel?: string\n datePresets?: string[]\n /** Adds a Remove action to the panel (`@remove`). */\n onRemove?: () => void\n removeLabel?: string\n addLabel?: string\n operatorLabel?: string\n searchPlaceholder?: string\n valuePlaceholder?: string\n doneLabel?: string\n closeLabel?: string\n yesLabel?: string\n noLabel?: string\n}\n\n// Attributes and listeners go to the chip itself; the root is a renderless popover.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<FilterChipProps>(), {\n kind: 'filter',\n field: undefined,\n type: 'enum',\n icon: undefined,\n operators: undefined,\n options: () => [],\n status: undefined,\n valueLabel: 'values',\n datePresets: () => defaultDatePresets,\n onRemove: undefined,\n removeLabel: 'Remove',\n addLabel: 'Filter',\n operatorLabel: 'Operator',\n searchPlaceholder: 'Search…',\n valuePlaceholder: 'Type a value',\n doneLabel: 'Done',\n closeLabel: 'Close',\n yesLabel: 'Yes',\n noLabel: 'No'\n})\n\nconst op = defineModel<string>('op')\nconst value = defineModel<FilterValue>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n /** Your own editor, in place of the type's. */\n default?: () => unknown\n}>()\n\nconst attrs = useAttrs()\n\nconst ops = computed(() => props.operators ?? filterOperators[props.type])\nconst current = computed(() => ops.value.find((o) => o.value === op.value))\nconst needsValue = computed(() => !current.value?.noValue)\nconst text = computed(() =>\n needsValue.value\n ? describeFilter(value.value, props.options, {\n valueLabel: props.valueLabel,\n yesLabel: props.yesLabel,\n noLabel: props.noLabel\n })\n : ''\n)\nconst isChecklist = computed(() => ['enum', 'person', 'company'].includes(props.type))\n\nconst query = ref('')\nconst listed = computed(() => {\n const q = query.value.trim().toLowerCase()\n return q ? props.options.filter((o) => o.label.toLowerCase().includes(q)) : props.options\n})\n\nconst picked = computed(() => (Array.isArray(value.value) ? value.value : []))\n\nfunction toggle(choice: string | number) {\n value.value = picked.value.includes(choice) ? picked.value.filter((v) => v !== choice) : [...picked.value, choice]\n}\n\nfunction remove() {\n open.value = false\n props.onRemove?.()\n}\n\nconst chip =\n 'inline-flex h-7.5 items-center gap-1.5 rounded-lg border text-13 whitespace-nowrap outline-hidden transition-[border-color,box-shadow] duration-150 focus-visible:sui-ring'\nconst row =\n 'flex min-h-8 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\nconst rowOn = 'bg-sui-accent-wash font-semibold text-sui-accent'\nconst rowOff = 'text-sui-ink hover:bg-sui-sunken'\nconst hueDots: Record<string, string> = {\n blue: 'bg-sui-hue-blue-ink',\n green: 'bg-sui-hue-green-ink',\n orange: 'bg-sui-hue-orange-ink',\n pink: 'bg-sui-hue-pink-ink',\n purple: 'bg-sui-hue-purple-ink',\n slate: 'bg-sui-hue-slate-ink',\n red: 'bg-sui-hue-red-ink',\n amber: 'bg-sui-hue-amber-ink'\n}\n</script>\n\n<!-- A view's filter as a sentence chip (\"Stage is any of Lead, Customer\"), whose panel changes it as you go. -->\n<template>\n <button\n v-if=\"kind === 'add'\"\n v-bind=\"attrs\"\n class=\"border-dashed border-sui-line-strong px-2 font-medium text-sui-ink-muted hover:border-sui-ink-muted hover:text-sui-ink-body\"\n :class=\"chip\"\n type=\"button\"\n >\n <FilterGlyph name=\"plus\" />\n {{ addLabel }}\n </button>\n <PopoverRoot v-else v-model:open=\"open\">\n <PopoverTrigger\n v-bind=\"attrs\"\n class=\"border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-sui-accent hover:border-sui-accent-bright data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n :class=\"chip\"\n >\n <Icon v-if=\"icon\" class=\"size-3.5 opacity-80\" color=\"accent\" :name=\"icon\" />\n <FilterGlyph v-else class=\"opacity-80\" :name=\"type\" />\n <!-- Real spaces between the words, so the chip's name reads as a sentence. -->\n <span\n ><span class=\"font-medium\">{{ field }}</span\n ><template v-if=\"current\">{{ ' ' }}{{ current.label }}</template\n ><template v-if=\"text\"\n >{{ ' ' }}<span class=\"font-semibold\">{{ text }}</span></template\n ></span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <Icon v-if=\"icon\" color=\"ink-muted\" :name=\"icon\" />\n <FilterGlyph v-else class=\"size-4\" :name=\"type\" />\n <span class=\"text-sm font-semibold text-sui-ink\">{{ field }}</span>\n <DropdownMenuRoot v-if=\"ops.length > 1\">\n <DropdownMenuTrigger\n :aria-label=\"`${operatorLabel}: ${current?.label ?? ''}`\"\n class=\"inline-flex h-7 items-center gap-0.5 rounded-md pr-1 pl-1.5 text-13.5 font-medium text-sui-accent outline-hidden transition-colors duration-150 hover:bg-sui-accent-wash focus-visible:sui-ring-inset data-[state=open]:bg-sui-accent-wash\"\n >\n {{ current?.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </DropdownMenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuContent\n align=\"start\"\n class=\"z-[1100] w-44 rounded-lg border border-sui-line bg-sui-surface p-1 shadow-lg data-[state=open]:animate-sui-menu-in\"\n :side-offset=\"4\"\n >\n <DropdownMenuRadioGroup v-model=\"op\">\n <DropdownMenuRadioItem\n v-for=\"operator in ops\"\n :key=\"operator.value\"\n class=\"flex h-8 cursor-pointer items-center justify-between rounded-md px-2.5 text-13.5 text-sui-ink-body outline-hidden select-none data-highlighted:bg-sui-sunken data-[state=checked]:font-semibold data-[state=checked]:text-sui-ink\"\n :value=\"operator.value\"\n >\n {{ operator.label }}\n <CheckIcon v-if=\"operator.value === op\" />\n </DropdownMenuRadioItem>\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenuPortal>\n </DropdownMenuRoot>\n <span v-else class=\"text-13.5 text-sui-ink-muted\">{{ current?.label }}</span>\n <button\n v-if=\"onRemove\"\n class=\"ml-auto inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-danger-soft hover:text-sui-danger focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"remove\"\n >\n <FilterGlyph name=\"trash\" />\n {{ removeLabel }}\n </button>\n </div>\n\n <slot v-if=\"$slots.default\" />\n <template v-else-if=\"needsValue\">\n <template v-if=\"isChecklist\">\n <div\n v-if=\"options.length > 6\"\n class=\"mx-2 mb-1 flex h-9 items-center gap-2 rounded-lg border border-sui-line-subtle px-2.5 text-sui-ink-muted\"\n >\n <FilterGlyph class=\"size-4\" name=\"search\" />\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <input\n v-model=\"query\"\n :aria-label=\"searchPlaceholder\"\n autofocus\n class=\"h-full min-w-0 flex-1 border-none bg-transparent p-0 text-sm text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n <div class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\">\n <label\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"[row, picked.includes(option.value) ? rowOn : rowOff]\"\n >\n <Checkbox :model-value=\"picked.includes(option.value)\" @update:model-value=\"toggle(option.value)\" />\n <span v-if=\"option.color\" class=\"size-1.5 shrink-0 rounded-full\" :class=\"hueDots[option.color]\" />\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <span v-if=\"option.count != null\" class=\"text-xs font-normal text-sui-ink-muted tabular-nums\">{{\n option.count\n }}</span>\n </label>\n </div>\n </template>\n <div v-else-if=\"type === 'text' || type === 'number'\" class=\"px-2 pt-1 pb-2\">\n <Input\n :aria-label=\"field\"\n autofocus\n :model-value=\"value == null ? '' : String(value)\"\n :placeholder=\"valuePlaceholder\"\n :type=\"type === 'number' ? 'number' : 'text'\"\n @update:model-value=\"value = $event ?? null\"\n />\n </div>\n <RadioGroupRoot\n v-else-if=\"type === 'date'\"\n :aria-label=\"field\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"typeof value === 'string' ? value : undefined\"\n @update:model-value=\"value = String($event)\"\n >\n <RadioGroupItem\n v-for=\"preset in datePresets\"\n :key=\"preset\"\n :class=\"[row, preset === value ? rowOn : rowOff]\"\n :value=\"preset\"\n >\n <FilterGlyph\n class=\"size-4\"\n :class=\"preset === value ? 'text-sui-accent' : 'text-sui-ink-muted'\"\n name=\"date\"\n />\n <span class=\"min-w-0 flex-1 truncate\">{{ preset }}</span>\n <CheckIcon v-if=\"preset === value\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <div v-else-if=\"type === 'boolean'\" class=\"px-2.5 pt-1 pb-2.5\">\n <SegmentedControl\n :aria-label=\"field\"\n :model-value=\"value ? 'yes' : 'no'\"\n :options=\"[\n { value: 'yes', label: yesLabel },\n { value: 'no', label: noLabel }\n ]\"\n @update:model-value=\"value = $event === 'yes'\"\n />\n </div>\n </template>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"FilterChip.js","names":["_sfc_main","FilterChip_vue_vue_type_script_setup_true_lang_default","FilterChip_default"],"sources":["../../../src/components/Filters/FilterChip.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, useAttrs } from 'vue'\n\nimport {\n DropdownMenuContent,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuRoot,\n DropdownMenuTrigger,\n PopoverContent,\n PopoverPortal,\n PopoverRoot,\n PopoverTrigger,\n RadioGroupItem,\n RadioGroupRoot\n} from 'reka-ui'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Icon from '../Icon/Icon.vue'\nimport Input from '../Input/Input.vue'\nimport SegmentedControl from '../SegmentedControl/SegmentedControl.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport { datePresets as defaultDatePresets, describeFilter, filterOperators } from './defaults'\nimport type { FilterFieldType, FilterOperator, FilterOption, FilterSummary, FilterValue } from './types'\n\nexport interface FilterChipProps {\n /** A filter, read as a sentence; `add` is the quiet \"+ Filter\" that starts one (FilterMenu includes it). */\n kind?: 'filter' | 'add'\n field?: string\n /** Picks the editor: a checklist for enum, person and company, an input for text and number, presets for date, Yes / No for boolean. */\n type?: FilterFieldType\n /** An app icon for the field, instead of the type's glyph. */\n icon?: string\n /** The operators offered; English ones by type by default. */\n operators?: FilterOperator[]\n /** The choices for enum, person and company. */\n options?: FilterOption[]\n /** Your own wording for the chip, in place of field, operator and value: \"Not archived\", \"Created in the last 7 days\". */\n summary?: FilterSummary\n /** The live result count in the panel's footer, e.g. \"120 people\". */\n status?: string\n /** The plural noun once more than two values are picked: \"3 stages\". */\n valueLabel?: string\n datePresets?: string[]\n /** Adds a Remove action to the panel (`@remove`). */\n onRemove?: () => void\n removeLabel?: string\n addLabel?: string\n operatorLabel?: string\n searchPlaceholder?: string\n valuePlaceholder?: string\n doneLabel?: string\n closeLabel?: string\n yesLabel?: string\n noLabel?: string\n}\n\n// Attributes and listeners go to the chip itself; the root is a renderless popover.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<FilterChipProps>(), {\n kind: 'filter',\n field: undefined,\n type: 'enum',\n icon: undefined,\n operators: undefined,\n options: () => [],\n summary: undefined,\n status: undefined,\n valueLabel: 'values',\n datePresets: () => defaultDatePresets,\n onRemove: undefined,\n removeLabel: 'Remove',\n addLabel: 'Filter',\n operatorLabel: 'Operator',\n searchPlaceholder: 'Search…',\n valuePlaceholder: 'Type a value',\n doneLabel: 'Done',\n closeLabel: 'Close',\n yesLabel: 'Yes',\n noLabel: 'No'\n})\n\nconst op = defineModel<string>('op')\nconst value = defineModel<FilterValue>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n /** Your own editor, in place of the type's. */\n default?: () => unknown\n}>()\n\nconst attrs = useAttrs()\n\nconst ops = computed(() => props.operators ?? filterOperators[props.type])\nconst current = computed(() => ops.value.find((o) => o.value === op.value))\nconst needsValue = computed(() => !current.value?.noValue)\nconst text = computed(() =>\n needsValue.value\n ? describeFilter(value.value, props.options, {\n valueLabel: props.valueLabel,\n yesLabel: props.yesLabel,\n noLabel: props.noLabel\n })\n : ''\n)\nconst words = computed(() => ({\n field: props.summary?.field ?? props.field,\n op: props.summary ? props.summary.op : current.value?.label,\n value: props.summary ? props.summary.value : text.value\n}))\nconst isChecklist = computed(() => ['enum', 'person', 'company'].includes(props.type))\n\nconst query = ref('')\nconst listed = computed(() => {\n const q = query.value.trim().toLowerCase()\n return q ? props.options.filter((o) => o.label.toLowerCase().includes(q)) : props.options\n})\n\nconst picked = computed(() => (Array.isArray(value.value) ? value.value : []))\n\nfunction toggle(choice: string | number) {\n value.value = picked.value.includes(choice) ? picked.value.filter((v) => v !== choice) : [...picked.value, choice]\n}\n\nfunction remove() {\n open.value = false\n props.onRemove?.()\n}\n\nconst chip =\n 'inline-flex h-7.5 items-center gap-1.5 rounded-lg border text-13 whitespace-nowrap outline-hidden transition-[border-color,box-shadow] duration-150 focus-visible:sui-ring'\nconst row =\n 'flex min-h-8 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\nconst rowOn = 'bg-sui-accent-wash font-semibold text-sui-accent'\nconst rowOff = 'text-sui-ink hover:bg-sui-sunken'\nconst hueDots: Record<string, string> = {\n blue: 'bg-sui-hue-blue-ink',\n green: 'bg-sui-hue-green-ink',\n orange: 'bg-sui-hue-orange-ink',\n pink: 'bg-sui-hue-pink-ink',\n purple: 'bg-sui-hue-purple-ink',\n slate: 'bg-sui-hue-slate-ink',\n red: 'bg-sui-hue-red-ink',\n amber: 'bg-sui-hue-amber-ink'\n}\n</script>\n\n<!-- A view's filter as a sentence chip (\"Stage is any of Lead, Customer\"), whose panel changes it as you go. -->\n<template>\n <button\n v-if=\"kind === 'add'\"\n v-bind=\"attrs\"\n class=\"border-dashed border-sui-line-strong px-2 font-medium text-sui-ink-muted hover:border-sui-ink-muted hover:text-sui-ink-body\"\n :class=\"chip\"\n type=\"button\"\n >\n <FilterGlyph name=\"plus\" />\n {{ addLabel }}\n </button>\n <PopoverRoot v-else v-model:open=\"open\">\n <PopoverTrigger\n v-bind=\"attrs\"\n class=\"border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-sui-accent hover:border-sui-accent-bright data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n :class=\"chip\"\n >\n <Icon v-if=\"icon\" class=\"size-3.5 opacity-80\" color=\"accent\" :name=\"icon\" />\n <FilterGlyph v-else class=\"opacity-80\" :name=\"type\" />\n <!-- Real spaces between the words, so the chip's name reads as a sentence. -->\n <span\n ><span class=\"font-medium\">{{ words.field }}</span\n ><template v-if=\"words.op\">{{ ' ' }}{{ words.op }}</template\n ><template v-if=\"words.value\"\n >{{ ' ' }}<span class=\"font-semibold\">{{ words.value }}</span></template\n ></span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <Icon v-if=\"icon\" color=\"ink-muted\" :name=\"icon\" />\n <FilterGlyph v-else class=\"size-4\" :name=\"type\" />\n <span class=\"text-sm font-semibold text-sui-ink\">{{ field }}</span>\n <DropdownMenuRoot v-if=\"ops.length > 1\">\n <DropdownMenuTrigger\n :aria-label=\"`${operatorLabel}: ${current?.label ?? ''}`\"\n class=\"inline-flex h-7 items-center gap-0.5 rounded-md pr-1 pl-1.5 text-13.5 font-medium text-sui-accent outline-hidden transition-colors duration-150 hover:bg-sui-accent-wash focus-visible:sui-ring-inset data-[state=open]:bg-sui-accent-wash\"\n >\n {{ current?.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </DropdownMenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuContent\n align=\"start\"\n class=\"z-[1100] w-44 rounded-lg border border-sui-line bg-sui-surface p-1 shadow-lg data-[state=open]:animate-sui-menu-in\"\n :side-offset=\"4\"\n >\n <DropdownMenuRadioGroup v-model=\"op\">\n <DropdownMenuRadioItem\n v-for=\"operator in ops\"\n :key=\"operator.value\"\n class=\"flex h-8 cursor-pointer items-center justify-between rounded-md px-2.5 text-13.5 text-sui-ink-body outline-hidden select-none data-highlighted:bg-sui-sunken data-[state=checked]:font-semibold data-[state=checked]:text-sui-ink\"\n :value=\"operator.value\"\n >\n {{ operator.label }}\n <CheckIcon v-if=\"operator.value === op\" />\n </DropdownMenuRadioItem>\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenuPortal>\n </DropdownMenuRoot>\n <span v-else class=\"text-13.5 text-sui-ink-muted\">{{ current?.label }}</span>\n <button\n v-if=\"onRemove\"\n class=\"ml-auto inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-danger-soft hover:text-sui-danger focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"remove\"\n >\n <FilterGlyph name=\"trash\" />\n {{ removeLabel }}\n </button>\n </div>\n\n <slot v-if=\"$slots.default\" />\n <template v-else-if=\"needsValue\">\n <template v-if=\"isChecklist\">\n <div\n v-if=\"options.length > 6\"\n class=\"mx-2 mb-1 flex h-9 items-center gap-2 rounded-lg border border-sui-line-subtle px-2.5 text-sui-ink-muted\"\n >\n <FilterGlyph class=\"size-4\" name=\"search\" />\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <input\n v-model=\"query\"\n :aria-label=\"searchPlaceholder\"\n autofocus\n class=\"h-full min-w-0 flex-1 border-none bg-transparent p-0 text-sm text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n <div class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\">\n <label\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"[row, picked.includes(option.value) ? rowOn : rowOff]\"\n >\n <Checkbox :model-value=\"picked.includes(option.value)\" @update:model-value=\"toggle(option.value)\" />\n <span v-if=\"option.color\" class=\"size-1.5 shrink-0 rounded-full\" :class=\"hueDots[option.color]\" />\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <span v-if=\"option.count != null\" class=\"text-xs font-normal text-sui-ink-muted tabular-nums\">{{\n option.count\n }}</span>\n </label>\n </div>\n </template>\n <div v-else-if=\"type === 'text' || type === 'number'\" class=\"px-2 pt-1 pb-2\">\n <Input\n :aria-label=\"field\"\n autofocus\n :model-value=\"value == null ? '' : String(value)\"\n :placeholder=\"valuePlaceholder\"\n :type=\"type === 'number' ? 'number' : 'text'\"\n @update:model-value=\"value = $event ?? null\"\n />\n </div>\n <RadioGroupRoot\n v-else-if=\"type === 'date'\"\n :aria-label=\"field\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"typeof value === 'string' ? value : undefined\"\n @update:model-value=\"value = String($event)\"\n >\n <RadioGroupItem\n v-for=\"preset in datePresets\"\n :key=\"preset\"\n :class=\"[row, preset === value ? rowOn : rowOff]\"\n :value=\"preset\"\n >\n <FilterGlyph\n class=\"size-4\"\n :class=\"preset === value ? 'text-sui-accent' : 'text-sui-ink-muted'\"\n name=\"date\"\n />\n <span class=\"min-w-0 flex-1 truncate\">{{ preset }}</span>\n <CheckIcon v-if=\"preset === value\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <div v-else-if=\"type === 'boolean'\" class=\"px-2.5 pt-1 pb-2.5\">\n <SegmentedControl\n :aria-label=\"field\"\n :model-value=\"value === null ? '' : value ? 'yes' : 'no'\"\n :options=\"[\n { value: 'yes', label: yesLabel },\n { value: 'no', label: noLabel }\n ]\"\n @update:model-value=\"value = $event === 'yes'\"\n />\n </div>\n </template>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FilterFieldType, FilterOperator, FilterOption, FilterValue } from './types';
|
|
1
|
+
import type { FilterFieldType, FilterOperator, FilterOption, FilterSummary, FilterValue } from './types';
|
|
2
2
|
export interface FilterChipProps {
|
|
3
3
|
/** A filter, read as a sentence; `add` is the quiet "+ Filter" that starts one (FilterMenu includes it). */
|
|
4
4
|
kind?: 'filter' | 'add';
|
|
@@ -11,6 +11,8 @@ export interface FilterChipProps {
|
|
|
11
11
|
operators?: FilterOperator[];
|
|
12
12
|
/** The choices for enum, person and company. */
|
|
13
13
|
options?: FilterOption[];
|
|
14
|
+
/** Your own wording for the chip, in place of field, operator and value: "Not archived", "Created in the last 7 days". */
|
|
15
|
+
summary?: FilterSummary;
|
|
14
16
|
/** The live result count in the panel's footer, e.g. "120 people". */
|
|
15
17
|
status?: string;
|
|
16
18
|
/** The plural noun once more than two values are picked: "3 stages". */
|
|
@@ -49,6 +51,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
49
51
|
"onUpdate:op"?: ((value: string | undefined) => any) | undefined;
|
|
50
52
|
}>, {
|
|
51
53
|
type: FilterFieldType;
|
|
54
|
+
summary: FilterSummary;
|
|
52
55
|
icon: string;
|
|
53
56
|
closeLabel: string;
|
|
54
57
|
status: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import e from "../Icon/Icon.js";
|
|
2
2
|
import t from "../Checkbox/Checkbox.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
3
|
+
import ee from "../Input/Input.js";
|
|
4
|
+
import te from "../SegmentedControl/SegmentedControl.js";
|
|
5
|
+
import n from "../internal/CheckIcon.js";
|
|
6
|
+
import r from "./FilterFrame.js";
|
|
7
7
|
import i from "./FilterGlyph.js";
|
|
8
8
|
import { datePresets as a, describeFilter as o, filterOperators as s } from "./defaults.js";
|
|
9
9
|
import { Fragment as c, computed as l, createBlock as u, createCommentVNode as d, createElementBlock as f, createElementVNode as p, createTextVNode as m, createVNode as h, defineComponent as g, mergeModels as _, mergeProps as v, normalizeClass as y, openBlock as b, ref as ne, renderList as x, renderSlot as re, toDisplayString as S, unref as C, useAttrs as ie, useModel as w, vModelText as ae, withCtx as T, withDirectives as oe } from "vue";
|
|
@@ -34,6 +34,7 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
34
34
|
icon: { default: void 0 },
|
|
35
35
|
operators: { default: void 0 },
|
|
36
36
|
options: { default: () => [] },
|
|
37
|
+
summary: { default: void 0 },
|
|
37
38
|
status: { default: void 0 },
|
|
38
39
|
valueLabel: { default: "values" },
|
|
39
40
|
datePresets: { default: () => a },
|
|
@@ -76,25 +77,29 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
76
77
|
"update:open"
|
|
77
78
|
],
|
|
78
79
|
setup(a) {
|
|
79
|
-
let g = a, _ = w(a, "op"), G = w(a, "modelValue"), K = w(a, "open"), q = ie(), J = l(() => g.operators ?? s[g.type]), Y = l(() => J.value.find((e) => e.value === _.value)), X = l(() => !Y.value?.noValue),
|
|
80
|
+
let g = a, _ = w(a, "op"), G = w(a, "modelValue"), K = w(a, "open"), q = ie(), J = l(() => g.operators ?? s[g.type]), Y = l(() => J.value.find((e) => e.value === _.value)), X = l(() => !Y.value?.noValue), _e = l(() => X.value ? o(G.value, g.options, {
|
|
80
81
|
valueLabel: g.valueLabel,
|
|
81
82
|
yesLabel: g.yesLabel,
|
|
82
83
|
noLabel: g.noLabel
|
|
83
|
-
}) : ""),
|
|
84
|
+
}) : ""), Z = l(() => ({
|
|
85
|
+
field: g.summary?.field ?? g.field,
|
|
86
|
+
op: g.summary ? g.summary.op : Y.value?.label,
|
|
87
|
+
value: g.summary ? g.summary.value : _e.value
|
|
88
|
+
})), ve = l(() => [
|
|
84
89
|
"enum",
|
|
85
90
|
"person",
|
|
86
91
|
"company"
|
|
87
|
-
].includes(g.type)), Q = ne(""),
|
|
92
|
+
].includes(g.type)), Q = ne(""), ye = l(() => {
|
|
88
93
|
let e = Q.value.trim().toLowerCase();
|
|
89
94
|
return e ? g.options.filter((t) => t.label.toLowerCase().includes(e)) : g.options;
|
|
90
95
|
}), $ = l(() => Array.isArray(G.value) ? G.value : []);
|
|
91
|
-
function
|
|
96
|
+
function be(e) {
|
|
92
97
|
G.value = $.value.includes(e) ? $.value.filter((t) => t !== e) : [...$.value, e];
|
|
93
98
|
}
|
|
94
|
-
function
|
|
99
|
+
function xe() {
|
|
95
100
|
K.value = !1, g.onRemove?.();
|
|
96
101
|
}
|
|
97
|
-
let
|
|
102
|
+
let Se = {
|
|
98
103
|
blue: "bg-sui-hue-blue-ink",
|
|
99
104
|
green: "bg-sui-hue-green-ink",
|
|
100
105
|
orange: "bg-sui-hue-orange-ink",
|
|
@@ -125,9 +130,9 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
125
130
|
name: a.type
|
|
126
131
|
}, null, 8, ["name"])),
|
|
127
132
|
p("span", null, [
|
|
128
|
-
p("span", j, S(
|
|
129
|
-
|
|
130
|
-
Z.value ? (b(), f(c, { key: 1 }, [s[7] ||= m(S(" "), -1), p("span", M, S(Z.value), 1)], 64)) : d("", !0)
|
|
133
|
+
p("span", j, S(Z.value.field), 1),
|
|
134
|
+
Z.value.op ? (b(), f(c, { key: 0 }, [m(S(" ") + S(Z.value.op), 1)], 64)) : d("", !0),
|
|
135
|
+
Z.value.value ? (b(), f(c, { key: 1 }, [s[7] ||= m(S(" "), -1), p("span", M, S(Z.value.value), 1)], 64)) : d("", !0)
|
|
131
136
|
]),
|
|
132
137
|
s[8] ||= p("svg", {
|
|
133
138
|
"aria-hidden": "true",
|
|
@@ -144,7 +149,7 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
144
149
|
class: "z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in",
|
|
145
150
|
"side-offset": 8
|
|
146
151
|
}, {
|
|
147
|
-
default: T(() => [h(
|
|
152
|
+
default: T(() => [h(r, {
|
|
148
153
|
"close-label": a.closeLabel,
|
|
149
154
|
"enter-label": a.doneLabel,
|
|
150
155
|
status: a.status,
|
|
@@ -189,7 +194,7 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
189
194
|
class: "flex h-8 cursor-pointer items-center justify-between rounded-md px-2.5 text-13.5 text-sui-ink-body outline-hidden select-none data-highlighted:bg-sui-sunken data-[state=checked]:font-semibold data-[state=checked]:text-sui-ink",
|
|
190
195
|
value: e.value
|
|
191
196
|
}, {
|
|
192
|
-
default: T(() => [m(S(e.label) + " ", 1), e.value === _.value ? (b(), u(
|
|
197
|
+
default: T(() => [m(S(e.label) + " ", 1), e.value === _.value ? (b(), u(n, { key: 0 })) : d("", !0)]),
|
|
193
198
|
_: 2
|
|
194
199
|
}, 1032, ["value"]))), 128))]),
|
|
195
200
|
_: 1
|
|
@@ -204,9 +209,9 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
204
209
|
key: 4,
|
|
205
210
|
class: "ml-auto inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-danger-soft hover:text-sui-danger focus-visible:sui-ring-inset",
|
|
206
211
|
type: "button",
|
|
207
|
-
onClick:
|
|
212
|
+
onClick: xe
|
|
208
213
|
}, [h(i, { name: "trash" }), m(" " + S(a.removeLabel), 1)])) : d("", !0)
|
|
209
|
-
]), o.$slots.default ? re(o.$slots, "default", {}, void 0, void 0, 0) : X.value ? (b(), f(c, { key: 1 }, [
|
|
214
|
+
]), o.$slots.default ? re(o.$slots, "default", {}, void 0, void 0, 0) : X.value ? (b(), f(c, { key: 1 }, [ve.value ? (b(), f(c, { key: 0 }, [a.options.length > 6 ? (b(), f("div", I, [h(i, {
|
|
210
215
|
class: "size-4",
|
|
211
216
|
name: "search"
|
|
212
217
|
}), oe(p("input", {
|
|
@@ -215,21 +220,21 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
215
220
|
autofocus: "",
|
|
216
221
|
class: "h-full min-w-0 flex-1 border-none bg-transparent p-0 text-sm text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden",
|
|
217
222
|
placeholder: a.searchPlaceholder
|
|
218
|
-
}, null, 8, L), [[ae, Q.value]])])) : d("", !0), p("div", R, [(b(!0), f(c, null, x(
|
|
223
|
+
}, null, 8, L), [[ae, Q.value]])])) : d("", !0), p("div", R, [(b(!0), f(c, null, x(ye.value, (e) => (b(), f("label", {
|
|
219
224
|
key: e.value,
|
|
220
225
|
class: y([H, $.value.includes(e.value) ? U : W])
|
|
221
226
|
}, [
|
|
222
227
|
h(t, {
|
|
223
228
|
"model-value": $.value.includes(e.value),
|
|
224
|
-
"onUpdate:modelValue": (t) =>
|
|
229
|
+
"onUpdate:modelValue": (t) => be(e.value)
|
|
225
230
|
}, null, 8, ["model-value", "onUpdate:modelValue"]),
|
|
226
231
|
e.color ? (b(), f("span", {
|
|
227
232
|
key: 0,
|
|
228
|
-
class: y(["size-1.5 shrink-0 rounded-full",
|
|
233
|
+
class: y(["size-1.5 shrink-0 rounded-full", Se[e.color]])
|
|
229
234
|
}, null, 2)) : d("", !0),
|
|
230
235
|
p("span", z, S(e.label), 1),
|
|
231
236
|
e.count == null ? d("", !0) : (b(), f("span", B, S(e.count), 1))
|
|
232
|
-
], 2))), 128))])], 64)) : a.type === "text" || a.type === "number" ? (b(), f("div", me, [h(
|
|
237
|
+
], 2))), 128))])], 64)) : a.type === "text" || a.type === "number" ? (b(), f("div", me, [h(ee, {
|
|
233
238
|
"aria-label": a.field,
|
|
234
239
|
autofocus: "",
|
|
235
240
|
"model-value": G.value == null ? "" : String(G.value),
|
|
@@ -259,14 +264,14 @@ var j = { class: "font-medium" }, M = { class: "font-semibold" }, N = { class: "
|
|
|
259
264
|
name: "date"
|
|
260
265
|
}, null, 8, ["class"]),
|
|
261
266
|
p("span", he, S(e), 1),
|
|
262
|
-
e === G.value ? (b(), u(
|
|
267
|
+
e === G.value ? (b(), u(n, { key: 0 })) : d("", !0)
|
|
263
268
|
]),
|
|
264
269
|
_: 2
|
|
265
270
|
}, 1032, ["class", "value"]))), 128))]),
|
|
266
271
|
_: 1
|
|
267
|
-
}, 8, ["aria-label", "model-value"])) : a.type === "boolean" ? (b(), f("div", ge, [h(
|
|
272
|
+
}, 8, ["aria-label", "model-value"])) : a.type === "boolean" ? (b(), f("div", ge, [h(te, {
|
|
268
273
|
"aria-label": a.field,
|
|
269
|
-
"model-value": G.value ? "yes" : "no",
|
|
274
|
+
"model-value": G.value === null ? "" : G.value ? "yes" : "no",
|
|
270
275
|
options: [{
|
|
271
276
|
value: "yes",
|
|
272
277
|
label: a.yesLabel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterChip.vue_vue_type_script_setup_true_lang.js","names":["$slots","defaultDatePresets","_useModel","_openBlock","_createElementBlock","_mergeProps","_unref","_createVNode","FilterGlyph","_createTextVNode","_toDisplayString","_createBlock","_withCtx","Icon","_createElementVNode","_Fragment","_createCommentVNode","FilterFrame","_renderList","CheckIcon","_renderSlot","_withDirectives","_vModelText","_normalizeClass","Checkbox","Input","SegmentedControl","Icon_default","Checkbox_default","Input_default","SegmentedControl_default","CheckIcon_default","FilterFrame_default","FilterGlyph_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","chip","row","rowOn","rowOff","FilterChip_vue_vue_type_script_setup_true_lang_default","datePresets","\"op\"","\"opModifiers\"","\"modelValue\"","\"modelModifiers\"","\"open\"","\"openModifiers\"","props","__props","op","useModel","value","open","attrs","useAttrs","ops","computed","filterOperators","current","o","needsValue","text","describeFilter","isChecklist","query","ref","listed","q","picked","toggle","choice","v","remove","hueDots","openBlock","createElementBlock","mergeProps","unref","createVNode","createTextVNode","toDisplayString","createBlock","PopoverRoot","_cache","$event","withCtx","PopoverTrigger","createElementVNode","Fragment","createCommentVNode","PopoverPortal","PopoverContent","DropdownMenuRoot","DropdownMenuTrigger","DropdownMenuPortal","DropdownMenuContent","DropdownMenuRadioGroup","renderList","DropdownMenuRadioItem","operator","_ctx","renderSlot","withDirectives","vModelText","option","normalizeClass","RadioGroupRoot","RadioGroupItem","preset"],"sources":["../../../src/components/Filters/FilterChip.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, useAttrs } from 'vue'\n\nimport {\n DropdownMenuContent,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuRoot,\n DropdownMenuTrigger,\n PopoverContent,\n PopoverPortal,\n PopoverRoot,\n PopoverTrigger,\n RadioGroupItem,\n RadioGroupRoot\n} from 'reka-ui'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Icon from '../Icon/Icon.vue'\nimport Input from '../Input/Input.vue'\nimport SegmentedControl from '../SegmentedControl/SegmentedControl.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport { datePresets as defaultDatePresets, describeFilter, filterOperators } from './defaults'\nimport type { FilterFieldType, FilterOperator, FilterOption, FilterValue } from './types'\n\nexport interface FilterChipProps {\n /** A filter, read as a sentence; `add` is the quiet \"+ Filter\" that starts one (FilterMenu includes it). */\n kind?: 'filter' | 'add'\n field?: string\n /** Picks the editor: a checklist for enum, person and company, an input for text and number, presets for date, Yes / No for boolean. */\n type?: FilterFieldType\n /** An app icon for the field, instead of the type's glyph. */\n icon?: string\n /** The operators offered; English ones by type by default. */\n operators?: FilterOperator[]\n /** The choices for enum, person and company. */\n options?: FilterOption[]\n /** The live result count in the panel's footer, e.g. \"120 people\". */\n status?: string\n /** The plural noun once more than two values are picked: \"3 stages\". */\n valueLabel?: string\n datePresets?: string[]\n /** Adds a Remove action to the panel (`@remove`). */\n onRemove?: () => void\n removeLabel?: string\n addLabel?: string\n operatorLabel?: string\n searchPlaceholder?: string\n valuePlaceholder?: string\n doneLabel?: string\n closeLabel?: string\n yesLabel?: string\n noLabel?: string\n}\n\n// Attributes and listeners go to the chip itself; the root is a renderless popover.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<FilterChipProps>(), {\n kind: 'filter',\n field: undefined,\n type: 'enum',\n icon: undefined,\n operators: undefined,\n options: () => [],\n status: undefined,\n valueLabel: 'values',\n datePresets: () => defaultDatePresets,\n onRemove: undefined,\n removeLabel: 'Remove',\n addLabel: 'Filter',\n operatorLabel: 'Operator',\n searchPlaceholder: 'Search…',\n valuePlaceholder: 'Type a value',\n doneLabel: 'Done',\n closeLabel: 'Close',\n yesLabel: 'Yes',\n noLabel: 'No'\n})\n\nconst op = defineModel<string>('op')\nconst value = defineModel<FilterValue>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n /** Your own editor, in place of the type's. */\n default?: () => unknown\n}>()\n\nconst attrs = useAttrs()\n\nconst ops = computed(() => props.operators ?? filterOperators[props.type])\nconst current = computed(() => ops.value.find((o) => o.value === op.value))\nconst needsValue = computed(() => !current.value?.noValue)\nconst text = computed(() =>\n needsValue.value\n ? describeFilter(value.value, props.options, {\n valueLabel: props.valueLabel,\n yesLabel: props.yesLabel,\n noLabel: props.noLabel\n })\n : ''\n)\nconst isChecklist = computed(() => ['enum', 'person', 'company'].includes(props.type))\n\nconst query = ref('')\nconst listed = computed(() => {\n const q = query.value.trim().toLowerCase()\n return q ? props.options.filter((o) => o.label.toLowerCase().includes(q)) : props.options\n})\n\nconst picked = computed(() => (Array.isArray(value.value) ? value.value : []))\n\nfunction toggle(choice: string | number) {\n value.value = picked.value.includes(choice) ? picked.value.filter((v) => v !== choice) : [...picked.value, choice]\n}\n\nfunction remove() {\n open.value = false\n props.onRemove?.()\n}\n\nconst chip =\n 'inline-flex h-7.5 items-center gap-1.5 rounded-lg border text-13 whitespace-nowrap outline-hidden transition-[border-color,box-shadow] duration-150 focus-visible:sui-ring'\nconst row =\n 'flex min-h-8 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\nconst rowOn = 'bg-sui-accent-wash font-semibold text-sui-accent'\nconst rowOff = 'text-sui-ink hover:bg-sui-sunken'\nconst hueDots: Record<string, string> = {\n blue: 'bg-sui-hue-blue-ink',\n green: 'bg-sui-hue-green-ink',\n orange: 'bg-sui-hue-orange-ink',\n pink: 'bg-sui-hue-pink-ink',\n purple: 'bg-sui-hue-purple-ink',\n slate: 'bg-sui-hue-slate-ink',\n red: 'bg-sui-hue-red-ink',\n amber: 'bg-sui-hue-amber-ink'\n}\n</script>\n\n<!-- A view's filter as a sentence chip (\"Stage is any of Lead, Customer\"), whose panel changes it as you go. -->\n<template>\n <button\n v-if=\"kind === 'add'\"\n v-bind=\"attrs\"\n class=\"border-dashed border-sui-line-strong px-2 font-medium text-sui-ink-muted hover:border-sui-ink-muted hover:text-sui-ink-body\"\n :class=\"chip\"\n type=\"button\"\n >\n <FilterGlyph name=\"plus\" />\n {{ addLabel }}\n </button>\n <PopoverRoot v-else v-model:open=\"open\">\n <PopoverTrigger\n v-bind=\"attrs\"\n class=\"border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-sui-accent hover:border-sui-accent-bright data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n :class=\"chip\"\n >\n <Icon v-if=\"icon\" class=\"size-3.5 opacity-80\" color=\"accent\" :name=\"icon\" />\n <FilterGlyph v-else class=\"opacity-80\" :name=\"type\" />\n <!-- Real spaces between the words, so the chip's name reads as a sentence. -->\n <span\n ><span class=\"font-medium\">{{ field }}</span\n ><template v-if=\"current\">{{ ' ' }}{{ current.label }}</template\n ><template v-if=\"text\"\n >{{ ' ' }}<span class=\"font-semibold\">{{ text }}</span></template\n ></span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <Icon v-if=\"icon\" color=\"ink-muted\" :name=\"icon\" />\n <FilterGlyph v-else class=\"size-4\" :name=\"type\" />\n <span class=\"text-sm font-semibold text-sui-ink\">{{ field }}</span>\n <DropdownMenuRoot v-if=\"ops.length > 1\">\n <DropdownMenuTrigger\n :aria-label=\"`${operatorLabel}: ${current?.label ?? ''}`\"\n class=\"inline-flex h-7 items-center gap-0.5 rounded-md pr-1 pl-1.5 text-13.5 font-medium text-sui-accent outline-hidden transition-colors duration-150 hover:bg-sui-accent-wash focus-visible:sui-ring-inset data-[state=open]:bg-sui-accent-wash\"\n >\n {{ current?.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </DropdownMenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuContent\n align=\"start\"\n class=\"z-[1100] w-44 rounded-lg border border-sui-line bg-sui-surface p-1 shadow-lg data-[state=open]:animate-sui-menu-in\"\n :side-offset=\"4\"\n >\n <DropdownMenuRadioGroup v-model=\"op\">\n <DropdownMenuRadioItem\n v-for=\"operator in ops\"\n :key=\"operator.value\"\n class=\"flex h-8 cursor-pointer items-center justify-between rounded-md px-2.5 text-13.5 text-sui-ink-body outline-hidden select-none data-highlighted:bg-sui-sunken data-[state=checked]:font-semibold data-[state=checked]:text-sui-ink\"\n :value=\"operator.value\"\n >\n {{ operator.label }}\n <CheckIcon v-if=\"operator.value === op\" />\n </DropdownMenuRadioItem>\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenuPortal>\n </DropdownMenuRoot>\n <span v-else class=\"text-13.5 text-sui-ink-muted\">{{ current?.label }}</span>\n <button\n v-if=\"onRemove\"\n class=\"ml-auto inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-danger-soft hover:text-sui-danger focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"remove\"\n >\n <FilterGlyph name=\"trash\" />\n {{ removeLabel }}\n </button>\n </div>\n\n <slot v-if=\"$slots.default\" />\n <template v-else-if=\"needsValue\">\n <template v-if=\"isChecklist\">\n <div\n v-if=\"options.length > 6\"\n class=\"mx-2 mb-1 flex h-9 items-center gap-2 rounded-lg border border-sui-line-subtle px-2.5 text-sui-ink-muted\"\n >\n <FilterGlyph class=\"size-4\" name=\"search\" />\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <input\n v-model=\"query\"\n :aria-label=\"searchPlaceholder\"\n autofocus\n class=\"h-full min-w-0 flex-1 border-none bg-transparent p-0 text-sm text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n <div class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\">\n <label\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"[row, picked.includes(option.value) ? rowOn : rowOff]\"\n >\n <Checkbox :model-value=\"picked.includes(option.value)\" @update:model-value=\"toggle(option.value)\" />\n <span v-if=\"option.color\" class=\"size-1.5 shrink-0 rounded-full\" :class=\"hueDots[option.color]\" />\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <span v-if=\"option.count != null\" class=\"text-xs font-normal text-sui-ink-muted tabular-nums\">{{\n option.count\n }}</span>\n </label>\n </div>\n </template>\n <div v-else-if=\"type === 'text' || type === 'number'\" class=\"px-2 pt-1 pb-2\">\n <Input\n :aria-label=\"field\"\n autofocus\n :model-value=\"value == null ? '' : String(value)\"\n :placeholder=\"valuePlaceholder\"\n :type=\"type === 'number' ? 'number' : 'text'\"\n @update:model-value=\"value = $event ?? null\"\n />\n </div>\n <RadioGroupRoot\n v-else-if=\"type === 'date'\"\n :aria-label=\"field\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"typeof value === 'string' ? value : undefined\"\n @update:model-value=\"value = String($event)\"\n >\n <RadioGroupItem\n v-for=\"preset in datePresets\"\n :key=\"preset\"\n :class=\"[row, preset === value ? rowOn : rowOff]\"\n :value=\"preset\"\n >\n <FilterGlyph\n class=\"size-4\"\n :class=\"preset === value ? 'text-sui-accent' : 'text-sui-ink-muted'\"\n name=\"date\"\n />\n <span class=\"min-w-0 flex-1 truncate\">{{ preset }}</span>\n <CheckIcon v-if=\"preset === value\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <div v-else-if=\"type === 'boolean'\" class=\"px-2.5 pt-1 pb-2.5\">\n <SegmentedControl\n :aria-label=\"field\"\n :model-value=\"value ? 'yes' : 'no'\"\n :options=\"[\n { value: 'yes', label: yesLabel },\n { value: 'no', label: noLabel }\n ]\"\n @update:model-value=\"value = $event === 'yes'\"\n />\n </div>\n </template>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FilterChip.vue_vue_type_script_setup_true_lang.js","names":["$slots","defaultDatePresets","_useModel","_openBlock","_createElementBlock","_mergeProps","_unref","_createVNode","FilterGlyph","_createTextVNode","_toDisplayString","_createBlock","_withCtx","Icon","_createElementVNode","_Fragment","_createCommentVNode","FilterFrame","_renderList","CheckIcon","_renderSlot","_withDirectives","_vModelText","_normalizeClass","Checkbox","Input","SegmentedControl","Icon_default","Checkbox_default","Input_default","SegmentedControl_default","CheckIcon_default","FilterFrame_default","FilterGlyph_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","chip","row","rowOn","rowOff","FilterChip_vue_vue_type_script_setup_true_lang_default","datePresets","\"op\"","\"opModifiers\"","\"modelValue\"","\"modelModifiers\"","\"open\"","\"openModifiers\"","props","__props","op","useModel","value","open","attrs","useAttrs","ops","computed","filterOperators","current","o","needsValue","text","describeFilter","words","isChecklist","query","ref","listed","q","picked","toggle","choice","v","remove","hueDots","openBlock","createElementBlock","mergeProps","unref","createVNode","createTextVNode","toDisplayString","createBlock","PopoverRoot","_cache","$event","withCtx","PopoverTrigger","createElementVNode","Fragment","createCommentVNode","PopoverPortal","PopoverContent","DropdownMenuRoot","DropdownMenuTrigger","DropdownMenuPortal","DropdownMenuContent","DropdownMenuRadioGroup","renderList","DropdownMenuRadioItem","operator","_ctx","renderSlot","withDirectives","vModelText","option","normalizeClass","RadioGroupRoot","RadioGroupItem","preset"],"sources":["../../../src/components/Filters/FilterChip.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, useAttrs } from 'vue'\n\nimport {\n DropdownMenuContent,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuRoot,\n DropdownMenuTrigger,\n PopoverContent,\n PopoverPortal,\n PopoverRoot,\n PopoverTrigger,\n RadioGroupItem,\n RadioGroupRoot\n} from 'reka-ui'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Icon from '../Icon/Icon.vue'\nimport Input from '../Input/Input.vue'\nimport SegmentedControl from '../SegmentedControl/SegmentedControl.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport { datePresets as defaultDatePresets, describeFilter, filterOperators } from './defaults'\nimport type { FilterFieldType, FilterOperator, FilterOption, FilterSummary, FilterValue } from './types'\n\nexport interface FilterChipProps {\n /** A filter, read as a sentence; `add` is the quiet \"+ Filter\" that starts one (FilterMenu includes it). */\n kind?: 'filter' | 'add'\n field?: string\n /** Picks the editor: a checklist for enum, person and company, an input for text and number, presets for date, Yes / No for boolean. */\n type?: FilterFieldType\n /** An app icon for the field, instead of the type's glyph. */\n icon?: string\n /** The operators offered; English ones by type by default. */\n operators?: FilterOperator[]\n /** The choices for enum, person and company. */\n options?: FilterOption[]\n /** Your own wording for the chip, in place of field, operator and value: \"Not archived\", \"Created in the last 7 days\". */\n summary?: FilterSummary\n /** The live result count in the panel's footer, e.g. \"120 people\". */\n status?: string\n /** The plural noun once more than two values are picked: \"3 stages\". */\n valueLabel?: string\n datePresets?: string[]\n /** Adds a Remove action to the panel (`@remove`). */\n onRemove?: () => void\n removeLabel?: string\n addLabel?: string\n operatorLabel?: string\n searchPlaceholder?: string\n valuePlaceholder?: string\n doneLabel?: string\n closeLabel?: string\n yesLabel?: string\n noLabel?: string\n}\n\n// Attributes and listeners go to the chip itself; the root is a renderless popover.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<FilterChipProps>(), {\n kind: 'filter',\n field: undefined,\n type: 'enum',\n icon: undefined,\n operators: undefined,\n options: () => [],\n summary: undefined,\n status: undefined,\n valueLabel: 'values',\n datePresets: () => defaultDatePresets,\n onRemove: undefined,\n removeLabel: 'Remove',\n addLabel: 'Filter',\n operatorLabel: 'Operator',\n searchPlaceholder: 'Search…',\n valuePlaceholder: 'Type a value',\n doneLabel: 'Done',\n closeLabel: 'Close',\n yesLabel: 'Yes',\n noLabel: 'No'\n})\n\nconst op = defineModel<string>('op')\nconst value = defineModel<FilterValue>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n /** Your own editor, in place of the type's. */\n default?: () => unknown\n}>()\n\nconst attrs = useAttrs()\n\nconst ops = computed(() => props.operators ?? filterOperators[props.type])\nconst current = computed(() => ops.value.find((o) => o.value === op.value))\nconst needsValue = computed(() => !current.value?.noValue)\nconst text = computed(() =>\n needsValue.value\n ? describeFilter(value.value, props.options, {\n valueLabel: props.valueLabel,\n yesLabel: props.yesLabel,\n noLabel: props.noLabel\n })\n : ''\n)\nconst words = computed(() => ({\n field: props.summary?.field ?? props.field,\n op: props.summary ? props.summary.op : current.value?.label,\n value: props.summary ? props.summary.value : text.value\n}))\nconst isChecklist = computed(() => ['enum', 'person', 'company'].includes(props.type))\n\nconst query = ref('')\nconst listed = computed(() => {\n const q = query.value.trim().toLowerCase()\n return q ? props.options.filter((o) => o.label.toLowerCase().includes(q)) : props.options\n})\n\nconst picked = computed(() => (Array.isArray(value.value) ? value.value : []))\n\nfunction toggle(choice: string | number) {\n value.value = picked.value.includes(choice) ? picked.value.filter((v) => v !== choice) : [...picked.value, choice]\n}\n\nfunction remove() {\n open.value = false\n props.onRemove?.()\n}\n\nconst chip =\n 'inline-flex h-7.5 items-center gap-1.5 rounded-lg border text-13 whitespace-nowrap outline-hidden transition-[border-color,box-shadow] duration-150 focus-visible:sui-ring'\nconst row =\n 'flex min-h-8 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\nconst rowOn = 'bg-sui-accent-wash font-semibold text-sui-accent'\nconst rowOff = 'text-sui-ink hover:bg-sui-sunken'\nconst hueDots: Record<string, string> = {\n blue: 'bg-sui-hue-blue-ink',\n green: 'bg-sui-hue-green-ink',\n orange: 'bg-sui-hue-orange-ink',\n pink: 'bg-sui-hue-pink-ink',\n purple: 'bg-sui-hue-purple-ink',\n slate: 'bg-sui-hue-slate-ink',\n red: 'bg-sui-hue-red-ink',\n amber: 'bg-sui-hue-amber-ink'\n}\n</script>\n\n<!-- A view's filter as a sentence chip (\"Stage is any of Lead, Customer\"), whose panel changes it as you go. -->\n<template>\n <button\n v-if=\"kind === 'add'\"\n v-bind=\"attrs\"\n class=\"border-dashed border-sui-line-strong px-2 font-medium text-sui-ink-muted hover:border-sui-ink-muted hover:text-sui-ink-body\"\n :class=\"chip\"\n type=\"button\"\n >\n <FilterGlyph name=\"plus\" />\n {{ addLabel }}\n </button>\n <PopoverRoot v-else v-model:open=\"open\">\n <PopoverTrigger\n v-bind=\"attrs\"\n class=\"border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-sui-accent hover:border-sui-accent-bright data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n :class=\"chip\"\n >\n <Icon v-if=\"icon\" class=\"size-3.5 opacity-80\" color=\"accent\" :name=\"icon\" />\n <FilterGlyph v-else class=\"opacity-80\" :name=\"type\" />\n <!-- Real spaces between the words, so the chip's name reads as a sentence. -->\n <span\n ><span class=\"font-medium\">{{ words.field }}</span\n ><template v-if=\"words.op\">{{ ' ' }}{{ words.op }}</template\n ><template v-if=\"words.value\"\n >{{ ' ' }}<span class=\"font-semibold\">{{ words.value }}</span></template\n ></span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <Icon v-if=\"icon\" color=\"ink-muted\" :name=\"icon\" />\n <FilterGlyph v-else class=\"size-4\" :name=\"type\" />\n <span class=\"text-sm font-semibold text-sui-ink\">{{ field }}</span>\n <DropdownMenuRoot v-if=\"ops.length > 1\">\n <DropdownMenuTrigger\n :aria-label=\"`${operatorLabel}: ${current?.label ?? ''}`\"\n class=\"inline-flex h-7 items-center gap-0.5 rounded-md pr-1 pl-1.5 text-13.5 font-medium text-sui-accent outline-hidden transition-colors duration-150 hover:bg-sui-accent-wash focus-visible:sui-ring-inset data-[state=open]:bg-sui-accent-wash\"\n >\n {{ current?.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </DropdownMenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuContent\n align=\"start\"\n class=\"z-[1100] w-44 rounded-lg border border-sui-line bg-sui-surface p-1 shadow-lg data-[state=open]:animate-sui-menu-in\"\n :side-offset=\"4\"\n >\n <DropdownMenuRadioGroup v-model=\"op\">\n <DropdownMenuRadioItem\n v-for=\"operator in ops\"\n :key=\"operator.value\"\n class=\"flex h-8 cursor-pointer items-center justify-between rounded-md px-2.5 text-13.5 text-sui-ink-body outline-hidden select-none data-highlighted:bg-sui-sunken data-[state=checked]:font-semibold data-[state=checked]:text-sui-ink\"\n :value=\"operator.value\"\n >\n {{ operator.label }}\n <CheckIcon v-if=\"operator.value === op\" />\n </DropdownMenuRadioItem>\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenuPortal>\n </DropdownMenuRoot>\n <span v-else class=\"text-13.5 text-sui-ink-muted\">{{ current?.label }}</span>\n <button\n v-if=\"onRemove\"\n class=\"ml-auto inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-danger-soft hover:text-sui-danger focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"remove\"\n >\n <FilterGlyph name=\"trash\" />\n {{ removeLabel }}\n </button>\n </div>\n\n <slot v-if=\"$slots.default\" />\n <template v-else-if=\"needsValue\">\n <template v-if=\"isChecklist\">\n <div\n v-if=\"options.length > 6\"\n class=\"mx-2 mb-1 flex h-9 items-center gap-2 rounded-lg border border-sui-line-subtle px-2.5 text-sui-ink-muted\"\n >\n <FilterGlyph class=\"size-4\" name=\"search\" />\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <input\n v-model=\"query\"\n :aria-label=\"searchPlaceholder\"\n autofocus\n class=\"h-full min-w-0 flex-1 border-none bg-transparent p-0 text-sm text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n :placeholder=\"searchPlaceholder\"\n />\n </div>\n <div class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\">\n <label\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"[row, picked.includes(option.value) ? rowOn : rowOff]\"\n >\n <Checkbox :model-value=\"picked.includes(option.value)\" @update:model-value=\"toggle(option.value)\" />\n <span v-if=\"option.color\" class=\"size-1.5 shrink-0 rounded-full\" :class=\"hueDots[option.color]\" />\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <span v-if=\"option.count != null\" class=\"text-xs font-normal text-sui-ink-muted tabular-nums\">{{\n option.count\n }}</span>\n </label>\n </div>\n </template>\n <div v-else-if=\"type === 'text' || type === 'number'\" class=\"px-2 pt-1 pb-2\">\n <Input\n :aria-label=\"field\"\n autofocus\n :model-value=\"value == null ? '' : String(value)\"\n :placeholder=\"valuePlaceholder\"\n :type=\"type === 'number' ? 'number' : 'text'\"\n @update:model-value=\"value = $event ?? null\"\n />\n </div>\n <RadioGroupRoot\n v-else-if=\"type === 'date'\"\n :aria-label=\"field\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"typeof value === 'string' ? value : undefined\"\n @update:model-value=\"value = String($event)\"\n >\n <RadioGroupItem\n v-for=\"preset in datePresets\"\n :key=\"preset\"\n :class=\"[row, preset === value ? rowOn : rowOff]\"\n :value=\"preset\"\n >\n <FilterGlyph\n class=\"size-4\"\n :class=\"preset === value ? 'text-sui-accent' : 'text-sui-ink-muted'\"\n name=\"date\"\n />\n <span class=\"min-w-0 flex-1 truncate\">{{ preset }}</span>\n <CheckIcon v-if=\"preset === value\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <div v-else-if=\"type === 'boolean'\" class=\"px-2.5 pt-1 pb-2.5\">\n <SegmentedControl\n :aria-label=\"field\"\n :model-value=\"value === null ? '' : value ? 'yes' : 'no'\"\n :options=\"[\n { value: 'yes', label: yesLabel },\n { value: 'no', label: noLabel }\n ]\"\n @update:model-value=\"value = $event === 'yes'\"\n />\n </div>\n </template>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SortMenu.js","names":["_sfc_main","SortMenu_vue_vue_type_script_setup_true_lang_default","SortMenu_default"],"sources":["../../../src/components/Filters/SortMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\n\nimport { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, RadioGroupItem, RadioGroupRoot } from 'reka-ui'\n\nimport Button from '../Button/Button.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport SortArrow from './SortArrow.vue'\nimport { sortDirections } from './defaults'\nimport type { FilterField, FilterFieldType, ViewSort } from './types'\n\nexport interface SortMenuProps {\n /** The sortable fields. */\n options: FilterField[]\n /** chip: the sort in the view bar, which shows only while there is one. button: the page header's Sort button. */\n as?: 'chip' | 'button'\n /** Boards can't sort: disable the button, with a Tooltip saying why. */\n disabled?: boolean\n status?: string\n /** The two directions' words for each field type, ascending first. */\n directionLabels?: Partial<Record<FilterFieldType, [string, string]>>\n title?: string\n buttonLabel?: string\n clearLabel?: string\n directionLabel?: string\n doneLabel?: string\n closeLabel?: string\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SortMenuProps>(), {\n as: 'chip',\n status: undefined,\n directionLabels: () => ({}),\n title: 'Sort by',\n buttonLabel: 'Sort',\n clearLabel: 'Clear sort',\n directionLabel: 'Direction',\n doneLabel: 'Done',\n closeLabel: 'Close'\n})\n\nconst sort = defineModel<ViewSort | null>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\nconst attrs = useAttrs()\n\nconst current = computed(() => (sort.value ? props.options.find((o) => o.name === sort.value!.name) : undefined))\nconst words = (type: FilterFieldType) => props.directionLabels[type] ?? sortDirections[type]\nconst dirLabel = computed(() => (current.value ? words(current.value.type)[sort.value!.dir === 'desc' ? 1 : 0] : ''))\n\nfunction pickField(name: string) {\n sort.value = { name, dir: sort.value?.name === name ? sort.value.dir : 'asc' }\n}\n\nfunction clear() {\n sort.value = null\n open.value = false\n}\n\nconst row =\n 'flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\n</script>\n\n<!-- A view's sort: the header's Sort button or the view bar's chip (\"Name, A → Z\") open a panel of fields and a direction. -->\n<template>\n <PopoverRoot v-if=\"as === 'button' || current\" v-model:open=\"open\">\n <PopoverTrigger v-if=\"as === 'button'\" as-child>\n <Button v-bind=\"attrs\" :disabled=\"disabled\" :variant=\"current ? 'tinted' : 'secondary'\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n {{ buttonLabel }}\n </Button>\n </PopoverTrigger>\n <PopoverTrigger\n v-else\n v-bind=\"attrs\"\n class=\"inline-flex h-7.5 items-center gap-1.5 rounded-lg border border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-13 whitespace-nowrap text-sui-accent outline-hidden transition-[border-color,box-shadow] duration-150 hover:border-sui-accent-bright focus-visible:sui-ring data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n >\n <SortArrow :dir=\"sort!.dir\" />\n <span\n ><span class=\"font-medium\">{{ current!.label }},</span>{{ ' ' }}{{ dirLabel }}</span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n <span class=\"text-sm font-semibold text-sui-ink\">{{ title }}</span>\n <button\n v-if=\"current\"\n class=\"ml-auto inline-flex h-7 items-center rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-sunken hover:text-sui-ink focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"clear\"\n >\n {{ clearLabel }}\n </button>\n </div>\n <RadioGroupRoot\n :aria-label=\"title\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"sort?.name\"\n @update:model-value=\"pickField($event as string)\"\n >\n <RadioGroupItem\n v-for=\"option in options\"\n :key=\"option.name\"\n :class=\"[\n row,\n option.name === sort?.name\n ? 'bg-sui-accent-wash font-semibold text-sui-accent'\n : 'text-sui-ink hover:bg-sui-sunken'\n ]\"\n :value=\"option.name\"\n >\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md\"\n :class=\"\n option.name === sort?.name ? 'bg-sui-surface text-sui-accent' : 'bg-sui-sunken text-sui-ink-body'\n \"\n >\n <FilterGlyph :name=\"option.type\" />\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <CheckIcon v-if=\"option.name === sort?.name\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <RadioGroupRoot\n v-if=\"current\"\n :aria-label=\"directionLabel\"\n class=\"mx-3 mb-3 grid grid-cols-2 gap-0.5 rounded-lg bg-sui-sunken p-0.75\"\n :model-value=\"sort!.dir\"\n orientation=\"horizontal\"\n @update:model-value=\"sort = { name: sort!.name, dir: $event as ViewSort['dir'] }\"\n >\n <RadioGroupItem\n v-for=\"(dir, i) in ['asc', 'desc'] as const\"\n :key=\"dir\"\n class=\"flex h-9 items-center justify-center gap-1.5 rounded-md text-13 font-medium outline-hidden transition-colors duration-150 focus-visible:sui-ring-inset\"\n :class=\"\n sort!.dir === dir\n ? 'bg-sui-surface text-sui-ink shadow-(--sui-shadow-raised) ring-1 ring-sui-line-subtle'\n : 'text-sui-ink-body hover:text-sui-ink'\n \"\n :value=\"dir\"\n >\n <SortArrow :dir=\"dir\" />\n {{ words(current.type)[i] }}\n </RadioGroupItem>\n </RadioGroupRoot>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"SortMenu.js","names":["_sfc_main","SortMenu_vue_vue_type_script_setup_true_lang_default","SortMenu_default"],"sources":["../../../src/components/Filters/SortMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\n\nimport { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, RadioGroupItem, RadioGroupRoot } from 'reka-ui'\n\nimport Button from '../Button/Button.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport SortArrow from './SortArrow.vue'\nimport { sortDirections } from './defaults'\nimport type { FilterField, FilterFieldType, ViewSort } from './types'\n\nexport interface SortMenuProps {\n /** The sortable fields. */\n options: FilterField[]\n /** chip: the sort in the view bar, which shows only while there is one. button: the page header's Sort button. */\n as?: 'chip' | 'button'\n /** Boards can't sort: disable the button, with a Tooltip saying why. */\n disabled?: boolean\n status?: string\n /** The two directions' words for each field type, ascending first. */\n directionLabels?: Partial<Record<FilterFieldType, [string, string]>>\n title?: string\n buttonLabel?: string\n clearLabel?: string\n directionLabel?: string\n doneLabel?: string\n closeLabel?: string\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SortMenuProps>(), {\n as: 'chip',\n status: undefined,\n directionLabels: () => ({}),\n title: 'Sort by',\n buttonLabel: 'Sort',\n clearLabel: 'Clear sort',\n directionLabel: 'Direction',\n doneLabel: 'Done',\n closeLabel: 'Close'\n})\n\nconst sort = defineModel<ViewSort | null>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\nconst attrs = useAttrs()\n\nconst current = computed(() => (sort.value ? props.options.find((o) => o.name === sort.value!.name) : undefined))\nconst words = (type: FilterFieldType) => props.directionLabels[type] ?? sortDirections[type]\nconst dirLabel = computed(() => (current.value ? words(current.value.type)[sort.value!.dir === 'desc' ? 1 : 0] : ''))\n\nfunction pickField(name: string) {\n const field = props.options.find((o) => o.name === name)\n sort.value = { name, dir: sort.value?.name === name ? sort.value.dir : (field?.sortDir ?? 'asc') }\n}\n\nfunction clear() {\n sort.value = null\n open.value = false\n}\n\nconst row =\n 'flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\n</script>\n\n<!-- A view's sort: the header's Sort button or the view bar's chip (\"Name, A → Z\") open a panel of fields and a direction. -->\n<template>\n <PopoverRoot v-if=\"as === 'button' || current\" v-model:open=\"open\">\n <PopoverTrigger v-if=\"as === 'button'\" as-child>\n <Button v-bind=\"attrs\" :disabled=\"disabled\" :variant=\"current ? 'tinted' : 'secondary'\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n {{ buttonLabel }}\n </Button>\n </PopoverTrigger>\n <PopoverTrigger\n v-else\n v-bind=\"attrs\"\n class=\"inline-flex h-7.5 items-center gap-1.5 rounded-lg border border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-13 whitespace-nowrap text-sui-accent outline-hidden transition-[border-color,box-shadow] duration-150 hover:border-sui-accent-bright focus-visible:sui-ring data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n >\n <SortArrow :dir=\"sort!.dir\" />\n <span\n ><span class=\"font-medium\">{{ current!.label }},</span>{{ ' ' }}{{ dirLabel }}</span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n <span class=\"text-sm font-semibold text-sui-ink\">{{ title }}</span>\n <button\n v-if=\"current\"\n class=\"ml-auto inline-flex h-7 items-center rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-sunken hover:text-sui-ink focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"clear\"\n >\n {{ clearLabel }}\n </button>\n </div>\n <RadioGroupRoot\n :aria-label=\"title\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"sort?.name\"\n @update:model-value=\"pickField($event as string)\"\n >\n <RadioGroupItem\n v-for=\"option in options\"\n :key=\"option.name\"\n :class=\"[\n row,\n option.name === sort?.name\n ? 'bg-sui-accent-wash font-semibold text-sui-accent'\n : 'text-sui-ink hover:bg-sui-sunken'\n ]\"\n :value=\"option.name\"\n >\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md\"\n :class=\"\n option.name === sort?.name ? 'bg-sui-surface text-sui-accent' : 'bg-sui-sunken text-sui-ink-body'\n \"\n >\n <FilterGlyph :name=\"option.type\" />\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <CheckIcon v-if=\"option.name === sort?.name\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <RadioGroupRoot\n v-if=\"current\"\n :aria-label=\"directionLabel\"\n class=\"mx-3 mb-3 grid grid-cols-2 gap-0.5 rounded-lg bg-sui-sunken p-0.75\"\n :model-value=\"sort!.dir\"\n orientation=\"horizontal\"\n @update:model-value=\"sort = { name: sort!.name, dir: $event as ViewSort['dir'] }\"\n >\n <RadioGroupItem\n v-for=\"(dir, i) in ['asc', 'desc'] as const\"\n :key=\"dir\"\n class=\"flex h-9 items-center justify-center gap-1.5 rounded-md text-13 font-medium outline-hidden transition-colors duration-150 focus-visible:sui-ring-inset\"\n :class=\"\n sort!.dir === dir\n ? 'bg-sui-surface text-sui-ink shadow-(--sui-shadow-raised) ring-1 ring-sui-line-subtle'\n : 'text-sui-ink-body hover:text-sui-ink'\n \"\n :value=\"dir\"\n >\n <SortArrow :dir=\"dir\" />\n {{ words(current.type)[i] }}\n </RadioGroupItem>\n </RadioGroupRoot>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
|
|
@@ -35,9 +35,10 @@ var j = { class: "font-medium" }, M = { class: "flex h-11 items-center gap-1.5 p
|
|
|
35
35
|
setup(m) {
|
|
36
36
|
let h = m, I = C(m, "modelValue"), L = C(m, "open"), R = S(), z = s(() => I.value ? h.options.find((e) => e.name === I.value.name) : void 0), B = (e) => h.directionLabels[e] ?? i[e], V = s(() => z.value ? B(z.value.type)[+(I.value.dir === "desc")] : "");
|
|
37
37
|
function H(e) {
|
|
38
|
+
let t = h.options.find((t) => t.name === e);
|
|
38
39
|
I.value = {
|
|
39
40
|
name: e,
|
|
40
|
-
dir: I.value?.name === e ? I.value.dir : "asc"
|
|
41
|
+
dir: I.value?.name === e ? I.value.dir : t?.sortDir ?? "asc"
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
function U() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SortMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createBlock","_unref","_withCtx","_createVNode","Button","_mergeProps","_createElementVNode","_createTextVNode","_toDisplayString","SortArrow","FilterFrame","_createElementBlock","_createCommentVNode","_Fragment","_renderList","_normalizeClass","FilterGlyph","CheckIcon","Button_default","CheckIcon_default","FilterFrame_default","FilterGlyph_default","SortArrow_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","row","SortMenu_vue_vue_type_script_setup_true_lang_default","\"modelValue\"","\"modelModifiers\"","\"open\"","\"openModifiers\"","props","__props","sort","useModel","open","attrs","useAttrs","current","computed","o","words","type","sortDirections","dirLabel","pickField","name","clear","openBlock","createBlock","unref","PopoverRoot","_cache","$event","withCtx","PopoverTrigger","createVNode","mergeProps","createElementVNode","createTextVNode","toDisplayString","PopoverPortal","PopoverContent","createElementBlock","createCommentVNode","RadioGroupRoot","Fragment","renderList","RadioGroupItem","option","normalizeClass","dir","i"],"sources":["../../../src/components/Filters/SortMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\n\nimport { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, RadioGroupItem, RadioGroupRoot } from 'reka-ui'\n\nimport Button from '../Button/Button.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport SortArrow from './SortArrow.vue'\nimport { sortDirections } from './defaults'\nimport type { FilterField, FilterFieldType, ViewSort } from './types'\n\nexport interface SortMenuProps {\n /** The sortable fields. */\n options: FilterField[]\n /** chip: the sort in the view bar, which shows only while there is one. button: the page header's Sort button. */\n as?: 'chip' | 'button'\n /** Boards can't sort: disable the button, with a Tooltip saying why. */\n disabled?: boolean\n status?: string\n /** The two directions' words for each field type, ascending first. */\n directionLabels?: Partial<Record<FilterFieldType, [string, string]>>\n title?: string\n buttonLabel?: string\n clearLabel?: string\n directionLabel?: string\n doneLabel?: string\n closeLabel?: string\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SortMenuProps>(), {\n as: 'chip',\n status: undefined,\n directionLabels: () => ({}),\n title: 'Sort by',\n buttonLabel: 'Sort',\n clearLabel: 'Clear sort',\n directionLabel: 'Direction',\n doneLabel: 'Done',\n closeLabel: 'Close'\n})\n\nconst sort = defineModel<ViewSort | null>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\nconst attrs = useAttrs()\n\nconst current = computed(() => (sort.value ? props.options.find((o) => o.name === sort.value!.name) : undefined))\nconst words = (type: FilterFieldType) => props.directionLabels[type] ?? sortDirections[type]\nconst dirLabel = computed(() => (current.value ? words(current.value.type)[sort.value!.dir === 'desc' ? 1 : 0] : ''))\n\nfunction pickField(name: string) {\n sort.value = { name, dir: sort.value?.name === name ? sort.value.dir : 'asc' }\n}\n\nfunction clear() {\n sort.value = null\n open.value = false\n}\n\nconst row =\n 'flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\n</script>\n\n<!-- A view's sort: the header's Sort button or the view bar's chip (\"Name, A → Z\") open a panel of fields and a direction. -->\n<template>\n <PopoverRoot v-if=\"as === 'button' || current\" v-model:open=\"open\">\n <PopoverTrigger v-if=\"as === 'button'\" as-child>\n <Button v-bind=\"attrs\" :disabled=\"disabled\" :variant=\"current ? 'tinted' : 'secondary'\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n {{ buttonLabel }}\n </Button>\n </PopoverTrigger>\n <PopoverTrigger\n v-else\n v-bind=\"attrs\"\n class=\"inline-flex h-7.5 items-center gap-1.5 rounded-lg border border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-13 whitespace-nowrap text-sui-accent outline-hidden transition-[border-color,box-shadow] duration-150 hover:border-sui-accent-bright focus-visible:sui-ring data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n >\n <SortArrow :dir=\"sort!.dir\" />\n <span\n ><span class=\"font-medium\">{{ current!.label }},</span>{{ ' ' }}{{ dirLabel }}</span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n <span class=\"text-sm font-semibold text-sui-ink\">{{ title }}</span>\n <button\n v-if=\"current\"\n class=\"ml-auto inline-flex h-7 items-center rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-sunken hover:text-sui-ink focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"clear\"\n >\n {{ clearLabel }}\n </button>\n </div>\n <RadioGroupRoot\n :aria-label=\"title\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"sort?.name\"\n @update:model-value=\"pickField($event as string)\"\n >\n <RadioGroupItem\n v-for=\"option in options\"\n :key=\"option.name\"\n :class=\"[\n row,\n option.name === sort?.name\n ? 'bg-sui-accent-wash font-semibold text-sui-accent'\n : 'text-sui-ink hover:bg-sui-sunken'\n ]\"\n :value=\"option.name\"\n >\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md\"\n :class=\"\n option.name === sort?.name ? 'bg-sui-surface text-sui-accent' : 'bg-sui-sunken text-sui-ink-body'\n \"\n >\n <FilterGlyph :name=\"option.type\" />\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <CheckIcon v-if=\"option.name === sort?.name\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <RadioGroupRoot\n v-if=\"current\"\n :aria-label=\"directionLabel\"\n class=\"mx-3 mb-3 grid grid-cols-2 gap-0.5 rounded-lg bg-sui-sunken p-0.75\"\n :model-value=\"sort!.dir\"\n orientation=\"horizontal\"\n @update:model-value=\"sort = { name: sort!.name, dir: $event as ViewSort['dir'] }\"\n >\n <RadioGroupItem\n v-for=\"(dir, i) in ['asc', 'desc'] as const\"\n :key=\"dir\"\n class=\"flex h-9 items-center justify-center gap-1.5 rounded-md text-13 font-medium outline-hidden transition-colors duration-150 focus-visible:sui-ring-inset\"\n :class=\"\n sort!.dir === dir\n ? 'bg-sui-surface text-sui-ink shadow-(--sui-shadow-raised) ring-1 ring-sui-line-subtle'\n : 'text-sui-ink-body hover:text-sui-ink'\n \"\n :value=\"dir\"\n >\n <SortArrow :dir=\"dir\" />\n {{ words(current.type)[i] }}\n </RadioGroupItem>\n </RadioGroupRoot>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SortMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createBlock","_unref","_withCtx","_createVNode","Button","_mergeProps","_createElementVNode","_createTextVNode","_toDisplayString","SortArrow","FilterFrame","_createElementBlock","_createCommentVNode","_Fragment","_renderList","_normalizeClass","FilterGlyph","CheckIcon","Button_default","CheckIcon_default","FilterFrame_default","FilterGlyph_default","SortArrow_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","row","SortMenu_vue_vue_type_script_setup_true_lang_default","\"modelValue\"","\"modelModifiers\"","\"open\"","\"openModifiers\"","props","__props","sort","useModel","open","attrs","useAttrs","current","computed","o","words","type","sortDirections","dirLabel","pickField","field","name","clear","openBlock","createBlock","unref","PopoverRoot","_cache","$event","withCtx","PopoverTrigger","createVNode","mergeProps","createElementVNode","createTextVNode","toDisplayString","PopoverPortal","PopoverContent","createElementBlock","createCommentVNode","RadioGroupRoot","Fragment","renderList","RadioGroupItem","option","normalizeClass","dir","i"],"sources":["../../../src/components/Filters/SortMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\n\nimport { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, RadioGroupItem, RadioGroupRoot } from 'reka-ui'\n\nimport Button from '../Button/Button.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport FilterFrame from './FilterFrame.vue'\nimport FilterGlyph from './FilterGlyph.vue'\nimport SortArrow from './SortArrow.vue'\nimport { sortDirections } from './defaults'\nimport type { FilterField, FilterFieldType, ViewSort } from './types'\n\nexport interface SortMenuProps {\n /** The sortable fields. */\n options: FilterField[]\n /** chip: the sort in the view bar, which shows only while there is one. button: the page header's Sort button. */\n as?: 'chip' | 'button'\n /** Boards can't sort: disable the button, with a Tooltip saying why. */\n disabled?: boolean\n status?: string\n /** The two directions' words for each field type, ascending first. */\n directionLabels?: Partial<Record<FilterFieldType, [string, string]>>\n title?: string\n buttonLabel?: string\n clearLabel?: string\n directionLabel?: string\n doneLabel?: string\n closeLabel?: string\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SortMenuProps>(), {\n as: 'chip',\n status: undefined,\n directionLabels: () => ({}),\n title: 'Sort by',\n buttonLabel: 'Sort',\n clearLabel: 'Clear sort',\n directionLabel: 'Direction',\n doneLabel: 'Done',\n closeLabel: 'Close'\n})\n\nconst sort = defineModel<ViewSort | null>({ default: null })\nconst open = defineModel<boolean>('open', { default: false })\n\nconst attrs = useAttrs()\n\nconst current = computed(() => (sort.value ? props.options.find((o) => o.name === sort.value!.name) : undefined))\nconst words = (type: FilterFieldType) => props.directionLabels[type] ?? sortDirections[type]\nconst dirLabel = computed(() => (current.value ? words(current.value.type)[sort.value!.dir === 'desc' ? 1 : 0] : ''))\n\nfunction pickField(name: string) {\n const field = props.options.find((o) => o.name === name)\n sort.value = { name, dir: sort.value?.name === name ? sort.value.dir : (field?.sortDir ?? 'asc') }\n}\n\nfunction clear() {\n sort.value = null\n open.value = false\n}\n\nconst row =\n 'flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1 text-left text-13.5 outline-hidden transition-colors duration-100 focus-visible:sui-ring-inset'\n</script>\n\n<!-- A view's sort: the header's Sort button or the view bar's chip (\"Name, A → Z\") open a panel of fields and a direction. -->\n<template>\n <PopoverRoot v-if=\"as === 'button' || current\" v-model:open=\"open\">\n <PopoverTrigger v-if=\"as === 'button'\" as-child>\n <Button v-bind=\"attrs\" :disabled=\"disabled\" :variant=\"current ? 'tinted' : 'secondary'\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n {{ buttonLabel }}\n </Button>\n </PopoverTrigger>\n <PopoverTrigger\n v-else\n v-bind=\"attrs\"\n class=\"inline-flex h-7.5 items-center gap-1.5 rounded-lg border border-sui-accent-line bg-sui-accent-wash pr-1.5 pl-2 text-13 whitespace-nowrap text-sui-accent outline-hidden transition-[border-color,box-shadow] duration-150 hover:border-sui-accent-bright focus-visible:sui-ring data-[state=open]:border-sui-accent-solid data-[state=open]:shadow-[0_0_0_3px_rgb(var(--sui-focus-ring))]\"\n >\n <SortArrow :dir=\"sort!.dir\" />\n <span\n ><span class=\"font-medium\">{{ current!.label }},</span>{{ ' ' }}{{ dirLabel }}</span\n >\n <svg\n aria-hidden=\"true\"\n class=\"size-3.5 shrink-0 fill-none stroke-current opacity-70\"\n stroke-linecap=\"round\"\n stroke-width=\"2.2\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </PopoverTrigger>\n <PopoverPortal>\n <PopoverContent\n align=\"start\"\n class=\"z-[1100] w-85 max-w-[calc(100vw-2rem)] origin-(--reka-popover-content-transform-origin) rounded-xl border border-sui-line bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-overlay-in\"\n :side-offset=\"8\"\n >\n <FilterFrame :close-label=\"closeLabel\" :enter-label=\"doneLabel\" :status=\"status\" @enter=\"open = false\">\n <div class=\"flex h-11 items-center gap-1.5 pr-1.5 pl-3.5 text-sui-ink-muted\">\n <svg\n aria-hidden=\"true\"\n class=\"size-4 fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M5 13V3M2.5 5.5 5 3l2.5 2.5M11 3v10M8.5 10.5 11 13l2.5-2.5\" />\n </svg>\n <span class=\"text-sm font-semibold text-sui-ink\">{{ title }}</span>\n <button\n v-if=\"current\"\n class=\"ml-auto inline-flex h-7 items-center rounded-lg px-2 text-12.5 font-medium text-sui-ink-muted outline-hidden transition-colors duration-150 hover:bg-sui-sunken hover:text-sui-ink focus-visible:sui-ring-inset\"\n type=\"button\"\n @click=\"clear\"\n >\n {{ clearLabel }}\n </button>\n </div>\n <RadioGroupRoot\n :aria-label=\"title\"\n class=\"max-h-64 overflow-auto px-1.5 pt-1 pb-1.5\"\n :model-value=\"sort?.name\"\n @update:model-value=\"pickField($event as string)\"\n >\n <RadioGroupItem\n v-for=\"option in options\"\n :key=\"option.name\"\n :class=\"[\n row,\n option.name === sort?.name\n ? 'bg-sui-accent-wash font-semibold text-sui-accent'\n : 'text-sui-ink hover:bg-sui-sunken'\n ]\"\n :value=\"option.name\"\n >\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md\"\n :class=\"\n option.name === sort?.name ? 'bg-sui-surface text-sui-accent' : 'bg-sui-sunken text-sui-ink-body'\n \"\n >\n <FilterGlyph :name=\"option.type\" />\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{{ option.label }}</span>\n <CheckIcon v-if=\"option.name === sort?.name\" />\n </RadioGroupItem>\n </RadioGroupRoot>\n <RadioGroupRoot\n v-if=\"current\"\n :aria-label=\"directionLabel\"\n class=\"mx-3 mb-3 grid grid-cols-2 gap-0.5 rounded-lg bg-sui-sunken p-0.75\"\n :model-value=\"sort!.dir\"\n orientation=\"horizontal\"\n @update:model-value=\"sort = { name: sort!.name, dir: $event as ViewSort['dir'] }\"\n >\n <RadioGroupItem\n v-for=\"(dir, i) in ['asc', 'desc'] as const\"\n :key=\"dir\"\n class=\"flex h-9 items-center justify-center gap-1.5 rounded-md text-13 font-medium outline-hidden transition-colors duration-150 focus-visible:sui-ring-inset\"\n :class=\"\n sort!.dir === dir\n ? 'bg-sui-surface text-sui-ink shadow-(--sui-shadow-raised) ring-1 ring-sui-line-subtle'\n : 'text-sui-ink-body hover:text-sui-ink'\n \"\n :value=\"dir\"\n >\n <SortArrow :dir=\"dir\" />\n {{ words(current.type)[i] }}\n </RadioGroupItem>\n </RadioGroupRoot>\n </FilterFrame>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":"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"}
|
|
@@ -5,4 +5,4 @@ export type { FilterMenuProps } from './FilterMenu.vue';
|
|
|
5
5
|
export { default as SortMenu } from './SortMenu.vue';
|
|
6
6
|
export type { SortMenuProps } from './SortMenu.vue';
|
|
7
7
|
export { datePresets, describeFilter, filterOperators, sortDirections } from './defaults';
|
|
8
|
-
export type { FilterField, FilterFieldType, FilterOperator, FilterOption, FilterValue, ViewSort } from './types';
|
|
8
|
+
export type { FilterField, FilterFieldType, FilterOperator, FilterOption, FilterSummary, FilterValue, ViewSort } from './types';
|
|
@@ -5,6 +5,14 @@ export interface FilterField {
|
|
|
5
5
|
type: FilterFieldType;
|
|
6
6
|
/** A heading the picker lists the field under, e.g. "Activity". */
|
|
7
7
|
group?: string;
|
|
8
|
+
/** The direction SortMenu starts this field in; ascending by default. Newest first suits most dates. */
|
|
9
|
+
sortDir?: 'asc' | 'desc';
|
|
10
|
+
}
|
|
11
|
+
/** How a filter chip reads: the field, the operator and the value, each optional. */
|
|
12
|
+
export interface FilterSummary {
|
|
13
|
+
field?: string;
|
|
14
|
+
op?: string;
|
|
15
|
+
value?: string;
|
|
8
16
|
}
|
|
9
17
|
export interface FilterOperator {
|
|
10
18
|
value: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Table.js","names":["_sfc_main","Table_vue_vue_type_script_setup_true_lang_default","Table_default"],"sources":["../../../src/components/Table/Table.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed } from 'vue'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { TableColumn, TableSort } from './types'\n\nexport interface TableProps<T> {\n columns: TableColumn[]\n rows: T[]\n /** The row field that identifies it. */\n rowKey?: keyof T & string\n /** A checkbox column bound to `v-model:selected`; selected rows tint accent-wash. */\n selectable?: boolean\n /** Skeleton rows in place of the body. */\n loading?: boolean\n loadingRows?: number\n /** md: 57px rows. sm: 40px, for dense lists. */\n size?: 'md' | 'sm'\n /** The table's name for screen readers. */\n caption?: string\n selectAllLabel?: string\n selectRowLabel?: string\n /** Makes rows clickable (`@row-click`), e.g. to open the record. */\n onRowClick?: (row: T) => void\n}\n\nconst props = withDefaults(defineProps<TableProps<T>>(), {\n rowKey: 'id' as never,\n loadingRows: 5,\n size: 'md',\n caption: undefined,\n selectAllLabel: 'Select all rows',\n selectRowLabel: 'Select row',\n onRowClick: undefined\n})\n\nconst sort = defineModel<TableSort | null>('sort', { default: null })\nconst selected = defineModel<unknown[]>('selected', { default: () => [] })\n\nconst slots = defineSlots<\n {\n /** Shown in place of the body when there are no rows, e.g. an EmptyState. */\n empty?: () => unknown\n /** Replaces the body when given, e.g. an inset danger Alert with a retry. */\n error?: () => unknown\n /** A canvas strip under the rows: a rows-per-page Select, the range, Pagination. */\n footer?: () => unknown\n } & {\n [K in `cell-${string}`]?: (props: { row: T; value: unknown }) => unknown\n }\n>()\n\nconst idOf = (row: T): unknown => row[props.rowKey ?? 'id']\nconst ids = computed(() => props.rows.map(idOf))\nconst allSelected = computed(() => ids.value.length > 0 && ids.value.every((id) => selected.value.includes(id)))\nconst someSelected = computed(() => !allSelected.value && ids.value.some((id) => selected.value.includes(id)))\n\nfunction toggleAll() {\n selected.value = allSelected.value\n ? selected.value.filter((id) => !ids.value.includes(id))\n : [...new Set([...selected.value, ...ids.value])]\n}\n\nfunction toggle(id: unknown) {\n selected.value = selected.value.includes(id) ? selected.value.filter((x) => x !== id) : [...selected.value, id]\n}\n\n// Sorting a column cycles ascending, descending, unsorted.\nfunction sortBy(key: string) {\n const dir = sort.value?.key === key ? sort.value.dir : null\n const next = dir === null ? 'asc' : dir === 'asc' ? 'desc' : null\n sort.value = next ? { key, dir: next } : null\n}\n\nfunction dirOf(key: string) {\n return sort.value?.key === key ? sort.value.dir : null\n}\n\nfunction ariaSort(column: TableColumn) {\n const dir = dirOf(column.key)\n if (dir) return dir === 'asc' ? 'ascending' : 'descending'\n return column.sortable ? 'none' : undefined\n}\n\nconst state = computed(() => {\n if (slots.error) return 'error'\n if (props.loading) return 'loading'\n return props.rows.length === 0 && slots.empty ? 'empty' : 'rows'\n})\n\nconst columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\nconst alignClass = { left: 'text-left', center: 'text-center', right: 'text-right' }\nconst skeletonWidths = ['60%', '45%', '70%', '35%', '55%']\n\nconst sm = computed(() => props.size === 'sm')\nconst th = computed(() => [\n 'border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap',\n sm.value ? 'h-9' : 'h-11'\n])\nconst td = computed(() => [\n '
|
|
1
|
+
{"version":3,"file":"Table.js","names":["_sfc_main","Table_vue_vue_type_script_setup_true_lang_default","Table_default"],"sources":["../../../src/components/Table/Table.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed } from 'vue'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { TableColumn, TableSort } from './types'\n\nexport interface TableProps<T> {\n columns: TableColumn[]\n rows: T[]\n /** The row field that identifies it. */\n rowKey?: keyof T & string\n /** A checkbox column bound to `v-model:selected`; selected rows tint accent-wash. */\n selectable?: boolean\n /** Skeleton rows in place of the body. */\n loading?: boolean\n loadingRows?: number\n /** md: 57px rows. sm: 40px, for dense lists. */\n size?: 'md' | 'sm'\n /** The table's name for screen readers. */\n caption?: string\n selectAllLabel?: string\n selectRowLabel?: string\n /** Makes rows clickable (`@row-click`), e.g. to open the record. */\n onRowClick?: (row: T) => void\n}\n\nconst props = withDefaults(defineProps<TableProps<T>>(), {\n rowKey: 'id' as never,\n loadingRows: 5,\n size: 'md',\n caption: undefined,\n selectAllLabel: 'Select all rows',\n selectRowLabel: 'Select row',\n onRowClick: undefined\n})\n\nconst sort = defineModel<TableSort | null>('sort', { default: null })\nconst selected = defineModel<unknown[]>('selected', { default: () => [] })\n\nconst slots = defineSlots<\n {\n /** Shown in place of the body when there are no rows, e.g. an EmptyState. */\n empty?: () => unknown\n /** Replaces the body when given, e.g. an inset danger Alert with a retry. */\n error?: () => unknown\n /** A canvas strip under the rows: a rows-per-page Select, the range, Pagination. */\n footer?: () => unknown\n } & {\n [K in `cell-${string}`]?: (props: { row: T; value: unknown }) => unknown\n }\n>()\n\nconst idOf = (row: T): unknown => row[props.rowKey ?? 'id']\nconst ids = computed(() => props.rows.map(idOf))\nconst allSelected = computed(() => ids.value.length > 0 && ids.value.every((id) => selected.value.includes(id)))\nconst someSelected = computed(() => !allSelected.value && ids.value.some((id) => selected.value.includes(id)))\n\nfunction toggleAll() {\n selected.value = allSelected.value\n ? selected.value.filter((id) => !ids.value.includes(id))\n : [...new Set([...selected.value, ...ids.value])]\n}\n\nfunction toggle(id: unknown) {\n selected.value = selected.value.includes(id) ? selected.value.filter((x) => x !== id) : [...selected.value, id]\n}\n\n// Sorting a column cycles ascending, descending, unsorted.\nfunction sortBy(key: string) {\n const dir = sort.value?.key === key ? sort.value.dir : null\n const next = dir === null ? 'asc' : dir === 'asc' ? 'desc' : null\n sort.value = next ? { key, dir: next } : null\n}\n\nfunction dirOf(key: string) {\n return sort.value?.key === key ? sort.value.dir : null\n}\n\nfunction ariaSort(column: TableColumn) {\n const dir = dirOf(column.key)\n if (dir) return dir === 'asc' ? 'ascending' : 'descending'\n return column.sortable ? 'none' : undefined\n}\n\nconst state = computed(() => {\n if (slots.error) return 'error'\n if (props.loading) return 'loading'\n return props.rows.length === 0 && slots.empty ? 'empty' : 'rows'\n})\n\nconst columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\nconst alignClass = { left: 'text-left', center: 'text-center', right: 'text-right' }\nconst skeletonWidths = ['60%', '45%', '70%', '35%', '55%']\n\nconst sm = computed(() => props.size === 'sm')\nconst th = computed(() => [\n 'border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap',\n sm.value ? 'h-9' : 'h-11'\n])\nconst td = computed(() => [\n 'border-b border-sui-line-subtle align-middle text-sui-ink-body group-last/row:border-b-0',\n sm.value ? 'h-10' : 'h-[3.5625rem]'\n])\nconst pad = computed(() => (sm.value ? 'px-3 py-1.5' : 'px-4 py-3'))\nconst headPad = computed(() => (sm.value ? 'px-3' : 'px-4'))\nconst checkCell = 'w-12 pl-4'\n</script>\n\n<!-- A data table: sunken header strip, sortable headers, selection, loading, empty and error states, a footer. -->\n<template>\n <div class=\"overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sm text-sui-ink\">\n <div class=\"overflow-x-auto\">\n <table class=\"w-full min-w-[var(--sui-table-min,720px)] table-fixed border-collapse\">\n <caption v-if=\"caption\" class=\"sr-only\">\n {{\n caption\n }}\n </caption>\n <colgroup>\n <col v-if=\"selectable\" class=\"w-12\" />\n <col\n v-for=\"column in columns\"\n :key=\"column.key\"\n :style=\"{ width: typeof column.width === 'number' ? `${column.width}px` : column.width }\"\n />\n </colgroup>\n <thead>\n <tr>\n <th v-if=\"selectable\" class=\"bg-sui-sunken\" :class=\"[th, checkCell]\">\n <Checkbox\n :aria-label=\"selectAllLabel\"\n :disabled=\"loading || rows.length === 0\"\n :indeterminate=\"someSelected\"\n :model-value=\"allSelected\"\n @update:model-value=\"toggleAll\"\n />\n </th>\n <component\n :is=\"column.label ? 'th' : 'td'\"\n v-for=\"column in columns\"\n :key=\"column.key\"\n :aria-sort=\"ariaSort(column)\"\n :class=\"[\n th,\n headPad,\n alignClass[column.align ?? 'left'],\n dirOf(column.key) ? 'bg-sui-accent-wash text-sui-accent' : 'bg-sui-sunken text-sui-ink-subtle'\n ]\"\n :scope=\"column.label ? 'col' : undefined\"\n >\n <button\n v-if=\"column.sortable\"\n class=\"inline-flex h-full w-full items-center gap-1.5 rounded-md outline-hidden focus-visible:sui-ring-inset\"\n :class=\"[\n column.align === 'right' && 'justify-end',\n column.align === 'center' && 'justify-center',\n !dirOf(column.key) && 'hover:text-sui-ink'\n ]\"\n type=\"button\"\n @click=\"sortBy(column.key)\"\n >\n {{ column.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3 shrink-0 fill-none stroke-current\"\n :class=\"!dirOf(column.key) && 'opacity-50'\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 12 12\"\n >\n <path v-if=\"dirOf(column.key) === 'asc'\" d=\"M3 7.5 6 4.5l3 3\" />\n <path v-else-if=\"dirOf(column.key) === 'desc'\" d=\"M3 4.5 6 7.5l3-3\" />\n <path v-else d=\"M3.5 4.5 6 2l2.5 2.5M3.5 7.5 6 10l2.5-2.5\" />\n </svg>\n </button>\n <template v-else>{{ column.label }}</template>\n </component>\n </tr>\n </thead>\n <tbody :aria-busy=\"loading || undefined\">\n <tr v-if=\"state === 'error' || state === 'empty'\">\n <td class=\"p-0\" :colspan=\"columnCount\">\n <slot :name=\"state\" />\n </td>\n </tr>\n <template v-else-if=\"state === 'loading'\">\n <tr v-for=\"i in loadingRows\" :key=\"i\" aria-hidden=\"true\" class=\"group/row\">\n <td v-if=\"selectable\" :class=\"[td, checkCell]\"><Skeleton size=\"1rem\" /></td>\n <td v-for=\"(column, j) in columns\" :key=\"column.key\" :class=\"[td, pad]\">\n <Skeleton height=\"0.75rem\" :width=\"skeletonWidths[(i + j) % 5]\" />\n </td>\n </tr>\n </template>\n <template v-else>\n <tr\n v-for=\"row in rows\"\n :key=\"idOf(row) as PropertyKey\"\n :aria-selected=\"selectable ? selected.includes(idOf(row)) : undefined\"\n class=\"group/row transition-colors duration-150\"\n :class=\"[\n selected.includes(idOf(row)) ? 'bg-sui-accent-wash' : 'hover:bg-sui-sunken',\n onRowClick && 'cursor-pointer'\n ]\"\n @click=\"onRowClick?.(row)\"\n >\n <td v-if=\"selectable\" :class=\"[td, checkCell]\" @click.stop>\n <Checkbox\n :aria-label=\"selectRowLabel\"\n :model-value=\"selected.includes(idOf(row))\"\n @update:model-value=\"toggle(idOf(row))\"\n />\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n td,\n pad,\n alignClass[column.align ?? 'left'],\n column.wrap ? 'break-words whitespace-normal' : 'truncate'\n ]\"\n >\n <slot :name=\"`cell-${column.key}`\" :row=\"row\" :value=\"row[column.key]\">\n <template v-if=\"row[column.key] != null && row[column.key] !== ''\">{{ row[column.key] }}</template>\n <span v-else class=\"text-sui-ink-muted\">–</span>\n </slot>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n <div\n v-if=\"$slots.footer\"\n class=\"flex flex-wrap items-center justify-between gap-3 border-t border-sui-line-subtle bg-sui-canvas py-2 pr-3 pl-4 text-13 text-sui-ink-muted\"\n >\n <slot name=\"footer\" />\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -79,7 +79,7 @@ var C = { class: "overflow-hidden rounded-xl border border-sui-line-subtle bg-su
|
|
|
79
79
|
"70%",
|
|
80
80
|
"35%",
|
|
81
81
|
"55%"
|
|
82
|
-
], X = r(() => d.size === "sm"), Z = r(() => ["border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap", X.value ? "h-9" : "h-11"]), Q = r(() => ["
|
|
82
|
+
], X = r(() => d.size === "sm"), Z = r(() => ["border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap", X.value ? "h-9" : "h-11"]), Q = r(() => ["border-b border-sui-line-subtle align-middle text-sui-ink-body group-last/row:border-b-0", X.value ? "h-10" : "h-[3.5625rem]"]), $ = r(() => X.value ? "px-3 py-1.5" : "px-4 py-3"), ae = r(() => X.value ? "px-3" : "px-4");
|
|
83
83
|
return (r, d) => (m(), o("div", C, [s("div", w, [s("table", T, [
|
|
84
84
|
u.caption ? (m(), o("caption", E, v(u.caption), 1)) : a("", !0),
|
|
85
85
|
s("colgroup", null, [u.selectable ? (m(), o("col", D)) : a("", !0), (m(!0), o(n, null, h(u.columns, (e) => (m(), o("col", {
|
|
@@ -172,7 +172,8 @@ var C = { class: "overflow-hidden rounded-xl border border-sui-line-subtle bg-su
|
|
|
172
172
|
class: f([
|
|
173
173
|
Q.value,
|
|
174
174
|
$.value,
|
|
175
|
-
J[t.align ?? "left"]
|
|
175
|
+
J[t.align ?? "left"],
|
|
176
|
+
t.wrap ? "break-words whitespace-normal" : "truncate"
|
|
176
177
|
])
|
|
177
178
|
}, [g(r.$slots, `cell-${t.key}`, {
|
|
178
179
|
row: e,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Table.vue_vue_type_script_setup_true_lang.js","names":["$slots","_useModel","_useSlots","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeStyle","_normalizeClass","_createVNode","Checkbox","_createBlock","_resolveDynamicComponent","_withCtx","_createTextVNode","_renderSlot","Skeleton","_withModifiers","Skeleton_default","Checkbox_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","checkCell","Table_vue_vue_type_script_setup_true_lang_default","\"sort\"","\"sortModifiers\"","\"selected\"","\"selectedModifiers\"","props","__props","sort","useModel","selected","slots","useSlots","idOf","row","ids","computed","allSelected","id","someSelected","toggleAll","toggle","x","sortBy","dir","key","next","dirOf","ariaSort","column","state","columnCount","alignClass","skeletonWidths","sm","th","td","pad","headPad","openBlock","createElementBlock","createElementVNode","toDisplayString","createCommentVNode","Fragment","renderList","normalizeStyle","normalizeClass","createVNode","createBlock","resolveDynamicComponent","withCtx","createTextVNode","renderSlot","_ctx","i","j","_cache","withModifiers"],"sources":["../../../src/components/Table/Table.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed } from 'vue'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { TableColumn, TableSort } from './types'\n\nexport interface TableProps<T> {\n columns: TableColumn[]\n rows: T[]\n /** The row field that identifies it. */\n rowKey?: keyof T & string\n /** A checkbox column bound to `v-model:selected`; selected rows tint accent-wash. */\n selectable?: boolean\n /** Skeleton rows in place of the body. */\n loading?: boolean\n loadingRows?: number\n /** md: 57px rows. sm: 40px, for dense lists. */\n size?: 'md' | 'sm'\n /** The table's name for screen readers. */\n caption?: string\n selectAllLabel?: string\n selectRowLabel?: string\n /** Makes rows clickable (`@row-click`), e.g. to open the record. */\n onRowClick?: (row: T) => void\n}\n\nconst props = withDefaults(defineProps<TableProps<T>>(), {\n rowKey: 'id' as never,\n loadingRows: 5,\n size: 'md',\n caption: undefined,\n selectAllLabel: 'Select all rows',\n selectRowLabel: 'Select row',\n onRowClick: undefined\n})\n\nconst sort = defineModel<TableSort | null>('sort', { default: null })\nconst selected = defineModel<unknown[]>('selected', { default: () => [] })\n\nconst slots = defineSlots<\n {\n /** Shown in place of the body when there are no rows, e.g. an EmptyState. */\n empty?: () => unknown\n /** Replaces the body when given, e.g. an inset danger Alert with a retry. */\n error?: () => unknown\n /** A canvas strip under the rows: a rows-per-page Select, the range, Pagination. */\n footer?: () => unknown\n } & {\n [K in `cell-${string}`]?: (props: { row: T; value: unknown }) => unknown\n }\n>()\n\nconst idOf = (row: T): unknown => row[props.rowKey ?? 'id']\nconst ids = computed(() => props.rows.map(idOf))\nconst allSelected = computed(() => ids.value.length > 0 && ids.value.every((id) => selected.value.includes(id)))\nconst someSelected = computed(() => !allSelected.value && ids.value.some((id) => selected.value.includes(id)))\n\nfunction toggleAll() {\n selected.value = allSelected.value\n ? selected.value.filter((id) => !ids.value.includes(id))\n : [...new Set([...selected.value, ...ids.value])]\n}\n\nfunction toggle(id: unknown) {\n selected.value = selected.value.includes(id) ? selected.value.filter((x) => x !== id) : [...selected.value, id]\n}\n\n// Sorting a column cycles ascending, descending, unsorted.\nfunction sortBy(key: string) {\n const dir = sort.value?.key === key ? sort.value.dir : null\n const next = dir === null ? 'asc' : dir === 'asc' ? 'desc' : null\n sort.value = next ? { key, dir: next } : null\n}\n\nfunction dirOf(key: string) {\n return sort.value?.key === key ? sort.value.dir : null\n}\n\nfunction ariaSort(column: TableColumn) {\n const dir = dirOf(column.key)\n if (dir) return dir === 'asc' ? 'ascending' : 'descending'\n return column.sortable ? 'none' : undefined\n}\n\nconst state = computed(() => {\n if (slots.error) return 'error'\n if (props.loading) return 'loading'\n return props.rows.length === 0 && slots.empty ? 'empty' : 'rows'\n})\n\nconst columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\nconst alignClass = { left: 'text-left', center: 'text-center', right: 'text-right' }\nconst skeletonWidths = ['60%', '45%', '70%', '35%', '55%']\n\nconst sm = computed(() => props.size === 'sm')\nconst th = computed(() => [\n 'border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap',\n sm.value ? 'h-9' : 'h-11'\n])\nconst td = computed(() => [\n 'truncate border-b border-sui-line-subtle align-middle text-sui-ink-body group-last/row:border-b-0',\n sm.value ? 'h-10' : 'h-[3.5625rem]'\n])\nconst pad = computed(() => (sm.value ? 'px-3 py-1.5' : 'px-4 py-3'))\nconst headPad = computed(() => (sm.value ? 'px-3' : 'px-4'))\nconst checkCell = 'w-12 pl-4'\n</script>\n\n<!-- A data table: sunken header strip, sortable headers, selection, loading, empty and error states, a footer. -->\n<template>\n <div class=\"overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sm text-sui-ink\">\n <div class=\"overflow-x-auto\">\n <table class=\"w-full min-w-[var(--sui-table-min,720px)] table-fixed border-collapse\">\n <caption v-if=\"caption\" class=\"sr-only\">\n {{\n caption\n }}\n </caption>\n <colgroup>\n <col v-if=\"selectable\" class=\"w-12\" />\n <col\n v-for=\"column in columns\"\n :key=\"column.key\"\n :style=\"{ width: typeof column.width === 'number' ? `${column.width}px` : column.width }\"\n />\n </colgroup>\n <thead>\n <tr>\n <th v-if=\"selectable\" class=\"bg-sui-sunken\" :class=\"[th, checkCell]\">\n <Checkbox\n :aria-label=\"selectAllLabel\"\n :disabled=\"loading || rows.length === 0\"\n :indeterminate=\"someSelected\"\n :model-value=\"allSelected\"\n @update:model-value=\"toggleAll\"\n />\n </th>\n <component\n :is=\"column.label ? 'th' : 'td'\"\n v-for=\"column in columns\"\n :key=\"column.key\"\n :aria-sort=\"ariaSort(column)\"\n :class=\"[\n th,\n headPad,\n alignClass[column.align ?? 'left'],\n dirOf(column.key) ? 'bg-sui-accent-wash text-sui-accent' : 'bg-sui-sunken text-sui-ink-subtle'\n ]\"\n :scope=\"column.label ? 'col' : undefined\"\n >\n <button\n v-if=\"column.sortable\"\n class=\"inline-flex h-full w-full items-center gap-1.5 rounded-md outline-hidden focus-visible:sui-ring-inset\"\n :class=\"[\n column.align === 'right' && 'justify-end',\n column.align === 'center' && 'justify-center',\n !dirOf(column.key) && 'hover:text-sui-ink'\n ]\"\n type=\"button\"\n @click=\"sortBy(column.key)\"\n >\n {{ column.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3 shrink-0 fill-none stroke-current\"\n :class=\"!dirOf(column.key) && 'opacity-50'\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 12 12\"\n >\n <path v-if=\"dirOf(column.key) === 'asc'\" d=\"M3 7.5 6 4.5l3 3\" />\n <path v-else-if=\"dirOf(column.key) === 'desc'\" d=\"M3 4.5 6 7.5l3-3\" />\n <path v-else d=\"M3.5 4.5 6 2l2.5 2.5M3.5 7.5 6 10l2.5-2.5\" />\n </svg>\n </button>\n <template v-else>{{ column.label }}</template>\n </component>\n </tr>\n </thead>\n <tbody :aria-busy=\"loading || undefined\">\n <tr v-if=\"state === 'error' || state === 'empty'\">\n <td class=\"p-0\" :colspan=\"columnCount\">\n <slot :name=\"state\" />\n </td>\n </tr>\n <template v-else-if=\"state === 'loading'\">\n <tr v-for=\"i in loadingRows\" :key=\"i\" aria-hidden=\"true\" class=\"group/row\">\n <td v-if=\"selectable\" :class=\"[td, checkCell]\"><Skeleton size=\"1rem\" /></td>\n <td v-for=\"(column, j) in columns\" :key=\"column.key\" :class=\"[td, pad]\">\n <Skeleton height=\"0.75rem\" :width=\"skeletonWidths[(i + j) % 5]\" />\n </td>\n </tr>\n </template>\n <template v-else>\n <tr\n v-for=\"row in rows\"\n :key=\"idOf(row) as PropertyKey\"\n :aria-selected=\"selectable ? selected.includes(idOf(row)) : undefined\"\n class=\"group/row transition-colors duration-150\"\n :class=\"[\n selected.includes(idOf(row)) ? 'bg-sui-accent-wash' : 'hover:bg-sui-sunken',\n onRowClick && 'cursor-pointer'\n ]\"\n @click=\"onRowClick?.(row)\"\n >\n <td v-if=\"selectable\" :class=\"[td, checkCell]\" @click.stop>\n <Checkbox\n :aria-label=\"selectRowLabel\"\n :model-value=\"selected.includes(idOf(row))\"\n @update:model-value=\"toggle(idOf(row))\"\n />\n </td>\n <td v-for=\"column in columns\" :key=\"column.key\" :class=\"[td, pad, alignClass[column.align ?? 'left']]\">\n <slot :name=\"`cell-${column.key}`\" :row=\"row\" :value=\"row[column.key]\">\n <template v-if=\"row[column.key] != null && row[column.key] !== ''\">{{ row[column.key] }}</template>\n <span v-else class=\"text-sui-ink-muted\">–</span>\n </slot>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n <div\n v-if=\"$slots.footer\"\n class=\"flex flex-wrap items-center justify-between gap-3 border-t border-sui-line-subtle bg-sui-canvas py-2 pr-3 pl-4 text-13 text-sui-ink-muted\"\n >\n <slot name=\"footer\" />\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Table.vue_vue_type_script_setup_true_lang.js","names":["$slots","_useModel","_useSlots","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeStyle","_normalizeClass","_createVNode","Checkbox","_createBlock","_resolveDynamicComponent","_withCtx","_createTextVNode","_renderSlot","Skeleton","_withModifiers","Skeleton_default","Checkbox_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","checkCell","Table_vue_vue_type_script_setup_true_lang_default","\"sort\"","\"sortModifiers\"","\"selected\"","\"selectedModifiers\"","props","__props","sort","useModel","selected","slots","useSlots","idOf","row","ids","computed","allSelected","id","someSelected","toggleAll","toggle","x","sortBy","dir","key","next","dirOf","ariaSort","column","state","columnCount","alignClass","skeletonWidths","sm","th","td","pad","headPad","openBlock","createElementBlock","createElementVNode","toDisplayString","createCommentVNode","Fragment","renderList","normalizeStyle","normalizeClass","createVNode","createBlock","resolveDynamicComponent","withCtx","createTextVNode","renderSlot","_ctx","i","j","_cache","withModifiers"],"sources":["../../../src/components/Table/Table.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { computed } from 'vue'\n\nimport Checkbox from '../Checkbox/Checkbox.vue'\nimport Skeleton from '../Skeleton/Skeleton.vue'\nimport type { TableColumn, TableSort } from './types'\n\nexport interface TableProps<T> {\n columns: TableColumn[]\n rows: T[]\n /** The row field that identifies it. */\n rowKey?: keyof T & string\n /** A checkbox column bound to `v-model:selected`; selected rows tint accent-wash. */\n selectable?: boolean\n /** Skeleton rows in place of the body. */\n loading?: boolean\n loadingRows?: number\n /** md: 57px rows. sm: 40px, for dense lists. */\n size?: 'md' | 'sm'\n /** The table's name for screen readers. */\n caption?: string\n selectAllLabel?: string\n selectRowLabel?: string\n /** Makes rows clickable (`@row-click`), e.g. to open the record. */\n onRowClick?: (row: T) => void\n}\n\nconst props = withDefaults(defineProps<TableProps<T>>(), {\n rowKey: 'id' as never,\n loadingRows: 5,\n size: 'md',\n caption: undefined,\n selectAllLabel: 'Select all rows',\n selectRowLabel: 'Select row',\n onRowClick: undefined\n})\n\nconst sort = defineModel<TableSort | null>('sort', { default: null })\nconst selected = defineModel<unknown[]>('selected', { default: () => [] })\n\nconst slots = defineSlots<\n {\n /** Shown in place of the body when there are no rows, e.g. an EmptyState. */\n empty?: () => unknown\n /** Replaces the body when given, e.g. an inset danger Alert with a retry. */\n error?: () => unknown\n /** A canvas strip under the rows: a rows-per-page Select, the range, Pagination. */\n footer?: () => unknown\n } & {\n [K in `cell-${string}`]?: (props: { row: T; value: unknown }) => unknown\n }\n>()\n\nconst idOf = (row: T): unknown => row[props.rowKey ?? 'id']\nconst ids = computed(() => props.rows.map(idOf))\nconst allSelected = computed(() => ids.value.length > 0 && ids.value.every((id) => selected.value.includes(id)))\nconst someSelected = computed(() => !allSelected.value && ids.value.some((id) => selected.value.includes(id)))\n\nfunction toggleAll() {\n selected.value = allSelected.value\n ? selected.value.filter((id) => !ids.value.includes(id))\n : [...new Set([...selected.value, ...ids.value])]\n}\n\nfunction toggle(id: unknown) {\n selected.value = selected.value.includes(id) ? selected.value.filter((x) => x !== id) : [...selected.value, id]\n}\n\n// Sorting a column cycles ascending, descending, unsorted.\nfunction sortBy(key: string) {\n const dir = sort.value?.key === key ? sort.value.dir : null\n const next = dir === null ? 'asc' : dir === 'asc' ? 'desc' : null\n sort.value = next ? { key, dir: next } : null\n}\n\nfunction dirOf(key: string) {\n return sort.value?.key === key ? sort.value.dir : null\n}\n\nfunction ariaSort(column: TableColumn) {\n const dir = dirOf(column.key)\n if (dir) return dir === 'asc' ? 'ascending' : 'descending'\n return column.sortable ? 'none' : undefined\n}\n\nconst state = computed(() => {\n if (slots.error) return 'error'\n if (props.loading) return 'loading'\n return props.rows.length === 0 && slots.empty ? 'empty' : 'rows'\n})\n\nconst columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\nconst alignClass = { left: 'text-left', center: 'text-center', right: 'text-right' }\nconst skeletonWidths = ['60%', '45%', '70%', '35%', '55%']\n\nconst sm = computed(() => props.size === 'sm')\nconst th = computed(() => [\n 'border-b border-sui-line-subtle text-13 font-medium whitespace-nowrap',\n sm.value ? 'h-9' : 'h-11'\n])\nconst td = computed(() => [\n 'border-b border-sui-line-subtle align-middle text-sui-ink-body group-last/row:border-b-0',\n sm.value ? 'h-10' : 'h-[3.5625rem]'\n])\nconst pad = computed(() => (sm.value ? 'px-3 py-1.5' : 'px-4 py-3'))\nconst headPad = computed(() => (sm.value ? 'px-3' : 'px-4'))\nconst checkCell = 'w-12 pl-4'\n</script>\n\n<!-- A data table: sunken header strip, sortable headers, selection, loading, empty and error states, a footer. -->\n<template>\n <div class=\"overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sm text-sui-ink\">\n <div class=\"overflow-x-auto\">\n <table class=\"w-full min-w-[var(--sui-table-min,720px)] table-fixed border-collapse\">\n <caption v-if=\"caption\" class=\"sr-only\">\n {{\n caption\n }}\n </caption>\n <colgroup>\n <col v-if=\"selectable\" class=\"w-12\" />\n <col\n v-for=\"column in columns\"\n :key=\"column.key\"\n :style=\"{ width: typeof column.width === 'number' ? `${column.width}px` : column.width }\"\n />\n </colgroup>\n <thead>\n <tr>\n <th v-if=\"selectable\" class=\"bg-sui-sunken\" :class=\"[th, checkCell]\">\n <Checkbox\n :aria-label=\"selectAllLabel\"\n :disabled=\"loading || rows.length === 0\"\n :indeterminate=\"someSelected\"\n :model-value=\"allSelected\"\n @update:model-value=\"toggleAll\"\n />\n </th>\n <component\n :is=\"column.label ? 'th' : 'td'\"\n v-for=\"column in columns\"\n :key=\"column.key\"\n :aria-sort=\"ariaSort(column)\"\n :class=\"[\n th,\n headPad,\n alignClass[column.align ?? 'left'],\n dirOf(column.key) ? 'bg-sui-accent-wash text-sui-accent' : 'bg-sui-sunken text-sui-ink-subtle'\n ]\"\n :scope=\"column.label ? 'col' : undefined\"\n >\n <button\n v-if=\"column.sortable\"\n class=\"inline-flex h-full w-full items-center gap-1.5 rounded-md outline-hidden focus-visible:sui-ring-inset\"\n :class=\"[\n column.align === 'right' && 'justify-end',\n column.align === 'center' && 'justify-center',\n !dirOf(column.key) && 'hover:text-sui-ink'\n ]\"\n type=\"button\"\n @click=\"sortBy(column.key)\"\n >\n {{ column.label }}\n <svg\n aria-hidden=\"true\"\n class=\"size-3 shrink-0 fill-none stroke-current\"\n :class=\"!dirOf(column.key) && 'opacity-50'\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 12 12\"\n >\n <path v-if=\"dirOf(column.key) === 'asc'\" d=\"M3 7.5 6 4.5l3 3\" />\n <path v-else-if=\"dirOf(column.key) === 'desc'\" d=\"M3 4.5 6 7.5l3-3\" />\n <path v-else d=\"M3.5 4.5 6 2l2.5 2.5M3.5 7.5 6 10l2.5-2.5\" />\n </svg>\n </button>\n <template v-else>{{ column.label }}</template>\n </component>\n </tr>\n </thead>\n <tbody :aria-busy=\"loading || undefined\">\n <tr v-if=\"state === 'error' || state === 'empty'\">\n <td class=\"p-0\" :colspan=\"columnCount\">\n <slot :name=\"state\" />\n </td>\n </tr>\n <template v-else-if=\"state === 'loading'\">\n <tr v-for=\"i in loadingRows\" :key=\"i\" aria-hidden=\"true\" class=\"group/row\">\n <td v-if=\"selectable\" :class=\"[td, checkCell]\"><Skeleton size=\"1rem\" /></td>\n <td v-for=\"(column, j) in columns\" :key=\"column.key\" :class=\"[td, pad]\">\n <Skeleton height=\"0.75rem\" :width=\"skeletonWidths[(i + j) % 5]\" />\n </td>\n </tr>\n </template>\n <template v-else>\n <tr\n v-for=\"row in rows\"\n :key=\"idOf(row) as PropertyKey\"\n :aria-selected=\"selectable ? selected.includes(idOf(row)) : undefined\"\n class=\"group/row transition-colors duration-150\"\n :class=\"[\n selected.includes(idOf(row)) ? 'bg-sui-accent-wash' : 'hover:bg-sui-sunken',\n onRowClick && 'cursor-pointer'\n ]\"\n @click=\"onRowClick?.(row)\"\n >\n <td v-if=\"selectable\" :class=\"[td, checkCell]\" @click.stop>\n <Checkbox\n :aria-label=\"selectRowLabel\"\n :model-value=\"selected.includes(idOf(row))\"\n @update:model-value=\"toggle(idOf(row))\"\n />\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :class=\"[\n td,\n pad,\n alignClass[column.align ?? 'left'],\n column.wrap ? 'break-words whitespace-normal' : 'truncate'\n ]\"\n >\n <slot :name=\"`cell-${column.key}`\" :row=\"row\" :value=\"row[column.key]\">\n <template v-if=\"row[column.key] != null && row[column.key] !== ''\">{{ row[column.key] }}</template>\n <span v-else class=\"text-sui-ink-muted\">–</span>\n </slot>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n <div\n v-if=\"$slots.footer\"\n class=\"flex flex-wrap items-center justify-between gap-3 border-t border-sui-line-subtle bg-sui-canvas py-2 pr-3 pl-4 text-13 text-sui-ink-muted\"\n >\n <slot name=\"footer\" />\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;A,GA0GMsC,IAAY,a;;;;;;;;;;;;;;;;;;;;;;;;;EA/ElB,IAAMM,IAAQC,GAURC,IAAO7C,EAA6B4C,GAAC,MAAyB,GAC9DG,IAAW/C,EAAsB4C,GAAC,UAAiC,GAEnEI,IAAQ/C,EAAA,GAaRiD,KAAQ,MAAoBC,EAAIR,EAAM,UAAU,OAChDS,IAAMC,QAAeV,EAAM,KAAK,IAAIO,CAAI,CAAC,GACzCI,IAAcD,QAAeD,EAAI,MAAM,SAAS,KAAKA,EAAI,MAAM,OAAO,MAAOL,EAAS,MAAM,SAASQ,CAAE,CAAC,CAAC,GACzGC,IAAeH,QAAe,CAACC,EAAY,SAASF,EAAI,MAAM,MAAM,MAAOL,EAAS,MAAM,SAASQ,CAAE,CAAC,CAAC;EAE7G,SAASE,IAAY;GACnB,EAAS,QAAQH,EAAY,QACzBP,EAAS,MAAM,QAAQ,MAAO,CAACK,EAAI,MAAM,SAASG,CAAE,CAAC,IACrD,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAGR,EAAS,OAAO,GAAGK,EAAI,KAAK,CAAC,CAAC;EACpD;EAEA,SAASM,EAAO,GAAa;GAC3B,EAAS,QAAQX,EAAS,MAAM,SAASQ,CAAE,IAAIR,EAAS,MAAM,QAAQ,MAAMY,MAAMJ,CAAE,IAAI,CAAC,GAAGR,EAAS,OAAOQ,CAAE;EAChH;EAGA,SAASK,EAAO,GAAa;GAC3B,IAAMC,IAAMhB,EAAK,OAAO,QAAQiB,IAAMjB,EAAK,MAAM,MAAM,MACjDkB,IAAOF,MAAQ,OAAO,QAAQA,MAAQ,QAAQ,SAAS;GAC7D,EAAK,QAAQE,IAAO;IAAE;IAAK,KAAKA;GAAK,IAAI;EAC3C;EAEA,SAASC,EAAM,GAAa;GAC1B,OAAOnB,EAAK,OAAO,QAAQiB,IAAMjB,EAAK,MAAM,MAAM;EACpD;EAEA,SAASoB,EAAS,GAAqB;GACrC,IAAMJ,IAAMG,EAAME,EAAO,GAAG;GAE5B,OADIL,IAAYA,MAAQ,QAAQ,cAAc,eACvCK,EAAO,WAAW,SAAS,KAAA;EACpC;EAEA,IAAMC,IAAQd,QACRL,EAAM,QAAc,UACpBL,EAAM,UAAgB,YACnBA,EAAM,KAAK,WAAW,KAAKK,EAAM,QAAQ,UAAU,MAC3D,GAEKoB,IAAcf,QAAeV,EAAM,QAAQ,SAAU,KAAM,UAAmB,GAC9E0B,IAAa;GAAE,MAAM;GAAa,QAAQ;GAAe,OAAO;EAAa,GAC7EC,IAAiB;GAAC;GAAO;GAAO;GAAO;GAAO;EAAK,GAEnDC,IAAKlB,QAAeV,EAAM,SAAS,IAAI,GACvC6B,IAAKnB,QAAe,CACxB,yEACAkB,EAAG,QAAQ,QAAQ,MACrB,CAAC,GACKE,IAAKpB,QAAe,CACxB,4FACAkB,EAAG,QAAQ,SAAS,eACtB,CAAC,GACKG,IAAMrB,QAAgBkB,EAAG,QAAQ,gBAAgB,WAAY,GAC7DI,KAAUtB,QAAgBkB,EAAG,QAAQ,SAAS,MAAO;E,kBAMzDrE,EAAA,GAAAC,EAiIM,OAjINmB,GAiIM,CAhIJlB,EAyHM,OAzHNmB,GAyHM,CAxHJnB,EAuHQ,SAvHRoB,GAuHQ;GAtHSoB,EAAA,WAAf1C,EAAA,GAAAC,EAIU,WAJVsB,GAIUpB,EAFNuC,EAAA,OAAO,GAAA,CAAA,K;GAGXxC,EAOW,YAAA,MAAA,CANEwC,EAAA,cAAX1C,EAAA,GAAAC,EAAsC,OAAtCuB,CAAsC,K,YACtCxB,EAAA,EAAA,GAAAC,EAIEI,GAAA,MAAAC,EAHiBoC,EAAA,UAAV,OADT1C,EAAA,GAAAC,EAIE,OAAA;IAFC,KAAK+D,EAAO;IACZ,OAAKzD,EAAA,EAAA,OAAA,OAAkByD,EAAO,SAAK,WAAA,GAAmBA,EAAO,MAAK,MAAOA,EAAO,MAAK,CAAA;;GAG1F9D,EAqDQ,SAAA,MAAA,CApDNA,EAmDK,MAAA,MAAA,CAlDOwC,EAAA,cAAV1C,EAAA,GAAAC,EAQK,MAAA;;IARiB,OAAKO,EAAA,CAAC,iBAAe,CAAU8D,EAAA,OAAInC,CAAS,CAAA,CAAA;G,IAChE1B,EAMEC,GAAA;IALC,cAAYgC,EAAA;IACZ,UAAUA,EAAA,WAAWA,EAAA,KAAK,WAAM;IAChC,eAAeY,EAAA;IACf,eAAaF,EAAA;IACb,uBAAoBG;;;;;;G,uBAGzBvD,EAAA,EAAA,GAAAC,EAwCYI,GAAA,MAAAC,EAtCOoC,EAAA,UAAV,OAFT1C,EAAA,GAAAW,EAwCYC,EAvCLoD,EAAO,QAAK,OAAA,IAAA,GAAA;IAEhB,KAAKA,EAAO;IACZ,aAAWD,EAASC,CAAM;IAC1B,OAAKxD,EAAA;KAAoB8D,EAAA;KAAoBG,GAAA;KAAyBN,EAAWH,EAAO,SAAK;KAA6BF,EAAME,EAAO,GAAG,IAAA,uCAAA;;IAM1I,OAAOA,EAAO,QAAK,QAAW,KAAA;;IAE/B,SAAAnD,QAyBS,CAxBDmD,EAAO,YADfhE,EAAA,GAAAC,EAyBS,UAAA;;KAvBP,OAAKO,EAAA,CAAC,yGAAuG;MACjFwD,EAAO,UAAK,WAAA;MAAiDA,EAAO,UAAK,YAAA;MAAsD,CAAAF,EAAME,EAAO,GAAG,KAAA;;KAK3K,MAAK;KACJ,UAAK,MAAEN,EAAOM,EAAO,GAAG;I,IAEtBlD,EAAAX,EAAA6D,EAAO,KAAK,IAAG,KAClB,CAAA,IAAAhE,EAAA,GAAAC,EAYM,OAAA;KAXJ,eAAY;KACZ,OAAKO,EAAA,CAAC,4CAA0C,CACvCsD,EAAME,EAAO,GAAG,KAAA,YAAA,CAAA;KACzB,kBAAe;KACf,mBAAgB;KAChB,gBAAa;KACb,SAAQ;I,IAEIF,EAAME,EAAO,GAAG,MAAA,SAA5BhE,EAAA,GAAAC,EAAgE,QAAhEyB,EAAgE,KAC/CoC,EAAME,EAAO,GAAG,MAAA,UAAjChE,EAAA,GAAAC,EAAsE,QAAtE0B,EAAsE,MACtE3B,EAAA,GAAAC,EAA6D,QAA7D2B,EAA6D,E,qBAGjE5B,EAAA,GAAAC,EAA8CI,GAAA,EAAA,KAAA,EAAA,GAAA,CAA1BS,EAAAX,EAAA6D,EAAO,KAAK,GAAA,CAAA,C;;;;;;;GAItC9D,EAkDQ,SAAA,EAlDA,aAAWwC,EAAA,WAAW,KAAA,E,IAClBuB,EAAA,UAAK,WAAgBA,EAAA,UAAK,WAApCjE,EAAA,GAAAC,EAIK,MAAA6B,GAAA,CAHH5B,EAEK,MAAA;IAFD,OAAM;IAAO,SAASgE,EAAA;G,IACxBnD,EAAsB0E,EAAA,QAATxB,EAAA,KAAK,C,cAGDA,EAAA,UAAK,aACxBjE,EAAA,EAAA,GAAAC,EAKKI,GAAA,EAAA,KAAA,EAAA,GAAAC,EALWoC,EAAA,cAAL,OAAX1C,EAAA,GAAAC,EAKK,MAAA;IALyB,KAAKyF;IAAG,eAAY;IAAO,OAAM;G,IACnDhD,EAAA,cAAV1C,EAAA,GAAAC,EAA4E,MAAA;;IAArD,OAAKO,EAAA,CAAG+D,EAAA,OAAIpC,CAAS,CAAA;G,IAAG1B,EAAwBO,GAAA,EAAd,MAAK,OAAM,CAAA,C,qBACpEhB,EAAA,EAAA,GAAAC,EAEKI,GAAA,MAAAC,EAFqBoC,EAAA,UAAd,GAAQ,OAApB1C,EAAA,GAAAC,EAEK,MAAA;IAF+B,KAAK+D,EAAO;IAAM,OAAKxD,EAAA,CAAG+D,EAAA,OAAIC,EAAA,KAAG,CAAA;G,IACnE/D,EAAkEO,GAAA;IAAxD,QAAO;IAAW,OAAOoD,GAAgBsB,IAAIC,KAAC;G,mDAK5D3F,EAAA,EAAA,GAAAC,EAiCKI,GAAA,EAAA,KAAA,EAAA,GAAAC,EAhCWoC,EAAA,OAAP,OADT1C,EAAA,GAAAC,EAiCK,MAAA;IA/BF,KAAK+C,EAAKC,CAAG;IACb,iBAAeP,EAAA,aAAaG,EAAA,MAAS,SAASG,EAAKC,CAAG,CAAA,IAAK,KAAA;IAC5D,OAAKzC,EAAA,CAAC,4CAA0C,CACtBqC,EAAA,MAAS,SAASG,EAAKC,CAAG,CAAA,IAAA,uBAAA,uBAAmEP,EAAA,cAAU,gB;IAIhI,UAAK,MAAEA,EAAA,aAAaO,CAAG;G,IAEdP,EAAA,cAAV1C,EAAA,GAAAC,EAMK,MAAA;;IANkB,OAAKO,EAAA,CAAG+D,EAAA,OAAIpC,CAAS,CAAA;IAAI,SAAK,AAAAyD,EAAA,OAAA3E,QAAN,CAAA,GAAW,CAAA,MAAA,CAAA;G,IACxDR,EAIEC,GAAA;IAHC,cAAYgC,EAAA;IACZ,eAAaG,EAAA,MAAS,SAASG,EAAKC,CAAG,CAAA;IACvC,wBAAkB,MAAEO,EAAOR,EAAKC,CAAG,CAAA;;;;;G,uBAGxCjD,EAAA,EAAA,GAAAC,EAcKI,GAAA,MAAAC,EAbcoC,EAAA,UAAV,OADT1C,EAAA,GAAAC,EAcK,MAAA;IAZF,KAAK+D,EAAO;IACZ,OAAKxD,EAAA;KAAsB+D,EAAA;KAAsBC,EAAA;KAAuBL,EAAWH,EAAO,SAAK;KAA+BA,EAAO,OAAI,kCAAA;;G,IAO1IjD,EAGO0E,EAAA,QAAA,QAHczB,EAAO,OAAG;IAAU;IAAM,OAAOf,EAAIe,EAAO;GAAjE,SAGO,CAFWf,EAAIe,EAAO,QAAG,QAAaf,EAAIe,EAAO,SAAG,MAAzDhE,EAAA,GAAAC,EAAmGI,GAAA,EAAA,KAAA,EAAA,GAAA,CAA7BS,EAAAX,EAAA8C,EAAIe,EAAO,IAAG,GAAA,CAAA,C,WACpFhE,EAAA,GAAAC,EAAgD,QAAhDgC,GAAwC,GAAC,E;E,MAS/CpC,EAAAA,OAAO,UADfG,EAAA,GAAAC,EAKM,OALNiC,GAKM,CADJnB,EAAsB0E,EAAA,QAAA,QAAA,C"}
|
|
@@ -5,6 +5,8 @@ export interface TableColumn {
|
|
|
5
5
|
width?: number | string;
|
|
6
6
|
align?: 'left' | 'center' | 'right';
|
|
7
7
|
sortable?: boolean;
|
|
8
|
+
/** Lets the cell's content wrap onto more lines, e.g. a name over a summary; cells otherwise truncate to one. */
|
|
9
|
+
wrap?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export interface TableSort {
|
|
10
12
|
key: string;
|