@braedonsaunders/appkit-ui 0.2.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/LICENSE +661 -0
- package/README.md +11 -0
- package/account-menu.d.ts +49 -0
- package/account-menu.d.ts.map +1 -0
- package/account-menu.js +66 -0
- package/account-menu.js.map +1 -0
- package/alert.d.ts +13 -0
- package/alert.d.ts.map +1 -0
- package/alert.js +22 -0
- package/alert.js.map +1 -0
- package/animated-number.d.ts +13 -0
- package/animated-number.d.ts.map +1 -0
- package/animated-number.js +53 -0
- package/animated-number.js.map +1 -0
- package/api-reference.d.ts +44 -0
- package/api-reference.d.ts.map +1 -0
- package/api-reference.js +82 -0
- package/api-reference.js.map +1 -0
- package/app-shell.d.ts +39 -0
- package/app-shell.d.ts.map +1 -0
- package/app-shell.js +21 -0
- package/app-shell.js.map +1 -0
- package/app-sidebar.d.ts +22 -0
- package/app-sidebar.d.ts.map +1 -0
- package/app-sidebar.js +30 -0
- package/app-sidebar.js.map +1 -0
- package/avatar.d.ts +9 -0
- package/avatar.d.ts.map +1 -0
- package/avatar.js +18 -0
- package/avatar.js.map +1 -0
- package/badge.d.ts +9 -0
- package/badge.d.ts.map +1 -0
- package/badge.js +23 -0
- package/badge.js.map +1 -0
- package/button.d.ts +24 -0
- package/button.d.ts.map +1 -0
- package/button.js +50 -0
- package/button.js.map +1 -0
- package/card.d.ts +21 -0
- package/card.d.ts.map +1 -0
- package/card.js +36 -0
- package/card.js.map +1 -0
- package/checkbox.d.ts +4 -0
- package/checkbox.d.ts.map +1 -0
- package/checkbox.js +6 -0
- package/checkbox.js.map +1 -0
- package/confirm-dialog.d.ts +23 -0
- package/confirm-dialog.d.ts.map +1 -0
- package/confirm-dialog.js +67 -0
- package/confirm-dialog.js.map +1 -0
- package/context-menu.d.ts +47 -0
- package/context-menu.d.ts.map +1 -0
- package/context-menu.js +90 -0
- package/context-menu.js.map +1 -0
- package/dialog.d.ts +25 -0
- package/dialog.d.ts.map +1 -0
- package/dialog.js +89 -0
- package/dialog.js.map +1 -0
- package/document-title.d.ts +11 -0
- package/document-title.d.ts.map +1 -0
- package/document-title.js +17 -0
- package/document-title.js.map +1 -0
- package/drawer.d.ts +63 -0
- package/drawer.d.ts.map +1 -0
- package/drawer.js +188 -0
- package/drawer.js.map +1 -0
- package/empty-state.d.ts +10 -0
- package/empty-state.d.ts.map +1 -0
- package/empty-state.js +7 -0
- package/empty-state.js.map +1 -0
- package/file-uploader.d.ts +67 -0
- package/file-uploader.d.ts.map +1 -0
- package/file-uploader.js +156 -0
- package/file-uploader.js.map +1 -0
- package/filter-chips.d.ts +47 -0
- package/filter-chips.d.ts.map +1 -0
- package/filter-chips.js +62 -0
- package/filter-chips.js.map +1 -0
- package/global-search.d.ts +41 -0
- package/global-search.d.ts.map +1 -0
- package/global-search.js +116 -0
- package/global-search.js.map +1 -0
- package/index.d.ts +70 -0
- package/index.d.ts.map +1 -0
- package/index.js +70 -0
- package/index.js.map +1 -0
- package/input.d.ts +9 -0
- package/input.d.ts.map +1 -0
- package/input.js +11 -0
- package/input.js.map +1 -0
- package/label.d.ts +3 -0
- package/label.d.ts.map +1 -0
- package/label.js +6 -0
- package/label.js.map +1 -0
- package/line-grid.d.ts +48 -0
- package/line-grid.d.ts.map +1 -0
- package/line-grid.js +155 -0
- package/line-grid.js.map +1 -0
- package/link-context.d.ts +44 -0
- package/link-context.d.ts.map +1 -0
- package/link-context.js +32 -0
- package/link-context.js.map +1 -0
- package/list-nav.d.ts +28 -0
- package/list-nav.d.ts.map +1 -0
- package/list-nav.js +41 -0
- package/list-nav.js.map +1 -0
- package/list-pagination.d.ts +27 -0
- package/list-pagination.d.ts.map +1 -0
- package/list-pagination.js +43 -0
- package/list-pagination.js.map +1 -0
- package/list-params.d.ts +41 -0
- package/list-params.d.ts.map +1 -0
- package/list-params.js +84 -0
- package/list-params.js.map +1 -0
- package/list-search-input-state.d.ts +13 -0
- package/list-search-input-state.d.ts.map +1 -0
- package/list-search-input-state.js +27 -0
- package/list-search-input-state.js.map +1 -0
- package/list-search-input.d.ts +16 -0
- package/list-search-input.d.ts.map +1 -0
- package/list-search-input.js +51 -0
- package/list-search-input.js.map +1 -0
- package/mobile-tab-bar.d.ts +14 -0
- package/mobile-tab-bar.d.ts.map +1 -0
- package/mobile-tab-bar.js +25 -0
- package/mobile-tab-bar.js.map +1 -0
- package/multi-select.d.ts +32 -0
- package/multi-select.d.ts.map +1 -0
- package/multi-select.js +52 -0
- package/multi-select.js.map +1 -0
- package/nav-overflow.d.ts +12 -0
- package/nav-overflow.d.ts.map +1 -0
- package/nav-overflow.js +30 -0
- package/nav-overflow.js.map +1 -0
- package/navigation-config-editor.d.ts +26 -0
- package/navigation-config-editor.d.ts.map +1 -0
- package/navigation-config-editor.js +57 -0
- package/navigation-config-editor.js.map +1 -0
- package/navigation-config.d.ts +48 -0
- package/navigation-config.d.ts.map +1 -0
- package/navigation-config.js +75 -0
- package/navigation-config.js.map +1 -0
- package/navigation-mode.d.ts +16 -0
- package/navigation-mode.d.ts.map +1 -0
- package/navigation-mode.js +30 -0
- package/navigation-mode.js.map +1 -0
- package/notifications-bell.d.ts +22 -0
- package/notifications-bell.d.ts.map +1 -0
- package/notifications-bell.js +30 -0
- package/notifications-bell.js.map +1 -0
- package/org-chart.d.ts +85 -0
- package/org-chart.d.ts.map +1 -0
- package/org-chart.js +193 -0
- package/org-chart.js.map +1 -0
- package/package.json +61 -0
- package/page-header.d.ts +32 -0
- package/page-header.d.ts.map +1 -0
- package/page-header.js +14 -0
- package/page-header.js.map +1 -0
- package/page-layout.d.ts +29 -0
- package/page-layout.d.ts.map +1 -0
- package/page-layout.js +23 -0
- package/page-layout.js.map +1 -0
- package/page-transition.d.ts +13 -0
- package/page-transition.d.ts.map +1 -0
- package/page-transition.js +14 -0
- package/page-transition.js.map +1 -0
- package/paged-table.d.ts +54 -0
- package/paged-table.d.ts.map +1 -0
- package/paged-table.js +74 -0
- package/paged-table.js.map +1 -0
- package/popover.d.ts +16 -0
- package/popover.d.ts.map +1 -0
- package/popover.js +99 -0
- package/popover.js.map +1 -0
- package/progress.d.ts +7 -0
- package/progress.d.ts.map +1 -0
- package/progress.js +8 -0
- package/progress.js.map +1 -0
- package/prompt-dialog.d.ts +21 -0
- package/prompt-dialog.d.ts.map +1 -0
- package/prompt-dialog.js +109 -0
- package/prompt-dialog.js.map +1 -0
- package/record-list.d.ts +76 -0
- package/record-list.d.ts.map +1 -0
- package/record-list.js +51 -0
- package/record-list.js.map +1 -0
- package/search-select.d.ts +42 -0
- package/search-select.d.ts.map +1 -0
- package/search-select.js +194 -0
- package/search-select.js.map +1 -0
- package/select-options.d.ts +16 -0
- package/select-options.d.ts.map +1 -0
- package/select-options.js +119 -0
- package/select-options.js.map +1 -0
- package/select.d.ts +24 -0
- package/select.d.ts.map +1 -0
- package/select.js +65 -0
- package/select.js.map +1 -0
- package/separator.d.ts +6 -0
- package/separator.d.ts.map +1 -0
- package/separator.js +7 -0
- package/separator.js.map +1 -0
- package/settings-layout.d.ts +79 -0
- package/settings-layout.d.ts.map +1 -0
- package/settings-layout.js +74 -0
- package/settings-layout.js.map +1 -0
- package/sidebar-nav.d.ts +67 -0
- package/sidebar-nav.d.ts.map +1 -0
- package/sidebar-nav.js +291 -0
- package/sidebar-nav.js.map +1 -0
- package/signature-pad.d.ts +16 -0
- package/signature-pad.d.ts.map +1 -0
- package/signature-pad.js +355 -0
- package/signature-pad.js.map +1 -0
- package/skeleton.d.ts +9 -0
- package/skeleton.d.ts.map +1 -0
- package/skeleton.js +12 -0
- package/skeleton.js.map +1 -0
- package/slider.d.ts +13 -0
- package/slider.d.ts.map +1 -0
- package/slider.js +15 -0
- package/slider.js.map +1 -0
- package/sortable-th.d.ts +28 -0
- package/sortable-th.d.ts.map +1 -0
- package/sortable-th.js +35 -0
- package/sortable-th.js.map +1 -0
- package/sparkline.d.ts +16 -0
- package/sparkline.d.ts.map +1 -0
- package/sparkline.js +46 -0
- package/sparkline.js.map +1 -0
- package/spinner.d.ts +11 -0
- package/spinner.d.ts.map +1 -0
- package/spinner.js +11 -0
- package/spinner.js.map +1 -0
- package/styles.css +166 -0
- package/subtab-nav.d.ts +17 -0
- package/subtab-nav.d.ts.map +1 -0
- package/subtab-nav.js +11 -0
- package/subtab-nav.js.map +1 -0
- package/switch.d.ts +9 -0
- package/switch.d.ts.map +1 -0
- package/switch.js +11 -0
- package/switch.js.map +1 -0
- package/tab-content.d.ts +24 -0
- package/tab-content.d.ts.map +1 -0
- package/tab-content.js +26 -0
- package/tab-content.js.map +1 -0
- package/table.d.ts +28 -0
- package/table.d.ts.map +1 -0
- package/table.js +21 -0
- package/table.js.map +1 -0
- package/tabs.d.ts +18 -0
- package/tabs.d.ts.map +1 -0
- package/tabs.js +18 -0
- package/tabs.js.map +1 -0
- package/text-context.d.ts +9 -0
- package/text-context.d.ts.map +1 -0
- package/text-context.js +12 -0
- package/text-context.js.map +1 -0
- package/textarea.d.ts +4 -0
- package/textarea.d.ts.map +1 -0
- package/textarea.js +6 -0
- package/textarea.js.map +1 -0
- package/theme-script.d.ts +3 -0
- package/theme-script.d.ts.map +1 -0
- package/theme-script.js +6 -0
- package/theme-script.js.map +1 -0
- package/theme.d.ts +28 -0
- package/theme.d.ts.map +1 -0
- package/theme.js +92 -0
- package/theme.js.map +1 -0
- package/toast.d.ts +52 -0
- package/toast.d.ts.map +1 -0
- package/toast.js +178 -0
- package/toast.js.map +1 -0
- package/tooltip.d.ts +12 -0
- package/tooltip.d.ts.map +1 -0
- package/tooltip.js +14 -0
- package/tooltip.js.map +1 -0
- package/top-nav.d.ts +18 -0
- package/top-nav.d.ts.map +1 -0
- package/top-nav.js +160 -0
- package/top-nav.js.map +1 -0
- package/upload-limits.d.ts +4 -0
- package/upload-limits.d.ts.map +1 -0
- package/upload-limits.js +21 -0
- package/upload-limits.js.map +1 -0
- package/use-element-size.d.ts +7 -0
- package/use-element-size.d.ts.map +1 -0
- package/use-element-size.js +21 -0
- package/use-element-size.js.map +1 -0
- package/utils.d.ts +4 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +7 -0
- package/utils.js.map +1 -0
- package/workspace-launcher.d.ts +50 -0
- package/workspace-launcher.d.ts.map +1 -0
- package/workspace-launcher.js +127 -0
- package/workspace-launcher.js.map +1 -0
package/line-grid.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-grid.js","sourceRoot":"","sources":["../src/line-grid.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n/**\n * LineGrid — a spreadsheet-grade line-item editor for documents (invoices,\n * bills, journals, estimates). Controlled: rows in, rows out; the parent owns\n * persistence and any computed columns.\n *\n * - Enter commits + moves down (appending a row at the bottom)\n * - Alt+↑/↓ moves the row, ⌘/Ctrl+D duplicates, ⌘/Ctrl+⌫ deletes\n * - per-row grip menu: insert above/below, duplicate, remove\n * - amount cells edit raw, normalize to 2dp on blur, flag non-numeric\n * - columns are data: text / amount / select / readonly (+ custom render)\n */\n\nimport * as React from 'react'\nimport { ArrowDown, ArrowUp, Copy, GripVertical, Plus, Trash2 } from 'lucide-react'\nimport { Button } from './button'\nimport { ContextMenu, useContextMenu } from './context-menu'\nimport { SearchSelect, type SelectOption } from './search-select'\nimport { cn } from './utils'\n\nexport type LineGridColumn<Row extends Record<string, unknown>> = {\n key: string\n label: string\n /** CSS grid track, e.g. 'minmax(180px,2fr)' or '110px'. */\n width: string\n type: 'text' | 'amount' | 'select' | 'readonly'\n align?: 'left' | 'right'\n options?: SelectOption[]\n placeholder?: string\n required?: boolean\n /** Renderer for readonly columns (computed cells). */\n render?: (row: Row, index: number) => React.ReactNode\n}\n\nexport type LineGridLabels = {\n addLine: string\n keyboardHint: string\n insertAbove: string\n insertBelow: string\n duplicate: string\n removeLine: string\n clearLine: string\n lineActions: (n: number) => string\n}\n\nconst DEFAULT_LABELS: LineGridLabels = {\n addLine: 'Add line',\n keyboardHint: 'Enter to add a row · Alt+↑/↓ to move · ⌘D to duplicate · ⌘⌫ to remove',\n insertAbove: 'Insert above',\n insertBelow: 'Insert below',\n duplicate: 'Duplicate',\n removeLine: 'Remove line',\n clearLine: 'Clear line',\n lineActions: (n) => `Line ${n} actions`,\n}\n\nfunction normalizeAmount(v: string): string {\n const n = Number(v)\n if (v.trim() === '' || Number.isNaN(n)) return v\n return n.toFixed(2)\n}\n\nexport type LineGridProps<Row extends Record<string, unknown>> = {\n columns: LineGridColumn<Row>[]\n rows: Row[]\n onRowsChange: (rows: Row[]) => void\n emptyRow: () => Row\n readOnly?: boolean\n minRows?: number\n footer?: React.ReactNode\n labels?: Partial<LineGridLabels>\n}\n\nexport function LineGrid<Row extends Record<string, unknown>>({\n columns,\n rows,\n onRowsChange,\n emptyRow,\n readOnly = false,\n minRows = 1,\n footer,\n labels: labelOverrides,\n}: LineGridProps<Row>) {\n const labels = { ...DEFAULT_LABELS, ...labelOverrides }\n const containerRef = React.useRef<HTMLDivElement>(null)\n const focusedCol = React.useRef(0)\n const menu = useContextMenu()\n const [menuRow, setMenuRow] = React.useState<number | null>(null)\n\n const template = readOnly\n ? columns.map((c) => c.width).join(' ')\n : `34px ${columns.map((c) => c.width).join(' ')}`\n\n const setCell = React.useCallback(\n (i: number, key: string, value: unknown) => {\n onRowsChange(rows.map((r, j) => (j === i ? { ...r, [key]: value } : r)))\n },\n [rows, onRowsChange],\n )\n\n function focusCell(row: number, col: number) {\n requestAnimationFrame(() => {\n const el = containerRef.current?.querySelector<HTMLElement>(\n `[data-lg-row=\"${row}\"][data-lg-col=\"${col}\"] input, [data-lg-row=\"${row}\"][data-lg-col=\"${col}\"] button`,\n )\n el?.focus()\n })\n }\n\n const insertRow = (at: number) => {\n const next = [...rows]\n next.splice(at, 0, emptyRow())\n onRowsChange(next)\n focusCell(at, 0)\n }\n const duplicateRow = (i: number) => {\n const next = [...rows]\n next.splice(i + 1, 0, { ...rows[i]! })\n onRowsChange(next)\n focusCell(i + 1, 0)\n }\n const removeRow = (i: number) => {\n if (rows.length <= minRows) {\n onRowsChange(rows.map((r, j) => (j === i ? emptyRow() : r)))\n return\n }\n onRowsChange(rows.filter((_, j) => j !== i))\n }\n const moveRow = (i: number, delta: number) => {\n const j = i + delta\n if (j < 0 || j >= rows.length) return\n const next = [...rows]\n const [row] = next.splice(i, 1)\n next.splice(j, 0, row!)\n onRowsChange(next)\n focusCell(j, focusedCol.current)\n }\n\n function handleKeyDown(e: React.KeyboardEvent, i: number, colIndex: number) {\n focusedCol.current = colIndex\n const mod = e.metaKey || e.ctrlKey\n if (e.key === 'Enter' && !e.shiftKey && !mod) {\n if ((e.target as HTMLElement).getAttribute('aria-expanded') === 'true') return\n e.preventDefault()\n if (i === rows.length - 1) {\n onRowsChange([...rows, emptyRow()])\n focusCell(i + 1, colIndex)\n } else {\n focusCell(i + 1, colIndex)\n }\n return\n }\n if (e.altKey && e.key === 'ArrowUp') {\n e.preventDefault()\n moveRow(i, -1)\n } else if (e.altKey && e.key === 'ArrowDown') {\n e.preventDefault()\n moveRow(i, 1)\n } else if (mod && (e.key === 'd' || e.key === 'D')) {\n e.preventDefault()\n duplicateRow(i)\n } else if (mod && e.key === 'Backspace') {\n e.preventDefault()\n removeRow(i)\n focusCell(Math.max(0, i - 1), colIndex)\n }\n }\n\n const cellBase = 'flex min-h-[38px] items-center border-b border-border-subtle px-1'\n const inputBase =\n 'w-full rounded-sm border-0 bg-transparent px-1.5 py-1 text-sm text-fg outline-none focus:ring-2 focus:ring-ring/50'\n\n return (\n <div>\n <div ref={containerRef} className=\"overflow-x-auto rounded-lg border border-border bg-surface\">\n <div className=\"grid min-w-fit\" style={{ gridTemplateColumns: template }}>\n {!readOnly ? <div className=\"border-b border-border\" /> : null}\n {columns.map((c) => (\n <div\n key={c.key}\n className={cn(\n 'border-b border-border px-2.5 py-2 text-[11px] font-semibold tracking-wide text-fg-muted uppercase',\n c.align === 'right' && 'text-right',\n )}\n >\n {c.label}\n {c.required && !readOnly ? <span className=\"text-danger\"> *</span> : null}\n </div>\n ))}\n\n {rows.map((row, i) => (\n <React.Fragment key={i}>\n {!readOnly ? (\n <div className={cn(cellBase, 'justify-center px-0')}>\n <button\n type=\"button\"\n aria-label={labels.lineActions(i + 1)}\n onClick={(e) => {\n setMenuRow(i)\n menu.openBelow(e.currentTarget)\n }}\n className=\"group flex size-7 items-center justify-center rounded text-fg-subtle transition-colors hover:bg-surface-hover hover:text-fg-muted\"\n >\n <span className=\"text-[11px] tabular-nums group-hover:hidden\">{i + 1}</span>\n <GripVertical size={13} className=\"hidden group-hover:block\" />\n </button>\n </div>\n ) : null}\n\n {columns.map((c, colIndex) => {\n const value = row[c.key]\n if (readOnly || c.type === 'readonly') {\n let display: React.ReactNode\n if (c.render) display = c.render(row, i)\n else if (c.type === 'select' && value)\n display = c.options?.find((o) => o.value === value)?.label ?? ''\n else display = (value as string) ?? ''\n return (\n <div\n key={c.key}\n className={cn(cellBase, 'px-2.5 text-sm', c.align === 'right' && 'justify-end tabular-nums')}\n >\n {display}\n </div>\n )\n }\n return (\n <div\n key={c.key}\n data-lg-row={i}\n data-lg-col={colIndex}\n className={cellBase}\n onKeyDown={(e) => handleKeyDown(e, i, colIndex)}\n >\n {c.type === 'select' ? (\n <SearchSelect\n value={(value as string) ?? ''}\n onChange={(v) => setCell(i, c.key, v)}\n options={c.options ?? []}\n placeholder={c.placeholder ?? '—'}\n triggerClassName=\"h-auto min-h-0 border-0 bg-transparent px-1.5 py-1 focus-visible:ring-1\"\n />\n ) : c.type === 'amount' ? (\n <input\n inputMode=\"decimal\"\n value={(value as string) ?? ''}\n placeholder={c.placeholder ?? '0.00'}\n aria-invalid={\n value !== '' && value != null && Number.isNaN(Number(value)) ? true : undefined\n }\n onChange={(e) => setCell(i, c.key, e.target.value)}\n onBlur={(e) => setCell(i, c.key, normalizeAmount(e.target.value))}\n className={cn(\n inputBase,\n 'text-right tabular-nums',\n value !== '' && value != null && Number.isNaN(Number(value)) &&\n 'text-danger focus:ring-danger/50',\n )}\n />\n ) : (\n <input\n value={(value as string) ?? ''}\n placeholder={c.placeholder}\n onChange={(e) => setCell(i, c.key, e.target.value)}\n className={inputBase}\n />\n )}\n </div>\n )\n })}\n </React.Fragment>\n ))}\n </div>\n </div>\n\n <div className=\"mt-2 flex items-center justify-between gap-3\">\n {!readOnly ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => insertRow(rows.length)}>\n <Plus size={14} /> {labels.addLine}\n </Button>\n ) : (\n <span />\n )}\n {footer}\n </div>\n {!readOnly ? <p className=\"mt-1.5 text-[11px] text-fg-subtle\">{labels.keyboardHint}</p> : null}\n\n <ContextMenu\n open={menu.open && menuRow != null}\n position={menu.position}\n onClose={() => {\n menu.close()\n setMenuRow(null)\n }}\n items={\n menuRow == null\n ? []\n : [\n { key: 'above', label: labels.insertAbove, icon: ArrowUp, onSelect: () => insertRow(menuRow) },\n { key: 'below', label: labels.insertBelow, icon: ArrowDown, onSelect: () => insertRow(menuRow + 1) },\n { key: 'dup', label: labels.duplicate, icon: Copy, onSelect: () => duplicateRow(menuRow) },\n { key: 'sep', separator: true },\n {\n key: 'rm',\n label: rows.length > minRows ? labels.removeLine : labels.clearLine,\n icon: Trash2,\n danger: true,\n onSelect: () => removeRow(menuRow),\n },\n ]\n }\n />\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type UiLinkComponent = React.ComponentType<React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
3
|
+
href: string;
|
|
4
|
+
}>;
|
|
5
|
+
/** Inject an app router link once so appkit primitives never force full reloads. */
|
|
6
|
+
export declare function UiLinkProvider({ link, children, }: {
|
|
7
|
+
link: UiLinkComponent;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}): React.JSX.Element;
|
|
10
|
+
export declare function UiLink({ href, ...rest }: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
11
|
+
href: string;
|
|
12
|
+
}): React.JSX.Element;
|
|
13
|
+
/** How every navigation primitive renders one entry. */
|
|
14
|
+
export type LinkRender = (props: {
|
|
15
|
+
href: string;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
className: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
ariaCurrent?: 'page' | 'true';
|
|
20
|
+
role?: string;
|
|
21
|
+
dataWalkthrough?: string;
|
|
22
|
+
}) => React.ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* The fallback every navigation primitive uses when the app passes no
|
|
25
|
+
* `linkRender`. It resolves through `UiLink`, so a `UiLinkProvider` at the root
|
|
26
|
+
* is enough to make the whole shell route client-side. A raw `<a>` here would
|
|
27
|
+
* full-reload the document on every click, which tears down the router and
|
|
28
|
+
* skips page transitions entirely.
|
|
29
|
+
*/
|
|
30
|
+
export declare const defaultLinkRender: LinkRender;
|
|
31
|
+
export type BackLinkProps = {
|
|
32
|
+
href: string;
|
|
33
|
+
label: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
};
|
|
36
|
+
export type BackLinkLike = React.ComponentType<BackLinkProps>;
|
|
37
|
+
export type BackLinkComponent = BackLinkLike;
|
|
38
|
+
/** Inject an app-aware history resolver for every PageHeader back link. */
|
|
39
|
+
export declare function UiBackLinkProvider({ backLink, children, }: {
|
|
40
|
+
backLink: BackLinkLike;
|
|
41
|
+
children: React.ReactNode;
|
|
42
|
+
}): React.JSX.Element;
|
|
43
|
+
export declare function UiBackLink({ href, label, className }: BackLinkProps): React.JSX.Element;
|
|
44
|
+
//# sourceMappingURL=link-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-context.d.ts","sourceRoot":"","sources":["../src/link-context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,eAAe,GAAG,KAAK,CAAC,aAAa,CAC/C,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CACjE,CAAA;AAID,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,eAAe,CAAA;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,qBAEA;AAED,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,GAAG,IAAI,EACR,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,qBAGlE;AAED,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,KAAK,KAAK,CAAC,SAAS,CAAA;AAErB;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,UAmB/B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAA;AAC7D,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAA;AAI5C,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,YAAY,CAAA;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,qBAEA;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,aAAa,qBAQnE"}
|
package/link-context.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
const UiLinkContext = React.createContext(null);
|
|
5
|
+
/** Inject an app router link once so appkit primitives never force full reloads. */
|
|
6
|
+
export function UiLinkProvider({ link, children, }) {
|
|
7
|
+
return _jsx(UiLinkContext.Provider, { value: link, children: children });
|
|
8
|
+
}
|
|
9
|
+
export function UiLink({ href, ...rest }) {
|
|
10
|
+
const Link = React.useContext(UiLinkContext) ?? 'a';
|
|
11
|
+
return _jsx(Link, { href: href, ...rest });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The fallback every navigation primitive uses when the app passes no
|
|
15
|
+
* `linkRender`. It resolves through `UiLink`, so a `UiLinkProvider` at the root
|
|
16
|
+
* is enough to make the whole shell route client-side. A raw `<a>` here would
|
|
17
|
+
* full-reload the document on every click, which tears down the router and
|
|
18
|
+
* skips page transitions entirely.
|
|
19
|
+
*/
|
|
20
|
+
export const defaultLinkRender = ({ href, children, className, title, ariaCurrent, role, dataWalkthrough, }) => (_jsx(UiLink, { href: href, className: className, title: title, "aria-current": ariaCurrent, role: role, "data-walkthrough": dataWalkthrough, children: children }));
|
|
21
|
+
const UiBackLinkContext = React.createContext(null);
|
|
22
|
+
/** Inject an app-aware history resolver for every PageHeader back link. */
|
|
23
|
+
export function UiBackLinkProvider({ backLink, children, }) {
|
|
24
|
+
return _jsx(UiBackLinkContext.Provider, { value: backLink, children: children });
|
|
25
|
+
}
|
|
26
|
+
export function UiBackLink({ href, label, className }) {
|
|
27
|
+
const BackLink = React.useContext(UiBackLinkContext);
|
|
28
|
+
if (BackLink)
|
|
29
|
+
return _jsx(BackLink, { href: href, label: label, className: className });
|
|
30
|
+
return (_jsxs(UiLink, { href: href, className: className, children: ["\u2190 ", label] }));
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=link-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-context.js","sourceRoot":"","sources":["../src/link-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAyB,IAAI,CAAC,CAAA;AAEvE,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,GAIT;IACC,OAAO,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,IAAI,YAAG,QAAQ,GAA0B,CAAA;AACjF,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,EACrB,IAAI,EACJ,GAAG,IAAI,EAC0D;IACjE,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,GAAG,CAAA;IACnD,OAAO,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,KAAM,IAAI,GAAI,CAAA;AACvC,CAAC;AAaD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAe,CAAC,EAC5C,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,KAAK,EACL,WAAW,EACX,IAAI,EACJ,eAAe,GAChB,EAAE,EAAE,CAAC,CACJ,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,kBACE,WAAW,EACzB,IAAI,EAAE,IAAI,sBACQ,eAAe,YAEhC,QAAQ,GACF,CACV,CAAA;AAMD,MAAM,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAAsB,IAAI,CAAC,CAAA;AAExE,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,GAIT;IACC,OAAO,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAAG,QAAQ,GAA8B,CAAA;AAC7F,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAiB;IAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAA;IACpD,IAAI,QAAQ;QAAE,OAAO,KAAC,QAAQ,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;IACjF,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,wBACnC,KAAK,IACD,CACV,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nexport type UiLinkComponent = React.ComponentType<\n React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }\n>\n\nconst UiLinkContext = React.createContext<UiLinkComponent | null>(null)\n\n/** Inject an app router link once so appkit primitives never force full reloads. */\nexport function UiLinkProvider({\n link,\n children,\n}: {\n link: UiLinkComponent\n children: React.ReactNode\n}) {\n return <UiLinkContext.Provider value={link}>{children}</UiLinkContext.Provider>\n}\n\nexport function UiLink({\n href,\n ...rest\n}: React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) {\n const Link = React.useContext(UiLinkContext) ?? 'a'\n return <Link href={href} {...rest} />\n}\n\n/** How every navigation primitive renders one entry. */\nexport type LinkRender = (props: {\n href: string\n children: React.ReactNode\n className: string\n title?: string\n ariaCurrent?: 'page' | 'true'\n role?: string\n dataWalkthrough?: string\n}) => React.ReactNode\n\n/**\n * The fallback every navigation primitive uses when the app passes no\n * `linkRender`. It resolves through `UiLink`, so a `UiLinkProvider` at the root\n * is enough to make the whole shell route client-side. A raw `<a>` here would\n * full-reload the document on every click, which tears down the router and\n * skips page transitions entirely.\n */\nexport const defaultLinkRender: LinkRender = ({\n href,\n children,\n className,\n title,\n ariaCurrent,\n role,\n dataWalkthrough,\n}) => (\n <UiLink\n href={href}\n className={className}\n title={title}\n aria-current={ariaCurrent}\n role={role}\n data-walkthrough={dataWalkthrough}\n >\n {children}\n </UiLink>\n)\n\nexport type BackLinkProps = { href: string; label: string; className?: string }\nexport type BackLinkLike = React.ComponentType<BackLinkProps>\nexport type BackLinkComponent = BackLinkLike\n\nconst UiBackLinkContext = React.createContext<BackLinkLike | null>(null)\n\n/** Inject an app-aware history resolver for every PageHeader back link. */\nexport function UiBackLinkProvider({\n backLink,\n children,\n}: {\n backLink: BackLinkLike\n children: React.ReactNode\n}) {\n return <UiBackLinkContext.Provider value={backLink}>{children}</UiBackLinkContext.Provider>\n}\n\nexport function UiBackLink({ href, label, className }: BackLinkProps) {\n const BackLink = React.useContext(UiBackLinkContext)\n if (BackLink) return <BackLink href={href} label={label} className={className} />\n return (\n <UiLink href={href} className={className}>\n ← {label}\n </UiLink>\n )\n}\n"]}
|
package/list-nav.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Router bridge for the URL-driven list kit (SearchInput, FilterChips,
|
|
4
|
+
* SortableTh, Pagination). The query string is the canonical list state, so
|
|
5
|
+
* these components navigate. An app wires its client router once:
|
|
6
|
+
*
|
|
7
|
+
* <ListNavProvider value={{ pathname, search, replace: (h) => router.replace(h, { scroll: false }), push: (h) => router.push(h, { scroll: false }) }}>
|
|
8
|
+
*
|
|
9
|
+
* Without a provider the components fall back to full navigations via
|
|
10
|
+
* window.location — correct everywhere, just not soft.
|
|
11
|
+
*/
|
|
12
|
+
export type ListNav = {
|
|
13
|
+
pathname: string;
|
|
14
|
+
/** Current query string WITHOUT the leading `?`. */
|
|
15
|
+
search: string;
|
|
16
|
+
replace: (href: string) => void;
|
|
17
|
+
push: (href: string) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function ListNavProvider({ value, children }: {
|
|
20
|
+
value: ListNav;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}): React.JSX.Element;
|
|
23
|
+
/** The wired router, or a window-backed fallback (null during SSR). */
|
|
24
|
+
export declare function useListNav(): ListNav | null;
|
|
25
|
+
/** Anchor click handler: soft-navigate through the wired router when the click
|
|
26
|
+
* is a plain left-click (no modifiers), else let the browser handle it. */
|
|
27
|
+
export declare function useListNavClick(href: string): (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
28
|
+
//# sourceMappingURL=list-nav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-nav.d.ts","sourceRoot":"","sources":["../src/list-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;;;;;GASG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,CAAA;AAID,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAEjG;AAED,uEAAuE;AACvE,wBAAgB,UAAU,IAAI,OAAO,GAAG,IAAI,CAiB3C;AAED;4EAC4E;AAC5E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAW9F"}
|
package/list-nav.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
const ListNavContext = React.createContext(null);
|
|
5
|
+
export function ListNavProvider({ value, children }) {
|
|
6
|
+
return _jsx(ListNavContext.Provider, { value: value, children: children });
|
|
7
|
+
}
|
|
8
|
+
/** The wired router, or a window-backed fallback (null during SSR). */
|
|
9
|
+
export function useListNav() {
|
|
10
|
+
const wired = React.useContext(ListNavContext);
|
|
11
|
+
const [fallback, setFallback] = React.useState(null);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
if (wired)
|
|
14
|
+
return;
|
|
15
|
+
const make = () => ({
|
|
16
|
+
pathname: window.location.pathname,
|
|
17
|
+
search: window.location.search.replace(/^\?/, ''),
|
|
18
|
+
replace: (href) => window.location.replace(href),
|
|
19
|
+
push: (href) => window.location.assign(href),
|
|
20
|
+
});
|
|
21
|
+
setFallback(make());
|
|
22
|
+
const onPop = () => setFallback(make());
|
|
23
|
+
window.addEventListener('popstate', onPop);
|
|
24
|
+
return () => window.removeEventListener('popstate', onPop);
|
|
25
|
+
}, [wired]);
|
|
26
|
+
return wired ?? fallback;
|
|
27
|
+
}
|
|
28
|
+
/** Anchor click handler: soft-navigate through the wired router when the click
|
|
29
|
+
* is a plain left-click (no modifiers), else let the browser handle it. */
|
|
30
|
+
export function useListNavClick(href) {
|
|
31
|
+
const nav = React.useContext(ListNavContext);
|
|
32
|
+
return React.useCallback((e) => {
|
|
33
|
+
if (!nav)
|
|
34
|
+
return;
|
|
35
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
36
|
+
return;
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
nav.push(href);
|
|
39
|
+
}, [nav, href]);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=list-nav.js.map
|
package/list-nav.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-nav.js","sourceRoot":"","sources":["../src/list-nav.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,MAAM,cAAc,GAAG,KAAK,CAAC,aAAa,CAAiB,IAAI,CAAC,CAAA;AAEhE,MAAM,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAiD;IAChG,OAAO,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA2B,CAAA;AACpF,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;IAC9C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAiB,IAAI,CAAC,CAAA;IACpE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,KAAK;YAAE,OAAM;QACjB,MAAM,IAAI,GAAG,GAAY,EAAE,CAAC,CAAC;YAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;YAClC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;YACjD,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;YAChD,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;SAC7C,CAAC,CAAA;QACF,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;QACnB,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;QACvC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;QAC1C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;IAC5D,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IACX,OAAO,KAAK,IAAI,QAAQ,CAAA;AAC1B,CAAC;AAED;4EAC4E;AAC5E,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;IAC5C,OAAO,KAAK,CAAC,WAAW,CACtB,CAAC,CAAsC,EAAE,EAAE;QACzC,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM;YAAE,OAAM;QACpG,CAAC,CAAC,cAAc,EAAE,CAAA;QAClB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC,EACD,CAAC,GAAG,EAAE,IAAI,CAAC,CACZ,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/**\n * Router bridge for the URL-driven list kit (SearchInput, FilterChips,\n * SortableTh, Pagination). The query string is the canonical list state, so\n * these components navigate. An app wires its client router once:\n *\n * <ListNavProvider value={{ pathname, search, replace: (h) => router.replace(h, { scroll: false }), push: (h) => router.push(h, { scroll: false }) }}>\n *\n * Without a provider the components fall back to full navigations via\n * window.location — correct everywhere, just not soft.\n */\nexport type ListNav = {\n pathname: string\n /** Current query string WITHOUT the leading `?`. */\n search: string\n replace: (href: string) => void\n push: (href: string) => void\n}\n\nconst ListNavContext = React.createContext<ListNav | null>(null)\n\nexport function ListNavProvider({ value, children }: { value: ListNav; children: React.ReactNode }) {\n return <ListNavContext.Provider value={value}>{children}</ListNavContext.Provider>\n}\n\n/** The wired router, or a window-backed fallback (null during SSR). */\nexport function useListNav(): ListNav | null {\n const wired = React.useContext(ListNavContext)\n const [fallback, setFallback] = React.useState<ListNav | null>(null)\n React.useEffect(() => {\n if (wired) return\n const make = (): ListNav => ({\n pathname: window.location.pathname,\n search: window.location.search.replace(/^\\?/, ''),\n replace: (href) => window.location.replace(href),\n push: (href) => window.location.assign(href),\n })\n setFallback(make())\n const onPop = () => setFallback(make())\n window.addEventListener('popstate', onPop)\n return () => window.removeEventListener('popstate', onPop)\n }, [wired])\n return wired ?? fallback\n}\n\n/** Anchor click handler: soft-navigate through the wired router when the click\n * is a plain left-click (no modifiers), else let the browser handle it. */\nexport function useListNavClick(href: string): (e: React.MouseEvent<HTMLAnchorElement>) => void {\n const nav = React.useContext(ListNavContext)\n return React.useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n if (!nav) return\n if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return\n e.preventDefault()\n nav.push(href)\n },\n [nav, href],\n )\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type ListSearchParams } from './list-params.js';
|
|
3
|
+
export type PaginationLabels = {
|
|
4
|
+
showing: (from: string, to: string, total: string) => React.ReactNode;
|
|
5
|
+
noResults: string;
|
|
6
|
+
prev: string;
|
|
7
|
+
next: string;
|
|
8
|
+
pageOf: (page: number, pages: number) => string;
|
|
9
|
+
outOfRange: (page: string) => string;
|
|
10
|
+
goToPage: (page: string) => string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* URL-driven pagination row: showing X–Y of N + prev/next. When the current
|
|
14
|
+
* page is past the end (a filter shrank the list), offers a jump to the last
|
|
15
|
+
* page. Localized copy is supplied through the labels prop.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Pagination({ basePath, currentParams, total, page, perPage, pageParamKey, labels: labelOverrides, }: {
|
|
18
|
+
basePath: string;
|
|
19
|
+
currentParams: ListSearchParams;
|
|
20
|
+
total: number;
|
|
21
|
+
page: number;
|
|
22
|
+
perPage: number;
|
|
23
|
+
/** URL param that carries the page number. Sub-tables pass a prefixed key. */
|
|
24
|
+
pageParamKey?: string;
|
|
25
|
+
labels?: Partial<PaginationLabels>;
|
|
26
|
+
}): React.JSX.Element;
|
|
27
|
+
//# sourceMappingURL=list-pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-pagination.d.ts","sourceRoot":"","sources":["../src/list-pagination.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAGhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACrE,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACnC,CAAA;AAkBD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,aAAa,EACb,KAAK,EACL,IAAI,EACJ,OAAO,EACP,YAAqB,EACrB,MAAM,EAAE,cAAc,GACvB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,gBAAgB,CAAA;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACnC,qBAwCA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { mergeHref } from './list-params.js';
|
|
6
|
+
import { useListNavClick } from './list-nav.js';
|
|
7
|
+
const DEFAULT_LABELS = {
|
|
8
|
+
showing: (from, to, total) => (_jsxs(_Fragment, { children: ["Showing ", _jsx("strong", { className: "font-medium text-fg", children: from }), "\u2013", _jsx("strong", { className: "font-medium text-fg", children: to }), " of", ' ', _jsx("strong", { className: "font-medium text-fg", children: total })] })),
|
|
9
|
+
noResults: 'No results',
|
|
10
|
+
prev: 'Prev',
|
|
11
|
+
next: 'Next',
|
|
12
|
+
pageOf: (page, pages) => `Page ${page} of ${pages}`,
|
|
13
|
+
outOfRange: (page) => `Page ${page} is out of range`,
|
|
14
|
+
goToPage: (page) => `Go to page ${page}`,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* URL-driven pagination row: showing X–Y of N + prev/next. When the current
|
|
18
|
+
* page is past the end (a filter shrank the list), offers a jump to the last
|
|
19
|
+
* page. Localized copy is supplied through the labels prop.
|
|
20
|
+
*/
|
|
21
|
+
export function Pagination({ basePath, currentParams, total, page, perPage, pageParamKey = 'page', labels: labelOverrides, }) {
|
|
22
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
23
|
+
const pageCount = Math.max(1, Math.ceil(total / perPage));
|
|
24
|
+
const isOutOfRange = total > 0 && page > pageCount;
|
|
25
|
+
const from = total === 0 ? 0 : (page - 1) * perPage + 1;
|
|
26
|
+
const to = Math.min(total, page * perPage);
|
|
27
|
+
const prevHref = mergeHref(basePath, currentParams, { [pageParamKey]: page > 1 ? page - 1 : 1 });
|
|
28
|
+
const nextHref = mergeHref(basePath, currentParams, { [pageParamKey]: Math.min(pageCount, page + 1) });
|
|
29
|
+
const lastPageHref = mergeHref(basePath, currentParams, { [pageParamKey]: pageCount });
|
|
30
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm text-fg-muted", children: [_jsx("span", { children: isOutOfRange
|
|
31
|
+
? labels.outOfRange(page.toLocaleString())
|
|
32
|
+
: total === 0
|
|
33
|
+
? labels.noResults
|
|
34
|
+
: labels.showing(from.toLocaleString(), to.toLocaleString(), total.toLocaleString()) }), isOutOfRange ? (_jsxs(PageButton, { href: lastPageHref, "aria-label": labels.goToPage(pageCount.toLocaleString()), children: [_jsx(ChevronLeft, { size: 14 }), labels.goToPage(pageCount.toLocaleString())] })) : pageCount > 1 ? (_jsxs("div", { className: "flex items-center gap-1", children: [_jsxs(PageButton, { href: prevHref, disabled: page <= 1, children: [_jsx(ChevronLeft, { size: 14 }), labels.prev] }), _jsx("span", { className: "px-2 text-fg-subtle", children: labels.pageOf(page, pageCount) }), _jsxs(PageButton, { href: nextHref, disabled: page >= pageCount, children: [labels.next, _jsx(ChevronRight, { size: 14 })] })] })) : null] }));
|
|
35
|
+
}
|
|
36
|
+
function PageButton({ href, disabled, children, ...rest }) {
|
|
37
|
+
const navClick = useListNavClick(href);
|
|
38
|
+
if (disabled) {
|
|
39
|
+
return (_jsx("span", { className: "inline-flex cursor-not-allowed items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-fg-subtle", ...rest, children: children }));
|
|
40
|
+
}
|
|
41
|
+
return (_jsx("a", { href: href, onClick: navClick, className: "inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-fg hover:bg-surface-hover", ...rest, children: children }));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=list-pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-pagination.js","sourceRoot":"","sources":["../src/list-pagination.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { mergeHref, type ListSearchParams } from './list-params'\nimport { useListNavClick } from './list-nav'\n\nexport type PaginationLabels = {\n showing: (from: string, to: string, total: string) => React.ReactNode\n noResults: string\n prev: string\n next: string\n pageOf: (page: number, pages: number) => string\n outOfRange: (page: string) => string\n goToPage: (page: string) => string\n}\n\nconst DEFAULT_LABELS: PaginationLabels = {\n showing: (from, to, total) => (\n <>\n Showing <strong className=\"font-medium text-fg\">{from}</strong>–\n <strong className=\"font-medium text-fg\">{to}</strong> of{' '}\n <strong className=\"font-medium text-fg\">{total}</strong>\n </>\n ),\n noResults: 'No results',\n prev: 'Prev',\n next: 'Next',\n pageOf: (page, pages) => `Page ${page} of ${pages}`,\n outOfRange: (page) => `Page ${page} is out of range`,\n goToPage: (page) => `Go to page ${page}`,\n}\n\n/**\n * URL-driven pagination row: showing X–Y of N + prev/next. When the current\n * page is past the end (a filter shrank the list), offers a jump to the last\n * page. Localized copy is supplied through the labels prop.\n */\nexport function Pagination({\n basePath,\n currentParams,\n total,\n page,\n perPage,\n pageParamKey = 'page',\n labels: labelOverrides,\n}: {\n basePath: string\n currentParams: ListSearchParams\n total: number\n page: number\n perPage: number\n /** URL param that carries the page number. Sub-tables pass a prefixed key. */\n pageParamKey?: string\n labels?: Partial<PaginationLabels>\n}) {\n const labels = { ...DEFAULT_LABELS, ...labelOverrides }\n const pageCount = Math.max(1, Math.ceil(total / perPage))\n const isOutOfRange = total > 0 && page > pageCount\n const from = total === 0 ? 0 : (page - 1) * perPage + 1\n const to = Math.min(total, page * perPage)\n\n const prevHref = mergeHref(basePath, currentParams, { [pageParamKey]: page > 1 ? page - 1 : 1 })\n const nextHref = mergeHref(basePath, currentParams, { [pageParamKey]: Math.min(pageCount, page + 1) })\n const lastPageHref = mergeHref(basePath, currentParams, { [pageParamKey]: pageCount })\n\n return (\n <div className=\"flex items-center justify-between gap-2 px-3 py-2 text-sm text-fg-muted\">\n <span>\n {isOutOfRange\n ? labels.outOfRange(page.toLocaleString())\n : total === 0\n ? labels.noResults\n : labels.showing(from.toLocaleString(), to.toLocaleString(), total.toLocaleString())}\n </span>\n {isOutOfRange ? (\n <PageButton href={lastPageHref} aria-label={labels.goToPage(pageCount.toLocaleString())}>\n <ChevronLeft size={14} />\n {labels.goToPage(pageCount.toLocaleString())}\n </PageButton>\n ) : pageCount > 1 ? (\n <div className=\"flex items-center gap-1\">\n <PageButton href={prevHref} disabled={page <= 1}>\n <ChevronLeft size={14} />\n {labels.prev}\n </PageButton>\n <span className=\"px-2 text-fg-subtle\">{labels.pageOf(page, pageCount)}</span>\n <PageButton href={nextHref} disabled={page >= pageCount}>\n {labels.next}\n <ChevronRight size={14} />\n </PageButton>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction PageButton({\n href,\n disabled,\n children,\n ...rest\n}: {\n href: string\n disabled?: boolean\n children: React.ReactNode\n} & React.HTMLAttributes<HTMLAnchorElement>) {\n const navClick = useListNavClick(href)\n if (disabled) {\n return (\n <span\n className=\"inline-flex cursor-not-allowed items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-fg-subtle\"\n {...(rest as object)}\n >\n {children}\n </span>\n )\n }\n return (\n <a\n href={href}\n onClick={navClick}\n className=\"inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-fg hover:bg-surface-hover\"\n {...(rest as object)}\n >\n {children}\n </a>\n )\n}\n"]}
|
package/list-params.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type ListParams<S extends string = string> = {
|
|
2
|
+
q: string | undefined;
|
|
3
|
+
sort: S;
|
|
4
|
+
dir: 'asc' | 'desc';
|
|
5
|
+
page: number;
|
|
6
|
+
perPage: number;
|
|
7
|
+
};
|
|
8
|
+
export type ListSearchParams = Record<string, string | string[] | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* True when `value` is a UUID. Use to guard `[id]` route params before querying
|
|
11
|
+
* a uuid PK — a non-UUID segment otherwise throws a Postgres "invalid input
|
|
12
|
+
* syntax for type uuid" error instead of a clean 404.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isUuid(value: string): boolean;
|
|
15
|
+
export declare function parseListParams<S extends string>(searchParams: ListSearchParams, config: {
|
|
16
|
+
sort: S;
|
|
17
|
+
dir?: 'asc' | 'desc';
|
|
18
|
+
perPage?: number;
|
|
19
|
+
allowedSorts: readonly S[];
|
|
20
|
+
}): ListParams<S>;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a list that shares its route with other independently paged lists.
|
|
23
|
+
* `prefix="review"` reads reviewQ/reviewSort/reviewDir/reviewPage/reviewPerPage,
|
|
24
|
+
* keeping one sub-list's controls from resetting another one.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parsePrefixedListParams<S extends string>(searchParams: ListSearchParams, prefix: string, config: {
|
|
27
|
+
sort: S;
|
|
28
|
+
dir?: 'asc' | 'desc';
|
|
29
|
+
perPage?: number;
|
|
30
|
+
allowedSorts: readonly S[];
|
|
31
|
+
}): ListParams<S>;
|
|
32
|
+
export declare function pickString(v: string | string[] | undefined): string | undefined;
|
|
33
|
+
export declare function clamp(n: number, min: number, max: number): number;
|
|
34
|
+
export declare function buildHref(base: string, params: Record<string, string | number | undefined | null>): string;
|
|
35
|
+
export declare function mergeHref(base: string, current: ListSearchParams, overrides: Record<string, string | number | undefined | null>): string;
|
|
36
|
+
/**
|
|
37
|
+
* Build an export URL that carries the current list filters but drops
|
|
38
|
+
* pagination params (export is always "all visible rows that match").
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildExportHref(base: string, current: ListSearchParams): string;
|
|
41
|
+
//# sourceMappingURL=list-params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-params.d.ts","sourceRoot":"","sources":["../src/list-params.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IAClD,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrB,IAAI,EAAE,CAAC,CAAA;IACP,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;AAI5E;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,YAAY,EAAE,gBAAgB,EAC9B,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,CAAC,EAAE,CAAA;CAAE,GACtF,UAAU,CAAC,CAAC,CAAC,CAaf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EACtD,YAAY,EAAE,gBAAgB,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,CAAC,EAAE,CAAA;CAAE,GACtF,UAAU,CAAC,CAAC,CAAC,CAWf;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG/E;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,GACzD,MAAM,CAQR;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,GAC5D,MAAM,CASR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAO/E"}
|
package/list-params.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Parse common list query params (?q=&sort=&dir=&page=&perPage=) from the
|
|
2
|
+
// `searchParams` an App-Router page receives. The query string is the canonical
|
|
3
|
+
// state for list filters: refreshes and shared links work, and the list shell
|
|
4
|
+
// needs no client state.
|
|
5
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
+
/**
|
|
7
|
+
* True when `value` is a UUID. Use to guard `[id]` route params before querying
|
|
8
|
+
* a uuid PK — a non-UUID segment otherwise throws a Postgres "invalid input
|
|
9
|
+
* syntax for type uuid" error instead of a clean 404.
|
|
10
|
+
*/
|
|
11
|
+
export function isUuid(value) {
|
|
12
|
+
return UUID_RE.test(value);
|
|
13
|
+
}
|
|
14
|
+
export function parseListParams(searchParams, config) {
|
|
15
|
+
const q = pickString(searchParams.q);
|
|
16
|
+
const rawSort = pickString(searchParams.sort);
|
|
17
|
+
const sort = config.allowedSorts.includes(rawSort) ? rawSort : config.sort;
|
|
18
|
+
const dir = pickString(searchParams.dir) === 'asc'
|
|
19
|
+
? 'asc'
|
|
20
|
+
: pickString(searchParams.dir) === 'desc'
|
|
21
|
+
? 'desc'
|
|
22
|
+
: (config.dir ?? 'desc');
|
|
23
|
+
const page = clamp(Number(pickString(searchParams.page) ?? '1'), 1, 10_000);
|
|
24
|
+
const perPage = clamp(Number(pickString(searchParams.perPage) ?? String(config.perPage ?? 25)), 5, 100);
|
|
25
|
+
return { q: q && q.length ? q : undefined, sort, dir, page, perPage };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a list that shares its route with other independently paged lists.
|
|
29
|
+
* `prefix="review"` reads reviewQ/reviewSort/reviewDir/reviewPage/reviewPerPage,
|
|
30
|
+
* keeping one sub-list's controls from resetting another one.
|
|
31
|
+
*/
|
|
32
|
+
export function parsePrefixedListParams(searchParams, prefix, config) {
|
|
33
|
+
return parseListParams({
|
|
34
|
+
q: searchParams[`${prefix}Q`],
|
|
35
|
+
sort: searchParams[`${prefix}Sort`],
|
|
36
|
+
dir: searchParams[`${prefix}Dir`],
|
|
37
|
+
page: searchParams[`${prefix}Page`],
|
|
38
|
+
perPage: searchParams[`${prefix}PerPage`],
|
|
39
|
+
}, config);
|
|
40
|
+
}
|
|
41
|
+
export function pickString(v) {
|
|
42
|
+
if (Array.isArray(v))
|
|
43
|
+
return v[0];
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
export function clamp(n, min, max) {
|
|
47
|
+
if (!Number.isFinite(n))
|
|
48
|
+
return min;
|
|
49
|
+
return Math.max(min, Math.min(max, Math.trunc(n)));
|
|
50
|
+
}
|
|
51
|
+
export function buildHref(base, params) {
|
|
52
|
+
const usp = new URLSearchParams();
|
|
53
|
+
for (const [k, v] of Object.entries(params)) {
|
|
54
|
+
if (v === undefined || v === null || v === '')
|
|
55
|
+
continue;
|
|
56
|
+
usp.set(k, String(v));
|
|
57
|
+
}
|
|
58
|
+
const q = usp.toString();
|
|
59
|
+
return q ? `${base}?${q}` : base;
|
|
60
|
+
}
|
|
61
|
+
export function mergeHref(base, current, overrides) {
|
|
62
|
+
const merged = {};
|
|
63
|
+
for (const [k, v] of Object.entries(current)) {
|
|
64
|
+
merged[k] = pickString(v) ?? undefined;
|
|
65
|
+
}
|
|
66
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
67
|
+
merged[k] = v;
|
|
68
|
+
}
|
|
69
|
+
return buildHref(base, merged);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build an export URL that carries the current list filters but drops
|
|
73
|
+
* pagination params (export is always "all visible rows that match").
|
|
74
|
+
*/
|
|
75
|
+
export function buildExportHref(base, current) {
|
|
76
|
+
const merged = {};
|
|
77
|
+
for (const [k, v] of Object.entries(current)) {
|
|
78
|
+
if (k === 'page' || k === 'perPage')
|
|
79
|
+
continue;
|
|
80
|
+
merged[k] = pickString(v) ?? undefined;
|
|
81
|
+
}
|
|
82
|
+
return buildHref(base, merged);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=list-params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-params.js","sourceRoot":"","sources":["../src/list-params.ts"],"names":[],"mappings":"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","sourcesContent":["// Parse common list query params (?q=&sort=&dir=&page=&perPage=) from the\n// `searchParams` an App-Router page receives. The query string is the canonical\n// state for list filters: refreshes and shared links work, and the list shell\n// needs no client state.\n\nexport type ListParams<S extends string = string> = {\n q: string | undefined\n sort: S\n dir: 'asc' | 'desc'\n page: number\n perPage: number\n}\n\nexport type ListSearchParams = Record<string, string | string[] | undefined>\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * True when `value` is a UUID. Use to guard `[id]` route params before querying\n * a uuid PK — a non-UUID segment otherwise throws a Postgres \"invalid input\n * syntax for type uuid\" error instead of a clean 404.\n */\nexport function isUuid(value: string): boolean {\n return UUID_RE.test(value)\n}\n\nexport function parseListParams<S extends string>(\n searchParams: ListSearchParams,\n config: { sort: S; dir?: 'asc' | 'desc'; perPage?: number; allowedSorts: readonly S[] },\n): ListParams<S> {\n const q = pickString(searchParams.q)\n const rawSort = pickString(searchParams.sort)\n const sort = config.allowedSorts.includes(rawSort as S) ? (rawSort as S) : config.sort\n const dir =\n pickString(searchParams.dir) === 'asc'\n ? 'asc'\n : pickString(searchParams.dir) === 'desc'\n ? 'desc'\n : (config.dir ?? 'desc')\n const page = clamp(Number(pickString(searchParams.page) ?? '1'), 1, 10_000)\n const perPage = clamp(Number(pickString(searchParams.perPage) ?? String(config.perPage ?? 25)), 5, 100)\n return { q: q && q.length ? q : undefined, sort, dir, page, perPage }\n}\n\n/**\n * Parse a list that shares its route with other independently paged lists.\n * `prefix=\"review\"` reads reviewQ/reviewSort/reviewDir/reviewPage/reviewPerPage,\n * keeping one sub-list's controls from resetting another one.\n */\nexport function parsePrefixedListParams<S extends string>(\n searchParams: ListSearchParams,\n prefix: string,\n config: { sort: S; dir?: 'asc' | 'desc'; perPage?: number; allowedSorts: readonly S[] },\n): ListParams<S> {\n return parseListParams(\n {\n q: searchParams[`${prefix}Q`],\n sort: searchParams[`${prefix}Sort`],\n dir: searchParams[`${prefix}Dir`],\n page: searchParams[`${prefix}Page`],\n perPage: searchParams[`${prefix}PerPage`],\n },\n config,\n )\n}\n\nexport function pickString(v: string | string[] | undefined): string | undefined {\n if (Array.isArray(v)) return v[0]\n return v\n}\n\nexport function clamp(n: number, min: number, max: number): number {\n if (!Number.isFinite(n)) return min\n return Math.max(min, Math.min(max, Math.trunc(n)))\n}\n\nexport function buildHref(\n base: string,\n params: Record<string, string | number | undefined | null>,\n): string {\n const usp = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null || v === '') continue\n usp.set(k, String(v))\n }\n const q = usp.toString()\n return q ? `${base}?${q}` : base\n}\n\nexport function mergeHref(\n base: string,\n current: ListSearchParams,\n overrides: Record<string, string | number | undefined | null>,\n): string {\n const merged: Record<string, string | number | undefined | null> = {}\n for (const [k, v] of Object.entries(current)) {\n merged[k] = pickString(v) ?? undefined\n }\n for (const [k, v] of Object.entries(overrides)) {\n merged[k] = v\n }\n return buildHref(base, merged)\n}\n\n/**\n * Build an export URL that carries the current list filters but drops\n * pagination params (export is always \"all visible rows that match\").\n */\nexport function buildExportHref(base: string, current: ListSearchParams): string {\n const merged: Record<string, string | number | undefined | null> = {}\n for (const [k, v] of Object.entries(current)) {\n if (k === 'page' || k === 'perPage') continue\n merged[k] = pickString(v) ?? undefined\n }\n return buildHref(base, merged)\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type SearchInputEditState = {
|
|
2
|
+
value: string;
|
|
3
|
+
/**
|
|
4
|
+
* The user has a local edit that must win over URL updates until every
|
|
5
|
+
* in-flight navigation settles. This prevents an older server response from
|
|
6
|
+
* replacing text entered after that request began.
|
|
7
|
+
*/
|
|
8
|
+
dirty: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function createSearchInputEditState(urlValue: string): SearchInputEditState;
|
|
11
|
+
export declare function applySearchInputEdit(value: string, urlValue: string, navigationPending: boolean): SearchInputEditState;
|
|
12
|
+
export declare function reconcileSearchInputUrl(state: SearchInputEditState, urlValue: string, navigationPending: boolean): SearchInputEditState;
|
|
13
|
+
//# sourceMappingURL=list-search-input-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-search-input-state.d.ts","sourceRoot":"","sources":["../src/list-search-input-state.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAEjF;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,OAAO,GACzB,oBAAoB,CAOtB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,oBAAoB,EAC3B,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,OAAO,GACzB,oBAAoB,CActB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function createSearchInputEditState(urlValue) {
|
|
2
|
+
return { value: urlValue, dirty: false };
|
|
3
|
+
}
|
|
4
|
+
export function applySearchInputEdit(value, urlValue, navigationPending) {
|
|
5
|
+
return {
|
|
6
|
+
value,
|
|
7
|
+
// A pending navigation may still carry an older value even when the new
|
|
8
|
+
// edit happens to equal the URL currently visible to this render.
|
|
9
|
+
dirty: value !== urlValue || navigationPending,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function reconcileSearchInputUrl(state, urlValue, navigationPending) {
|
|
13
|
+
if (state.dirty) {
|
|
14
|
+
// While navigation is pending, URL values can arrive out of order. Keep
|
|
15
|
+
// the user's latest edit authoritative. It becomes clean only after all
|
|
16
|
+
// transitions settle with that exact value in the URL.
|
|
17
|
+
if (!navigationPending && state.value === urlValue) {
|
|
18
|
+
return { value: state.value, dirty: false };
|
|
19
|
+
}
|
|
20
|
+
return state;
|
|
21
|
+
}
|
|
22
|
+
// With no local edit, browser history and links remain authoritative.
|
|
23
|
+
if (state.value === urlValue)
|
|
24
|
+
return state;
|
|
25
|
+
return { value: urlValue, dirty: false };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=list-search-input-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-search-input-state.js","sourceRoot":"","sources":["../src/list-search-input-state.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IACzD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,KAAa,EACb,QAAgB,EAChB,iBAA0B;IAE1B,OAAO;QACL,KAAK;QACL,wEAAwE;QACxE,kEAAkE;QAClE,KAAK,EAAE,KAAK,KAAK,QAAQ,IAAI,iBAAiB;KAC/C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,KAA2B,EAC3B,QAAgB,EAChB,iBAA0B;IAE1B,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,wEAAwE;QACxE,wEAAwE;QACxE,uDAAuD;QACvD,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACnD,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;QAC7C,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,sEAAsE;IACtE,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC1C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AAC1C,CAAC","sourcesContent":["export type SearchInputEditState = {\n value: string\n /**\n * The user has a local edit that must win over URL updates until every\n * in-flight navigation settles. This prevents an older server response from\n * replacing text entered after that request began.\n */\n dirty: boolean\n}\n\nexport function createSearchInputEditState(urlValue: string): SearchInputEditState {\n return { value: urlValue, dirty: false }\n}\n\nexport function applySearchInputEdit(\n value: string,\n urlValue: string,\n navigationPending: boolean,\n): SearchInputEditState {\n return {\n value,\n // A pending navigation may still carry an older value even when the new\n // edit happens to equal the URL currently visible to this render.\n dirty: value !== urlValue || navigationPending,\n }\n}\n\nexport function reconcileSearchInputUrl(\n state: SearchInputEditState,\n urlValue: string,\n navigationPending: boolean,\n): SearchInputEditState {\n if (state.dirty) {\n // While navigation is pending, URL values can arrive out of order. Keep\n // the user's latest edit authoritative. It becomes clean only after all\n // transitions settle with that exact value in the URL.\n if (!navigationPending && state.value === urlValue) {\n return { value: state.value, dirty: false }\n }\n return state\n }\n\n // With no local edit, browser history and links remain authoritative.\n if (state.value === urlValue) return state\n return { value: urlValue, dirty: false }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* URL-driven search box: debounced 250ms, writes `?q=` (configurable) and
|
|
4
|
+
* resets the page param. Keeps focus by replacing (not pushing) through the
|
|
5
|
+
* wired ListNav router. Localized copy is supplied through props.
|
|
6
|
+
*/
|
|
7
|
+
export declare function SearchInput({ placeholder, searchLabel, clearLabel, paramKey, pageParamKey, className, }: {
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
searchLabel?: string;
|
|
10
|
+
clearLabel?: string;
|
|
11
|
+
paramKey?: string;
|
|
12
|
+
/** Pagination param to reset when the search changes (sub-tables use prefixed params). */
|
|
13
|
+
pageParamKey?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
//# sourceMappingURL=list-search-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-search-input.d.ts","sourceRoot":"","sources":["../src/list-search-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,WAAuB,EACvB,WAAsB,EACtB,UAA2B,EAC3B,QAAc,EACd,YAAqB,EACrB,SAAS,GACV,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBA8DA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Search, X } from 'lucide-react';
|
|
5
|
+
import { Input } from './input.js';
|
|
6
|
+
import { useListNav } from './list-nav.js';
|
|
7
|
+
import { applySearchInputEdit, createSearchInputEditState, reconcileSearchInputUrl, } from './list-search-input-state.js';
|
|
8
|
+
/**
|
|
9
|
+
* URL-driven search box: debounced 250ms, writes `?q=` (configurable) and
|
|
10
|
+
* resets the page param. Keeps focus by replacing (not pushing) through the
|
|
11
|
+
* wired ListNav router. Localized copy is supplied through props.
|
|
12
|
+
*/
|
|
13
|
+
export function SearchInput({ placeholder = 'Search…', searchLabel = 'Search', clearLabel = 'Clear search', paramKey = 'q', pageParamKey = 'page', className, }) {
|
|
14
|
+
const nav = useListNav();
|
|
15
|
+
const urlValue = React.useMemo(() => new URLSearchParams(nav?.search ?? '').get(paramKey) ?? '', [nav?.search, paramKey]);
|
|
16
|
+
const [edit, setEdit] = React.useState(() => createSearchInputEditState(urlValue));
|
|
17
|
+
const [navigationPending, startTransition] = React.useTransition();
|
|
18
|
+
const value = edit.value;
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
setEdit((current) => reconcileSearchInputUrl(current, urlValue, navigationPending));
|
|
21
|
+
}, [navigationPending, urlValue]);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (!nav)
|
|
24
|
+
return;
|
|
25
|
+
const handle = setTimeout(() => {
|
|
26
|
+
// No-op when the input already matches the URL (mount, external URL
|
|
27
|
+
// change) — navigating anyway would strip the page param and reset
|
|
28
|
+
// deep-linked pagination back to page 1.
|
|
29
|
+
const current = new URLSearchParams(nav.search).get(paramKey) ?? '';
|
|
30
|
+
if (value === current)
|
|
31
|
+
return;
|
|
32
|
+
const next = new URLSearchParams(nav.search);
|
|
33
|
+
if (value)
|
|
34
|
+
next.set(paramKey, value);
|
|
35
|
+
else
|
|
36
|
+
next.delete(paramKey);
|
|
37
|
+
next.delete(pageParamKey);
|
|
38
|
+
const qs = next.toString();
|
|
39
|
+
// Transition keeps the current page (and this input's focus) mounted
|
|
40
|
+
// while the next server render streams in.
|
|
41
|
+
startTransition(() => {
|
|
42
|
+
nav.replace(qs ? `${nav.pathname}?${qs}` : nav.pathname);
|
|
43
|
+
});
|
|
44
|
+
}, 250);
|
|
45
|
+
return () => clearTimeout(handle);
|
|
46
|
+
}, [nav, pageParamKey, paramKey, value]);
|
|
47
|
+
return (_jsxs("div", { className: className ?? 'relative w-full sm:w-72', children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-2 text-fg-subtle", size: 16 }), _jsx(Input, { type: "search", "aria-label": searchLabel, placeholder: placeholder, value: value, onChange: (e) => setEdit(applySearchInputEdit(e.target.value, urlValue, navigationPending)),
|
|
48
|
+
// Hide the browser's native search clear (×) — we render our own.
|
|
49
|
+
className: "h-8 pl-9 pr-9 [&::-webkit-search-cancel-button]:hidden" }), value ? (_jsx("button", { type: "button", "aria-label": clearLabel, onClick: () => setEdit(applySearchInputEdit('', urlValue, navigationPending)), className: "absolute right-2.5 top-2 text-fg-subtle hover:text-fg-muted", children: _jsx(X, { size: 16 }) })) : null] }));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=list-search-input.js.map
|