@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,118 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
export type EntityMediaCardProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
statusLine?: string;
|
|
8
|
+
imageUrl?: string;
|
|
9
|
+
hideImage?: boolean;
|
|
10
|
+
/** Initials when no image */
|
|
11
|
+
fallbackLabel?: string;
|
|
12
|
+
tags?: ReactNode;
|
|
13
|
+
overlays?: ReactNode;
|
|
14
|
+
menu?: ReactNode;
|
|
15
|
+
meta?: ReactNode;
|
|
16
|
+
href?: string;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
size?: "large" | "small" | "compact";
|
|
19
|
+
orientation?: "stack" | "row";
|
|
20
|
+
className?: string;
|
|
21
|
+
imageClassName?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Entity card with photo (or initials fallback) — contact / directory style.
|
|
26
|
+
*/
|
|
27
|
+
export function EntityMediaCard({
|
|
28
|
+
title,
|
|
29
|
+
subtitle,
|
|
30
|
+
statusLine,
|
|
31
|
+
imageUrl,
|
|
32
|
+
hideImage,
|
|
33
|
+
fallbackLabel,
|
|
34
|
+
tags,
|
|
35
|
+
overlays,
|
|
36
|
+
menu,
|
|
37
|
+
meta,
|
|
38
|
+
href,
|
|
39
|
+
onClick,
|
|
40
|
+
size = "large",
|
|
41
|
+
orientation = "stack",
|
|
42
|
+
className,
|
|
43
|
+
imageClassName,
|
|
44
|
+
}: EntityMediaCardProps) {
|
|
45
|
+
const Comp: "a" | "button" | "div" = href ? "a" : onClick ? "button" : "div";
|
|
46
|
+
const initials =
|
|
47
|
+
fallbackLabel ||
|
|
48
|
+
title
|
|
49
|
+
.split(/\s+/)
|
|
50
|
+
.slice(0, 2)
|
|
51
|
+
.map((w) => w[0]?.toUpperCase() ?? "")
|
|
52
|
+
.join("") ||
|
|
53
|
+
"?";
|
|
54
|
+
|
|
55
|
+
const photoAspect =
|
|
56
|
+
size === "compact" ? "aspect-square" : size === "small" ? "aspect-[4/3]" : "aspect-square";
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Comp
|
|
60
|
+
href={href}
|
|
61
|
+
type={onClick && !href ? "button" : undefined}
|
|
62
|
+
onClick={onClick}
|
|
63
|
+
data-slot="entity-media-card"
|
|
64
|
+
className={cn(
|
|
65
|
+
"group flex overflow-hidden rounded-2xl border border-white/45 bg-card text-left shadow-[0_8px_30px_rgb(0,0,0,0.04)] transition-[transform,border-color] duration-200 dark:border-white/12",
|
|
66
|
+
"hover:-translate-y-0.5 hover:border-primary/15",
|
|
67
|
+
orientation === "row" ? "flex-row" : "flex-col",
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
"relative shrink-0 overflow-hidden bg-muted",
|
|
74
|
+
orientation === "row" ? "w-[42%] min-w-[7rem]" : "w-full",
|
|
75
|
+
orientation === "stack" && photoAspect,
|
|
76
|
+
imageClassName,
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{!hideImage && imageUrl ? (
|
|
80
|
+
<img
|
|
81
|
+
src={imageUrl}
|
|
82
|
+
alt=""
|
|
83
|
+
className="size-full object-cover transition-transform duration-700 group-hover:scale-110"
|
|
84
|
+
/>
|
|
85
|
+
) : (
|
|
86
|
+
<div className="flex size-full items-center justify-center bg-gradient-to-br from-primary/20 via-muted to-accent-highlight/20 text-2xl font-black text-primary">
|
|
87
|
+
{initials}
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
{overlays ? <div className="absolute inset-0 p-2">{overlays}</div> : null}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div
|
|
94
|
+
className={cn(
|
|
95
|
+
"relative flex min-w-0 flex-1 flex-col bg-white dark:bg-card",
|
|
96
|
+
size === "compact" ? "gap-1 p-2.5" : "gap-1.5 p-3 sm:p-4",
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<div className="flex items-start justify-between gap-2">
|
|
100
|
+
<div className="min-w-0">
|
|
101
|
+
<h3 className="truncate font-bold tracking-tight group-hover:text-primary">{title}</h3>
|
|
102
|
+
{subtitle ? (
|
|
103
|
+
<p className="truncate text-xs font-medium text-muted-foreground">{subtitle}</p>
|
|
104
|
+
) : null}
|
|
105
|
+
{statusLine ? (
|
|
106
|
+
<p className="mt-0.5 text-[9px] font-bold uppercase tracking-widest text-muted-foreground/80">
|
|
107
|
+
{statusLine}
|
|
108
|
+
</p>
|
|
109
|
+
) : null}
|
|
110
|
+
</div>
|
|
111
|
+
{menu}
|
|
112
|
+
</div>
|
|
113
|
+
{meta}
|
|
114
|
+
{tags ? <div className="mt-auto flex flex-wrap gap-1 pt-1">{tags}</div> : null}
|
|
115
|
+
</div>
|
|
116
|
+
</Comp>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FilterSheet — shared filter chrome for floating page toolbars.
|
|
3
|
+
*
|
|
4
|
+
* Popover on desktop / tablet, bottom drawer on small screens.
|
|
5
|
+
* Feature pages supply trigger + body content; this shell owns layout,
|
|
6
|
+
* clear/close, and the mobile Done footer.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { X } from "lucide-react";
|
|
10
|
+
import { cn } from "../lib/cn";
|
|
11
|
+
import { useIsMobile } from "../lib/use-media-query";
|
|
12
|
+
import { Button } from "../primitives/Button";
|
|
13
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
|
|
14
|
+
import {
|
|
15
|
+
Drawer,
|
|
16
|
+
DrawerContent,
|
|
17
|
+
DrawerDescription,
|
|
18
|
+
DrawerHeader,
|
|
19
|
+
DrawerTitle,
|
|
20
|
+
} from "./BottomSheet";
|
|
21
|
+
import {
|
|
22
|
+
ChromeAttachedCaret,
|
|
23
|
+
FLOATING_PAGE_CHROME_POPOVER_OFFSET,
|
|
24
|
+
floatingPageChromeAttachedPopoverClass,
|
|
25
|
+
} from "./FloatingPageChrome";
|
|
26
|
+
|
|
27
|
+
export type FilterSheetProps = {
|
|
28
|
+
open: boolean;
|
|
29
|
+
onOpenChange: (open: boolean) => void;
|
|
30
|
+
/** Desktop / `md+` control — typically a `Button` using `floatingPageChromePillClass`. */
|
|
31
|
+
trigger: React.ReactElement<{ onClick?: React.MouseEventHandler }>;
|
|
32
|
+
/** Optional compact control for small screens; defaults to `trigger` if omitted. */
|
|
33
|
+
mobileTrigger?: React.ReactElement<{ onClick?: React.MouseEventHandler }>;
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
hasActiveFilters?: boolean;
|
|
37
|
+
onClear?: () => void;
|
|
38
|
+
clearLabel?: string;
|
|
39
|
+
doneLabel?: string;
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
contentClassName?: string;
|
|
42
|
+
popoverClassName?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function mergeMobileTriggerClick(
|
|
46
|
+
trigger: React.ReactElement<{ onClick?: React.MouseEventHandler }>,
|
|
47
|
+
openDrawer: () => void,
|
|
48
|
+
): React.ReactElement {
|
|
49
|
+
return React.cloneElement(trigger, {
|
|
50
|
+
onClick: (e: React.MouseEvent) => {
|
|
51
|
+
trigger.props.onClick?.(e);
|
|
52
|
+
openDrawer();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Responsive filter panel used by directory, history, and similar list pages.
|
|
59
|
+
*/
|
|
60
|
+
export function FilterSheet({
|
|
61
|
+
open,
|
|
62
|
+
onOpenChange,
|
|
63
|
+
trigger,
|
|
64
|
+
mobileTrigger,
|
|
65
|
+
title,
|
|
66
|
+
description,
|
|
67
|
+
hasActiveFilters = false,
|
|
68
|
+
onClear,
|
|
69
|
+
clearLabel = "Clear",
|
|
70
|
+
doneLabel = "Done",
|
|
71
|
+
children,
|
|
72
|
+
contentClassName,
|
|
73
|
+
popoverClassName,
|
|
74
|
+
}: FilterSheetProps) {
|
|
75
|
+
const isMobile = useIsMobile();
|
|
76
|
+
const mobileControl = mobileTrigger ?? trigger;
|
|
77
|
+
|
|
78
|
+
const clearControl =
|
|
79
|
+
hasActiveFilters && onClear ? (
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={onClear}
|
|
83
|
+
className="flex items-center gap-1 rounded-lg px-2 py-1 text-muted-foreground/80 transition-colors hover:bg-muted/50 hover:text-foreground"
|
|
84
|
+
>
|
|
85
|
+
<X className="h-3.5 w-3.5" />
|
|
86
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider">
|
|
87
|
+
{clearLabel}
|
|
88
|
+
</span>
|
|
89
|
+
</button>
|
|
90
|
+
) : null;
|
|
91
|
+
|
|
92
|
+
if (isMobile) {
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
{mergeMobileTriggerClick(mobileControl, () => onOpenChange(true))}
|
|
96
|
+
<Drawer open={open} onOpenChange={onOpenChange}>
|
|
97
|
+
<DrawerContent
|
|
98
|
+
className={cn(
|
|
99
|
+
"glass-panel max-h-[92vh] rounded-t-2xl border-border/60 bg-background/90 pb-0 backdrop-blur-xl sm:hidden",
|
|
100
|
+
contentClassName,
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
<DrawerHeader className="flex flex-row items-start justify-between gap-3 space-y-0 border-b border-border/40 px-4 pb-3 pt-2 text-left">
|
|
104
|
+
<div className="min-w-0 space-y-1 pr-8">
|
|
105
|
+
<DrawerTitle className="text-base font-bold tracking-tight text-foreground">
|
|
106
|
+
{title}
|
|
107
|
+
</DrawerTitle>
|
|
108
|
+
{description ? (
|
|
109
|
+
<DrawerDescription className="text-xs text-muted-foreground">
|
|
110
|
+
{description}
|
|
111
|
+
</DrawerDescription>
|
|
112
|
+
) : null}
|
|
113
|
+
</div>
|
|
114
|
+
<div className="flex shrink-0 items-center gap-1">{clearControl}</div>
|
|
115
|
+
</DrawerHeader>
|
|
116
|
+
<div className="max-h-[min(65vh,32rem)] overflow-y-auto px-0">
|
|
117
|
+
<div className="flex flex-col gap-3 p-3 pt-1">{children}</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div className="flex justify-end gap-2 border-t border-border/40 p-4 pb-[calc(1rem+env(safe-area-inset-bottom))]">
|
|
120
|
+
<Button
|
|
121
|
+
type="button"
|
|
122
|
+
variant="outline"
|
|
123
|
+
onClick={() => onOpenChange(false)}
|
|
124
|
+
className="h-9 rounded-xl px-4 text-xs font-semibold uppercase tracking-wider"
|
|
125
|
+
>
|
|
126
|
+
{doneLabel}
|
|
127
|
+
</Button>
|
|
128
|
+
</div>
|
|
129
|
+
</DrawerContent>
|
|
130
|
+
</Drawer>
|
|
131
|
+
</>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Popover open={open} onOpenChange={onOpenChange}>
|
|
137
|
+
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
|
138
|
+
<PopoverContent
|
|
139
|
+
align="end"
|
|
140
|
+
side="bottom"
|
|
141
|
+
sideOffset={FLOATING_PAGE_CHROME_POPOVER_OFFSET}
|
|
142
|
+
className={cn(
|
|
143
|
+
floatingPageChromeAttachedPopoverClass,
|
|
144
|
+
"w-[min(calc(100vw-1rem),24rem)]",
|
|
145
|
+
popoverClassName,
|
|
146
|
+
contentClassName,
|
|
147
|
+
)}
|
|
148
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
149
|
+
>
|
|
150
|
+
<ChromeAttachedCaret align="end" />
|
|
151
|
+
<div className="border-b border-border/40 px-3 py-2.5 text-left">
|
|
152
|
+
<div className="flex items-start justify-between gap-2">
|
|
153
|
+
<div className="min-w-0 pr-2">
|
|
154
|
+
<p className="text-sm font-bold tracking-tight text-foreground">{title}</p>
|
|
155
|
+
{description ? (
|
|
156
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
157
|
+
) : null}
|
|
158
|
+
</div>
|
|
159
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
160
|
+
{clearControl}
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
onClick={() => onOpenChange(false)}
|
|
164
|
+
className="flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground/60 transition-colors hover:bg-muted/50 hover:text-foreground"
|
|
165
|
+
aria-label="Close"
|
|
166
|
+
>
|
|
167
|
+
<X className="h-4 w-4" />
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="flex max-h-[min(70vh,28rem)] flex-col gap-3 overflow-y-auto p-3">
|
|
173
|
+
{children}
|
|
174
|
+
</div>
|
|
175
|
+
</PopoverContent>
|
|
176
|
+
</Popover>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating page chrome — Apple liquid-glass toolbar under the app nav.
|
|
3
|
+
*
|
|
4
|
+
* Back and the page title share an identity control; search, filters, and
|
|
5
|
+
* actions sit on the same frosted tray. Pair with `app-page-chrome` (or pass
|
|
6
|
+
* `children` to `FloatingPageChromeBar`) so page content scrolls below the
|
|
7
|
+
* bar instead of under it.
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode, Ref } from "react";
|
|
10
|
+
import { ChevronLeft, Search, X } from "lucide-react";
|
|
11
|
+
import { cn } from "../lib/cn";
|
|
12
|
+
import { Input } from "../primitives/Input";
|
|
13
|
+
|
|
14
|
+
/** Shared control height for the sticky chrome row. */
|
|
15
|
+
export const FLOATING_PAGE_CHROME_HEIGHT_CLASS = "h-9";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pinned rail below the fixed app chrome. Not sticky — pair with
|
|
19
|
+
* `app-page-chrome` so page content scrolls in `.app-page-chrome-body`.
|
|
20
|
+
*/
|
|
21
|
+
export const floatingPageChromeShellClass =
|
|
22
|
+
"shrink-0 app-frame-pad pt-2 pb-1.5";
|
|
23
|
+
|
|
24
|
+
/** Inner Apple glass capsule that holds identity, search, and actions. */
|
|
25
|
+
export const floatingPageChromeTrayClass =
|
|
26
|
+
"glass-page-toolbar flex w-full min-w-0 flex-col gap-2 px-1.5 py-1 sm:px-2";
|
|
27
|
+
|
|
28
|
+
/** App-bar utility island (settings + notifications) — still a standalone chip. */
|
|
29
|
+
const glassChip =
|
|
30
|
+
"border border-border/50 bg-card/80 shadow-lg backdrop-blur-xl dark:border-white/10 dark:bg-zinc-900/70 dark:shadow-black/30";
|
|
31
|
+
|
|
32
|
+
/** Inset control sitting on the page toolbar glass (not a second chip). */
|
|
33
|
+
const pageToolbarControl =
|
|
34
|
+
"border border-white/35 bg-white/20 shadow-none backdrop-blur-sm dark:border-white/10 dark:bg-white/[0.07]";
|
|
35
|
+
|
|
36
|
+
/** Combined back + page name — flat on the glass tray. */
|
|
37
|
+
export const floatingPageChromeIdentityClass =
|
|
38
|
+
"flex h-9 min-w-0 shrink items-center rounded-full";
|
|
39
|
+
|
|
40
|
+
/** Search field as an inset pill on the glass tray. */
|
|
41
|
+
export const floatingPageChromeSearchWrapClass = cn(
|
|
42
|
+
"relative flex h-9 w-full max-w-md items-center rounded-full",
|
|
43
|
+
pageToolbarControl,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const floatingPageChromeSearchInputClass =
|
|
47
|
+
"h-9 w-full rounded-full border-0 bg-transparent py-0 pl-9 pr-8 text-sm leading-none shadow-none focus-visible:ring-0";
|
|
48
|
+
|
|
49
|
+
/** View & filters / sort — inset on the glass tray, same height as search. */
|
|
50
|
+
export const floatingPageChromePillClass = cn(
|
|
51
|
+
"h-9 gap-1.5 rounded-full px-2.5 text-xs font-semibold hover:bg-white/40 data-[state=open]:bg-white/50 dark:hover:bg-white/10 dark:data-[state=open]:bg-white/12",
|
|
52
|
+
pageToolbarControl,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/** Icon-only control (mobile search, mobile sort). */
|
|
56
|
+
export const floatingPageChromeIconPillClass = cn(
|
|
57
|
+
"h-9 w-9 shrink-0 rounded-full hover:bg-white/40 data-[state=open]:bg-white/50 dark:hover:bg-white/10 dark:data-[state=open]:bg-white/12",
|
|
58
|
+
pageToolbarControl,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
/** Cluster of related icon actions (add / layout / more). */
|
|
62
|
+
export const floatingPageChromeClusterClass = cn(
|
|
63
|
+
"inline-flex h-9 w-fit items-center gap-0.5 rounded-full p-0.5",
|
|
64
|
+
pageToolbarControl,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/** App-bar utility island (settings + notifications). */
|
|
68
|
+
export const appChromeActionClusterClass = cn(
|
|
69
|
+
"inline-flex h-10 w-fit items-center gap-0.5 rounded-full p-0.5",
|
|
70
|
+
glassChip,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/** Primary CTA in the chrome row. */
|
|
74
|
+
export const floatingPageChromePrimaryClass =
|
|
75
|
+
"h-9 gap-1.5 rounded-full px-3 text-xs font-semibold shadow-lg";
|
|
76
|
+
|
|
77
|
+
/** Gap between a trigger pill and its attached menu (leaves room for the caret). */
|
|
78
|
+
export const FLOATING_PAGE_CHROME_POPOVER_OFFSET = 10;
|
|
79
|
+
|
|
80
|
+
export const floatingPageChromeAttachedPopoverClass =
|
|
81
|
+
"overflow-visible rounded-2xl border border-border/50 bg-card/95 p-0 shadow-[0_16px_40px_-18px_rgba(15,23,42,0.42)] backdrop-blur-xl dark:border-white/10 dark:bg-zinc-900/92";
|
|
82
|
+
|
|
83
|
+
/** Inner buttons sitting inside a glass cluster (no second chip). */
|
|
84
|
+
export const appChromeClusterButtonClass =
|
|
85
|
+
"relative z-[70] h-8 w-8 shrink-0 rounded-full px-0 text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground data-[state=open]:bg-muted/80 data-[state=open]:text-foreground";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Small pointer that visually joins a popover to its trigger.
|
|
89
|
+
* `align="end"` matches popover `align="end"` (caret sits under the right-hand control).
|
|
90
|
+
*/
|
|
91
|
+
export function ChromeAttachedCaret({
|
|
92
|
+
align = "end",
|
|
93
|
+
}: {
|
|
94
|
+
align?: "start" | "end";
|
|
95
|
+
}) {
|
|
96
|
+
return (
|
|
97
|
+
<span
|
|
98
|
+
aria-hidden
|
|
99
|
+
className={cn(
|
|
100
|
+
"pointer-events-none absolute -top-[7px] h-3.5 w-3.5 rotate-45 border-l border-t border-border/50 bg-card/95 dark:border-white/10 dark:bg-zinc-900/92",
|
|
101
|
+
align === "end" ? "right-5" : "left-5",
|
|
102
|
+
)}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type FloatingPageChromeIdentityProps = {
|
|
108
|
+
onBack?: () => void;
|
|
109
|
+
title: ReactNode;
|
|
110
|
+
count?: ReactNode;
|
|
111
|
+
/** Spoken name for the count badge, e.g. "253 people". */
|
|
112
|
+
countLabel?: string;
|
|
113
|
+
backLabel?: string;
|
|
114
|
+
className?: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export function FloatingPageChromeIdentity({
|
|
118
|
+
onBack,
|
|
119
|
+
title,
|
|
120
|
+
count,
|
|
121
|
+
countLabel,
|
|
122
|
+
backLabel = "Go back",
|
|
123
|
+
className,
|
|
124
|
+
}: FloatingPageChromeIdentityProps) {
|
|
125
|
+
const backClassName =
|
|
126
|
+
"flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-foreground transition-colors hover:bg-muted/70 active:scale-[0.96]";
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
data-slot="floating-page-chrome-identity"
|
|
131
|
+
className={cn(
|
|
132
|
+
floatingPageChromeIdentityClass,
|
|
133
|
+
"max-w-[min(28rem,calc(100%-6.5rem))]",
|
|
134
|
+
className,
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{onBack ? (
|
|
138
|
+
<button type="button" onClick={onBack} aria-label={backLabel} className={backClassName}>
|
|
139
|
+
<ChevronLeft className="h-4 w-4" aria-hidden />
|
|
140
|
+
</button>
|
|
141
|
+
) : (
|
|
142
|
+
<span className="w-2.5 shrink-0" aria-hidden />
|
|
143
|
+
)}
|
|
144
|
+
<h1 className="min-w-0 truncate text-sm font-semibold tracking-tight text-foreground">
|
|
145
|
+
{title}
|
|
146
|
+
</h1>
|
|
147
|
+
{count != null && count !== false ? (
|
|
148
|
+
<span
|
|
149
|
+
className="page-header-count ml-1.5 mr-2.5"
|
|
150
|
+
aria-label={countLabel}
|
|
151
|
+
>
|
|
152
|
+
{count}
|
|
153
|
+
</span>
|
|
154
|
+
) : (
|
|
155
|
+
<span className="w-2.5 shrink-0" aria-hidden />
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type FloatingPageChromeSearchFieldProps = {
|
|
162
|
+
value: string;
|
|
163
|
+
onChange: (value: string) => void;
|
|
164
|
+
onClear?: () => void;
|
|
165
|
+
placeholder: string;
|
|
166
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
167
|
+
className?: string;
|
|
168
|
+
fullWidth?: boolean;
|
|
169
|
+
type?: "search" | "text";
|
|
170
|
+
"aria-label"?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Search pill used in the chrome row and the mobile reveal row. */
|
|
174
|
+
export function FloatingPageChromeSearchField({
|
|
175
|
+
value,
|
|
176
|
+
onChange,
|
|
177
|
+
onClear,
|
|
178
|
+
placeholder,
|
|
179
|
+
inputRef,
|
|
180
|
+
className,
|
|
181
|
+
fullWidth = false,
|
|
182
|
+
type = "search",
|
|
183
|
+
"aria-label": ariaLabel,
|
|
184
|
+
}: FloatingPageChromeSearchFieldProps) {
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
data-slot="floating-page-chrome-search"
|
|
188
|
+
className={cn(floatingPageChromeSearchWrapClass, fullWidth && "max-w-none", className)}
|
|
189
|
+
>
|
|
190
|
+
<Search className="pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/50" />
|
|
191
|
+
<Input
|
|
192
|
+
ref={inputRef}
|
|
193
|
+
type={type}
|
|
194
|
+
enterKeyHint="search"
|
|
195
|
+
placeholder={placeholder}
|
|
196
|
+
value={value}
|
|
197
|
+
onChange={(e) => onChange(e.target.value)}
|
|
198
|
+
className={floatingPageChromeSearchInputClass}
|
|
199
|
+
autoComplete="off"
|
|
200
|
+
aria-label={ariaLabel}
|
|
201
|
+
/>
|
|
202
|
+
{value ? (
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
onClick={onClear}
|
|
206
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
207
|
+
aria-label="Clear search"
|
|
208
|
+
>
|
|
209
|
+
<X className="h-3.5 w-3.5" />
|
|
210
|
+
</button>
|
|
211
|
+
) : null}
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type FloatingPageChromeBarProps = {
|
|
217
|
+
identity: ReactNode;
|
|
218
|
+
search?: ReactNode;
|
|
219
|
+
actions?: ReactNode;
|
|
220
|
+
below?: ReactNode;
|
|
221
|
+
/** Page body — scrolls independently so it cannot pass under the toolbar. */
|
|
222
|
+
children?: ReactNode;
|
|
223
|
+
className?: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Shared chrome row: identity, optional centered search, trailing actions.
|
|
228
|
+
* `below` is a second row (mobile search, compact TOC) and is not itself a full-bleed bar.
|
|
229
|
+
* Pass `children` to pin this bar above a nested scroller (content never slides under it).
|
|
230
|
+
*/
|
|
231
|
+
export function FloatingPageChromeBar({
|
|
232
|
+
identity,
|
|
233
|
+
search,
|
|
234
|
+
actions,
|
|
235
|
+
below,
|
|
236
|
+
children,
|
|
237
|
+
className,
|
|
238
|
+
}: FloatingPageChromeBarProps) {
|
|
239
|
+
const bar = (
|
|
240
|
+
<div
|
|
241
|
+
data-slot="floating-page-chrome"
|
|
242
|
+
className={cn(floatingPageChromeShellClass, children == null && className)}
|
|
243
|
+
>
|
|
244
|
+
<div className={floatingPageChromeTrayClass}>
|
|
245
|
+
<div className="flex h-9 w-full min-w-0 items-center gap-2">
|
|
246
|
+
{identity}
|
|
247
|
+
{search ? (
|
|
248
|
+
<>
|
|
249
|
+
<div className="min-w-0 flex-1 md:hidden" aria-hidden />
|
|
250
|
+
<div className="hidden min-w-0 flex-1 justify-center px-0.5 sm:px-2 md:flex">
|
|
251
|
+
{search}
|
|
252
|
+
</div>
|
|
253
|
+
</>
|
|
254
|
+
) : (
|
|
255
|
+
<div className="min-w-0 flex-1" aria-hidden />
|
|
256
|
+
)}
|
|
257
|
+
{actions ? (
|
|
258
|
+
<div className="flex h-9 shrink-0 items-center justify-end gap-1.5">{actions}</div>
|
|
259
|
+
) : null}
|
|
260
|
+
</div>
|
|
261
|
+
{below}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (children == null) return bar;
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div className={cn("app-page-chrome", className)}>
|
|
270
|
+
{bar}
|
|
271
|
+
<div className="app-page-chrome-body">{children}</div>
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Shell + glass tray for pages that compose the chrome row by hand. */
|
|
277
|
+
export function FloatingPageChromeShell({
|
|
278
|
+
children,
|
|
279
|
+
below,
|
|
280
|
+
className,
|
|
281
|
+
}: {
|
|
282
|
+
children: ReactNode;
|
|
283
|
+
below?: ReactNode;
|
|
284
|
+
className?: string;
|
|
285
|
+
}) {
|
|
286
|
+
return (
|
|
287
|
+
<div data-slot="floating-page-chrome-shell" className={cn(floatingPageChromeShellClass, className)}>
|
|
288
|
+
<div className={floatingPageChromeTrayClass}>
|
|
289
|
+
{children}
|
|
290
|
+
{below}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import { Label } from "../primitives/Label";
|
|
4
|
+
|
|
5
|
+
export type FormFieldProps = {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Label + control + helper/error stack for consistent form density.
|
|
17
|
+
*/
|
|
18
|
+
export function FormField({
|
|
19
|
+
id,
|
|
20
|
+
label,
|
|
21
|
+
description,
|
|
22
|
+
error,
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
required,
|
|
26
|
+
}: FormFieldProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div data-slot="form-field" className={cn("space-y-2", className)}>
|
|
29
|
+
<Label htmlFor={id}>
|
|
30
|
+
{label}
|
|
31
|
+
{required ? <span className="text-destructive">*</span> : null}
|
|
32
|
+
</Label>
|
|
33
|
+
{children}
|
|
34
|
+
{description && !error ? (
|
|
35
|
+
<p className="text-xs font-medium text-muted-foreground">{description}</p>
|
|
36
|
+
) : null}
|
|
37
|
+
{error ? (
|
|
38
|
+
<p className="text-xs font-semibold text-destructive" role="alert">
|
|
39
|
+
{error}
|
|
40
|
+
</p>
|
|
41
|
+
) : null}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
|
|
5
|
+
const glassCardVariants = cva("relative overflow-hidden", {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: "glass rounded-[var(--radius-card)]",
|
|
9
|
+
panel: "glass-panel rounded-[var(--radius-card)]",
|
|
10
|
+
subtle: "glass-subtle rounded-[var(--radius-card)]",
|
|
11
|
+
header: "glass-header",
|
|
12
|
+
menu: "glass-menu rounded-[var(--radius-card)]",
|
|
13
|
+
app: "app-card",
|
|
14
|
+
},
|
|
15
|
+
padding: {
|
|
16
|
+
none: "p-0",
|
|
17
|
+
sm: "p-3 sm:p-4",
|
|
18
|
+
md: "p-4 sm:p-5",
|
|
19
|
+
lg: "p-5 sm:p-6",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
padding: "md",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type GlassCardProps = React.ComponentProps<"div"> &
|
|
29
|
+
VariantProps<typeof glassCardVariants>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Opinionated glass surface — prefer this over raw `.glass*` class strings.
|
|
33
|
+
*/
|
|
34
|
+
const GlassCard = React.forwardRef<HTMLDivElement, GlassCardProps>(
|
|
35
|
+
({ className, variant, padding, ...props }, ref) => (
|
|
36
|
+
<div
|
|
37
|
+
ref={ref}
|
|
38
|
+
data-slot="glass-card"
|
|
39
|
+
data-variant={variant ?? "default"}
|
|
40
|
+
className={cn(glassCardVariants({ variant, padding }), className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
GlassCard.displayName = "GlassCard";
|
|
46
|
+
|
|
47
|
+
export { GlassCard, glassCardVariants };
|