@refraction-ui/astro 0.4.2 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/index.ts +1 -0
- package/dist/ai/ai-manager.ts +121 -0
- package/dist/ai/index.ts +23 -0
- package/dist/ai/mock-providers.ts +68 -0
- package/dist/ai/sanitizer.ts +174 -0
- package/dist/ai/tts-manager.ts +80 -0
- package/dist/ai/types.ts +72 -0
- package/dist/animated-text/animated-text.styles.ts +28 -0
- package/dist/animated-text/animated-text.ts +83 -0
- package/dist/animated-text/index.ts +15 -0
- package/dist/app-shell/app-shell.ts +252 -0
- package/dist/app-shell/auth-shell.ts +84 -0
- package/dist/app-shell/index.ts +22 -0
- package/dist/app-shell/page-shell.ts +125 -0
- package/dist/astro-accordion/Button.astro +1 -1
- package/dist/astro-ai/index.ts +1 -1
- package/dist/astro-animated-text/AnimatedText.astro +1 -1
- package/dist/astro-animated-text/TypewriterText.astro +1 -1
- package/dist/astro-app-shell/AppShell.astro +1 -1
- package/dist/astro-app-shell/AuthShell.astro +1 -1
- package/dist/astro-app-shell/AuthShellCard.astro +1 -1
- package/dist/astro-app-shell/PageShell.astro +1 -1
- package/dist/astro-app-shell/PageShellSection.astro +1 -1
- package/dist/astro-app-shell/index.ts +1 -1
- package/dist/astro-auth/index.ts +2 -2
- package/dist/astro-avatar/Avatar.astro +1 -1
- package/dist/astro-avatar/AvatarFallback.astro +1 -1
- package/dist/astro-avatar/AvatarImage.astro +1 -1
- package/dist/astro-avatar-group/AvatarGroup.astro +1 -1
- package/dist/astro-badge/Badge.astro +1 -1
- package/dist/astro-bottom-nav/BottomNav.astro +1 -1
- package/dist/astro-breadcrumbs/Breadcrumbs.astro +1 -1
- package/dist/astro-button/Button.astro +1 -1
- package/dist/astro-calendar/Calendar.astro +1 -1
- package/dist/astro-card/Card.astro +1 -1
- package/dist/astro-card/CardContent.astro +1 -1
- package/dist/astro-card/CardDescription.astro +1 -1
- package/dist/astro-card/CardFooter.astro +1 -1
- package/dist/astro-card/CardHeader.astro +1 -1
- package/dist/astro-card/CardTitle.astro +1 -1
- package/dist/astro-card-grid/CardGrid.astro +1 -1
- package/dist/astro-carousel/Button.astro +1 -1
- package/dist/astro-charts/Bars.astro +1 -1
- package/dist/astro-charts/Chart.astro +1 -1
- package/dist/astro-charts/Circles.astro +1 -1
- package/dist/astro-charts/Histogram.astro +1 -1
- package/dist/astro-charts/Line.astro +1 -1
- package/dist/astro-charts/PieChart.astro +1 -1
- package/dist/astro-charts/ScatterPlot.astro +1 -1
- package/dist/astro-charts/index.ts +1 -1
- package/dist/astro-checkbox/Checkbox.astro +1 -1
- package/dist/astro-code-editor/CodeEditor.astro +1 -1
- package/dist/astro-collapsible/Collapsible.astro +1 -1
- package/dist/astro-collapsible/CollapsibleContent.astro +1 -1
- package/dist/astro-command/Command.astro +1 -1
- package/dist/astro-command/CommandGroup.astro +1 -1
- package/dist/astro-command/CommandInput.astro +1 -1
- package/dist/astro-command/CommandItem.astro +1 -1
- package/dist/astro-command-input/CommandInput.astro +1 -1
- package/dist/astro-content-protection/ContentProtection.astro +1 -1
- package/dist/astro-data-table/DataTable.astro +1 -1
- package/dist/astro-date-picker/DatePicker.astro +1 -1
- package/dist/astro-device-frame/DeviceFrame.astro +1 -1
- package/dist/astro-dialog/DialogContent.astro +1 -1
- package/dist/astro-dialog/DialogOverlay.astro +1 -1
- package/dist/astro-diff-viewer/DiffViewer.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuContent.astro +1 -1
- package/dist/astro-dropdown-menu/DropdownMenuItem.astro +1 -1
- package/dist/astro-emoji-picker/EmojiPicker.astro +1 -1
- package/dist/astro-feedback-dialog/FeedbackDialog.astro +1 -1
- package/dist/astro-file-upload/FileUpload.astro +1 -1
- package/dist/astro-footer/Footer.astro +1 -1
- package/dist/astro-inline-editor/InlineEditor.astro +1 -1
- package/dist/astro-input/Input.astro +1 -1
- package/dist/astro-input-group/InputGroup.astro +1 -1
- package/dist/astro-input-group/InputGroupAddon.astro +1 -1
- package/dist/astro-input-group/InputGroupButton.astro +1 -1
- package/dist/astro-install-prompt/InstallPrompt.astro +1 -1
- package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +1 -1
- package/dist/astro-language-selector/LanguageSelector.astro +1 -1
- package/dist/astro-link-card/LinkCard.astro +1 -1
- package/dist/astro-markdown-renderer/MarkdownRenderer.astro +1 -1
- package/dist/astro-mobile-nav/MobileNav.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavLink.astro +1 -1
- package/dist/astro-mobile-nav/MobileNavTrigger.astro +1 -1
- package/dist/astro-navbar/Navbar.astro +1 -1
- package/dist/astro-otp-input/OtpInput.astro +1 -1
- package/dist/astro-payment/Payment.astro +1 -1
- package/dist/astro-popover/PopoverContent.astro +1 -1
- package/dist/astro-presence-indicator/PresenceIndicator.astro +1 -1
- package/dist/astro-progress-display/BadgeDisplay.astro +1 -1
- package/dist/astro-progress-display/ProgressBar.astro +1 -1
- package/dist/astro-progress-display/StatsGrid.astro +1 -1
- package/dist/astro-radio/RadioGroup.astro +1 -1
- package/dist/astro-radio/RadioItem.astro +1 -1
- package/dist/astro-reaction-bar/ReactionBar.astro +1 -1
- package/dist/astro-resizable-layout/ResizableDivider.astro +1 -1
- package/dist/astro-resizable-layout/ResizableLayout.astro +1 -1
- package/dist/astro-resizable-layout/ResizablePane.astro +1 -1
- package/dist/astro-resizable-layout/index.ts +1 -1
- package/dist/astro-rich-editor/index.ts +2 -2
- package/dist/astro-search-bar/SearchBar.astro +1 -1
- package/dist/astro-search-bar/SearchResults.astro +1 -1
- package/dist/astro-select/Select.astro +1 -1
- package/dist/astro-select/SelectContent.astro +1 -1
- package/dist/astro-select/SelectItem.astro +1 -1
- package/dist/astro-select/SelectTrigger.astro +1 -1
- package/dist/astro-sidebar/Sidebar.astro +1 -1
- package/dist/astro-skeleton/Skeleton.astro +1 -1
- package/dist/astro-slide-viewer/SlideViewer.astro +1 -1
- package/dist/astro-status-indicator/StatusIndicator.astro +1 -1
- package/dist/astro-switch/Switch.astro +1 -1
- package/dist/astro-table-of-contents/TableOfContents.astro +1 -1
- package/dist/astro-tabs/Tabs.astro +1 -1
- package/dist/astro-tabs/TabsList.astro +1 -1
- package/dist/astro-tabs/TabsTrigger.astro +1 -1
- package/dist/astro-textarea/Textarea.astro +1 -1
- package/dist/astro-theme/ThemeScript.astro +1 -1
- package/dist/astro-thread-view/ThreadView.astro +1 -1
- package/dist/astro-toast/Toast.astro +1 -1
- package/dist/astro-tooltip/TooltipContent.astro +1 -1
- package/dist/astro-version-selector/VersionSelector.astro +1 -1
- package/dist/astro-video-player/VideoPlayer.astro +1 -1
- package/dist/auth/adapters/mock.ts +74 -0
- package/dist/auth/auth-machine.ts +120 -0
- package/dist/auth/index.ts +22 -0
- package/dist/auth/rbac.ts +44 -0
- package/dist/auth/types.ts +44 -0
- package/dist/avatar/avatar.styles.ts +47 -0
- package/dist/avatar/avatar.ts +73 -0
- package/dist/avatar/index.ts +15 -0
- package/dist/avatar-group/avatar-group.styles.ts +63 -0
- package/dist/avatar-group/avatar-group.ts +106 -0
- package/dist/avatar-group/index.ts +18 -0
- package/dist/badge/badge.styles.ts +24 -0
- package/dist/badge/badge.ts +48 -0
- package/dist/badge/index.ts +10 -0
- package/dist/bottom-nav/bottom-nav.styles.ts +18 -0
- package/dist/bottom-nav/bottom-nav.ts +33 -0
- package/dist/bottom-nav/index.ts +2 -0
- package/dist/breadcrumbs/breadcrumbs.styles.ts +20 -0
- package/dist/breadcrumbs/breadcrumbs.ts +72 -0
- package/dist/breadcrumbs/index.ts +8 -0
- package/dist/button/button.styles.ts +38 -0
- package/dist/button/button.ts +64 -0
- package/dist/button/index.ts +10 -0
- package/dist/calendar/calendar.styles.ts +21 -0
- package/dist/calendar/calendar.ts +202 -0
- package/dist/calendar/index.ts +9 -0
- package/dist/callout/callout.ts +1 -0
- package/dist/callout/index.ts +1 -0
- package/dist/card/card.styles.ts +55 -0
- package/dist/card/card.ts +45 -0
- package/dist/card/index.ts +20 -0
- package/dist/card-grid/card-grid.ts +9 -0
- package/dist/card-grid/index.ts +1 -0
- package/dist/carousel/index.ts +1 -0
- package/dist/charts/axis.ts +50 -0
- package/dist/charts/chart.ts +73 -0
- package/dist/charts/colorblind.ts +100 -0
- package/dist/charts/data.ts +72 -0
- package/dist/charts/geometry.ts +54 -0
- package/dist/charts/index.ts +61 -0
- package/dist/charts/scales.ts +151 -0
- package/dist/checkbox/checkbox.styles.ts +33 -0
- package/dist/checkbox/checkbox.ts +73 -0
- package/dist/checkbox/index.ts +10 -0
- package/dist/code-block/code-block.ts +1 -0
- package/dist/code-block/index.ts +1 -0
- package/dist/code-editor/code-editor.styles.ts +32 -0
- package/dist/code-editor/code-editor.ts +140 -0
- package/dist/code-editor/index.ts +9 -0
- package/dist/collapsible/collapsible.styles.ts +14 -0
- package/dist/collapsible/collapsible.ts +93 -0
- package/dist/collapsible/index.ts +7 -0
- package/dist/command/command.styles.ts +27 -0
- package/dist/command/command.ts +179 -0
- package/dist/command/index.ts +14 -0
- package/dist/command-input/command-input.ts +176 -0
- package/dist/command-input/index.ts +1 -0
- package/dist/content-protection/content-protection.styles.ts +9 -0
- package/dist/content-protection/content-protection.ts +68 -0
- package/dist/content-protection/index.ts +8 -0
- package/dist/data-table/data-table.styles.ts +55 -0
- package/dist/data-table/data-table.ts +183 -0
- package/dist/data-table/index.ts +15 -0
- package/dist/date-picker/date-picker.styles.ts +31 -0
- package/dist/date-picker/date-picker.ts +333 -0
- package/dist/date-picker/index.ts +18 -0
- package/dist/device-frame/device-frame.styles.ts +21 -0
- package/dist/device-frame/device-frame.ts +112 -0
- package/dist/device-frame/index.ts +10 -0
- package/dist/dialog/dialog.styles.ts +8 -0
- package/dist/dialog/dialog.ts +133 -0
- package/dist/dialog/index.ts +8 -0
- package/dist/diff-viewer/diff-viewer.styles.ts +124 -0
- package/dist/diff-viewer/diff-viewer.ts +255 -0
- package/dist/diff-viewer/index.ts +20 -0
- package/dist/dropdown-menu/dropdown-menu.styles.ts +9 -0
- package/dist/dropdown-menu/dropdown-menu.ts +139 -0
- package/dist/dropdown-menu/index.ts +9 -0
- package/dist/emoji-picker/emoji-data.ts +282 -0
- package/dist/emoji-picker/emoji-picker.styles.ts +32 -0
- package/dist/emoji-picker/emoji-picker.ts +165 -0
- package/dist/emoji-picker/index.ts +25 -0
- package/dist/feedback-dialog/feedback-dialog.styles.ts +15 -0
- package/dist/feedback-dialog/feedback-dialog.ts +147 -0
- package/dist/feedback-dialog/index.ts +11 -0
- package/dist/file-upload/file-upload.styles.ts +30 -0
- package/dist/file-upload/file-upload.ts +260 -0
- package/dist/file-upload/index.ts +19 -0
- package/dist/footer/footer.styles.ts +5 -0
- package/dist/footer/footer.ts +32 -0
- package/dist/footer/index.ts +9 -0
- package/dist/index.ts +1 -1
- package/dist/inline-editor/index.ts +13 -0
- package/dist/inline-editor/inline-editor.styles.ts +42 -0
- package/dist/inline-editor/inline-editor.ts +96 -0
- package/dist/input/index.ts +9 -0
- package/dist/input/input.styles.ts +15 -0
- package/dist/input/input.ts +84 -0
- package/dist/input-group/index.ts +13 -0
- package/dist/input-group/input-group.styles.ts +50 -0
- package/dist/input-group/input-group.ts +44 -0
- package/dist/install-prompt/index.ts +9 -0
- package/dist/install-prompt/install-prompt.styles.ts +5 -0
- package/dist/install-prompt/install-prompt.ts +68 -0
- package/dist/keyboard-shortcut/index.ts +12 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.styles.ts +8 -0
- package/dist/keyboard-shortcut/keyboard-shortcut.ts +153 -0
- package/dist/language-selector/index.ts +8 -0
- package/dist/language-selector/language-selector.styles.ts +28 -0
- package/dist/language-selector/language-selector.ts +149 -0
- package/dist/link-card/index.ts +1 -0
- package/dist/link-card/link-card.ts +9 -0
- package/dist/markdown-renderer/index.ts +8 -0
- package/dist/markdown-renderer/markdown-renderer.styles.ts +31 -0
- package/dist/markdown-renderer/markdown-renderer.ts +251 -0
- package/dist/mobile-nav/index.ts +14 -0
- package/dist/mobile-nav/mobile-nav.styles.ts +39 -0
- package/dist/mobile-nav/mobile-nav.ts +74 -0
- package/dist/navbar/index.ts +9 -0
- package/dist/navbar/navbar.styles.ts +29 -0
- package/dist/navbar/navbar.ts +39 -0
- package/dist/otp-input/index.ts +13 -0
- package/dist/otp-input/otp-input.styles.ts +51 -0
- package/dist/otp-input/otp-input.ts +198 -0
- package/dist/pagination/index.ts +1 -0
- package/dist/payment/index.ts +1 -0
- package/dist/payment/payment.ts +16 -0
- package/dist/popover/index.ts +7 -0
- package/dist/popover/popover.styles.ts +16 -0
- package/dist/popover/popover.ts +89 -0
- package/dist/presence-indicator/index.ts +14 -0
- package/dist/presence-indicator/presence-indicator.styles.ts +29 -0
- package/dist/presence-indicator/presence-indicator.ts +63 -0
- package/dist/progress-display/index.ts +15 -0
- package/dist/progress-display/progress-display.styles.ts +72 -0
- package/dist/progress-display/progress-display.ts +60 -0
- package/dist/radio/index.ts +8 -0
- package/dist/radio/radio.styles.ts +34 -0
- package/dist/radio/radio.ts +70 -0
- package/dist/reaction-bar/index.ts +14 -0
- package/dist/reaction-bar/reaction-bar.styles.ts +23 -0
- package/dist/reaction-bar/reaction-bar.ts +87 -0
- package/dist/resizable-layout/index.ts +12 -0
- package/dist/resizable-layout/resizable-layout.styles.ts +50 -0
- package/dist/resizable-layout/resizable-layout.ts +139 -0
- package/dist/rich-editor/editor.ts +342 -0
- package/dist/rich-editor/emoji.ts +175 -0
- package/dist/rich-editor/history.ts +79 -0
- package/dist/rich-editor/index.ts +106 -0
- package/dist/rich-editor/markdown-shortcuts.ts +248 -0
- package/dist/rich-editor/mentions.ts +162 -0
- package/dist/rich-editor/model.ts +206 -0
- package/dist/rich-editor/operations.ts +772 -0
- package/dist/rich-editor/selection.ts +157 -0
- package/dist/rich-editor/serialization.ts +711 -0
- package/dist/rich-editor/slash-commands.ts +212 -0
- package/dist/search-bar/index.ts +7 -0
- package/dist/search-bar/search-bar.styles.ts +29 -0
- package/dist/search-bar/search-bar.ts +130 -0
- package/dist/select/index.ts +13 -0
- package/dist/select/select.styles.ts +52 -0
- package/dist/select/select.ts +152 -0
- package/dist/sidebar/index.ts +9 -0
- package/dist/sidebar/sidebar.styles.ts +27 -0
- package/dist/sidebar/sidebar.ts +59 -0
- package/dist/skeleton/index.ts +8 -0
- package/dist/skeleton/skeleton.styles.ts +16 -0
- package/dist/skeleton/skeleton.ts +39 -0
- package/dist/skip-to-content/index.ts +1 -0
- package/dist/skip-to-content/skip-to-content.ts +1 -0
- package/dist/slide-viewer/index.ts +16 -0
- package/dist/slide-viewer/slide-viewer.styles.ts +57 -0
- package/dist/slide-viewer/slide-viewer.ts +152 -0
- package/dist/slider/index.ts +1 -0
- package/dist/slider/slider.ts +2 -0
- package/dist/status-indicator/index.ts +15 -0
- package/dist/status-indicator/status-indicator.styles.ts +41 -0
- package/dist/status-indicator/status-indicator.ts +66 -0
- package/dist/steps/index.ts +1 -0
- package/dist/steps/steps.ts +1 -0
- package/dist/switch/index.ts +7 -0
- package/dist/switch/switch.styles.ts +55 -0
- package/dist/switch/switch.ts +63 -0
- package/dist/table-of-contents/index.ts +1 -0
- package/dist/table-of-contents/table-of-contents.ts +37 -0
- package/dist/tabs/index.ts +8 -0
- package/dist/tabs/tabs.styles.ts +9 -0
- package/dist/tabs/tabs.ts +133 -0
- package/dist/textarea/index.ts +7 -0
- package/dist/textarea/textarea.styles.ts +15 -0
- package/dist/textarea/textarea.ts +59 -0
- package/dist/theme/dom-adapters.ts +60 -0
- package/dist/theme/index.ts +18 -0
- package/dist/theme/theme-machine.ts +130 -0
- package/dist/theme/theme-script.ts +18 -0
- package/dist/thread-view/index.ts +26 -0
- package/dist/thread-view/thread-view.styles.ts +35 -0
- package/dist/thread-view/thread-view.ts +176 -0
- package/dist/toast/index.ts +13 -0
- package/dist/toast/toast.styles.ts +19 -0
- package/dist/toast/toast.ts +203 -0
- package/dist/tooltip/index.ts +7 -0
- package/dist/tooltip/tooltip.styles.ts +16 -0
- package/dist/tooltip/tooltip.ts +109 -0
- package/dist/version-selector/index.ts +12 -0
- package/dist/version-selector/version-selector.styles.ts +41 -0
- package/dist/version-selector/version-selector.ts +136 -0
- package/dist/video-player/index.ts +12 -0
- package/dist/video-player/video-player.styles.ts +42 -0
- package/dist/video-player/video-player.ts +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export interface ColumnDef<T = Record<string, unknown>> {
|
|
4
|
+
/** Unique column identifier */
|
|
5
|
+
id: string
|
|
6
|
+
/** Column header text */
|
|
7
|
+
header: string
|
|
8
|
+
/** Accessor function to get cell value from row */
|
|
9
|
+
accessor: (row: T) => unknown
|
|
10
|
+
/** Whether this column is sortable */
|
|
11
|
+
sortable?: boolean
|
|
12
|
+
/** Whether this column is filterable */
|
|
13
|
+
filterable?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SortDirection = 'asc' | 'desc'
|
|
17
|
+
|
|
18
|
+
export interface DataTableProps<T = Record<string, unknown>> {
|
|
19
|
+
/** Column definitions */
|
|
20
|
+
columns: ColumnDef<T>[]
|
|
21
|
+
/** Data rows */
|
|
22
|
+
data: T[]
|
|
23
|
+
/** Current sort column */
|
|
24
|
+
sortBy?: string
|
|
25
|
+
/** Current sort direction */
|
|
26
|
+
sortDir?: SortDirection
|
|
27
|
+
/** Callback when sort changes */
|
|
28
|
+
onSort?: (columnId: string, direction: SortDirection) => void
|
|
29
|
+
/** Active filters: columnId -> filter value */
|
|
30
|
+
filters?: Record<string, string>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DataTableState<T = Record<string, unknown>> {
|
|
34
|
+
sortedData: T[]
|
|
35
|
+
sortBy: string | null
|
|
36
|
+
sortDir: SortDirection
|
|
37
|
+
filters: Record<string, string>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DataTableAPI<T = Record<string, unknown>> {
|
|
41
|
+
/** Current table state */
|
|
42
|
+
state: DataTableState<T>
|
|
43
|
+
/** Toggle sort for a column */
|
|
44
|
+
sort(columnId: string): void
|
|
45
|
+
/** Set a filter value for a column */
|
|
46
|
+
setFilter(columnId: string, value: string): void
|
|
47
|
+
/** Get ARIA and role props for a header cell */
|
|
48
|
+
getHeaderProps(col: ColumnDef<T>): Partial<AccessibilityProps> & Record<string, unknown>
|
|
49
|
+
/** Get props for a data cell */
|
|
50
|
+
getCellProps(col: ColumnDef<T>, row: T): Record<string, unknown>
|
|
51
|
+
/** Get props for a data row */
|
|
52
|
+
getRowProps(row: T, index: number): Record<string, unknown>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createDataTable<T = Record<string, unknown>>(
|
|
56
|
+
props: DataTableProps<T>,
|
|
57
|
+
): DataTableAPI<T> {
|
|
58
|
+
const {
|
|
59
|
+
columns,
|
|
60
|
+
data,
|
|
61
|
+
sortBy: initialSortBy,
|
|
62
|
+
sortDir: initialSortDir = 'asc',
|
|
63
|
+
onSort,
|
|
64
|
+
filters: initialFilters,
|
|
65
|
+
} = props
|
|
66
|
+
|
|
67
|
+
let sortBy: string | null = initialSortBy ?? null
|
|
68
|
+
let sortDir: SortDirection = initialSortDir
|
|
69
|
+
let filters: Record<string, string> = { ...(initialFilters ?? {}) }
|
|
70
|
+
|
|
71
|
+
function getFilteredData(): T[] {
|
|
72
|
+
let result = [...data]
|
|
73
|
+
|
|
74
|
+
for (const [columnId, filterValue] of Object.entries(filters)) {
|
|
75
|
+
if (!filterValue) continue
|
|
76
|
+
const col = columns.find((c) => c.id === columnId)
|
|
77
|
+
if (!col) continue
|
|
78
|
+
const lowerFilter = filterValue.toLowerCase()
|
|
79
|
+
result = result.filter((row) => {
|
|
80
|
+
const cellValue = col.accessor(row)
|
|
81
|
+
return String(cellValue ?? '').toLowerCase().includes(lowerFilter)
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSortedData(): T[] {
|
|
89
|
+
const filtered = getFilteredData()
|
|
90
|
+
|
|
91
|
+
if (!sortBy) return filtered
|
|
92
|
+
|
|
93
|
+
const col = columns.find((c) => c.id === sortBy)
|
|
94
|
+
if (!col) return filtered
|
|
95
|
+
|
|
96
|
+
return [...filtered].sort((a, b) => {
|
|
97
|
+
const aVal = col.accessor(a)
|
|
98
|
+
const bVal = col.accessor(b)
|
|
99
|
+
const aStr = String(aVal ?? '')
|
|
100
|
+
const bStr = String(bVal ?? '')
|
|
101
|
+
|
|
102
|
+
const cmp = aStr.localeCompare(bStr, undefined, { numeric: true })
|
|
103
|
+
return sortDir === 'asc' ? cmp : -cmp
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sort(columnId: string): void {
|
|
108
|
+
const col = columns.find((c) => c.id === columnId)
|
|
109
|
+
if (!col?.sortable) return
|
|
110
|
+
|
|
111
|
+
if (sortBy === columnId) {
|
|
112
|
+
sortDir = sortDir === 'asc' ? 'desc' : 'asc'
|
|
113
|
+
} else {
|
|
114
|
+
sortBy = columnId
|
|
115
|
+
sortDir = 'asc'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onSort?.(sortBy, sortDir)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function setFilter(columnId: string, value: string): void {
|
|
122
|
+
filters = { ...filters, [columnId]: value }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getHeaderProps(
|
|
126
|
+
col: ColumnDef<T>,
|
|
127
|
+
): Partial<AccessibilityProps> & Record<string, unknown> {
|
|
128
|
+
const props: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
129
|
+
role: 'columnheader',
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (col.sortable) {
|
|
133
|
+
if (sortBy === col.id) {
|
|
134
|
+
props['aria-sort'] = sortDir === 'asc' ? 'ascending' : 'descending'
|
|
135
|
+
} else {
|
|
136
|
+
props['aria-sort'] = 'none'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return props
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getCellProps(
|
|
144
|
+
col: ColumnDef<T>,
|
|
145
|
+
_row: T,
|
|
146
|
+
): Record<string, unknown> {
|
|
147
|
+
return {
|
|
148
|
+
role: 'cell',
|
|
149
|
+
'data-column': col.id,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getRowProps(_row: T, index: number): Record<string, unknown> {
|
|
154
|
+
return {
|
|
155
|
+
role: 'row',
|
|
156
|
+
'data-row-index': index,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
get state(): DataTableState<T> {
|
|
162
|
+
return {
|
|
163
|
+
get sortedData() {
|
|
164
|
+
return getSortedData()
|
|
165
|
+
},
|
|
166
|
+
get sortBy() {
|
|
167
|
+
return sortBy
|
|
168
|
+
},
|
|
169
|
+
get sortDir() {
|
|
170
|
+
return sortDir
|
|
171
|
+
},
|
|
172
|
+
get filters() {
|
|
173
|
+
return { ...filters }
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
sort,
|
|
178
|
+
setFilter,
|
|
179
|
+
getHeaderProps,
|
|
180
|
+
getCellProps,
|
|
181
|
+
getRowProps,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createDataTable,
|
|
3
|
+
type ColumnDef,
|
|
4
|
+
type SortDirection,
|
|
5
|
+
type DataTableProps,
|
|
6
|
+
type DataTableState,
|
|
7
|
+
type DataTableAPI,
|
|
8
|
+
} from './data-table.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
tableVariants,
|
|
12
|
+
headerVariants,
|
|
13
|
+
cellVariants,
|
|
14
|
+
rowVariants,
|
|
15
|
+
} from './data-table.styles.js'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export const datePickerTriggerStyles =
|
|
4
|
+
'inline-flex items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer'
|
|
5
|
+
|
|
6
|
+
export const datePickerDropdownStyles =
|
|
7
|
+
'absolute z-50 mt-1 rounded-md border bg-popover p-4 text-popover-foreground shadow-md'
|
|
8
|
+
|
|
9
|
+
export const datePickerGridStyles = 'w-full border-collapse'
|
|
10
|
+
|
|
11
|
+
export const datePickerDayVariants = cva({
|
|
12
|
+
base: 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground',
|
|
13
|
+
variants: {
|
|
14
|
+
state: {
|
|
15
|
+
default: '',
|
|
16
|
+
selected: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
17
|
+
today: 'border border-accent-foreground',
|
|
18
|
+
disabled: 'text-muted-foreground opacity-50 pointer-events-none',
|
|
19
|
+
outside: 'text-muted-foreground opacity-30',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
state: 'default',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const datePickerTimeStyles =
|
|
28
|
+
'flex items-center gap-2 mt-3 pt-3 border-t'
|
|
29
|
+
|
|
30
|
+
export const datePickerTimeInputStyles =
|
|
31
|
+
'w-14 rounded-md border border-input bg-background px-2 py-1 text-sm text-center'
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import type { AccessibilityProps, KeyboardHandlerMap } from '../shared/index.ts'
|
|
2
|
+
import { generateId, Keys } from '../shared/index.ts'
|
|
3
|
+
|
|
4
|
+
export type DatePickerView = 'calendar' | 'time'
|
|
5
|
+
|
|
6
|
+
export interface DatePickerProps {
|
|
7
|
+
/** Selected date (controlled) */
|
|
8
|
+
value?: Date
|
|
9
|
+
/** Callback when value changes */
|
|
10
|
+
onChange?: (date: Date) => void
|
|
11
|
+
/** Minimum selectable date */
|
|
12
|
+
minDate?: Date
|
|
13
|
+
/** Maximum selectable date */
|
|
14
|
+
maxDate?: Date
|
|
15
|
+
/** Show time selection */
|
|
16
|
+
showTime?: boolean
|
|
17
|
+
/** Display format string */
|
|
18
|
+
format?: string
|
|
19
|
+
/** Placeholder text */
|
|
20
|
+
placeholder?: string
|
|
21
|
+
/** Whether the picker is open (controlled) */
|
|
22
|
+
open?: boolean
|
|
23
|
+
/** Default open state */
|
|
24
|
+
defaultOpen?: boolean
|
|
25
|
+
/** Callback when open changes */
|
|
26
|
+
onOpenChange?: (open: boolean) => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DatePickerState {
|
|
30
|
+
open: boolean
|
|
31
|
+
selectedDate: Date | undefined
|
|
32
|
+
view: DatePickerView
|
|
33
|
+
hours: number
|
|
34
|
+
minutes: number
|
|
35
|
+
currentMonth: Date
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CalendarDay {
|
|
39
|
+
date: Date
|
|
40
|
+
isToday: boolean
|
|
41
|
+
isSelected: boolean
|
|
42
|
+
isCurrentMonth: boolean
|
|
43
|
+
isDisabled: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DatePickerAPI {
|
|
47
|
+
/** Current state */
|
|
48
|
+
state: DatePickerState
|
|
49
|
+
/** Calendar days grid (42 cells) */
|
|
50
|
+
days: CalendarDay[]
|
|
51
|
+
/** Open the picker */
|
|
52
|
+
openPicker(): void
|
|
53
|
+
/** Close the picker */
|
|
54
|
+
closePicker(): void
|
|
55
|
+
/** Toggle the picker */
|
|
56
|
+
togglePicker(): void
|
|
57
|
+
/** Select a date from calendar */
|
|
58
|
+
selectDate(date: Date): void
|
|
59
|
+
/** Set hours */
|
|
60
|
+
setHours(hours: number): void
|
|
61
|
+
/** Set minutes */
|
|
62
|
+
setMinutes(minutes: number): void
|
|
63
|
+
/** Navigate to previous month */
|
|
64
|
+
prevMonth(): void
|
|
65
|
+
/** Navigate to next month */
|
|
66
|
+
nextMonth(): void
|
|
67
|
+
/** Switch between calendar and time views */
|
|
68
|
+
setView(view: DatePickerView): void
|
|
69
|
+
/** Format the selected date for display */
|
|
70
|
+
formatDisplay(): string
|
|
71
|
+
/** ARIA attributes for the trigger input */
|
|
72
|
+
triggerProps: Partial<AccessibilityProps> & Record<string, unknown>
|
|
73
|
+
/** ARIA attributes for the dropdown */
|
|
74
|
+
dropdownProps: Partial<AccessibilityProps> & Record<string, unknown>
|
|
75
|
+
/** ARIA props for the calendar grid */
|
|
76
|
+
gridProps: Partial<AccessibilityProps> & Record<string, unknown>
|
|
77
|
+
/** Get ARIA props for a day cell */
|
|
78
|
+
getDayAriaProps(day: CalendarDay): Partial<AccessibilityProps> & Record<string, unknown>
|
|
79
|
+
/** Keyboard handlers */
|
|
80
|
+
keyboardHandlers: KeyboardHandlerMap
|
|
81
|
+
/** Generated IDs */
|
|
82
|
+
ids: {
|
|
83
|
+
trigger: string
|
|
84
|
+
dropdown: string
|
|
85
|
+
grid: string
|
|
86
|
+
label: string
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isSameDay(a: Date, b: Date): boolean {
|
|
91
|
+
return (
|
|
92
|
+
a.getFullYear() === b.getFullYear() &&
|
|
93
|
+
a.getMonth() === b.getMonth() &&
|
|
94
|
+
a.getDate() === b.getDate()
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function startOfMonth(date: Date): Date {
|
|
99
|
+
return new Date(date.getFullYear(), date.getMonth(), 1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function startOfDay(date: Date): Date {
|
|
103
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function endOfDay(date: Date): Date {
|
|
107
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
111
|
+
|
|
112
|
+
function padZero(n: number): string {
|
|
113
|
+
return n < 10 ? `0${n}` : `${n}`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatDate(date: Date | undefined, format: string, showTime: boolean): string {
|
|
117
|
+
if (!date) return ''
|
|
118
|
+
const year = date.getFullYear()
|
|
119
|
+
const month = padZero(date.getMonth() + 1)
|
|
120
|
+
const day = padZero(date.getDate())
|
|
121
|
+
const hours = padZero(date.getHours())
|
|
122
|
+
const minutes = padZero(date.getMinutes())
|
|
123
|
+
|
|
124
|
+
let result = format
|
|
125
|
+
.replace('YYYY', String(year))
|
|
126
|
+
.replace('MM', month)
|
|
127
|
+
.replace('DD', day)
|
|
128
|
+
|
|
129
|
+
if (showTime) {
|
|
130
|
+
result = result.replace('HH', hours).replace('mm', minutes)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createDatePicker(props: DatePickerProps = {}): DatePickerAPI {
|
|
137
|
+
const {
|
|
138
|
+
value,
|
|
139
|
+
onChange,
|
|
140
|
+
minDate,
|
|
141
|
+
maxDate,
|
|
142
|
+
showTime = false,
|
|
143
|
+
format = showTime ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD',
|
|
144
|
+
placeholder = 'Select date...',
|
|
145
|
+
open: controlledOpen,
|
|
146
|
+
defaultOpen = false,
|
|
147
|
+
onOpenChange,
|
|
148
|
+
} = props
|
|
149
|
+
|
|
150
|
+
const today = new Date()
|
|
151
|
+
let internalOpen = controlledOpen ?? defaultOpen
|
|
152
|
+
let currentMonth = value ? startOfMonth(value) : startOfMonth(today)
|
|
153
|
+
let view: DatePickerView = 'calendar'
|
|
154
|
+
const hours = value ? value.getHours() : 0
|
|
155
|
+
const minutes = value ? value.getMinutes() : 0
|
|
156
|
+
|
|
157
|
+
const triggerId = generateId('rfr-datepicker-trigger')
|
|
158
|
+
const dropdownId = generateId('rfr-datepicker-dropdown')
|
|
159
|
+
const gridId = generateId('rfr-datepicker-grid')
|
|
160
|
+
const labelId = generateId('rfr-datepicker-label')
|
|
161
|
+
|
|
162
|
+
function isOpen(): boolean {
|
|
163
|
+
if (controlledOpen !== undefined) return controlledOpen
|
|
164
|
+
return internalOpen
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isDateDisabled(date: Date): boolean {
|
|
168
|
+
if (minDate && date < startOfDay(minDate)) return true
|
|
169
|
+
if (maxDate && date > endOfDay(maxDate)) return true
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildDays(): CalendarDay[] {
|
|
174
|
+
const year = currentMonth.getFullYear()
|
|
175
|
+
const month = currentMonth.getMonth()
|
|
176
|
+
const first = new Date(year, month, 1)
|
|
177
|
+
const startDow = first.getDay()
|
|
178
|
+
const gridStart = new Date(year, month, 1 - startDow)
|
|
179
|
+
|
|
180
|
+
const days: CalendarDay[] = []
|
|
181
|
+
for (let i = 0; i < 42; i++) {
|
|
182
|
+
const date = new Date(
|
|
183
|
+
gridStart.getFullYear(),
|
|
184
|
+
gridStart.getMonth(),
|
|
185
|
+
gridStart.getDate() + i,
|
|
186
|
+
)
|
|
187
|
+
days.push({
|
|
188
|
+
date,
|
|
189
|
+
isToday: isSameDay(date, today),
|
|
190
|
+
isSelected: value ? isSameDay(date, value) : false,
|
|
191
|
+
isCurrentMonth: date.getMonth() === month,
|
|
192
|
+
isDisabled: isDateDisabled(date),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
return days
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function openPicker(): void {
|
|
199
|
+
internalOpen = true
|
|
200
|
+
onOpenChange?.(true)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function closePicker(): void {
|
|
204
|
+
internalOpen = false
|
|
205
|
+
onOpenChange?.(false)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function togglePicker(): void {
|
|
209
|
+
if (isOpen()) {
|
|
210
|
+
closePicker()
|
|
211
|
+
} else {
|
|
212
|
+
openPicker()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function selectDate(date: Date): void {
|
|
217
|
+
if (isDateDisabled(date)) return
|
|
218
|
+
const newDate = new Date(date)
|
|
219
|
+
if (value) {
|
|
220
|
+
newDate.setHours(value.getHours())
|
|
221
|
+
newDate.setMinutes(value.getMinutes())
|
|
222
|
+
}
|
|
223
|
+
onChange?.(newDate)
|
|
224
|
+
if (!showTime) {
|
|
225
|
+
closePicker()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function setHours(h: number): void {
|
|
230
|
+
if (h < 0 || h > 23) return
|
|
231
|
+
const newDate = value ? new Date(value) : new Date()
|
|
232
|
+
newDate.setHours(h)
|
|
233
|
+
onChange?.(newDate)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function setMinutes(m: number): void {
|
|
237
|
+
if (m < 0 || m > 59) return
|
|
238
|
+
const newDate = value ? new Date(value) : new Date()
|
|
239
|
+
newDate.setMinutes(m)
|
|
240
|
+
onChange?.(newDate)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function prevMonth(): void {
|
|
244
|
+
currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function nextMonth(): void {
|
|
248
|
+
currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function setView(v: DatePickerView): void {
|
|
252
|
+
view = v
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function formatDisplay(): string {
|
|
256
|
+
return value ? formatDate(value, format, showTime) : placeholder
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const keyboardHandlers: KeyboardHandlerMap = {
|
|
260
|
+
[Keys.Escape]: (e) => {
|
|
261
|
+
e.preventDefault()
|
|
262
|
+
closePicker()
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const triggerProps: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
267
|
+
'aria-expanded': isOpen(),
|
|
268
|
+
'aria-controls': dropdownId,
|
|
269
|
+
'aria-haspopup': 'dialog',
|
|
270
|
+
id: triggerId,
|
|
271
|
+
role: 'combobox',
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const dropdownProps: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
275
|
+
role: 'dialog',
|
|
276
|
+
'aria-modal': true,
|
|
277
|
+
'aria-labelledby': labelId,
|
|
278
|
+
id: dropdownId,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const gridProps: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
282
|
+
role: 'grid',
|
|
283
|
+
'aria-labelledby': labelId,
|
|
284
|
+
id: gridId,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getDayAriaProps(day: CalendarDay): Partial<AccessibilityProps> & Record<string, unknown> {
|
|
288
|
+
return {
|
|
289
|
+
role: 'gridcell',
|
|
290
|
+
'aria-selected': day.isSelected,
|
|
291
|
+
'aria-disabled': day.isDisabled,
|
|
292
|
+
'aria-current': day.isToday ? ('date' as const) : undefined,
|
|
293
|
+
'aria-label': `${DAY_NAMES[day.date.getDay()]}, ${day.date.toLocaleDateString('en-US', {
|
|
294
|
+
month: 'long',
|
|
295
|
+
day: 'numeric',
|
|
296
|
+
year: 'numeric',
|
|
297
|
+
})}`,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
state: {
|
|
303
|
+
open: isOpen(),
|
|
304
|
+
selectedDate: value,
|
|
305
|
+
view,
|
|
306
|
+
hours,
|
|
307
|
+
minutes,
|
|
308
|
+
currentMonth,
|
|
309
|
+
},
|
|
310
|
+
days: buildDays(),
|
|
311
|
+
openPicker,
|
|
312
|
+
closePicker,
|
|
313
|
+
togglePicker,
|
|
314
|
+
selectDate,
|
|
315
|
+
setHours,
|
|
316
|
+
setMinutes,
|
|
317
|
+
prevMonth,
|
|
318
|
+
nextMonth,
|
|
319
|
+
setView,
|
|
320
|
+
formatDisplay,
|
|
321
|
+
triggerProps,
|
|
322
|
+
dropdownProps,
|
|
323
|
+
gridProps,
|
|
324
|
+
getDayAriaProps,
|
|
325
|
+
keyboardHandlers,
|
|
326
|
+
ids: {
|
|
327
|
+
trigger: triggerId,
|
|
328
|
+
dropdown: dropdownId,
|
|
329
|
+
grid: gridId,
|
|
330
|
+
label: labelId,
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createDatePicker,
|
|
3
|
+
formatDate,
|
|
4
|
+
type DatePickerProps,
|
|
5
|
+
type DatePickerState,
|
|
6
|
+
type DatePickerView,
|
|
7
|
+
type DatePickerAPI,
|
|
8
|
+
type CalendarDay,
|
|
9
|
+
} from './date-picker.js'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
datePickerTriggerStyles,
|
|
13
|
+
datePickerDropdownStyles,
|
|
14
|
+
datePickerGridStyles,
|
|
15
|
+
datePickerDayVariants,
|
|
16
|
+
datePickerTimeStyles,
|
|
17
|
+
datePickerTimeInputStyles,
|
|
18
|
+
} from './date-picker.styles.js'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export const deviceFrameVariants = cva({
|
|
4
|
+
base: 'relative overflow-hidden bg-black border-4 border-gray-800 shadow-xl',
|
|
5
|
+
variants: {
|
|
6
|
+
device: {
|
|
7
|
+
iphone: 'rounded-[44px] border-[6px]',
|
|
8
|
+
ipad: 'rounded-[18px] border-[6px]',
|
|
9
|
+
'android-phone': 'rounded-[24px] border-4',
|
|
10
|
+
'android-tablet': 'rounded-[16px] border-4',
|
|
11
|
+
},
|
|
12
|
+
orientation: {
|
|
13
|
+
portrait: '',
|
|
14
|
+
landscape: '',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
device: 'iphone',
|
|
19
|
+
orientation: 'portrait',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export type DeviceType = 'iphone' | 'ipad' | 'android-phone' | 'android-tablet'
|
|
4
|
+
export type DeviceOrientation = 'portrait' | 'landscape'
|
|
5
|
+
|
|
6
|
+
export interface DeviceFrameProps {
|
|
7
|
+
/** The type of device to display */
|
|
8
|
+
device: DeviceType
|
|
9
|
+
/** The orientation of the device */
|
|
10
|
+
orientation?: DeviceOrientation
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DeviceDimensions {
|
|
14
|
+
/** Width in pixels */
|
|
15
|
+
width: number
|
|
16
|
+
/** Height in pixels */
|
|
17
|
+
height: number
|
|
18
|
+
/** Border radius in pixels */
|
|
19
|
+
radius: number
|
|
20
|
+
/** Whether the device has a notch */
|
|
21
|
+
notch: boolean
|
|
22
|
+
/** Whether the device has a home indicator bar */
|
|
23
|
+
homeIndicator: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DeviceFrameAPI {
|
|
27
|
+
/** Computed dimensions for the device */
|
|
28
|
+
dimensions: DeviceDimensions
|
|
29
|
+
/** ARIA attributes for the device frame */
|
|
30
|
+
ariaProps: Partial<AccessibilityProps> & Record<string, unknown>
|
|
31
|
+
/** Data attributes for styling hooks */
|
|
32
|
+
dataAttributes: Record<string, string>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface DeviceSpec {
|
|
36
|
+
width: number
|
|
37
|
+
height: number
|
|
38
|
+
radius: number
|
|
39
|
+
notch: boolean
|
|
40
|
+
homeIndicator: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEVICE_SPECS: Record<DeviceType, DeviceSpec> = {
|
|
44
|
+
iphone: {
|
|
45
|
+
width: 375,
|
|
46
|
+
height: 812,
|
|
47
|
+
radius: 44,
|
|
48
|
+
notch: true,
|
|
49
|
+
homeIndicator: true,
|
|
50
|
+
},
|
|
51
|
+
ipad: {
|
|
52
|
+
width: 810,
|
|
53
|
+
height: 1080,
|
|
54
|
+
radius: 18,
|
|
55
|
+
notch: false,
|
|
56
|
+
homeIndicator: true,
|
|
57
|
+
},
|
|
58
|
+
'android-phone': {
|
|
59
|
+
width: 360,
|
|
60
|
+
height: 800,
|
|
61
|
+
radius: 24,
|
|
62
|
+
notch: false,
|
|
63
|
+
homeIndicator: false,
|
|
64
|
+
},
|
|
65
|
+
'android-tablet': {
|
|
66
|
+
width: 800,
|
|
67
|
+
height: 1280,
|
|
68
|
+
radius: 16,
|
|
69
|
+
notch: false,
|
|
70
|
+
homeIndicator: false,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DEVICE_LABELS: Record<DeviceType, string> = {
|
|
75
|
+
iphone: 'iPhone',
|
|
76
|
+
ipad: 'iPad',
|
|
77
|
+
'android-phone': 'Android Phone',
|
|
78
|
+
'android-tablet': 'Android Tablet',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createDeviceFrame(props: DeviceFrameProps): DeviceFrameAPI {
|
|
82
|
+
const { device, orientation = 'portrait' } = props
|
|
83
|
+
const spec = DEVICE_SPECS[device]
|
|
84
|
+
|
|
85
|
+
const isLandscape = orientation === 'landscape'
|
|
86
|
+
|
|
87
|
+
const dimensions: DeviceDimensions = {
|
|
88
|
+
width: isLandscape ? spec.height : spec.width,
|
|
89
|
+
height: isLandscape ? spec.width : spec.height,
|
|
90
|
+
radius: spec.radius,
|
|
91
|
+
notch: spec.notch,
|
|
92
|
+
homeIndicator: spec.homeIndicator,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {
|
|
96
|
+
role: 'img',
|
|
97
|
+
'aria-label': `${DEVICE_LABELS[device]} device frame in ${orientation} orientation`,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const dataAttributes: Record<string, string> = {
|
|
101
|
+
'data-device': device,
|
|
102
|
+
'data-orientation': orientation,
|
|
103
|
+
...(spec.notch ? { 'data-notch': 'true' } : {}),
|
|
104
|
+
...(spec.homeIndicator ? { 'data-home-indicator': 'true' } : {}),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
dimensions,
|
|
109
|
+
ariaProps,
|
|
110
|
+
dataAttributes,
|
|
111
|
+
}
|
|
112
|
+
}
|