@sovereignfs/ui 0.54.0 → 0.83.1
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.module.css +1 -1
- package/dist/Alert.module.css +1 -1
- package/dist/AppsLauncher.module.css +96 -0
- package/dist/Avatar.module.css +1 -1
- package/dist/Badge.module.css +30 -7
- package/dist/BalanceChip.module.css +1 -1
- package/dist/Button.module.css +18 -2
- package/dist/Card.module.css +1 -1
- package/dist/CardTile.module.css +181 -0
- package/dist/CodeTextarea.module.css +1 -1
- package/dist/Collapsible.module.css +1 -1
- package/dist/ColorPicker.module.css +132 -0
- package/dist/Combobox.module.css +2 -2
- package/dist/Command.module.css +1 -1
- package/dist/ConfirmDialog.module.css +2 -2
- package/dist/ConsentPrompt.module.css +21 -0
- package/dist/DatePicker.module.css +1 -1
- package/dist/Dialog.module.css +130 -21
- package/dist/Drawer.module.css +36 -7
- package/dist/Header.module.css +62 -0
- package/dist/HeaderFooterLayout.module.css +21 -0
- package/dist/IconPicker.module.css +1 -1
- package/dist/Input.module.css +1 -1
- package/dist/Kbd.module.css +1 -1
- package/dist/Markdown.module.css +24 -0
- package/dist/Menu.module.css +1 -0
- package/dist/Message.module.css +1 -1
- package/dist/MessageScroller.module.css +12 -1
- package/dist/MobileAppsDrawer.module.css +10 -9
- package/dist/MobileFooter.module.css +1 -1
- package/dist/MobileHeader.module.css +14 -1
- package/dist/NavList.module.css +130 -0
- package/dist/NavTabs.module.css +1 -1
- package/dist/NotificationsPanel.module.css +258 -0
- package/dist/OverlayHeader.module.css +1 -1
- package/dist/PageContainer.module.css +62 -0
- package/dist/PageHeader.module.css +54 -0
- package/dist/PageLayout.module.css +51 -0
- package/dist/Popover.module.css +1 -1
- package/dist/QuantityStepper.module.css +1 -1
- package/dist/Resizable.module.css +3 -3
- package/dist/RootLayout.module.css +12 -0
- package/dist/SegmentedControl.module.css +1 -1
- package/dist/Select.module.css +1 -1
- package/dist/Sheet.module.css +4 -1
- package/dist/SplitPane.module.css +4 -4
- package/dist/StatusBadge.module.css +1 -1
- package/dist/StepStrip.module.css +53 -0
- package/dist/SuggestionInput.module.css +2 -2
- package/dist/SwipableMobileCarousel.module.css +12 -3
- package/dist/SwipableMobileCarouselDots.module.css +15 -0
- package/dist/SwipeStack.module.css +101 -0
- package/dist/SystemBanner.module.css +10 -0
- package/dist/Table.module.css +2 -2
- package/dist/Tabs.module.css +1 -1
- package/dist/TagInput.module.css +2 -2
- package/dist/Textarea.module.css +9 -1
- package/dist/ThreeColumnLayout.module.css +35 -0
- package/dist/Toast.module.css +62 -3
- package/dist/Tooltip.module.css +1 -1
- package/dist/UserMenu.module.css +90 -0
- package/dist/index.d.ts +1047 -45
- package/dist/index.js +3908 -1456
- package/dist/tokens/primitives.css +11 -0
- package/dist/tokens/semantic.css +32 -0
- package/dist/tokens/theme-presets.css +33 -0
- package/package.json +25 -9
- package/src/__tests__/package-json.test.ts +8 -0
- package/src/components/Accordion/Accordion.module.css +1 -1
- package/src/components/Alert/Alert.module.css +1 -1
- package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
- package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
- package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
- package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
- package/src/components/Avatar/Avatar.module.css +1 -1
- package/src/components/Badge/Badge.module.css +30 -7
- package/src/components/Badge/Badge.stories.tsx +16 -4
- package/src/components/Badge/Badge.tsx +7 -4
- package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
- package/src/components/BalanceChip/BalanceChip.module.css +1 -1
- package/src/components/Button/Button.module.css +18 -2
- package/src/components/Card/Card.module.css +1 -1
- package/src/components/CardTile/CardTile.module.css +181 -0
- package/src/components/CardTile/CardTile.stories.tsx +86 -0
- package/src/components/CardTile/CardTile.tsx +128 -0
- package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
- package/src/components/Collapsible/Collapsible.module.css +1 -1
- package/src/components/ColorPicker/ColorPicker.module.css +132 -0
- package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
- package/src/components/ColorPicker/ColorPicker.tsx +157 -0
- package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
- package/src/components/Combobox/Combobox.module.css +2 -2
- package/src/components/Command/Command.module.css +1 -1
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
- package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
- package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
- package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
- package/src/components/ContextMenu/ContextMenu.tsx +1 -1
- package/src/components/DatePicker/DatePicker.module.css +1 -1
- package/src/components/Dialog/Dialog.module.css +130 -21
- package/src/components/Dialog/Dialog.stories.tsx +179 -2
- package/src/components/Dialog/Dialog.tsx +129 -35
- package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
- package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
- package/src/components/Drawer/Drawer.module.css +36 -7
- package/src/components/Drawer/Drawer.tsx +9 -10
- package/src/components/Drawer/__tests__/Drawer.test.tsx +33 -6
- package/src/components/GripIcon/GripIcon.tsx +27 -0
- package/src/components/Header/Header.module.css +62 -0
- package/src/components/Header/Header.stories.tsx +452 -0
- package/src/components/Header/Header.tsx +102 -0
- package/src/components/Header/__tests__/Header.test.tsx +104 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
- package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
- package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
- package/src/components/Icon/icons/bold.tsx +20 -0
- package/src/components/Icon/icons/calendar.tsx +4 -4
- package/src/components/Icon/icons/carrot.tsx +3 -3
- package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
- package/src/components/Icon/icons/code.tsx +21 -0
- package/src/components/Icon/icons/copy.tsx +21 -0
- package/src/components/Icon/icons/credit-card.tsx +21 -0
- package/src/components/Icon/icons/database.tsx +22 -0
- package/src/components/Icon/icons/download.tsx +22 -0
- package/src/components/Icon/icons/file-text.tsx +24 -0
- package/src/components/Icon/icons/folder-closed.tsx +21 -0
- package/src/components/Icon/icons/folder-open.tsx +20 -0
- package/src/components/Icon/icons/folder.tsx +20 -0
- package/src/components/Icon/icons/folders.tsx +21 -0
- package/src/components/Icon/icons/hat-glasses.tsx +24 -0
- package/src/components/Icon/icons/history.tsx +22 -0
- package/src/components/Icon/icons/inbox.tsx +21 -0
- package/src/components/Icon/icons/index.ts +161 -1
- package/src/components/Icon/icons/italic.tsx +22 -0
- package/src/components/Icon/icons/layers.tsx +22 -0
- package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
- package/src/components/Icon/icons/layout-grid.tsx +23 -0
- package/src/components/Icon/icons/link.tsx +21 -0
- package/src/components/Icon/icons/list-ordered.tsx +25 -0
- package/src/components/Icon/icons/list.tsx +25 -0
- package/src/components/Icon/icons/luggage.tsx +24 -0
- package/src/components/Icon/icons/map-pin.tsx +21 -0
- package/src/components/Icon/icons/minus.tsx +20 -0
- package/src/components/Icon/icons/paint-bucket.tsx +23 -0
- package/src/components/Icon/icons/panel-left.tsx +21 -0
- package/src/components/Icon/icons/pin.tsx +21 -0
- package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
- package/src/components/Icon/icons/refresh-cw.tsx +23 -0
- package/src/components/Icon/icons/rotate-cw.tsx +21 -0
- package/src/components/Icon/icons/route.tsx +22 -0
- package/src/components/Icon/icons/send.tsx +21 -0
- package/src/components/Icon/icons/share-2.tsx +24 -0
- package/src/components/Icon/icons/sheet.tsx +24 -0
- package/src/components/Icon/icons/square-kanban.tsx +23 -0
- package/src/components/Icon/icons/table.tsx +23 -0
- package/src/components/Icon/icons/user-round-plus.tsx +23 -0
- package/src/components/Icon/icons/users.tsx +23 -0
- package/src/components/IconPicker/IconPicker.module.css +1 -1
- package/src/components/Input/Input.module.css +1 -1
- package/src/components/Input/Input.tsx +8 -2
- package/src/components/Kbd/Kbd.module.css +1 -1
- package/src/components/Markdown/Markdown.module.css +24 -0
- package/src/components/Markdown/Markdown.stories.tsx +10 -0
- package/src/components/Markdown/Markdown.tsx +166 -29
- package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
- package/src/components/Menu/Menu.module.css +1 -0
- package/src/components/Menu/Menu.tsx +72 -22
- package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
- package/src/components/Menubar/Menubar.tsx +1 -1
- package/src/components/Message/Message.module.css +1 -1
- package/src/components/MessageScroller/MessageScroller.module.css +12 -1
- package/src/components/MessageScroller/MessageScroller.tsx +12 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +10 -9
- package/src/components/MobileFooter/MobileFooter.module.css +1 -1
- package/src/components/MobileFooter/MobileFooter.tsx +15 -2
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +58 -0
- package/src/components/MobileHeader/MobileHeader.module.css +14 -1
- package/src/components/MobileHeader/MobileHeader.tsx +15 -2
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +56 -1
- package/src/components/NavList/NavList.module.css +130 -0
- package/src/components/NavList/NavList.tsx +136 -0
- package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
- package/src/components/NavTabs/NavTabs.module.css +1 -1
- package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
- package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
- package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
- package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
- package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
- package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
- package/src/components/PageContainer/PageContainer.module.css +62 -0
- package/src/components/PageContainer/PageContainer.tsx +37 -4
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +41 -8
- package/src/components/PageHeader/PageHeader.module.css +54 -0
- package/src/components/PageHeader/PageHeader.tsx +146 -5
- package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
- package/src/components/PageLayout/PageLayout.module.css +51 -0
- package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
- package/src/components/PageLayout/PageLayout.tsx +51 -0
- package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
- package/src/components/Popover/Popover.module.css +1 -1
- package/src/components/Popover/Popover.tsx +86 -4
- package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
- package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
- package/src/components/Resizable/Resizable.module.css +3 -3
- package/src/components/RootLayout/RootLayout.module.css +12 -0
- package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
- package/src/components/RootLayout/RootLayout.tsx +185 -0
- package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
- package/src/components/Select/Select.module.css +1 -1
- package/src/components/Sheet/Sheet.module.css +4 -1
- package/src/components/Sheet/Sheet.tsx +5 -5
- package/src/components/SplitPane/SplitPane.module.css +4 -4
- package/src/components/StatusBadge/StatusBadge.module.css +1 -1
- package/src/components/StepStrip/StepStrip.module.css +53 -0
- package/src/components/StepStrip/StepStrip.tsx +74 -0
- package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +12 -3
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +25 -9
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +81 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +15 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +12 -1
- package/src/components/SwipeStack/SwipeStack.module.css +101 -0
- package/src/components/SwipeStack/SwipeStack.tsx +199 -0
- package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
- package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
- package/src/components/SystemBanner/SystemBanner.module.css +10 -0
- package/src/components/Table/Table.module.css +2 -2
- package/src/components/Tabs/Tabs.module.css +1 -1
- package/src/components/TagInput/TagInput.module.css +2 -2
- package/src/components/Textarea/Textarea.module.css +9 -1
- package/src/components/Textarea/Textarea.tsx +28 -5
- package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
- package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
- package/src/components/Toast/Toast.module.css +62 -3
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Tooltip/Tooltip.module.css +1 -1
- package/src/components/UserMenu/UserMenu.module.css +90 -0
- package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
- package/src/components/UserMenu/UserMenu.tsx +132 -0
- package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -0
- package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +118 -0
- package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +82 -0
- package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/usePublishShellChromeHeight.ts +96 -0
- package/src/hooks/useReorderSensors.ts +88 -0
- package/src/hooks/useSnapCarousel.ts +211 -6
- package/src/hooks/useSwipeStack.ts +235 -0
- package/src/index.ts +61 -1
- package/src/overlay-shell.ts +65 -0
- package/src/stories/DesignSystemOverview.stories.tsx +617 -11
- package/src/stories/GripIcon.stories.tsx +29 -0
- package/src/stories/LayoutSystem.stories.tsx +639 -0
- package/src/stories/MenuEntries.stories.tsx +86 -0
- package/src/stories/NavList.stories.tsx +147 -0
- package/src/stories/PageContainer.stories.tsx +52 -7
- package/src/stories/PageHeader.stories.tsx +54 -1
- package/src/stories/StepStrip.stories.tsx +76 -0
- package/src/stories/SwipableMobileCarousel.stories.tsx +58 -0
- package/src/stories/SwipeStack.stories.tsx +200 -0
- package/src/stories/Textarea.stories.tsx +12 -0
- package/src/stories/TokenGallery.stories.tsx +50 -0
- package/src/tokens/primitives.css +11 -0
- package/src/tokens/semantic.css +32 -0
- package/src/tokens/theme-presets.css +33 -0
- package/src/tokens/theme-presets.ts +72 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import styles from './Header.module.css';
|
|
3
|
+
|
|
4
|
+
export interface HeaderPlugin {
|
|
5
|
+
/** Plugin display name, rendered next to its icon. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Plugin icon — already sized (28px) and built by the consumer (typically
|
|
8
|
+
* an `<img src="/plugin-icons/<id>.svg">`), matching `MobileHeader`'s own
|
|
9
|
+
* router-agnostic convention for icon-shaped slots. */
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
/** Where the plugin name/icon links to — typically the plugin's own
|
|
12
|
+
* `routePrefix`. Omit to render plain (non-interactive) text instead of a
|
|
13
|
+
* link — e.g. a plugin's own home page linking to itself would be a no-op. */
|
|
14
|
+
href?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
|
18
|
+
/** Instance name — derives the brand badge's initial (its first character,
|
|
19
|
+
* uppercased) and the badge's accessible label. Does not render as text
|
|
20
|
+
* anywhere in this component (unlike `MobileHeader`'s optional `title`). */
|
|
21
|
+
instanceName: string;
|
|
22
|
+
/** Optional instance logo image; falls back to `instanceName`'s initial
|
|
23
|
+
* (matching the runtime shell's own sidebar/mobile-header fallback). */
|
|
24
|
+
instanceLogoUrl?: string;
|
|
25
|
+
/** Where the brand badge links — typically the Launcher plugin's route. */
|
|
26
|
+
homeHref: string;
|
|
27
|
+
/** The active plugin, rendered next to the brand badge. Omit for a page
|
|
28
|
+
* with no single active plugin (e.g. the Launcher itself). */
|
|
29
|
+
plugin?: HeaderPlugin;
|
|
30
|
+
/** Apps launcher trigger — already wired by the consumer (icon button plus
|
|
31
|
+
* whatever popover/drawer it opens). Always rendered, matching
|
|
32
|
+
* `MobileHeader`'s bell/avatarMenu boundary: this component owns layout
|
|
33
|
+
* only, never launcher/notification/account interaction logic. */
|
|
34
|
+
launcher: ReactNode;
|
|
35
|
+
/** Notification bell trigger — always rendered; not overridable. Same
|
|
36
|
+
* "just an icon slot" boundary as `MobileHeader`'s own `bell` prop. */
|
|
37
|
+
notifications: ReactNode;
|
|
38
|
+
/** Avatar / account menu trigger — always rendered; not overridable. */
|
|
39
|
+
avatarMenu: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The web top-bar header: brand badge (+ optional active plugin) on the
|
|
44
|
+
* left, launcher · notifications · avatar menu on the right. This is the
|
|
45
|
+
* `shell: minimal` counterpart to the runtime shell's own sidebar — a plugin
|
|
46
|
+
* that owns its whole viewport (no platform sidebar) renders this instead,
|
|
47
|
+
* the same way it renders `MobileHeader` in place of the platform's mobile
|
|
48
|
+
* chrome. Extracted into a shared component so a `shell: minimal` plugin
|
|
49
|
+
* doesn't have to hand-roll its own top bar from scratch.
|
|
50
|
+
*
|
|
51
|
+
* Presentational only — no data fetching, no account/notification/launcher
|
|
52
|
+
* logic of its own. `launcher`/`notifications`/`avatarMenu` are supplied
|
|
53
|
+
* fully built, exactly like `MobileHeader`'s `bell`/`avatarMenu`: this
|
|
54
|
+
* component stays router-agnostic and interaction-agnostic, and never
|
|
55
|
+
* duplicates a real `AccountMenu`/notification-panel/apps-switcher
|
|
56
|
+
* implementation.
|
|
57
|
+
*/
|
|
58
|
+
export function Header({
|
|
59
|
+
instanceName,
|
|
60
|
+
instanceLogoUrl,
|
|
61
|
+
homeHref,
|
|
62
|
+
plugin,
|
|
63
|
+
launcher,
|
|
64
|
+
notifications,
|
|
65
|
+
avatarMenu,
|
|
66
|
+
className,
|
|
67
|
+
...rest
|
|
68
|
+
}: HeaderProps) {
|
|
69
|
+
const brandInitial = instanceName.charAt(0).toUpperCase() || 'S';
|
|
70
|
+
const cls = [styles.header, className].filter(Boolean).join(' ');
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<header className={cls} {...rest}>
|
|
74
|
+
<div className={styles.left}>
|
|
75
|
+
<a href={homeHref} className={styles.brandBadge} aria-label={`${instanceName} home`}>
|
|
76
|
+
{instanceLogoUrl ? (
|
|
77
|
+
<img src={instanceLogoUrl} alt="" className={styles.brandLogoImg} />
|
|
78
|
+
) : (
|
|
79
|
+
brandInitial
|
|
80
|
+
)}
|
|
81
|
+
</a>
|
|
82
|
+
{plugin &&
|
|
83
|
+
(plugin.href ? (
|
|
84
|
+
<a href={plugin.href} className={styles.plugin}>
|
|
85
|
+
{plugin.icon}
|
|
86
|
+
<span className={styles.pluginName}>{plugin.name}</span>
|
|
87
|
+
</a>
|
|
88
|
+
) : (
|
|
89
|
+
<span className={styles.plugin}>
|
|
90
|
+
{plugin.icon}
|
|
91
|
+
<span className={styles.pluginName}>{plugin.name}</span>
|
|
92
|
+
</span>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
<div className={styles.right}>
|
|
96
|
+
{launcher}
|
|
97
|
+
{notifications}
|
|
98
|
+
{avatarMenu}
|
|
99
|
+
</div>
|
|
100
|
+
</header>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
+
import { Header } from '../Header';
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
cleanup();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('Header', () => {
|
|
11
|
+
it('always renders the brand badge, launcher, notifications, and avatar menu slots', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Header
|
|
14
|
+
instanceName="Sovereign"
|
|
15
|
+
homeHref="/launcher"
|
|
16
|
+
launcher={<span data-testid="launcher">launcher</span>}
|
|
17
|
+
notifications={<span data-testid="bell">bell</span>}
|
|
18
|
+
avatarMenu={<span data-testid="avatar">avatar</span>}
|
|
19
|
+
/>,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText('S')).toBeDefined();
|
|
23
|
+
expect(screen.getByTestId('launcher')).toBeDefined();
|
|
24
|
+
expect(screen.getByTestId('bell')).toBeDefined();
|
|
25
|
+
expect(screen.getByTestId('avatar')).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('derives the brand badge initial from instanceName, uppercased', () => {
|
|
29
|
+
render(
|
|
30
|
+
<Header
|
|
31
|
+
instanceName="acme"
|
|
32
|
+
homeHref="/launcher"
|
|
33
|
+
launcher={<span />}
|
|
34
|
+
notifications={<span />}
|
|
35
|
+
avatarMenu={<span />}
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(screen.getByText('A')).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('links the brand badge to homeHref', () => {
|
|
43
|
+
render(
|
|
44
|
+
<Header
|
|
45
|
+
instanceName="Sovereign"
|
|
46
|
+
homeHref="/launcher"
|
|
47
|
+
launcher={<span />}
|
|
48
|
+
notifications={<span />}
|
|
49
|
+
avatarMenu={<span />}
|
|
50
|
+
/>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(screen.getByRole('link', { name: 'Sovereign home' })).toHaveProperty(
|
|
54
|
+
'href',
|
|
55
|
+
expect.stringContaining('/launcher'),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders no plugin by default', () => {
|
|
60
|
+
render(
|
|
61
|
+
<Header
|
|
62
|
+
instanceName="Sovereign"
|
|
63
|
+
homeHref="/launcher"
|
|
64
|
+
launcher={<span />}
|
|
65
|
+
notifications={<span />}
|
|
66
|
+
avatarMenu={<span />}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(screen.queryByText('Notes')).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('renders the active plugin as a link when href is provided', () => {
|
|
74
|
+
render(
|
|
75
|
+
<Header
|
|
76
|
+
instanceName="Sovereign"
|
|
77
|
+
homeHref="/launcher"
|
|
78
|
+
plugin={{ name: 'Notes', icon: <span>icon</span>, href: '/notes' }}
|
|
79
|
+
launcher={<span />}
|
|
80
|
+
notifications={<span />}
|
|
81
|
+
avatarMenu={<span />}
|
|
82
|
+
/>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const link = screen.getByRole('link', { name: /Notes/ });
|
|
86
|
+
expect(link).toHaveProperty('href', expect.stringContaining('/notes'));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders the active plugin as plain text when href is omitted', () => {
|
|
90
|
+
render(
|
|
91
|
+
<Header
|
|
92
|
+
instanceName="Sovereign"
|
|
93
|
+
homeHref="/launcher"
|
|
94
|
+
plugin={{ name: 'Notes' }}
|
|
95
|
+
launcher={<span />}
|
|
96
|
+
notifications={<span />}
|
|
97
|
+
avatarMenu={<span />}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(screen.getByText('Notes')).toBeDefined();
|
|
102
|
+
expect(screen.queryByRole('link', { name: /Notes/ })).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.shell {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.header {
|
|
8
|
+
flex-shrink: 0;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.main {
|
|
13
|
+
flex: 1;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.footer {
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { HeaderFooterLayout } from './HeaderFooterLayout';
|
|
3
|
+
|
|
4
|
+
function Bar({ label }: { label: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
style={{
|
|
8
|
+
display: 'flex',
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
justifyContent: 'center',
|
|
11
|
+
height: '100%',
|
|
12
|
+
fontFamily: 'var(--sv-font-family)',
|
|
13
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
14
|
+
fontWeight: 600,
|
|
15
|
+
color: 'var(--sv-color-text-primary)',
|
|
16
|
+
background: 'var(--sv-color-surface-raised)',
|
|
17
|
+
borderBlock: '1px solid var(--sv-color-border)',
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
{label}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ScrollableMain() {
|
|
26
|
+
return (
|
|
27
|
+
<div style={{ padding: 'var(--sv-space-4)', fontFamily: 'var(--sv-font-family)' }}>
|
|
28
|
+
{Array.from({ length: 30 }, (_, index) => (
|
|
29
|
+
<p key={index} style={{ color: 'var(--sv-color-text-primary)' }}>
|
|
30
|
+
Row {index + 1} — the main region scrolls independently; header and footer stay put.
|
|
31
|
+
</p>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const meta = {
|
|
38
|
+
title: 'Components/HeaderFooterLayout',
|
|
39
|
+
component: HeaderFooterLayout,
|
|
40
|
+
parameters: {
|
|
41
|
+
layout: 'fullscreen',
|
|
42
|
+
docs: {
|
|
43
|
+
description: {
|
|
44
|
+
component:
|
|
45
|
+
'Header + main + optional footer, both fixed-height and independently optional, with main always claiming the remaining height. The vertical counterpart to ThreeColumnLayout — a plain flex column, no color opinions, no responsive behavior of its own.',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
args: {
|
|
50
|
+
children: <ScrollableMain />,
|
|
51
|
+
},
|
|
52
|
+
} satisfies Meta<typeof HeaderFooterLayout>;
|
|
53
|
+
|
|
54
|
+
export default meta;
|
|
55
|
+
type Story = StoryObj<typeof meta>;
|
|
56
|
+
|
|
57
|
+
/** The canonical usage — header + main + footer, all three present. */
|
|
58
|
+
export const Default: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<div style={{ height: '480px' }}>
|
|
61
|
+
<HeaderFooterLayout header={<Bar label="Header" />} footer={<Bar label="Footer" />}>
|
|
62
|
+
<ScrollableMain />
|
|
63
|
+
</HeaderFooterLayout>
|
|
64
|
+
</div>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const HeaderOnly: Story = {
|
|
69
|
+
render: () => (
|
|
70
|
+
<div style={{ height: '480px' }}>
|
|
71
|
+
<HeaderFooterLayout header={<Bar label="Header" />}>
|
|
72
|
+
<ScrollableMain />
|
|
73
|
+
</HeaderFooterLayout>
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const FooterOnly: Story = {
|
|
79
|
+
render: () => (
|
|
80
|
+
<div style={{ height: '480px' }}>
|
|
81
|
+
<HeaderFooterLayout footer={<Bar label="Footer" />}>
|
|
82
|
+
<ScrollableMain />
|
|
83
|
+
</HeaderFooterLayout>
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const MainOnly: Story = {
|
|
89
|
+
render: () => (
|
|
90
|
+
<div style={{ height: '480px' }}>
|
|
91
|
+
<HeaderFooterLayout>
|
|
92
|
+
<ScrollableMain />
|
|
93
|
+
</HeaderFooterLayout>
|
|
94
|
+
</div>
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const CustomHeights: Story = {
|
|
99
|
+
render: () => (
|
|
100
|
+
<div style={{ height: '480px' }}>
|
|
101
|
+
<HeaderFooterLayout
|
|
102
|
+
header={<Bar label="Header (100px)" />}
|
|
103
|
+
footer={<Bar label="Footer (48px)" />}
|
|
104
|
+
headerHeight={100}
|
|
105
|
+
footerHeight={48}
|
|
106
|
+
>
|
|
107
|
+
<ScrollableMain />
|
|
108
|
+
</HeaderFooterLayout>
|
|
109
|
+
</div>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import styles from './HeaderFooterLayout.module.css';
|
|
5
|
+
|
|
6
|
+
export interface HeaderFooterLayoutProps {
|
|
7
|
+
/** Main content — required, always fills whatever height header/footer don't claim. */
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
header?: ReactNode;
|
|
10
|
+
footer?: ReactNode;
|
|
11
|
+
/** px. Fixed height of the header, when present. Default 60 (matches
|
|
12
|
+
* MobileHeader/--sv-shell-header-height's own default). */
|
|
13
|
+
headerHeight?: number;
|
|
14
|
+
/** px. Fixed height of the footer, when present. Default 60 (matches
|
|
15
|
+
* MobileFooter/--sv-shell-footer-height's own default). */
|
|
16
|
+
footerHeight?: number;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* HeaderFooterLayout — header + main + optional footer, both fixed-height
|
|
22
|
+
* and independently optional, with main always claiming the remaining
|
|
23
|
+
* height. The vertical counterpart to ThreeColumnLayout: same purely
|
|
24
|
+
* structural approach (no color/background opinions, no awareness of what's
|
|
25
|
+
* inside each slot, no responsive behavior of its own — a plain flex
|
|
26
|
+
* column), just rotated. "Footer always at the bottom" falls out of flex
|
|
27
|
+
* layout alone; there is no position: fixed or safe-area handling here, so
|
|
28
|
+
* a caller composing this with the platform shell's own chrome still owns
|
|
29
|
+
* that separately.
|
|
30
|
+
*/
|
|
31
|
+
export function HeaderFooterLayout({
|
|
32
|
+
children,
|
|
33
|
+
header,
|
|
34
|
+
footer,
|
|
35
|
+
headerHeight = 60,
|
|
36
|
+
footerHeight = 60,
|
|
37
|
+
className,
|
|
38
|
+
}: HeaderFooterLayoutProps) {
|
|
39
|
+
return (
|
|
40
|
+
<div className={[styles.shell, className].filter(Boolean).join(' ')}>
|
|
41
|
+
{header && (
|
|
42
|
+
<div className={styles.header} style={{ height: headerHeight }}>
|
|
43
|
+
{header}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
<div className={styles.main}>{children}</div>
|
|
47
|
+
{footer && (
|
|
48
|
+
<div className={styles.footer} style={{ height: footerHeight }}>
|
|
49
|
+
{footer}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
+
import { HeaderFooterLayout } from '../HeaderFooterLayout';
|
|
5
|
+
|
|
6
|
+
afterEach(cleanup);
|
|
7
|
+
|
|
8
|
+
describe('HeaderFooterLayout', () => {
|
|
9
|
+
it('renders main content with no header/footer by default', () => {
|
|
10
|
+
render(<HeaderFooterLayout>Main content</HeaderFooterLayout>);
|
|
11
|
+
|
|
12
|
+
expect(screen.getByText('Main content')).toBeDefined();
|
|
13
|
+
expect(screen.queryByText('Header')).toBeNull();
|
|
14
|
+
expect(screen.queryByText('Footer')).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders header and footer when given', () => {
|
|
18
|
+
render(
|
|
19
|
+
<HeaderFooterLayout header={<span>Header</span>} footer={<span>Footer</span>}>
|
|
20
|
+
Main content
|
|
21
|
+
</HeaderFooterLayout>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByText('Header')).toBeDefined();
|
|
25
|
+
expect(screen.getByText('Main content')).toBeDefined();
|
|
26
|
+
expect(screen.getByText('Footer')).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders header without a footer', () => {
|
|
30
|
+
render(<HeaderFooterLayout header={<span>Header</span>}>Main content</HeaderFooterLayout>);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByText('Header')).toBeDefined();
|
|
33
|
+
expect(screen.queryByText('Footer')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders footer without a header', () => {
|
|
37
|
+
render(<HeaderFooterLayout footer={<span>Footer</span>}>Main content</HeaderFooterLayout>);
|
|
38
|
+
|
|
39
|
+
expect(screen.queryByText('Header')).toBeNull();
|
|
40
|
+
expect(screen.getByText('Footer')).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('applies headerHeight and footerHeight as inline heights', () => {
|
|
44
|
+
render(
|
|
45
|
+
<HeaderFooterLayout
|
|
46
|
+
header={<span>Header</span>}
|
|
47
|
+
footer={<span>Footer</span>}
|
|
48
|
+
headerHeight={48}
|
|
49
|
+
footerHeight={72}
|
|
50
|
+
>
|
|
51
|
+
Main content
|
|
52
|
+
</HeaderFooterLayout>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText('Header').parentElement?.style.height).toBe('48px');
|
|
56
|
+
expect(screen.getByText('Footer').parentElement?.style.height).toBe('72px');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function ArrowLeftRightIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M8 3 4 7l4 4" />
|
|
18
|
+
<path d="M4 7h16" />
|
|
19
|
+
<path d="m16 21 4-4-4-4" />
|
|
20
|
+
<path d="M20 17H4" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function BoldIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -14,10 +14,10 @@ export function CalendarIcon(props: SVGProps<SVGSVGElement>) {
|
|
|
14
14
|
strokeLinejoin="round"
|
|
15
15
|
{...props}
|
|
16
16
|
>
|
|
17
|
-
<path d="M8
|
|
18
|
-
<path d="M16
|
|
19
|
-
<rect
|
|
20
|
-
<path d="M3
|
|
17
|
+
<path d="M8 2v3" />
|
|
18
|
+
<path d="M16 2v3" />
|
|
19
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
20
|
+
<path d="M3 9h18" />
|
|
21
21
|
</svg>
|
|
22
22
|
);
|
|
23
23
|
}
|
|
@@ -14,9 +14,9 @@ export function CarrotIcon(props: SVGProps<SVGSVGElement>) {
|
|
|
14
14
|
strokeLinejoin="round"
|
|
15
15
|
{...props}
|
|
16
16
|
>
|
|
17
|
-
<path d="
|
|
18
|
-
<path d="
|
|
19
|
-
<path d="
|
|
17
|
+
<path d="M15 16a1 1 0 0 0-7-7q-4 4-5.987 12.385a.5.5 0 0 0 .602.602Q11 20 15 16l-3-3" />
|
|
18
|
+
<path d="M15 9q4 4 7 0-3-4-7 0 4-4 0-7-4 3 0 7" />
|
|
19
|
+
<path d="m8 15-2.58-2.58" />
|
|
20
20
|
</svg>
|
|
21
21
|
);
|
|
22
22
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function CircleChevronLeftIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<circle cx="12" cy="12" r="10" />
|
|
18
|
+
<path d="m14 16-4-4 4-4" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function CodeIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="m16 18 6-6-6-6" />
|
|
18
|
+
<path d="m8 6-6 6 6 6" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function CopyIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
18
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function CreditCardIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<rect width="20" height="14" x="2" y="5" rx="2" />
|
|
18
|
+
<line x1="2" x2="22" y1="10" y2="10" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function DatabaseIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
18
|
+
<path d="M3 5V19A9 3 0 0 0 21 19V5" />
|
|
19
|
+
<path d="M3 12A9 3 0 0 0 21 12" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Generated by scripts/generate-icons.ts — do not edit.
|
|
2
|
+
// Source: Lucide (https://lucide.dev) — ISC license, see packages/ui/NOTICE.
|
|
3
|
+
import type { SVGProps } from 'react';
|
|
4
|
+
|
|
5
|
+
export function DownloadIcon(props: SVGProps<SVGSVGElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M12 15V3" />
|
|
18
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
19
|
+
<path d="m7 10 5 5 5-5" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
}
|