@fragments-sdk/ui 0.3.0 → 0.4.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/fragments.json +1 -1
- package/package.json +9 -4
- package/src/components/Accordion/Accordion.fragment.tsx +186 -0
- package/src/components/Accordion/Accordion.module.scss +111 -0
- package/src/components/Accordion/index.tsx +271 -0
- package/src/components/Alert/Alert.fragment.tsx +66 -41
- package/src/components/Alert/Alert.module.scss +31 -21
- package/src/components/Alert/index.tsx +202 -73
- package/src/components/AppShell/AppShell.fragment.tsx +315 -0
- package/src/components/AppShell/AppShell.module.scss +213 -0
- package/src/components/AppShell/index.tsx +398 -0
- package/src/components/Avatar/index.tsx +8 -9
- package/src/components/Badge/Badge.module.scss +16 -10
- package/src/components/Badge/index.tsx +20 -6
- package/src/components/Box/Box.fragment.tsx +168 -0
- package/src/components/Box/Box.module.scss +84 -0
- package/src/components/Box/index.tsx +78 -0
- package/src/components/Button/Button.module.scss +42 -0
- package/src/components/Button/index.tsx +67 -33
- package/src/components/ButtonGroup/ButtonGroup.module.scss +37 -0
- package/src/components/ButtonGroup/index.tsx +40 -0
- package/src/components/Card/Card.fragment.tsx +51 -25
- package/src/components/Card/Card.module.scss +52 -5
- package/src/components/Card/index.tsx +154 -53
- package/src/components/Checkbox/Checkbox.module.scss +4 -4
- package/src/components/Checkbox/index.tsx +3 -4
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +201 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +224 -0
- package/src/components/CodeBlock/index.tsx +385 -0
- package/src/components/ColorChip/ColorChip.module.scss +165 -0
- package/src/components/ColorChip/index.tsx +157 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +109 -0
- package/src/components/ColorPicker/index.tsx +107 -0
- package/src/components/Dialog/Dialog.fragment.tsx +9 -0
- package/src/components/Dialog/Dialog.module.scss +26 -7
- package/src/components/Dialog/index.tsx +12 -15
- package/src/components/EmptyState/EmptyState.fragment.tsx +54 -71
- package/src/components/EmptyState/EmptyState.module.scss +9 -9
- package/src/components/EmptyState/index.tsx +104 -69
- package/src/components/Field/Field.fragment.tsx +165 -0
- package/src/components/Field/Field.module.scss +31 -0
- package/src/components/Field/index.tsx +143 -0
- package/src/components/Fieldset/Fieldset.fragment.tsx +166 -0
- package/src/components/Fieldset/Fieldset.module.scss +22 -0
- package/src/components/Fieldset/index.tsx +47 -0
- package/src/components/Form/Form.fragment.tsx +286 -0
- package/src/components/Form/Form.module.scss +8 -0
- package/src/components/Form/index.tsx +53 -0
- package/src/components/Grid/Grid.fragment.tsx +17 -17
- package/src/components/Grid/index.tsx +6 -1
- package/src/components/Header/Header.fragment.tsx +192 -0
- package/src/components/Header/Header.module.scss +209 -0
- package/src/components/Header/index.tsx +363 -0
- package/src/components/Icon/Icon.fragment.tsx +138 -0
- package/src/components/Icon/Icon.module.scss +38 -0
- package/src/components/Icon/index.tsx +58 -0
- package/src/components/Image/Image.fragment.tsx +195 -0
- package/src/components/Image/Image.module.scss +77 -0
- package/src/components/Image/index.tsx +95 -0
- package/src/components/Input/Input.module.scss +75 -2
- package/src/components/Input/index.tsx +60 -21
- package/src/components/Link/Link.fragment.tsx +132 -0
- package/src/components/Link/Link.module.scss +67 -0
- package/src/components/Link/index.tsx +57 -0
- package/src/components/List/List.fragment.tsx +152 -0
- package/src/components/List/List.module.scss +71 -0
- package/src/components/List/index.tsx +106 -0
- package/src/components/Listbox/Listbox.fragment.tsx +191 -0
- package/src/components/Listbox/Listbox.module.scss +97 -0
- package/src/components/Listbox/index.tsx +121 -0
- package/src/components/Menu/Menu.fragment.tsx +9 -0
- package/src/components/Menu/Menu.module.scss +17 -1
- package/src/components/Menu/index.tsx +3 -3
- package/src/components/Popover/Popover.fragment.tsx +9 -0
- package/src/components/Popover/Popover.module.scss +33 -10
- package/src/components/Popover/index.tsx +9 -11
- package/src/components/Progress/Progress.module.scss +11 -11
- package/src/components/Progress/index.tsx +34 -7
- package/src/components/Prompt/Prompt.fragment.tsx +231 -0
- package/src/components/Prompt/Prompt.module.scss +243 -0
- package/src/components/Prompt/index.tsx +439 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +3 -3
- package/src/components/RadioGroup/index.tsx +3 -4
- package/src/components/Select/Select.fragment.tsx +9 -0
- package/src/components/Select/index.tsx +6 -7
- package/src/components/Separator/index.tsx +7 -3
- package/src/components/Sidebar/Sidebar.fragment.tsx +9 -0
- package/src/components/Sidebar/Sidebar.module.scss +72 -47
- package/src/components/Sidebar/index.tsx +5 -3
- package/src/components/Skeleton/Skeleton.fragment.tsx +5 -5
- package/src/components/Skeleton/Skeleton.module.scss +11 -0
- package/src/components/Slider/Slider.module.scss +87 -0
- package/src/components/Slider/index.tsx +88 -0
- package/src/components/Stack/Stack.module.scss +120 -0
- package/src/components/Stack/index.tsx +148 -0
- package/src/components/Table/Table.fragment.tsx +7 -0
- package/src/components/Table/Table.module.scss +57 -0
- package/src/components/Table/index.tsx +44 -6
- package/src/components/Tabs/Tabs.fragment.tsx +9 -0
- package/src/components/Tabs/Tabs.module.scss +25 -10
- package/src/components/Tabs/index.tsx +11 -8
- package/src/components/Text/Text.module.scss +82 -0
- package/src/components/Text/index.tsx +58 -0
- package/src/components/Textarea/index.tsx +3 -7
- package/src/components/Theme/Theme.fragment.tsx +128 -0
- package/src/components/Theme/ThemeToggle.module.scss +82 -0
- package/src/components/Theme/index.tsx +343 -0
- package/src/components/Toast/Toast.fragment.tsx +5 -5
- package/src/components/Toast/Toast.module.scss +16 -1
- package/src/components/Toast/index.tsx +27 -11
- package/src/components/Toggle/Toggle.module.scss +25 -10
- package/src/components/Toggle/index.tsx +12 -0
- package/src/components/ToggleGroup/ToggleGroup.module.scss +134 -0
- package/src/components/ToggleGroup/index.tsx +144 -0
- package/src/components/Tooltip/Tooltip.module.scss +4 -4
- package/src/components/Tooltip/index.tsx +4 -2
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +134 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +13 -0
- package/src/components/VisuallyHidden/index.tsx +29 -0
- package/src/index.ts +195 -3
- package/src/recipes/AppShell.recipe.ts +175 -0
- package/src/recipes/CardGrid.recipe.ts +6 -2
- package/src/recipes/ChatInterface.recipe.ts +87 -0
- package/src/recipes/CodeExamples.recipe.ts +66 -0
- package/src/recipes/DashboardLayout.recipe.ts +46 -12
- package/src/recipes/DashboardNav.recipe.ts +183 -0
- package/src/recipes/LoginForm.recipe.ts +8 -1
- package/src/recipes/SettingsPage.recipe.ts +37 -20
- package/src/styles/globals.scss +31 -0
- package/src/tokens/_index.scss +3 -0
- package/src/tokens/_mixins.scss +54 -1
- package/src/tokens/_variables.scss +429 -64
- package/src/utils/a11y.tsx +439 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { AppShell } from './index.js';
|
|
4
|
+
import { Header } from '../Header/index.js';
|
|
5
|
+
import { Sidebar } from '../Sidebar/index.js';
|
|
6
|
+
import { ThemeToggle } from '../Theme/index.js';
|
|
7
|
+
|
|
8
|
+
// Demo icons
|
|
9
|
+
function HomeIcon() {
|
|
10
|
+
return (
|
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
|
|
12
|
+
<path d="M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ChartIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
|
|
20
|
+
<path d="M224,200h-8V40a8,8,0,0,0-8-8H152a8,8,0,0,0-8,8V80H96a8,8,0,0,0-8,8v40H48a8,8,0,0,0-8,8v64H32a8,8,0,0,0,0,16H224a8,8,0,0,0,0-16ZM160,48h40V200H160ZM104,96h40V200H104ZM56,144H88v56H56Z" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function GearIcon() {
|
|
26
|
+
return (
|
|
27
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
|
|
28
|
+
<path d="M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Zm88-29.84q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.21,107.21,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.71,107.71,0,0,0-26.25-10.87,8,8,0,0,0-7.06,1.49L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.21,107.21,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06Zm-16.1-6.5a73.93,73.93,0,0,1,0,8.68,8,8,0,0,0,1.74,5.48l14.19,17.73a91.57,91.57,0,0,1-6.23,15L187,173.11a8,8,0,0,0-5.1,2.64,74.11,74.11,0,0,1-6.14,6.14,8,8,0,0,0-2.64,5.1l-2.51,22.58a91.32,91.32,0,0,1-15,6.23l-17.74-14.19a8,8,0,0,0-5-1.75h-.48a73.93,73.93,0,0,1-8.68,0,8,8,0,0,0-5.48,1.74L100.45,215.8a91.57,91.57,0,0,1-15-6.23L82.89,187a8,8,0,0,0-2.64-5.1,74.11,74.11,0,0,1-6.14-6.14,8,8,0,0,0-5.1-2.64L46.43,170.6a91.32,91.32,0,0,1-6.23-15l14.19-17.74a8,8,0,0,0,1.74-5.48,73.93,73.93,0,0,1,0-8.68,8,8,0,0,0-1.74-5.48L40.2,100.45a91.57,91.57,0,0,1,6.23-15L69,82.89a8,8,0,0,0,5.1-2.64,74.11,74.11,0,0,1,6.14-6.14A8,8,0,0,0,82.89,69L85.4,46.43a91.32,91.32,0,0,1,15-6.23l17.74,14.19a8,8,0,0,0,5.48,1.74,73.93,73.93,0,0,1,8.68,0,8,8,0,0,0,5.48-1.74L155.55,40.2a91.57,91.57,0,0,1,15,6.23L173.11,69a8,8,0,0,0,2.64,5.1,74.11,74.11,0,0,1,6.14,6.14,8,8,0,0,0,5.1,2.64l22.58,2.51a91.32,91.32,0,0,1,6.23,15l-14.19,17.74A8,8,0,0,0,199.87,123.66Z" />
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function SearchIcon() {
|
|
34
|
+
return (
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor">
|
|
36
|
+
<path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z" />
|
|
37
|
+
</svg>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default defineSegment({
|
|
42
|
+
component: AppShell,
|
|
43
|
+
|
|
44
|
+
meta: {
|
|
45
|
+
name: 'AppShell',
|
|
46
|
+
description: 'Full layout wrapper integrating sidebar, header, main content, and optional aside panel. Supports two layout modes: stacked (header on top) and sidebar-inset (sidebar full height).',
|
|
47
|
+
category: 'layout',
|
|
48
|
+
status: 'stable',
|
|
49
|
+
tags: ['layout', 'shell', 'scaffold', 'dashboard', 'app-layout'],
|
|
50
|
+
since: '0.5.0',
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
usage: {
|
|
54
|
+
when: [
|
|
55
|
+
'Building dashboard-style applications',
|
|
56
|
+
'Apps with persistent sidebar navigation',
|
|
57
|
+
'Layouts requiring header, sidebar, and main content areas',
|
|
58
|
+
'Responsive layouts that need mobile drawer behavior',
|
|
59
|
+
],
|
|
60
|
+
whenNot: [
|
|
61
|
+
'Simple marketing pages (use standard layout)',
|
|
62
|
+
'Content-first sites without navigation (use simpler layout)',
|
|
63
|
+
'Single-page apps with minimal UI (use minimal layout)',
|
|
64
|
+
],
|
|
65
|
+
guidelines: [
|
|
66
|
+
'Use layout="stacked" when header should span full width (logo in header)',
|
|
67
|
+
'Use layout="sidebar-inset" when sidebar should be full height (logo in sidebar)',
|
|
68
|
+
'AppShell automatically wraps with SidebarProvider',
|
|
69
|
+
'Use AppShell.Sidebar to configure sidebar width and collapse behavior',
|
|
70
|
+
'Main content responds to sidebar collapsed state',
|
|
71
|
+
'Aside panel is hidden on mobile automatically',
|
|
72
|
+
],
|
|
73
|
+
accessibility: [
|
|
74
|
+
'Main content area has id="main-content" for skip links',
|
|
75
|
+
'Use Header.SkipLink for keyboard navigation',
|
|
76
|
+
'Sidebar drawer has proper focus trap on mobile',
|
|
77
|
+
'Keyboard navigation supported throughout',
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
props: {
|
|
82
|
+
children: {
|
|
83
|
+
type: 'node',
|
|
84
|
+
description: 'Layout content (use AppShell.Header, AppShell.Sidebar, AppShell.Main, AppShell.Aside)',
|
|
85
|
+
required: true,
|
|
86
|
+
},
|
|
87
|
+
layout: {
|
|
88
|
+
type: 'enum',
|
|
89
|
+
description: 'Layout mode for header/sidebar positioning',
|
|
90
|
+
values: ['stacked', 'sidebar-inset', 'inset'],
|
|
91
|
+
default: 'stacked',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
relations: [
|
|
96
|
+
{ component: 'ThemeProvider', relationship: 'parent', note: 'AppShell should be wrapped in ThemeProvider' },
|
|
97
|
+
{ component: 'Header', relationship: 'child', note: 'Header is placed inside AppShell.Header' },
|
|
98
|
+
{ component: 'Sidebar', relationship: 'child', note: 'Sidebar content goes inside AppShell.Sidebar' },
|
|
99
|
+
],
|
|
100
|
+
|
|
101
|
+
variants: [
|
|
102
|
+
{
|
|
103
|
+
name: 'Stacked Layout',
|
|
104
|
+
description: 'Header spans full width above sidebar (default). Best when brand/logo should be prominent in header.',
|
|
105
|
+
render: () => (
|
|
106
|
+
<div style={{ height: '400px', position: 'relative', overflow: 'hidden', border: '1px solid var(--fui-border)', borderRadius: '8px' }}>
|
|
107
|
+
<AppShell layout="stacked">
|
|
108
|
+
<AppShell.Header>
|
|
109
|
+
<Header>
|
|
110
|
+
<Header.Trigger />
|
|
111
|
+
<Header.Brand>MyApp</Header.Brand>
|
|
112
|
+
<Header.Nav>
|
|
113
|
+
<Header.NavItem active>Dashboard</Header.NavItem>
|
|
114
|
+
<Header.NavItem>Settings</Header.NavItem>
|
|
115
|
+
</Header.Nav>
|
|
116
|
+
<Header.Spacer />
|
|
117
|
+
<Header.Actions>
|
|
118
|
+
<ThemeToggle size="sm" />
|
|
119
|
+
</Header.Actions>
|
|
120
|
+
</Header>
|
|
121
|
+
</AppShell.Header>
|
|
122
|
+
|
|
123
|
+
<AppShell.Sidebar width="200px" collapsible="offcanvas">
|
|
124
|
+
<Sidebar.Nav>
|
|
125
|
+
<Sidebar.Section label="Menu">
|
|
126
|
+
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
127
|
+
<Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
|
|
128
|
+
<Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
|
|
129
|
+
</Sidebar.Section>
|
|
130
|
+
</Sidebar.Nav>
|
|
131
|
+
</AppShell.Sidebar>
|
|
132
|
+
|
|
133
|
+
<AppShell.Main padding="md">
|
|
134
|
+
<h2 style={{ margin: '0 0 8px' }}>Stacked Layout</h2>
|
|
135
|
+
<p style={{ margin: 0, color: 'var(--fui-text-secondary)' }}>
|
|
136
|
+
Header spans full width. Logo is in the header.
|
|
137
|
+
</p>
|
|
138
|
+
</AppShell.Main>
|
|
139
|
+
</AppShell>
|
|
140
|
+
</div>
|
|
141
|
+
),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'Sidebar Inset Layout',
|
|
145
|
+
description: 'Sidebar is full height, header sits next to it. Best for documentation sites or when sidebar branding is preferred.',
|
|
146
|
+
render: () => (
|
|
147
|
+
<div style={{ height: '400px', position: 'relative', overflow: 'hidden', border: '1px solid var(--fui-border)', borderRadius: '8px' }}>
|
|
148
|
+
<AppShell layout="sidebar-inset">
|
|
149
|
+
<AppShell.Header>
|
|
150
|
+
<Header>
|
|
151
|
+
<Header.Trigger />
|
|
152
|
+
<Header.Search>
|
|
153
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 12px', background: 'var(--fui-bg-secondary)', borderRadius: '6px', color: 'var(--fui-text-tertiary)', fontSize: '14px' }}>
|
|
154
|
+
<SearchIcon /> Search...
|
|
155
|
+
</div>
|
|
156
|
+
</Header.Search>
|
|
157
|
+
<Header.Spacer />
|
|
158
|
+
<Header.Actions>
|
|
159
|
+
<ThemeToggle size="sm" />
|
|
160
|
+
</Header.Actions>
|
|
161
|
+
</Header>
|
|
162
|
+
</AppShell.Header>
|
|
163
|
+
|
|
164
|
+
<AppShell.Sidebar width="200px" collapsible="offcanvas">
|
|
165
|
+
<Sidebar.Header>
|
|
166
|
+
<span style={{ fontWeight: 600 }}>MyApp</span>
|
|
167
|
+
</Sidebar.Header>
|
|
168
|
+
<Sidebar.Nav>
|
|
169
|
+
<Sidebar.Section label="Menu">
|
|
170
|
+
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
171
|
+
<Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
|
|
172
|
+
<Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
|
|
173
|
+
</Sidebar.Section>
|
|
174
|
+
</Sidebar.Nav>
|
|
175
|
+
<Sidebar.Footer>v1.0.0</Sidebar.Footer>
|
|
176
|
+
</AppShell.Sidebar>
|
|
177
|
+
|
|
178
|
+
<AppShell.Main padding="md">
|
|
179
|
+
<h2 style={{ margin: '0 0 8px' }}>Sidebar Inset Layout</h2>
|
|
180
|
+
<p style={{ margin: 0, color: 'var(--fui-text-secondary)' }}>
|
|
181
|
+
Sidebar is full height. Logo is in the sidebar header.
|
|
182
|
+
</p>
|
|
183
|
+
</AppShell.Main>
|
|
184
|
+
</AppShell>
|
|
185
|
+
</div>
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'With Aside Panel',
|
|
190
|
+
description: 'App shell with optional right panel for additional context or actions',
|
|
191
|
+
render: () => (
|
|
192
|
+
<div style={{ height: '400px', position: 'relative', overflow: 'hidden', border: '1px solid var(--fui-border)', borderRadius: '8px' }}>
|
|
193
|
+
<AppShell layout="stacked">
|
|
194
|
+
<AppShell.Header>
|
|
195
|
+
<Header>
|
|
196
|
+
<Header.Brand>App</Header.Brand>
|
|
197
|
+
<Header.Spacer />
|
|
198
|
+
<Header.Actions>
|
|
199
|
+
<ThemeToggle size="sm" />
|
|
200
|
+
</Header.Actions>
|
|
201
|
+
</Header>
|
|
202
|
+
</AppShell.Header>
|
|
203
|
+
|
|
204
|
+
<AppShell.Sidebar width="180px" collapsible="offcanvas">
|
|
205
|
+
<Sidebar.Nav>
|
|
206
|
+
<Sidebar.Section>
|
|
207
|
+
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
208
|
+
<Sidebar.Item icon={<ChartIcon />}>Stats</Sidebar.Item>
|
|
209
|
+
</Sidebar.Section>
|
|
210
|
+
</Sidebar.Nav>
|
|
211
|
+
</AppShell.Sidebar>
|
|
212
|
+
|
|
213
|
+
<AppShell.Main padding="md">
|
|
214
|
+
<h2 style={{ margin: '0 0 8px' }}>Main Content</h2>
|
|
215
|
+
<p style={{ margin: 0 }}>Content with aside panel on the right.</p>
|
|
216
|
+
</AppShell.Main>
|
|
217
|
+
|
|
218
|
+
<AppShell.Aside width="180px">
|
|
219
|
+
<div style={{ padding: '16px' }}>
|
|
220
|
+
<h3 style={{ margin: '0 0 8px', fontSize: '14px' }}>Aside Panel</h3>
|
|
221
|
+
<p style={{ margin: 0, fontSize: '13px', color: 'var(--fui-text-secondary)' }}>
|
|
222
|
+
Additional context, filters, or quick actions.
|
|
223
|
+
</p>
|
|
224
|
+
</div>
|
|
225
|
+
</AppShell.Aside>
|
|
226
|
+
</AppShell>
|
|
227
|
+
</div>
|
|
228
|
+
),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'Collapsible Icon Sidebar',
|
|
232
|
+
description: 'Sidebar that collapses to icons only on desktop',
|
|
233
|
+
render: () => (
|
|
234
|
+
<div style={{ height: '400px', position: 'relative', overflow: 'hidden', border: '1px solid var(--fui-border)', borderRadius: '8px' }}>
|
|
235
|
+
<AppShell layout="sidebar-inset">
|
|
236
|
+
<AppShell.Header>
|
|
237
|
+
<Header>
|
|
238
|
+
<Header.Trigger />
|
|
239
|
+
<Header.Spacer />
|
|
240
|
+
<Header.Actions>
|
|
241
|
+
<ThemeToggle size="sm" />
|
|
242
|
+
</Header.Actions>
|
|
243
|
+
</Header>
|
|
244
|
+
</AppShell.Header>
|
|
245
|
+
|
|
246
|
+
<AppShell.Sidebar collapsible="icon" width="200px" collapsedWidth="56px">
|
|
247
|
+
<Sidebar.Header>
|
|
248
|
+
<span style={{ fontWeight: 600 }}>App</span>
|
|
249
|
+
</Sidebar.Header>
|
|
250
|
+
<Sidebar.Nav>
|
|
251
|
+
<Sidebar.Section>
|
|
252
|
+
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
253
|
+
<Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
|
|
254
|
+
<Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
|
|
255
|
+
</Sidebar.Section>
|
|
256
|
+
</Sidebar.Nav>
|
|
257
|
+
<Sidebar.Footer>
|
|
258
|
+
<Sidebar.CollapseToggle />
|
|
259
|
+
</Sidebar.Footer>
|
|
260
|
+
</AppShell.Sidebar>
|
|
261
|
+
|
|
262
|
+
<AppShell.Main padding="md">
|
|
263
|
+
<p style={{ margin: 0 }}>Click the collapse button in the sidebar footer to toggle between expanded and icon-only modes.</p>
|
|
264
|
+
</AppShell.Main>
|
|
265
|
+
</AppShell>
|
|
266
|
+
</div>
|
|
267
|
+
),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'Inset Layout',
|
|
271
|
+
description: 'Modern shadcn-style layout with rounded main content area and visual separation from sidebar.',
|
|
272
|
+
render: () => (
|
|
273
|
+
<div style={{ height: '400px', position: 'relative', overflow: 'hidden', border: '1px solid var(--fui-border)', borderRadius: '8px' }}>
|
|
274
|
+
<AppShell layout="inset">
|
|
275
|
+
<AppShell.Header>
|
|
276
|
+
<Header>
|
|
277
|
+
<Header.Trigger />
|
|
278
|
+
<Header.Search>
|
|
279
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 12px', background: 'var(--fui-bg-primary)', borderRadius: '6px', color: 'var(--fui-text-tertiary)', fontSize: '14px', border: '1px solid var(--fui-border)' }}>
|
|
280
|
+
<SearchIcon /> Search...
|
|
281
|
+
</div>
|
|
282
|
+
</Header.Search>
|
|
283
|
+
<Header.Spacer />
|
|
284
|
+
<Header.Actions>
|
|
285
|
+
<ThemeToggle size="sm" />
|
|
286
|
+
</Header.Actions>
|
|
287
|
+
</Header>
|
|
288
|
+
</AppShell.Header>
|
|
289
|
+
|
|
290
|
+
<AppShell.Sidebar width="200px" collapsible="offcanvas">
|
|
291
|
+
<Sidebar.Header>
|
|
292
|
+
<span style={{ fontWeight: 600 }}>MyApp</span>
|
|
293
|
+
</Sidebar.Header>
|
|
294
|
+
<Sidebar.Nav>
|
|
295
|
+
<Sidebar.Section label="Menu">
|
|
296
|
+
<Sidebar.Item icon={<HomeIcon />} active>Home</Sidebar.Item>
|
|
297
|
+
<Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
|
|
298
|
+
<Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
|
|
299
|
+
</Sidebar.Section>
|
|
300
|
+
</Sidebar.Nav>
|
|
301
|
+
<Sidebar.Footer>v1.0.0</Sidebar.Footer>
|
|
302
|
+
</AppShell.Sidebar>
|
|
303
|
+
|
|
304
|
+
<AppShell.Main padding="md">
|
|
305
|
+
<h2 style={{ margin: '0 0 8px' }}>Inset Layout</h2>
|
|
306
|
+
<p style={{ margin: 0, color: 'var(--fui-text-secondary)' }}>
|
|
307
|
+
Main content has rounded corners and visual separation from the sidebar.
|
|
308
|
+
</p>
|
|
309
|
+
</AppShell.Main>
|
|
310
|
+
</AppShell>
|
|
311
|
+
</div>
|
|
312
|
+
),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// AppShell Root - CSS Grid Layout
|
|
6
|
+
// ============================================
|
|
7
|
+
// Grid areas: header, sidebar, main, aside
|
|
8
|
+
// Stacked: header spans full width, sidebar below header
|
|
9
|
+
// Sidebar-inset: sidebar spans full height
|
|
10
|
+
|
|
11
|
+
.root {
|
|
12
|
+
display: grid;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
min-height: 100dvh;
|
|
15
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
16
|
+
|
|
17
|
+
// Stacked layout (default):
|
|
18
|
+
// header header header
|
|
19
|
+
// sidebar main aside
|
|
20
|
+
grid-template-areas:
|
|
21
|
+
'header header header'
|
|
22
|
+
'sidebar main aside';
|
|
23
|
+
grid-template-columns:
|
|
24
|
+
var(--appshell-sidebar-width, 240px)
|
|
25
|
+
1fr
|
|
26
|
+
var(--appshell-aside-width, 0px);
|
|
27
|
+
grid-template-rows:
|
|
28
|
+
var(--appshell-header-height, 56px)
|
|
29
|
+
1fr;
|
|
30
|
+
|
|
31
|
+
// Smooth transition for sidebar collapse/expand
|
|
32
|
+
transition: grid-template-columns var(--fui-transition-normal, 200ms) ease;
|
|
33
|
+
|
|
34
|
+
// Mobile: single column layout
|
|
35
|
+
@include below-md {
|
|
36
|
+
grid-template-areas:
|
|
37
|
+
'header'
|
|
38
|
+
'main';
|
|
39
|
+
grid-template-columns: 1fr;
|
|
40
|
+
grid-template-rows:
|
|
41
|
+
var(--appshell-header-height, 56px)
|
|
42
|
+
1fr;
|
|
43
|
+
transition: none; // No column transition on mobile
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sidebar-inset layout: sidebar spans full height
|
|
48
|
+
// sidebar header header
|
|
49
|
+
// sidebar main aside
|
|
50
|
+
.sidebarInset {
|
|
51
|
+
grid-template-areas:
|
|
52
|
+
'sidebar header header'
|
|
53
|
+
'sidebar main aside';
|
|
54
|
+
|
|
55
|
+
@include below-md {
|
|
56
|
+
grid-template-areas:
|
|
57
|
+
'header'
|
|
58
|
+
'main';
|
|
59
|
+
grid-template-columns: 1fr;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Inset layout: shadcn-style with rounded main content
|
|
64
|
+
// Root uses primary (darker in dark mode) as the shell background
|
|
65
|
+
.insetLayout {
|
|
66
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// Header
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
.header {
|
|
74
|
+
grid-area: header;
|
|
75
|
+
position: sticky;
|
|
76
|
+
top: 0;
|
|
77
|
+
height: var(--appshell-header-height, 56px);
|
|
78
|
+
z-index: var(--fui-header-z-index, 40);
|
|
79
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Header in inset layout (rounded style)
|
|
83
|
+
// Uses primary bg to match the shell
|
|
84
|
+
.headerInsetRounded {
|
|
85
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================
|
|
89
|
+
// Sidebar
|
|
90
|
+
// ============================================
|
|
91
|
+
|
|
92
|
+
.sidebar {
|
|
93
|
+
grid-area: sidebar;
|
|
94
|
+
position: sticky;
|
|
95
|
+
top: var(--appshell-header-height, 56px);
|
|
96
|
+
height: calc(100vh - var(--appshell-header-height, 56px));
|
|
97
|
+
height: calc(100dvh - var(--appshell-header-height, 56px));
|
|
98
|
+
z-index: 30;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
|
|
101
|
+
// On mobile, sidebar becomes a fixed overlay
|
|
102
|
+
@include below-md {
|
|
103
|
+
position: fixed;
|
|
104
|
+
top: 0;
|
|
105
|
+
left: 0;
|
|
106
|
+
bottom: 0;
|
|
107
|
+
width: 0;
|
|
108
|
+
height: 100vh;
|
|
109
|
+
height: 100dvh;
|
|
110
|
+
z-index: 51;
|
|
111
|
+
|
|
112
|
+
// Allow the sidebar to appear as overlay on mobile
|
|
113
|
+
& > :global(.root) {
|
|
114
|
+
width: var(--appshell-sidebar-expanded-width, 240px);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Override Sidebar component's height
|
|
119
|
+
:global(.root) {
|
|
120
|
+
height: 100%;
|
|
121
|
+
|
|
122
|
+
@include below-md {
|
|
123
|
+
height: 100vh;
|
|
124
|
+
height: 100dvh;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sidebar in sidebar-inset layout: full height
|
|
130
|
+
.sidebarFullHeight {
|
|
131
|
+
top: 0;
|
|
132
|
+
height: 100vh;
|
|
133
|
+
height: 100dvh;
|
|
134
|
+
z-index: var(--fui-header-z-index, 40);
|
|
135
|
+
|
|
136
|
+
:global(.root) {
|
|
137
|
+
height: 100%;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sidebar in inset layout: no borders, primary background (matches shell)
|
|
142
|
+
.sidebarInsetRounded {
|
|
143
|
+
:global(.root) {
|
|
144
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// Main Content
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
.main {
|
|
153
|
+
grid-area: main;
|
|
154
|
+
min-height: 0; // Allow shrinking in grid
|
|
155
|
+
min-width: 0; // Prevent overflow
|
|
156
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
157
|
+
overflow-x: hidden;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Main content in inset layout (shadcn-style rounded)
|
|
161
|
+
// Uses secondary bg (lighter in dark mode) to create elevated card effect
|
|
162
|
+
.mainInset {
|
|
163
|
+
border-radius: var(--fui-radius-lg, $fui-radius-lg);
|
|
164
|
+
background-color: var(--fui-bg-secondary, $fui-bg-secondary);
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
|
|
167
|
+
@include below-md {
|
|
168
|
+
margin: var(--fui-space-2, $fui-space-2);
|
|
169
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Padding variants
|
|
174
|
+
.paddingSm {
|
|
175
|
+
padding: var(--fui-space-3, $fui-space-3);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.paddingMd {
|
|
179
|
+
padding: var(--fui-space-4, $fui-space-4);
|
|
180
|
+
|
|
181
|
+
@include breakpoint-md {
|
|
182
|
+
padding: var(--fui-space-6, $fui-space-6);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.paddingLg {
|
|
187
|
+
padding: var(--fui-space-6, $fui-space-6);
|
|
188
|
+
|
|
189
|
+
@include breakpoint-md {
|
|
190
|
+
padding: var(--fui-space-8, $fui-space-8);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================
|
|
195
|
+
// Aside (Right Panel)
|
|
196
|
+
// ============================================
|
|
197
|
+
|
|
198
|
+
.aside {
|
|
199
|
+
grid-area: aside;
|
|
200
|
+
position: sticky;
|
|
201
|
+
top: var(--appshell-header-height, 56px);
|
|
202
|
+
height: calc(100vh - var(--appshell-header-height, 56px));
|
|
203
|
+
height: calc(100dvh - var(--appshell-header-height, 56px));
|
|
204
|
+
width: var(--aside-width, 280px);
|
|
205
|
+
z-index: 30;
|
|
206
|
+
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
207
|
+
overflow-y: auto;
|
|
208
|
+
|
|
209
|
+
// Hide on mobile (grid area doesn't exist)
|
|
210
|
+
@include below-md {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
}
|