@spatika/react 1.0.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/README.md +72 -0
- package/dist/composites/ActionBar.d.ts +7 -0
- package/dist/composites/ActionBar.d.ts.map +1 -0
- package/dist/composites/ActionBar.js +5 -0
- package/dist/composites/AmountInput.d.ts +10 -0
- package/dist/composites/AmountInput.d.ts.map +1 -0
- package/dist/composites/AmountInput.js +7 -0
- package/dist/composites/AppHeader.d.ts +25 -0
- package/dist/composites/AppHeader.d.ts.map +1 -0
- package/dist/composites/AppHeader.js +13 -0
- package/dist/composites/AppSidebar.d.ts +30 -0
- package/dist/composites/AppSidebar.d.ts.map +1 -0
- package/dist/composites/AppSidebar.js +15 -0
- package/dist/composites/BottomSheet.d.ts +36 -0
- package/dist/composites/BottomSheet.d.ts.map +1 -0
- package/dist/composites/BottomSheet.js +37 -0
- package/dist/composites/Breadcrumb.d.ts +10 -0
- package/dist/composites/Breadcrumb.d.ts.map +1 -0
- package/dist/composites/Breadcrumb.js +10 -0
- package/dist/composites/Callout.d.ts +8 -0
- package/dist/composites/Callout.d.ts.map +1 -0
- package/dist/composites/Callout.js +5 -0
- package/dist/composites/Card.d.ts +31 -0
- package/dist/composites/Card.d.ts.map +1 -0
- package/dist/composites/Card.js +33 -0
- package/dist/composites/Chip.d.ts +13 -0
- package/dist/composites/Chip.d.ts.map +1 -0
- package/dist/composites/Chip.js +59 -0
- package/dist/composites/ChipGroup.d.ts +23 -0
- package/dist/composites/ChipGroup.d.ts.map +1 -0
- package/dist/composites/ChipGroup.js +27 -0
- package/dist/composites/CommandSearchField.d.ts +27 -0
- package/dist/composites/CommandSearchField.d.ts.map +1 -0
- package/dist/composites/CommandSearchField.js +17 -0
- package/dist/composites/CoverHero.d.ts +51 -0
- package/dist/composites/CoverHero.d.ts.map +1 -0
- package/dist/composites/CoverHero.js +71 -0
- package/dist/composites/CoverPattern.d.ts +12 -0
- package/dist/composites/CoverPattern.d.ts.map +1 -0
- package/dist/composites/CoverPattern.js +14 -0
- package/dist/composites/DataTableToolbar.d.ts +13 -0
- package/dist/composites/DataTableToolbar.d.ts.map +1 -0
- package/dist/composites/DataTableToolbar.js +7 -0
- package/dist/composites/DividerLabel.d.ts +5 -0
- package/dist/composites/DividerLabel.d.ts.map +1 -0
- package/dist/composites/DividerLabel.js +5 -0
- package/dist/composites/EmptyState.d.ts +16 -0
- package/dist/composites/EmptyState.d.ts.map +1 -0
- package/dist/composites/EmptyState.js +10 -0
- package/dist/composites/EntityCard.d.ts +31 -0
- package/dist/composites/EntityCard.d.ts.map +1 -0
- package/dist/composites/EntityCard.js +21 -0
- package/dist/composites/EntityMediaCard.d.ts +25 -0
- package/dist/composites/EntityMediaCard.d.ts.map +1 -0
- package/dist/composites/EntityMediaCard.js +17 -0
- package/dist/composites/FilterSheet.d.ts +34 -0
- package/dist/composites/FilterSheet.d.ts.map +1 -0
- package/dist/composites/FilterSheet.js +36 -0
- package/dist/composites/FloatingPageChrome.d.ts +90 -0
- package/dist/composites/FloatingPageChrome.d.ts.map +1 -0
- package/dist/composites/FloatingPageChrome.js +67 -0
- package/dist/composites/FormField.d.ts +15 -0
- package/dist/composites/FormField.d.ts.map +1 -0
- package/dist/composites/FormField.js +9 -0
- package/dist/composites/GlassCard.d.ts +13 -0
- package/dist/composites/GlassCard.d.ts.map +1 -0
- package/dist/composites/GlassCard.js +32 -0
- package/dist/composites/HeaderIconButton.d.ts +13 -0
- package/dist/composites/HeaderIconButton.d.ts.map +1 -0
- package/dist/composites/HeaderIconButton.js +12 -0
- package/dist/composites/ListRow.d.ts +9 -0
- package/dist/composites/ListRow.d.ts.map +1 -0
- package/dist/composites/ListRow.js +6 -0
- package/dist/composites/MediaEntryCard.d.ts +25 -0
- package/dist/composites/MediaEntryCard.d.ts.map +1 -0
- package/dist/composites/MediaEntryCard.js +10 -0
- package/dist/composites/MobileTabBar.d.ts +33 -0
- package/dist/composites/MobileTabBar.d.ts.map +1 -0
- package/dist/composites/MobileTabBar.js +19 -0
- package/dist/composites/NotificationBell.d.ts +28 -0
- package/dist/composites/NotificationBell.d.ts.map +1 -0
- package/dist/composites/NotificationBell.js +25 -0
- package/dist/composites/PageHeader.d.ts +11 -0
- package/dist/composites/PageHeader.d.ts.map +1 -0
- package/dist/composites/PageHeader.js +6 -0
- package/dist/composites/PageShell.d.ts +12 -0
- package/dist/composites/PageShell.d.ts.map +1 -0
- package/dist/composites/PageShell.js +8 -0
- package/dist/composites/PageStickyHeader.d.ts +33 -0
- package/dist/composites/PageStickyHeader.d.ts.map +1 -0
- package/dist/composites/PageStickyHeader.js +19 -0
- package/dist/composites/Pagination.d.ts +7 -0
- package/dist/composites/Pagination.d.ts.map +1 -0
- package/dist/composites/Pagination.js +6 -0
- package/dist/composites/PhotoViewer.d.ts +18 -0
- package/dist/composites/PhotoViewer.d.ts.map +1 -0
- package/dist/composites/PhotoViewer.js +37 -0
- package/dist/composites/PinPad.d.ts +29 -0
- package/dist/composites/PinPad.d.ts.map +1 -0
- package/dist/composites/PinPad.js +58 -0
- package/dist/composites/ProfileHero.d.ts +34 -0
- package/dist/composites/ProfileHero.d.ts.map +1 -0
- package/dist/composites/ProfileHero.js +21 -0
- package/dist/composites/QuickFilterRail.d.ts +25 -0
- package/dist/composites/QuickFilterRail.d.ts.map +1 -0
- package/dist/composites/QuickFilterRail.js +18 -0
- package/dist/composites/QuickSettings.d.ts +27 -0
- package/dist/composites/QuickSettings.d.ts.map +1 -0
- package/dist/composites/QuickSettings.js +18 -0
- package/dist/composites/RichTextEditor.d.ts +13 -0
- package/dist/composites/RichTextEditor.d.ts.map +1 -0
- package/dist/composites/RichTextEditor.js +23 -0
- package/dist/composites/SearchField.d.ts +8 -0
- package/dist/composites/SearchField.d.ts.map +1 -0
- package/dist/composites/SearchField.js +8 -0
- package/dist/composites/SectionPanel.d.ts +16 -0
- package/dist/composites/SectionPanel.d.ts.map +1 -0
- package/dist/composites/SectionPanel.js +8 -0
- package/dist/composites/SegmentedControl.d.ts +17 -0
- package/dist/composites/SegmentedControl.d.ts.map +1 -0
- package/dist/composites/SegmentedControl.js +11 -0
- package/dist/composites/StatCard.d.ts +11 -0
- package/dist/composites/StatCard.d.ts.map +1 -0
- package/dist/composites/StatCard.js +6 -0
- package/dist/composites/StatusDot.d.ts +11 -0
- package/dist/composites/StatusDot.d.ts.map +1 -0
- package/dist/composites/StatusDot.js +23 -0
- package/dist/composites/SurfaceCard.d.ts +12 -0
- package/dist/composites/SurfaceCard.d.ts.map +1 -0
- package/dist/composites/SurfaceCard.js +24 -0
- package/dist/composites/Timeline.d.ts +19 -0
- package/dist/composites/Timeline.d.ts.map +1 -0
- package/dist/composites/Timeline.js +12 -0
- package/dist/composites/Toast.d.ts +26 -0
- package/dist/composites/Toast.d.ts.map +1 -0
- package/dist/composites/Toast.js +49 -0
- package/dist/composites/Toolbar.d.ts +6 -0
- package/dist/composites/Toolbar.d.ts.map +1 -0
- package/dist/composites/Toolbar.js +5 -0
- package/dist/composites/ViewFiltersPanel.d.ts +21 -0
- package/dist/composites/ViewFiltersPanel.d.ts.map +1 -0
- package/dist/composites/ViewFiltersPanel.js +11 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/lib/animations.d.ts +62 -0
- package/dist/lib/animations.d.ts.map +1 -0
- package/dist/lib/animations.js +37 -0
- package/dist/lib/avatar-utils.d.ts +10 -0
- package/dist/lib/avatar-utils.d.ts.map +1 -0
- package/dist/lib/avatar-utils.js +49 -0
- package/dist/lib/chips.d.ts +10 -0
- package/dist/lib/chips.d.ts.map +1 -0
- package/dist/lib/chips.js +30 -0
- package/dist/lib/cn.d.ts +4 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +6 -0
- package/dist/lib/compose-refs.d.ts +6 -0
- package/dist/lib/compose-refs.d.ts.map +1 -0
- package/dist/lib/compose-refs.js +17 -0
- package/dist/lib/cover-chrome.d.ts +9 -0
- package/dist/lib/cover-chrome.d.ts.map +1 -0
- package/dist/lib/cover-chrome.js +23 -0
- package/dist/lib/cover-pattern.d.ts +75 -0
- package/dist/lib/cover-pattern.d.ts.map +1 -0
- package/dist/lib/cover-pattern.js +135 -0
- package/dist/lib/overlay-stack.d.ts +23 -0
- package/dist/lib/overlay-stack.d.ts.map +1 -0
- package/dist/lib/overlay-stack.js +37 -0
- package/dist/lib/portal.d.ts +12 -0
- package/dist/lib/portal.d.ts.map +1 -0
- package/dist/lib/portal.js +12 -0
- package/dist/lib/slot.d.ts +12 -0
- package/dist/lib/slot.d.ts.map +1 -0
- package/dist/lib/slot.js +55 -0
- package/dist/lib/surfaces.d.ts +14 -0
- package/dist/lib/surfaces.d.ts.map +1 -0
- package/dist/lib/surfaces.js +18 -0
- package/dist/lib/themes.d.ts +16 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +53 -0
- package/dist/lib/typography.d.ts +17 -0
- package/dist/lib/typography.d.ts.map +1 -0
- package/dist/lib/typography.js +16 -0
- package/dist/lib/use-controllable-state.d.ts +12 -0
- package/dist/lib/use-controllable-state.d.ts.map +1 -0
- package/dist/lib/use-controllable-state.js +20 -0
- package/dist/lib/use-cover-chrome-bleed.d.ts +9 -0
- package/dist/lib/use-cover-chrome-bleed.d.ts.map +1 -0
- package/dist/lib/use-cover-chrome-bleed.js +28 -0
- package/dist/lib/use-dismissable.d.ts +13 -0
- package/dist/lib/use-dismissable.d.ts.map +1 -0
- package/dist/lib/use-dismissable.js +33 -0
- package/dist/lib/use-floating-position.d.ts +27 -0
- package/dist/lib/use-floating-position.d.ts.map +1 -0
- package/dist/lib/use-floating-position.js +115 -0
- package/dist/lib/use-focus-trap.d.ts +7 -0
- package/dist/lib/use-focus-trap.d.ts.map +1 -0
- package/dist/lib/use-focus-trap.js +61 -0
- package/dist/lib/use-media-query.d.ts +5 -0
- package/dist/lib/use-media-query.d.ts.map +1 -0
- package/dist/lib/use-media-query.js +19 -0
- package/dist/primitives/Accordion.d.ts +24 -0
- package/dist/primitives/Accordion.d.ts.map +1 -0
- package/dist/primitives/Accordion.js +37 -0
- package/dist/primitives/Alert.d.ts +10 -0
- package/dist/primitives/Alert.d.ts.map +1 -0
- package/dist/primitives/Alert.js +25 -0
- package/dist/primitives/AlertDialog.d.ts +33 -0
- package/dist/primitives/AlertDialog.d.ts.map +1 -0
- package/dist/primitives/AlertDialog.js +96 -0
- package/dist/primitives/Avatar.d.ts +15 -0
- package/dist/primitives/Avatar.d.ts.map +1 -0
- package/dist/primitives/Avatar.js +52 -0
- package/dist/primitives/Badge.d.ts +12 -0
- package/dist/primitives/Badge.d.ts.map +1 -0
- package/dist/primitives/Badge.js +27 -0
- package/dist/primitives/Button.d.ts +14 -0
- package/dist/primitives/Button.d.ts.map +1 -0
- package/dist/primitives/Button.js +37 -0
- package/dist/primitives/Checkbox.d.ts +9 -0
- package/dist/primitives/Checkbox.d.ts.map +1 -0
- package/dist/primitives/Checkbox.js +22 -0
- package/dist/primitives/Command.d.ts +17 -0
- package/dist/primitives/Command.d.ts.map +1 -0
- package/dist/primitives/Command.js +33 -0
- package/dist/primitives/Dialog.d.ts +34 -0
- package/dist/primitives/Dialog.d.ts.map +1 -0
- package/dist/primitives/Dialog.js +96 -0
- package/dist/primitives/DropdownMenu.d.ts +65 -0
- package/dist/primitives/DropdownMenu.d.ts.map +1 -0
- package/dist/primitives/DropdownMenu.js +196 -0
- package/dist/primitives/Input.d.ts +4 -0
- package/dist/primitives/Input.d.ts.map +1 -0
- package/dist/primitives/Input.js +6 -0
- package/dist/primitives/Kbd.d.ts +4 -0
- package/dist/primitives/Kbd.d.ts.map +1 -0
- package/dist/primitives/Kbd.js +6 -0
- package/dist/primitives/Label.d.ts +4 -0
- package/dist/primitives/Label.d.ts.map +1 -0
- package/dist/primitives/Label.js +6 -0
- package/dist/primitives/NativeSelect.d.ts +7 -0
- package/dist/primitives/NativeSelect.d.ts.map +1 -0
- package/dist/primitives/NativeSelect.js +10 -0
- package/dist/primitives/Popover.d.ts +29 -0
- package/dist/primitives/Popover.d.ts.map +1 -0
- package/dist/primitives/Popover.js +95 -0
- package/dist/primitives/Progress.d.ts +8 -0
- package/dist/primitives/Progress.d.ts.map +1 -0
- package/dist/primitives/Progress.js +7 -0
- package/dist/primitives/RadioGroup.d.ts +13 -0
- package/dist/primitives/RadioGroup.d.ts.map +1 -0
- package/dist/primitives/RadioGroup.js +33 -0
- package/dist/primitives/Select.d.ts +33 -0
- package/dist/primitives/Select.d.ts.map +1 -0
- package/dist/primitives/Select.js +178 -0
- package/dist/primitives/Separator.d.ts +7 -0
- package/dist/primitives/Separator.d.ts.map +1 -0
- package/dist/primitives/Separator.js +6 -0
- package/dist/primitives/Sheet.d.ts +28 -0
- package/dist/primitives/Sheet.d.ts.map +1 -0
- package/dist/primitives/Sheet.js +96 -0
- package/dist/primitives/Skeleton.d.ts +4 -0
- package/dist/primitives/Skeleton.d.ts.map +1 -0
- package/dist/primitives/Skeleton.js +6 -0
- package/dist/primitives/Slider.d.ts +12 -0
- package/dist/primitives/Slider.d.ts.map +1 -0
- package/dist/primitives/Slider.js +23 -0
- package/dist/primitives/Spinner.d.ts +6 -0
- package/dist/primitives/Spinner.d.ts.map +1 -0
- package/dist/primitives/Spinner.js +7 -0
- package/dist/primitives/StarRating.d.ts +10 -0
- package/dist/primitives/StarRating.d.ts.map +1 -0
- package/dist/primitives/StarRating.js +11 -0
- package/dist/primitives/Switch.d.ts +8 -0
- package/dist/primitives/Switch.d.ts.map +1 -0
- package/dist/primitives/Switch.js +20 -0
- package/dist/primitives/Table.d.ts +13 -0
- package/dist/primitives/Table.d.ts.map +1 -0
- package/dist/primitives/Table.js +20 -0
- package/dist/primitives/Tabs.d.ts +17 -0
- package/dist/primitives/Tabs.d.ts.map +1 -0
- package/dist/primitives/Tabs.js +30 -0
- package/dist/primitives/Textarea.d.ts +4 -0
- package/dist/primitives/Textarea.d.ts.map +1 -0
- package/dist/primitives/Textarea.js +6 -0
- package/dist/primitives/Tooltip.d.ts +31 -0
- package/dist/primitives/Tooltip.d.ts.map +1 -0
- package/dist/primitives/Tooltip.js +94 -0
- package/dist/theme/OriginThemeProvider.d.ts +31 -0
- package/dist/theme/OriginThemeProvider.d.ts.map +1 -0
- package/dist/theme/OriginThemeProvider.js +37 -0
- package/dist/theme/SpatikaThemeProvider.d.ts +36 -0
- package/dist/theme/SpatikaThemeProvider.d.ts.map +1 -0
- package/dist/theme/SpatikaThemeProvider.js +38 -0
- package/package.json +58 -0
- package/src/composites/ActionBar.tsx +25 -0
- package/src/composites/AmountInput.tsx +40 -0
- package/src/composites/AppHeader.tsx +88 -0
- package/src/composites/AppSidebar.tsx +120 -0
- package/src/composites/BottomSheet.tsx +179 -0
- package/src/composites/Breadcrumb.tsx +42 -0
- package/src/composites/Callout.tsx +22 -0
- package/src/composites/Card.tsx +119 -0
- package/src/composites/Chip.tsx +87 -0
- package/src/composites/ChipGroup.component.test.tsx +31 -0
- package/src/composites/ChipGroup.test.ts +16 -0
- package/src/composites/ChipGroup.tsx +86 -0
- package/src/composites/CommandSearchField.tsx +124 -0
- package/src/composites/CoverHero.tsx +359 -0
- package/src/composites/CoverPattern.tsx +90 -0
- package/src/composites/DataTableToolbar.tsx +48 -0
- package/src/composites/DividerLabel.tsx +22 -0
- package/src/composites/EmptyState.tsx +55 -0
- package/src/composites/EntityCard.tsx +100 -0
- package/src/composites/EntityMediaCard.tsx +118 -0
- package/src/composites/FilterSheet.tsx +178 -0
- package/src/composites/FloatingPageChrome.tsx +294 -0
- package/src/composites/FormField.tsx +44 -0
- package/src/composites/GlassCard.tsx +47 -0
- package/src/composites/HeaderIconButton.tsx +49 -0
- package/src/composites/ListRow.tsx +29 -0
- package/src/composites/MediaEntryCard.tsx +95 -0
- package/src/composites/MobileTabBar.tsx +130 -0
- package/src/composites/NotificationBell.tsx +164 -0
- package/src/composites/PageHeader.tsx +27 -0
- package/src/composites/PageShell.tsx +20 -0
- package/src/composites/PageStickyHeader.tsx +157 -0
- package/src/composites/Pagination.tsx +41 -0
- package/src/composites/PhotoViewer.tsx +142 -0
- package/src/composites/PinPad.tsx +163 -0
- package/src/composites/ProfileHero.tsx +163 -0
- package/src/composites/QuickFilterRail.tsx +92 -0
- package/src/composites/QuickSettings.tsx +120 -0
- package/src/composites/RichTextEditor.tsx +103 -0
- package/src/composites/SearchField.tsx +38 -0
- package/src/composites/SectionPanel.tsx +59 -0
- package/src/composites/SegmentedControl.tsx +62 -0
- package/src/composites/StatCard.tsx +26 -0
- package/src/composites/StatusDot.tsx +40 -0
- package/src/composites/SurfaceCard.tsx +44 -0
- package/src/composites/Timeline.tsx +70 -0
- package/src/composites/Toast.tsx +135 -0
- package/src/composites/Toolbar.tsx +19 -0
- package/src/composites/ViewFiltersPanel.tsx +82 -0
- package/src/composites/chrome.test.tsx +170 -0
- package/src/composites/smoke.test.tsx +49 -0
- package/src/index.ts +355 -0
- package/src/lib/animations.ts +47 -0
- package/src/lib/avatar-utils.ts +52 -0
- package/src/lib/chips.ts +58 -0
- package/src/lib/cn.test.ts +16 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/compose-refs.ts +21 -0
- package/src/lib/cover-chrome.ts +31 -0
- package/src/lib/cover-pattern.ts +242 -0
- package/src/lib/overlay-stack.ts +44 -0
- package/src/lib/portal.tsx +21 -0
- package/src/lib/slot.tsx +69 -0
- package/src/lib/surfaces.ts +57 -0
- package/src/lib/themes.test.ts +73 -0
- package/src/lib/themes.ts +59 -0
- package/src/lib/tokens.test.ts +52 -0
- package/src/lib/typography.ts +27 -0
- package/src/lib/use-controllable-state.ts +38 -0
- package/src/lib/use-cover-chrome-bleed.ts +33 -0
- package/src/lib/use-dismissable.ts +49 -0
- package/src/lib/use-floating-position.ts +168 -0
- package/src/lib/use-focus-trap.ts +73 -0
- package/src/lib/use-media-query.ts +22 -0
- package/src/primitives/Accordion.tsx +114 -0
- package/src/primitives/Alert.tsx +57 -0
- package/src/primitives/AlertDialog.tsx +270 -0
- package/src/primitives/Avatar.tsx +140 -0
- package/src/primitives/Badge.test.tsx +15 -0
- package/src/primitives/Badge.tsx +43 -0
- package/src/primitives/Button.test.tsx +37 -0
- package/src/primitives/Button.tsx +58 -0
- package/src/primitives/Checkbox.tsx +75 -0
- package/src/primitives/Command.tsx +175 -0
- package/src/primitives/Dialog.tsx +241 -0
- package/src/primitives/DropdownMenu.tsx +552 -0
- package/src/primitives/Input.test.tsx +22 -0
- package/src/primitives/Input.tsx +22 -0
- package/src/primitives/Kbd.tsx +17 -0
- package/src/primitives/Label.tsx +20 -0
- package/src/primitives/NativeSelect.tsx +22 -0
- package/src/primitives/Popover.tsx +209 -0
- package/src/primitives/Progress.tsx +31 -0
- package/src/primitives/RadioGroup.tsx +116 -0
- package/src/primitives/Select.tsx +409 -0
- package/src/primitives/Separator.tsx +30 -0
- package/src/primitives/Sheet.tsx +265 -0
- package/src/primitives/Skeleton.tsx +16 -0
- package/src/primitives/Slider.tsx +97 -0
- package/src/primitives/Spinner.tsx +21 -0
- package/src/primitives/StarRating.tsx +53 -0
- package/src/primitives/Switch.tsx +68 -0
- package/src/primitives/Table.tsx +134 -0
- package/src/primitives/Tabs.tsx +97 -0
- package/src/primitives/Textarea.tsx +20 -0
- package/src/primitives/Tooltip.tsx +193 -0
- package/src/primitives/smoke.test.tsx +34 -0
- package/src/test/setup.ts +1 -0
- package/src/theme/SpatikaThemeProvider.test.tsx +82 -0
- package/src/theme/SpatikaThemeProvider.tsx +112 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useRef,
|
|
4
|
+
useState,
|
|
5
|
+
type CSSProperties,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { ChevronLeft } from "lucide-react";
|
|
9
|
+
import { cn } from "../lib/cn";
|
|
10
|
+
import {
|
|
11
|
+
coverFloatingIconClass,
|
|
12
|
+
coverMutedClass,
|
|
13
|
+
coverPlateClass,
|
|
14
|
+
coverTitleClass,
|
|
15
|
+
} from "../lib/cover-chrome";
|
|
16
|
+
import {
|
|
17
|
+
getCoverPattern,
|
|
18
|
+
type CoverPatternKind,
|
|
19
|
+
} from "../lib/cover-pattern";
|
|
20
|
+
import { useCoverChromeBleed } from "../lib/use-cover-chrome-bleed";
|
|
21
|
+
import { getInitials } from "../lib/avatar-utils";
|
|
22
|
+
import { CoverPattern } from "./CoverPattern";
|
|
23
|
+
|
|
24
|
+
export type CoverHeroAvatar = {
|
|
25
|
+
name: string;
|
|
26
|
+
src?: string;
|
|
27
|
+
href?: string;
|
|
28
|
+
onClick?: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type CoverHeroFact = {
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
value: ReactNode;
|
|
35
|
+
icon?: ReactNode;
|
|
36
|
+
wide?: boolean;
|
|
37
|
+
muted?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type CoverHeroProps = {
|
|
41
|
+
title: string;
|
|
42
|
+
kicker?: ReactNode;
|
|
43
|
+
facts?: CoverHeroFact[];
|
|
44
|
+
avatars?: CoverHeroAvatar[];
|
|
45
|
+
hiddenAvatarCount?: number;
|
|
46
|
+
coverUrl?: string;
|
|
47
|
+
hideImages?: boolean;
|
|
48
|
+
coverSeed?: string;
|
|
49
|
+
coverHue?: number;
|
|
50
|
+
coverKind?: CoverPatternKind | null;
|
|
51
|
+
dark?: boolean;
|
|
52
|
+
warm?: boolean;
|
|
53
|
+
/** Profile bleeds into a tab bar; story ends at the identity plate. */
|
|
54
|
+
variant?: "story" | "profile";
|
|
55
|
+
onBack?: () => void;
|
|
56
|
+
backLabel?: string;
|
|
57
|
+
floatingActions?: ReactNode;
|
|
58
|
+
compactActions?: ReactNode;
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
className?: string;
|
|
61
|
+
/** Offset used when the compact bar pins under app chrome. */
|
|
62
|
+
compactOffsetClassName?: string;
|
|
63
|
+
/** When false, skip writing cover-bleed attributes on `<html>` (catalog previews). */
|
|
64
|
+
bleed?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cover hero — generated / photo cover that bleeds into app chrome, floating
|
|
69
|
+
* glass controls, a notched identity plate, and a compact sticky bar once the
|
|
70
|
+
* hero scrolls away. Mirrors journalD StoryCoverHeader / Profile hero.
|
|
71
|
+
*/
|
|
72
|
+
export function CoverHero({
|
|
73
|
+
title,
|
|
74
|
+
kicker,
|
|
75
|
+
facts,
|
|
76
|
+
avatars = [],
|
|
77
|
+
hiddenAvatarCount = 0,
|
|
78
|
+
coverUrl,
|
|
79
|
+
hideImages,
|
|
80
|
+
coverSeed,
|
|
81
|
+
coverHue = 210,
|
|
82
|
+
coverKind,
|
|
83
|
+
dark,
|
|
84
|
+
warm,
|
|
85
|
+
variant = "story",
|
|
86
|
+
onBack,
|
|
87
|
+
backLabel = "Go back",
|
|
88
|
+
floatingActions,
|
|
89
|
+
compactActions,
|
|
90
|
+
children,
|
|
91
|
+
className,
|
|
92
|
+
compactOffsetClassName = "top-16",
|
|
93
|
+
bleed = true,
|
|
94
|
+
}: CoverHeroProps) {
|
|
95
|
+
const seed = coverSeed ?? title;
|
|
96
|
+
const scene = getCoverPattern({ seed, hue: coverHue, dark, warm, kind: coverKind });
|
|
97
|
+
const onDarkCover = scene.headerInk === "light";
|
|
98
|
+
const mediaClass = variant === "profile" ? "cover-media cover-media--profile" : "cover-media cover-media--story";
|
|
99
|
+
|
|
100
|
+
const visibleAvatars = avatars.slice(0, 3);
|
|
101
|
+
const extra = Math.max(0, hiddenAvatarCount || avatars.length - visibleAvatars.length);
|
|
102
|
+
const avatarCount = visibleAvatars.length + (extra > 0 ? 1 : 0);
|
|
103
|
+
const hasAvatars = avatarCount > 0;
|
|
104
|
+
|
|
105
|
+
const heroRef = useRef<HTMLElement>(null);
|
|
106
|
+
const plateRef = useRef<HTMLDivElement>(null);
|
|
107
|
+
const [coverUnderChrome, setCoverUnderChrome] = useState(true);
|
|
108
|
+
const [heroInView, setHeroInView] = useState(true);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
let frame = 0;
|
|
112
|
+
const measure = () => {
|
|
113
|
+
frame = 0;
|
|
114
|
+
const hero = heroRef.current;
|
|
115
|
+
const plate = plateRef.current;
|
|
116
|
+
if (!hero || !plate) return;
|
|
117
|
+
const chromeHeight =
|
|
118
|
+
document.querySelector<HTMLElement>(".app-chrome-header")?.offsetHeight ?? 64;
|
|
119
|
+
const heroRect = hero.getBoundingClientRect();
|
|
120
|
+
if (heroRect.height === 0) return;
|
|
121
|
+
setCoverUnderChrome(plate.getBoundingClientRect().top > chromeHeight + 1);
|
|
122
|
+
setHeroInView(heroRect.bottom > chromeHeight + 1);
|
|
123
|
+
};
|
|
124
|
+
const schedule = () => {
|
|
125
|
+
if (!frame) frame = window.requestAnimationFrame(measure);
|
|
126
|
+
};
|
|
127
|
+
measure();
|
|
128
|
+
window.addEventListener("scroll", schedule, { passive: true });
|
|
129
|
+
window.addEventListener("resize", schedule);
|
|
130
|
+
return () => {
|
|
131
|
+
if (frame) window.cancelAnimationFrame(frame);
|
|
132
|
+
window.removeEventListener("scroll", schedule);
|
|
133
|
+
window.removeEventListener("resize", schedule);
|
|
134
|
+
};
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
useCoverChromeBleed(bleed && coverUnderChrome, scene.chromeInk);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<>
|
|
141
|
+
<header
|
|
142
|
+
ref={heroRef}
|
|
143
|
+
data-slot="cover-hero"
|
|
144
|
+
className={cn("relative z-10 w-full overflow-visible", className)}
|
|
145
|
+
>
|
|
146
|
+
<div className={cn(mediaClass, "sm:rounded-b-[1.75rem]")} aria-hidden>
|
|
147
|
+
{!hideImages && coverUrl ? (
|
|
148
|
+
<img src={coverUrl} alt="" className="absolute inset-0 size-full object-cover" />
|
|
149
|
+
) : (
|
|
150
|
+
<CoverPattern seed={seed} hue={coverHue} dark={dark} warm={warm} kind={coverKind} />
|
|
151
|
+
)}
|
|
152
|
+
<div className="absolute inset-x-0 top-0 h-8 bg-gradient-to-b from-black/[0.04] to-transparent dark:from-black/12" />
|
|
153
|
+
<div className="absolute inset-x-0 bottom-0 h-[22%] bg-gradient-to-t from-black/[0.03] via-transparent to-transparent dark:from-black/10" />
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className="relative z-10 flex flex-col">
|
|
157
|
+
<div className="relative min-h-[8.5rem] sm:min-h-[10.5rem] lg:min-h-[12rem]">
|
|
158
|
+
<div className="absolute inset-x-0 top-0 z-30 flex items-start justify-between gap-2 px-3 pt-3 sm:px-5 sm:pt-4">
|
|
159
|
+
{onBack ? (
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={onBack}
|
|
163
|
+
aria-label={backLabel}
|
|
164
|
+
data-cover-ink={scene.headerInk}
|
|
165
|
+
className={cn(
|
|
166
|
+
"cover-floating-shell flex items-center gap-1 px-3 py-1.5 text-xs font-semibold transition-colors active:scale-[0.96]",
|
|
167
|
+
onDarkCover ? "text-white hover:bg-black/60" : "text-zinc-900 hover:bg-white/90",
|
|
168
|
+
)}
|
|
169
|
+
>
|
|
170
|
+
<ChevronLeft className="h-3.5 w-3.5 shrink-0" aria-hidden />
|
|
171
|
+
<span className="hidden sm:inline">Back</span>
|
|
172
|
+
</button>
|
|
173
|
+
) : (
|
|
174
|
+
<span />
|
|
175
|
+
)}
|
|
176
|
+
{floatingActions ? (
|
|
177
|
+
<div
|
|
178
|
+
data-cover-ink={scene.headerInk}
|
|
179
|
+
className="cover-floating-shell flex items-center gap-1 p-1"
|
|
180
|
+
>
|
|
181
|
+
{floatingActions}
|
|
182
|
+
</div>
|
|
183
|
+
) : null}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div ref={plateRef} className="relative z-20 px-3.5 pb-4 sm:px-5 sm:pb-5 lg:px-8 lg:pb-6">
|
|
188
|
+
<div
|
|
189
|
+
className="cover-identity story-identity"
|
|
190
|
+
style={{ "--story-avatar-count": avatarCount } as CSSProperties}
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
aria-hidden
|
|
194
|
+
className={cn(
|
|
195
|
+
"pointer-events-none absolute inset-0 rounded-[1.35rem]",
|
|
196
|
+
hasAvatars && "cover-identity__plate story-identity__plate",
|
|
197
|
+
coverPlateClass(scene.headerInk),
|
|
198
|
+
)}
|
|
199
|
+
/>
|
|
200
|
+
<div
|
|
201
|
+
className={cn(
|
|
202
|
+
"relative z-10 flex flex-col items-center px-3 pb-3 text-center sm:px-6 sm:pb-4",
|
|
203
|
+
hasAvatars ? "pt-0" : "pt-3 sm:pt-4",
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
{hasAvatars ? (
|
|
207
|
+
<div className="cover-identity__avatars story-identity__avatars">
|
|
208
|
+
<div className="flex -space-x-5 sm:-space-x-6">
|
|
209
|
+
{visibleAvatars.map((avatar, index) => {
|
|
210
|
+
const body = (
|
|
211
|
+
<span
|
|
212
|
+
style={{
|
|
213
|
+
width: "var(--story-avatar-size)",
|
|
214
|
+
height: "var(--story-avatar-size)",
|
|
215
|
+
}}
|
|
216
|
+
className="flex items-center justify-center overflow-hidden rounded-full border-[3px] border-white bg-muted text-lg font-black text-primary shadow-[0_16px_40px_rgba(0,0,0,0.38)] dark:border-white/30"
|
|
217
|
+
>
|
|
218
|
+
{!hideImages && avatar.src ? (
|
|
219
|
+
<img src={avatar.src} alt="" className="size-full object-cover" />
|
|
220
|
+
) : (
|
|
221
|
+
getInitials(avatar.name)
|
|
222
|
+
)}
|
|
223
|
+
</span>
|
|
224
|
+
);
|
|
225
|
+
return (
|
|
226
|
+
<span
|
|
227
|
+
key={avatar.name + index}
|
|
228
|
+
style={{ zIndex: visibleAvatars.length - index }}
|
|
229
|
+
className="relative"
|
|
230
|
+
>
|
|
231
|
+
{avatar.href ? (
|
|
232
|
+
<a
|
|
233
|
+
href={avatar.href}
|
|
234
|
+
onClick={avatar.onClick}
|
|
235
|
+
title={avatar.name}
|
|
236
|
+
aria-label={avatar.name}
|
|
237
|
+
className="block rounded-full outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
|
|
238
|
+
>
|
|
239
|
+
{body}
|
|
240
|
+
</a>
|
|
241
|
+
) : (
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={avatar.onClick}
|
|
245
|
+
title={avatar.name}
|
|
246
|
+
aria-label={avatar.name}
|
|
247
|
+
className="block rounded-full outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
|
|
248
|
+
>
|
|
249
|
+
{body}
|
|
250
|
+
</button>
|
|
251
|
+
)}
|
|
252
|
+
</span>
|
|
253
|
+
);
|
|
254
|
+
})}
|
|
255
|
+
{extra > 0 ? (
|
|
256
|
+
<span
|
|
257
|
+
style={{
|
|
258
|
+
width: "var(--story-avatar-size)",
|
|
259
|
+
height: "var(--story-avatar-size)",
|
|
260
|
+
}}
|
|
261
|
+
className="relative z-0 flex items-center justify-center rounded-full border-[3px] border-white bg-zinc-900/80 text-base font-black text-white dark:border-white/30"
|
|
262
|
+
>
|
|
263
|
+
+{extra}
|
|
264
|
+
</span>
|
|
265
|
+
) : null}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
) : null}
|
|
269
|
+
|
|
270
|
+
<div className={cn("w-full min-w-0", hasAvatars && "mt-2 sm:mt-2.5")}>
|
|
271
|
+
{kicker ? (
|
|
272
|
+
<div className="flex flex-wrap items-center justify-center gap-1.5">{kicker}</div>
|
|
273
|
+
) : null}
|
|
274
|
+
<h1
|
|
275
|
+
className={cn(
|
|
276
|
+
"mt-2 text-balance text-[1.15rem] font-black leading-tight tracking-tight sm:text-[1.35rem] lg:text-[1.65rem]",
|
|
277
|
+
coverTitleClass(scene.headerInk),
|
|
278
|
+
)}
|
|
279
|
+
>
|
|
280
|
+
{title}
|
|
281
|
+
</h1>
|
|
282
|
+
{facts && facts.length > 0 ? (
|
|
283
|
+
<dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2 sm:flex sm:flex-wrap sm:justify-center sm:gap-x-6">
|
|
284
|
+
{facts.map((fact) => (
|
|
285
|
+
<div
|
|
286
|
+
key={fact.key}
|
|
287
|
+
className={cn(
|
|
288
|
+
"flex min-w-0 items-center justify-center gap-2",
|
|
289
|
+
fact.wide && "col-span-2 sm:col-span-1",
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
{fact.icon ? (
|
|
293
|
+
<span
|
|
294
|
+
className={cn(
|
|
295
|
+
"hidden h-7 w-7 shrink-0 items-center justify-center rounded-lg border backdrop-blur-md sm:flex",
|
|
296
|
+
onDarkCover
|
|
297
|
+
? "border-white/20 bg-white/15 text-white/90"
|
|
298
|
+
: "border-zinc-950/10 bg-zinc-950/[0.06] text-zinc-700",
|
|
299
|
+
)}
|
|
300
|
+
>
|
|
301
|
+
{fact.icon}
|
|
302
|
+
</span>
|
|
303
|
+
) : null}
|
|
304
|
+
<div className="min-w-0 text-center sm:text-left">
|
|
305
|
+
<dt
|
|
306
|
+
className={cn(
|
|
307
|
+
"text-[9px] font-black uppercase tracking-[0.18em]",
|
|
308
|
+
coverMutedClass(scene.headerInk),
|
|
309
|
+
)}
|
|
310
|
+
>
|
|
311
|
+
{fact.label}
|
|
312
|
+
</dt>
|
|
313
|
+
<dd
|
|
314
|
+
className={cn(
|
|
315
|
+
"truncate text-[11px] font-bold sm:text-xs",
|
|
316
|
+
coverTitleClass(scene.headerInk),
|
|
317
|
+
fact.muted && "opacity-60",
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
{fact.value}
|
|
321
|
+
</dd>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
))}
|
|
325
|
+
</dl>
|
|
326
|
+
) : null}
|
|
327
|
+
{children}
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
</header>
|
|
334
|
+
|
|
335
|
+
<div className={cn("sticky z-[60] h-0", compactOffsetClassName)}>
|
|
336
|
+
{!heroInView ? (
|
|
337
|
+
<div className="glass-header cover-chrome-bar absolute inset-x-0 top-0 border-b border-border/50 bg-background/85 backdrop-blur-md dark:bg-background/94">
|
|
338
|
+
<div className="app-frame-pad flex items-center gap-2 py-1.5 sm:py-2">
|
|
339
|
+
{onBack ? (
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
onClick={onBack}
|
|
343
|
+
aria-label={backLabel}
|
|
344
|
+
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl text-foreground transition-colors hover:bg-muted"
|
|
345
|
+
>
|
|
346
|
+
<ChevronLeft className="h-4 w-4" aria-hidden />
|
|
347
|
+
</button>
|
|
348
|
+
) : null}
|
|
349
|
+
<h2 className="page-header-title min-w-0 flex-1 truncate">{title}</h2>
|
|
350
|
+
{compactActions}
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
) : null}
|
|
354
|
+
</div>
|
|
355
|
+
</>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { coverFloatingIconClass };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import {
|
|
4
|
+
getCoverPattern,
|
|
5
|
+
type CoverPatternInput,
|
|
6
|
+
} from "../lib/cover-pattern";
|
|
7
|
+
|
|
8
|
+
export type CoverPatternProps = CoverPatternInput & {
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Seeded nature-inspired cover when no cover photo is set.
|
|
14
|
+
* Scene kind is unique per seed; colors follow hue + theme.
|
|
15
|
+
*/
|
|
16
|
+
export function CoverPattern({
|
|
17
|
+
seed,
|
|
18
|
+
hue,
|
|
19
|
+
secondaryHue,
|
|
20
|
+
tertiaryHue,
|
|
21
|
+
dark,
|
|
22
|
+
warm,
|
|
23
|
+
kind,
|
|
24
|
+
className,
|
|
25
|
+
}: CoverPatternProps) {
|
|
26
|
+
const scene = useMemo(
|
|
27
|
+
() => getCoverPattern({ seed, hue, secondaryHue, tertiaryHue, dark, warm, kind }),
|
|
28
|
+
[seed, hue, secondaryHue, tertiaryHue, dark, warm, kind],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-cover-pattern={scene.kind}
|
|
34
|
+
className={cn("absolute inset-0 overflow-hidden", className)}
|
|
35
|
+
aria-hidden
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
className="absolute inset-0 transition-[background-color,background-image] duration-700 ease-out"
|
|
39
|
+
style={scene.skyStyle}
|
|
40
|
+
/>
|
|
41
|
+
<svg
|
|
42
|
+
viewBox="0 0 1600 420"
|
|
43
|
+
preserveAspectRatio="xMidYMid slice"
|
|
44
|
+
className="absolute inset-0 size-full"
|
|
45
|
+
>
|
|
46
|
+
{scene.ellipses.map((ellipse, index) => (
|
|
47
|
+
<ellipse
|
|
48
|
+
key={`e-${scene.uid}-${index}`}
|
|
49
|
+
cx={ellipse.cx}
|
|
50
|
+
cy={ellipse.cy}
|
|
51
|
+
rx={ellipse.rx}
|
|
52
|
+
ry={ellipse.ry}
|
|
53
|
+
fill={ellipse.fill}
|
|
54
|
+
opacity={ellipse.opacity}
|
|
55
|
+
/>
|
|
56
|
+
))}
|
|
57
|
+
{scene.circles.map((circle, index) => (
|
|
58
|
+
<circle
|
|
59
|
+
key={`c-${scene.uid}-${index}`}
|
|
60
|
+
cx={circle.cx}
|
|
61
|
+
cy={circle.cy}
|
|
62
|
+
r={circle.r}
|
|
63
|
+
fill={circle.fill}
|
|
64
|
+
opacity={circle.opacity}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
{scene.paths.map((path, index) => (
|
|
68
|
+
<path
|
|
69
|
+
key={`p-${scene.uid}-${index}`}
|
|
70
|
+
d={path.d}
|
|
71
|
+
fill={path.fill}
|
|
72
|
+
opacity={path.opacity}
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
{scene.polygons.map((polygon, index) => (
|
|
76
|
+
<polygon
|
|
77
|
+
key={`g-${scene.uid}-${index}`}
|
|
78
|
+
points={polygon.points}
|
|
79
|
+
fill={polygon.fill}
|
|
80
|
+
opacity={polygon.opacity}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</svg>
|
|
84
|
+
<div className="profile-cover-grain pointer-events-none absolute inset-0" />
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @deprecated Use CoverPattern — kept as the journalD name. */
|
|
90
|
+
export const ProfileCoverPattern = CoverPattern;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import { SearchField } from "./SearchField";
|
|
4
|
+
|
|
5
|
+
export type DataTableToolbarProps = {
|
|
6
|
+
query?: string;
|
|
7
|
+
onQueryChange?: (value: string) => void;
|
|
8
|
+
searchPlaceholder?: string;
|
|
9
|
+
filters?: ReactNode;
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
hideSearch?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Search + filter slot + actions row above data tables. */
|
|
16
|
+
export function DataTableToolbar({
|
|
17
|
+
query = "",
|
|
18
|
+
onQueryChange,
|
|
19
|
+
searchPlaceholder = "Search…",
|
|
20
|
+
filters,
|
|
21
|
+
actions,
|
|
22
|
+
className,
|
|
23
|
+
hideSearch,
|
|
24
|
+
}: DataTableToolbarProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-slot="data-table-toolbar"
|
|
28
|
+
className={cn(
|
|
29
|
+
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
|
|
34
|
+
{!hideSearch && onQueryChange ? (
|
|
35
|
+
<SearchField
|
|
36
|
+
value={query}
|
|
37
|
+
onChange={(e) => onQueryChange(e.target.value)}
|
|
38
|
+
placeholder={searchPlaceholder}
|
|
39
|
+
containerClassName="w-full sm:max-w-xs"
|
|
40
|
+
className="h-10 rounded-xl"
|
|
41
|
+
/>
|
|
42
|
+
) : null}
|
|
43
|
+
{filters ? <div className="flex flex-wrap items-center gap-2">{filters}</div> : null}
|
|
44
|
+
</div>
|
|
45
|
+
{actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cn } from "../lib/cn";
|
|
2
|
+
|
|
3
|
+
export function DividerLabel({
|
|
4
|
+
label,
|
|
5
|
+
className,
|
|
6
|
+
}: {
|
|
7
|
+
label: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="divider-label"
|
|
13
|
+
className={cn("flex items-center gap-3", className)}
|
|
14
|
+
>
|
|
15
|
+
<div className="h-px flex-1 bg-border/60" />
|
|
16
|
+
<span className="text-[10px] font-black uppercase tracking-widest text-muted-foreground">
|
|
17
|
+
{label}
|
|
18
|
+
</span>
|
|
19
|
+
<div className="h-px flex-1 bg-border/60" />
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LucideIcon } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { Button } from "../primitives/Button";
|
|
5
|
+
import { GlassCard } from "./GlassCard";
|
|
6
|
+
|
|
7
|
+
export type EmptyStateProps = {
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
actionLabel?: string;
|
|
12
|
+
onAction?: () => void;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Empty / zero-state panel for lists and dashboards.
|
|
19
|
+
*/
|
|
20
|
+
export function EmptyState({
|
|
21
|
+
icon: Icon,
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
actionLabel,
|
|
25
|
+
onAction,
|
|
26
|
+
children,
|
|
27
|
+
className,
|
|
28
|
+
}: EmptyStateProps) {
|
|
29
|
+
return (
|
|
30
|
+
<GlassCard
|
|
31
|
+
variant="subtle"
|
|
32
|
+
padding="lg"
|
|
33
|
+
data-slot="empty-state"
|
|
34
|
+
className={cn("flex flex-col items-center text-center", className)}
|
|
35
|
+
>
|
|
36
|
+
{Icon ? (
|
|
37
|
+
<div className="mb-4 rounded-2xl border border-border/40 bg-background/50 p-3 text-muted-foreground">
|
|
38
|
+
<Icon className="h-6 w-6" />
|
|
39
|
+
</div>
|
|
40
|
+
) : null}
|
|
41
|
+
<h3 className="text-base font-black tracking-tight text-foreground">{title}</h3>
|
|
42
|
+
{description ? (
|
|
43
|
+
<p className="mt-1.5 max-w-sm text-sm font-medium text-muted-foreground">
|
|
44
|
+
{description}
|
|
45
|
+
</p>
|
|
46
|
+
) : null}
|
|
47
|
+
{actionLabel && onAction ? (
|
|
48
|
+
<Button className="mt-4" onClick={onAction}>
|
|
49
|
+
{actionLabel}
|
|
50
|
+
</Button>
|
|
51
|
+
) : null}
|
|
52
|
+
{children}
|
|
53
|
+
</GlassCard>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntityCard — quiet glass list-row / tile baseline.
|
|
3
|
+
*
|
|
4
|
+
* Use for directory rows, trash items, and other entity summaries that need
|
|
5
|
+
* consistent glass surface, radius, and padding without feature-specific chrome.
|
|
6
|
+
*/
|
|
7
|
+
import type { ElementType, HTMLAttributes } from "react";
|
|
8
|
+
import { cn } from "../lib/cn";
|
|
9
|
+
|
|
10
|
+
type EntityCardElement = "div" | "li" | "article" | "section" | "button" | "a";
|
|
11
|
+
|
|
12
|
+
export type EntityCardProps = HTMLAttributes<HTMLElement> & {
|
|
13
|
+
/** Rendered HTML element. Defaults to `div`. */
|
|
14
|
+
as?: EntityCardElement;
|
|
15
|
+
/** Slightly denser padding for compact lists. */
|
|
16
|
+
density?: "default" | "compact";
|
|
17
|
+
/** Soft hover lift for interactive rows (navigation / selection). */
|
|
18
|
+
interactive?: boolean;
|
|
19
|
+
href?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Glass surface wrapper for entity list items.
|
|
24
|
+
*/
|
|
25
|
+
export function EntityCard({
|
|
26
|
+
as,
|
|
27
|
+
density = "default",
|
|
28
|
+
interactive = false,
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
href,
|
|
32
|
+
...rest
|
|
33
|
+
}: EntityCardProps) {
|
|
34
|
+
const Component = (as ?? (href ? "a" : "div")) as ElementType;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Component
|
|
38
|
+
{...(href ? { href } : {})}
|
|
39
|
+
data-slot="entity-card"
|
|
40
|
+
className={cn(
|
|
41
|
+
"entity-card",
|
|
42
|
+
density === "compact" && "entity-card-compact",
|
|
43
|
+
interactive && "entity-card-interactive",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Component>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type EntityCardTitleProps = HTMLAttributes<HTMLParagraphElement>;
|
|
54
|
+
|
|
55
|
+
/** Primary title inside an EntityCard. */
|
|
56
|
+
export function EntityCardTitle({ className, ...props }: EntityCardTitleProps) {
|
|
57
|
+
return (
|
|
58
|
+
<p
|
|
59
|
+
data-slot="entity-card-title"
|
|
60
|
+
className={cn(
|
|
61
|
+
"truncate text-sm font-semibold text-foreground sm:text-[15px]",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type EntityCardMetaProps = HTMLAttributes<HTMLParagraphElement>;
|
|
70
|
+
|
|
71
|
+
/** Secondary / muted line under the title. */
|
|
72
|
+
export function EntityCardMeta({ className, ...props }: EntityCardMetaProps) {
|
|
73
|
+
return (
|
|
74
|
+
<p
|
|
75
|
+
data-slot="entity-card-meta"
|
|
76
|
+
className={cn(
|
|
77
|
+
"mt-0.5 line-clamp-2 text-xs text-muted-foreground sm:text-[13px]",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Compact type / status chip for entity cards. */
|
|
86
|
+
export function EntityCardChip({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: HTMLAttributes<HTMLSpanElement>) {
|
|
90
|
+
return (
|
|
91
|
+
<span
|
|
92
|
+
data-slot="entity-card-chip"
|
|
93
|
+
className={cn(
|
|
94
|
+
"inline-flex shrink-0 rounded-full border border-border/50 bg-muted/40 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-muted-foreground",
|
|
95
|
+
className,
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|