rift-ds 1.12.0 → 1.14.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 +21 -7
- package/behaviors/ScrollRegion.d.ts +8 -0
- package/behaviors/ScrollRegion.js +51 -0
- package/behaviors/devWarning.d.ts +9 -0
- package/behaviors/devWarning.js +16 -0
- package/behaviors/useAnchoredPosition.d.ts +111 -0
- package/behaviors/useAnchoredPosition.js +153 -0
- package/behaviors/useFocusScope.d.ts +9 -2
- package/behaviors/useFocusScope.js +5 -1
- package/behaviors/visually-hidden.css +24 -0
- package/bin/rift-ds.mjs +186 -27
- package/bin/templates/app/components/rift/ColorModeScript.tsx +10 -0
- package/bin/templates/app/components/rift/RiftRoot.tsx +13 -0
- package/bin/templates/app/components/rift/font.css +7 -0
- package/bin/templates/app/components/rift/font.ts +14 -0
- package/bin/templates/blocks/app/dashboard/page.module.css +90 -0
- package/bin/templates/blocks/app/dashboard/page.tsx +141 -0
- package/bin/templates/blocks/app/sign-in/page.module.css +91 -0
- package/bin/templates/blocks/app/sign-in/page.tsx +95 -0
- package/bin/templates/blocks/app/starter/page.module.css +7 -0
- package/bin/templates/blocks/app/starter/page.tsx +31 -0
- package/bin/templates/blocks/components/blocks/AppShell.module.css +160 -0
- package/bin/templates/blocks/components/blocks/AppShell.tsx +113 -0
- package/bin/templates/blocks/registry.json +39 -0
- package/chat/AssistantTurn.js +4 -2
- package/chat/ChatAgentPane.css +5 -1
- package/chat/ChatWidget.css +0 -36
- package/chat/ResponseActions.js +2 -1
- package/chat/types.d.ts +8 -7
- package/chat/useChat.js +2 -2
- package/components/Accordion/Accordion.css +10 -3
- package/components/AgentPlan/AgentPlan.css +0 -12
- package/components/AgentPlan/AgentPlan.js +2 -1
- package/components/AgentStatus/AgentRing.d.ts +27 -0
- package/components/AgentStatus/AgentRing.js +9 -0
- package/components/AgentStatus/AgentStatus.css +638 -10
- package/components/AgentStatus/AgentStatus.d.ts +31 -3
- package/components/AgentStatus/AgentStatus.js +22 -4
- package/components/AgentStatus/AgentStatusPatterns.d.ts +1 -1
- package/components/AgentStatus/AgentStatusPatterns.js +2 -1
- package/components/AiButton/AiButton.css +211 -38
- package/components/AiButton/AiButton.d.ts +34 -0
- package/components/AiButton/AiButton.js +129 -9
- package/components/AnimatedNumber/AnimatedNumber.css +0 -12
- package/components/AnimatedNumber/AnimatedNumber.js +2 -1
- package/components/AppSidebar/AppSidebar.css +2 -2
- package/components/Attachment/Attachment.css +0 -17
- package/components/Attachment/AttachmentTile.js +3 -2
- package/components/Avatar/Avatar.css +54 -0
- package/components/Avatar/Avatar.d.ts +9 -1
- package/components/Avatar/Avatar.js +5 -1
- package/components/Avatar/avatarFill.d.ts +9 -0
- package/components/Avatar/avatarFill.js +13 -0
- package/components/AvatarGroup/AvatarGroup.js +13 -1
- package/components/BottomNav/BottomNav.css +2 -11
- package/components/BottomNav/BottomNav.js +2 -1
- package/components/Button/Button.css +7 -0
- package/components/Button/Button.d.ts +8 -1
- package/components/Button/Button.js +10 -1
- package/components/CardStack/CardStack.css +0 -11
- package/components/CardStack/CardStack.js +2 -1
- package/components/ChatMessage/ChatMessage.css +0 -12
- package/components/ChatMessage/ChatMessage.js +2 -1
- package/components/Checkbox/Checkbox.css +34 -25
- package/components/Checkbox/Checkbox.d.ts +34 -0
- package/components/Checkbox/Checkbox.js +65 -37
- package/components/CircularButton/CircularButton.css +8 -7
- package/components/CodeBlock/CodeBlock.d.ts +2 -0
- package/components/CodeBlock/CodeBlock.js +2 -0
- package/components/CodeDiff/CodeDiff.css +0 -11
- package/components/CodeDiff/CodeDiff.js +3 -2
- package/components/ColorPicker/ColorPicker.css +3 -4
- package/components/ColorPicker/ColorPicker.js +11 -0
- package/components/ColourSwatch/ColourSwatch.css +1 -0
- package/components/Combobox/Combobox.css +21 -13
- package/components/Combobox/Combobox.js +77 -56
- package/components/CommandPalette/CommandPalette.css +2 -25
- package/components/CommandPalette/CommandPalette.js +8 -5
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.css +88 -0
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.d.ts +31 -0
- package/components/CommandPaletteTrigger/CommandPaletteTrigger.js +49 -0
- package/components/Composer/Composer.css +3 -15
- package/components/Composer/Composer.js +3 -2
- package/components/ContactCard/ContactCard.css +1 -1
- package/components/ContextMenu/ContextMenu.css +1 -9
- package/components/ContextMenu/ContextMenu.js +3 -2
- package/components/DataTable/CellEditor.d.ts +37 -0
- package/components/DataTable/CellEditor.js +153 -0
- package/components/DataTable/DataTable.css +1180 -0
- package/components/DataTable/DataTable.d.ts +498 -27
- package/components/DataTable/DataTable.js +2363 -189
- package/components/DataTable/FilterEditor.d.ts +10 -0
- package/components/DataTable/FilterEditor.js +181 -0
- package/components/DataTable/TypedCell.d.ts +22 -0
- package/components/DataTable/TypedCell.js +80 -0
- package/components/DataTable/VirtualRows.d.ts +21 -0
- package/components/DataTable/VirtualRows.js +38 -0
- package/components/DataTable/aggregates.d.ts +29 -0
- package/components/DataTable/aggregates.js +52 -0
- package/components/DataTable/columnTypes.d.ts +49 -0
- package/components/DataTable/columnTypes.js +96 -0
- package/components/DataTable/dragGesture.d.ts +24 -0
- package/components/DataTable/dragGesture.js +62 -0
- package/components/DataTable/editorKinds.d.ts +24 -0
- package/components/DataTable/editorKinds.js +20 -0
- package/components/DataTable/filterModel.d.ts +84 -0
- package/components/DataTable/filterModel.js +159 -0
- package/components/DataTable/tableContext.d.ts +82 -0
- package/components/DataTable/tableContext.js +7 -0
- package/components/DataTable/views.d.ts +50 -0
- package/components/DataTable/views.js +39 -0
- package/components/DateInput/DateInput.css +6 -0
- package/components/Dialog/Dialog.css +2 -26
- package/components/Dialog/Dialog.js +8 -12
- package/components/Drawer/Drawer.css +2 -26
- package/components/Drawer/Drawer.js +10 -13
- package/components/Dropdown/ChosenMark.css +19 -0
- package/components/Dropdown/Dropdown.css +22 -18
- package/components/Dropdown/Dropdown.d.ts +29 -0
- package/components/Dropdown/Dropdown.js +55 -24
- package/components/DropdownMenu/DropdownMenu.css +33 -21
- package/components/DropdownMenu/DropdownMenu.d.ts +4 -0
- package/components/DropdownMenu/DropdownMenu.js +68 -16
- package/components/FilterBar/FilterBar.css +10 -14
- package/components/FilterBar/FilterBar.js +77 -50
- package/components/GanttChart/GanttChart.css +0 -13
- package/components/GanttChart/GanttChart.js +3 -2
- package/components/HoverCard/HoverCard.css +24 -44
- package/components/HoverCard/HoverCard.js +18 -2
- package/components/Input/Input.css +6 -0
- package/components/Inspector/InspectorToggleSwitch.js +5 -10
- package/components/InterruptCard/InterruptCard.css +8 -12
- package/components/InterruptCard/InterruptCard.d.ts +2 -0
- package/components/InterruptCard/InterruptCard.js +9 -2
- package/components/ModelPicker/ModelPicker.css +18 -56
- package/components/ModelPicker/ModelPicker.d.ts +2 -0
- package/components/ModelPicker/ModelPicker.js +28 -51
- package/components/MotionSwatch/MotionSwatch.css +1 -1
- package/components/NotificationCenter/NotificationCenter.css +1 -13
- package/components/NotificationCenter/NotificationCenter.js +2 -1
- package/components/Pagination/Pagination.css +8 -6
- package/components/PixelAvatar/pixelInks.d.ts +6 -0
- package/components/PixelAvatar/pixelInks.js +7 -3
- package/components/Popover/Popover.css +32 -30
- package/components/Popover/Popover.d.ts +8 -0
- package/components/Popover/Popover.js +16 -2
- package/components/PromptSuggestions/PromptSuggestions.css +0 -11
- package/components/PromptSuggestions/PromptSuggestions.js +3 -2
- package/components/Prose/Prose.css +47 -8
- package/components/Prose/Prose.d.ts +2 -0
- package/components/Prose/Prose.js +7 -2
- package/components/QuestionCard/QuestionCard.css +13 -22
- package/components/QuestionCard/QuestionCard.js +8 -13
- package/components/RadioButton/RadioButton.css +27 -11
- package/components/RadioButton/RadioButton.d.ts +18 -0
- package/components/RadioButton/RadioButton.js +12 -2
- package/components/RichDropdown/RichDropdown.css +17 -14
- package/components/RichDropdown/RichDropdown.js +15 -8
- package/components/SegmentedControl/SegmentedControl.css +10 -14
- package/components/SegmentedControl/SegmentedControl.d.ts +7 -0
- package/components/SegmentedControl/SegmentedControl.js +4 -2
- package/components/SelectionCard/SelectionCard.css +29 -160
- package/components/SelectionCard/SelectionCard.js +5 -35
- package/components/ShaderField/useShaderField.js +13 -1
- package/components/SidebarPanel/SidebarPanel.css +1 -1
- package/components/SourceChip/SourceChip.css +6 -12
- package/components/SourceChip/SourceChip.js +10 -35
- package/components/SourceTrail/SourceTrail.css +0 -13
- package/components/SourceTrail/SourceTrail.js +2 -1
- package/components/SpacingSwatch/SpacingSwatch.css +1 -1
- package/components/Stepper/Stepper.css +0 -13
- package/components/Stepper/Stepper.js +2 -1
- package/components/Table/Table.css +60 -40
- package/components/Table/Table.d.ts +8 -0
- package/components/Table/Table.js +33 -12
- package/components/TableToolbar/TableToolbar.css +494 -0
- package/components/TableToolbar/TableToolbar.d.ts +281 -0
- package/components/TableToolbar/TableToolbar.js +1321 -0
- package/components/ThemePicker/ThemePicker.css +189 -0
- package/components/ThemePicker/ThemePicker.d.ts +67 -0
- package/components/ThemePicker/ThemePicker.js +165 -0
- package/components/ThemeToggle/ThemeToggle.css +82 -0
- package/components/ThemeToggle/ThemeToggle.d.ts +42 -0
- package/components/ThemeToggle/ThemeToggle.js +98 -0
- package/components/ThemeToggle/colorMode.d.ts +42 -0
- package/components/ThemeToggle/colorMode.js +26 -0
- package/components/ThemeToggle/useColorMode.d.ts +8 -0
- package/components/ThemeToggle/useColorMode.js +44 -0
- package/components/ThreadPanel/ThreadPanel.css +2 -16
- package/components/ThreadPanel/ThreadPanel.js +3 -2
- package/components/ThreadTabs/ThreadTabs.css +1 -15
- package/components/ThreadTabs/ThreadTabs.js +2 -1
- package/components/TimePicker/TimePicker.css +11 -17
- package/components/TimePicker/TimePicker.js +13 -8
- package/components/ToggleGroup/ToggleGroup.css +19 -2
- package/components/ToggleSwitch/ToggleSwitch.css +33 -27
- package/components/ToggleSwitch/ToggleSwitch.d.ts +19 -0
- package/components/ToggleSwitch/ToggleSwitch.js +17 -10
- package/components/Tooltip/Tooltip.css +48 -52
- package/components/Tooltip/Tooltip.d.ts +10 -0
- package/components/Tooltip/Tooltip.js +27 -2
- package/components/TopAppBar/TopAppBar.css +5 -3
- package/components/Waveform/Waveform.css +0 -12
- package/components/Waveform/Waveform.js +2 -1
- package/components/registry.json +36 -4
- package/components/registry.json.d.ts +36 -4
- package/components/registry.json.js +1 -1
- package/index.d.ts +11 -0
- package/index.js +41 -6
- package/package.json +5 -1
- package/tokens/base.css +84 -0
- package/tokens/motion.d.ts +2 -0
- package/tokens/motion.js +2 -0
- package/tokens/presets/contrast.css +6 -2
- package/tokens/presets/coral.css +4 -2
- package/tokens/presets/forest.css +6 -0
- package/tokens/presets/gold.css +10 -2
- package/tokens/presets/mono.css +4 -2
- package/tokens/presets/pink.css +10 -0
- package/tokens/presets/terminal.css +8 -2
- package/tokens/presets/violet.css +10 -0
- package/tokens/presets/warm.css +4 -0
- package/tokens/presets/zest.css +6 -0
- package/tokens/registry.d.ts +1 -1
- package/tokens/registry.json +27 -1
- package/tokens/registry.json.d.ts +27 -1
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +36 -0
- package/tokens/tokens-forced-colors.css +31 -0
- package/tokens/tokens-light.css +49 -0
- package/tokens/tokens-primitives.css +9 -0
- package/tokens/tokens.css +1 -0
- package/cli/templates/chat/app/api/chat/feedback/route.d.ts +0 -3
- package/cli/templates/chat/app/api/chat/followups/route.d.ts +0 -3
- package/cli/templates/chat/app/api/chat/guardrails.d.ts +0 -10
- package/cli/templates/chat/app/api/chat/route.d.ts +0 -4
- package/cli/templates/chat/components/chat/ChatMount.d.ts +0 -1
- package/cli/templates/chat/components/chat/models.d.ts +0 -11
package/bin/rift-ds.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* The package's one bin: `npx rift-ds init
|
|
3
|
+
* The package's one bin: `npx rift-ds init`, `npx rift-ds init app`,
|
|
4
|
+
* `npx rift-ds init chat` and `npx rift-ds init block`.
|
|
4
5
|
*
|
|
5
6
|
* `init` fetches the generated agent skills from the live site into the
|
|
6
7
|
* project's .claude/skills/ folder, one folder per skill, then prints the
|
|
@@ -20,6 +21,22 @@
|
|
|
20
21
|
* installed version's own subpaths and must match it. It never overwrites:
|
|
21
22
|
* a file that exists is skipped and listed, so re-running is safe.
|
|
22
23
|
*
|
|
24
|
+
* `init app` sets a Next.js app router project up for the library the same
|
|
25
|
+
* way, from dist/bin/templates/app/: a root wrapper that hands Next's Link
|
|
26
|
+
* to every link-rendering component, a component that renders the
|
|
27
|
+
* colour-mode script before first paint, and the base theme's face through
|
|
28
|
+
* next/font, all under components/rift/. It never edits the root layout
|
|
29
|
+
* (every app already has one): it prints the whole layout to use instead,
|
|
30
|
+
* importing tokens.css, base.css and the font, rendering the script and
|
|
31
|
+
* wrapping the body, and points at init block for a first page.
|
|
32
|
+
*
|
|
33
|
+
* `init block <name>` copies one block, a whole starting page, from
|
|
34
|
+
* dist/bin/templates/blocks/: the files its registry.json entry lists,
|
|
35
|
+
* held at their target paths so the relative imports between them resolve
|
|
36
|
+
* as they do there. Blocks share files (the app shell), and the
|
|
37
|
+
* never-overwrite rule means a shared file is written by whichever block
|
|
38
|
+
* comes first. With no name it lists the blocks.
|
|
39
|
+
*
|
|
23
40
|
* The manifest is data from the network that decides where this command
|
|
24
41
|
* writes, so every name and path in it is checked before anything touches
|
|
25
42
|
* the disk: a skill name is one kebab-case folder, a file is a plain
|
|
@@ -28,7 +45,8 @@
|
|
|
28
45
|
* Zero dependencies by design: global fetch plus node:fs, so npx never
|
|
29
46
|
* installs anything beyond the package itself. SITE_ORIGIN, SKILL_NAME,
|
|
30
47
|
* MCP_SERVER_NAME and PACKAGE_NAME are stamped by build-package.mjs from
|
|
31
|
-
* scripts/brand.mjs (the one home for brand facts)
|
|
48
|
+
* scripts/brand.mjs (the one home for brand facts), and CHAT_SLUG from
|
|
49
|
+
* the chat registry, the widget page's slug; running this file
|
|
32
50
|
* straight from source fails fast rather than guessing a domain.
|
|
33
51
|
* SKILL_NAME is the library skill, kept only for --out without --skill,
|
|
34
52
|
* which has always meant that one.
|
|
@@ -41,9 +59,13 @@ const SITE_ORIGIN = 'https://rift-ds.com';
|
|
|
41
59
|
const SKILL_NAME = 'rift-design-system';
|
|
42
60
|
const MCP_SERVER_NAME = 'rift-ds';
|
|
43
61
|
const PACKAGE_NAME = 'rift-ds';
|
|
62
|
+
const CHAT_SLUG = 'ai-chat';
|
|
44
63
|
const MANIFEST_URL = `${SITE_ORIGIN}/skill/manifest.json`;
|
|
45
64
|
const DEFAULT_DIR = join('.claude', 'skills');
|
|
46
|
-
const
|
|
65
|
+
const TEMPLATES_ROOT = join(dirname(fileURLToPath(import.meta.url)), 'templates');
|
|
66
|
+
const TEMPLATES_DIR = join(TEMPLATES_ROOT, 'chat');
|
|
67
|
+
const APP_TEMPLATES_DIR = join(TEMPLATES_ROOT, 'app');
|
|
68
|
+
const BLOCKS_DIR = join(TEMPLATES_ROOT, 'blocks');
|
|
47
69
|
|
|
48
70
|
/* What `init chat` needs installed beside the package. The optional peers
|
|
49
71
|
each chat subpath imports; the demo's recharts is only for the scripted
|
|
@@ -58,7 +80,9 @@ const CHAT_PEERS = [
|
|
|
58
80
|
];
|
|
59
81
|
|
|
60
82
|
const USAGE = `Usage: npx ${PACKAGE_NAME} init [--skill <name>] [--dir <dir>]
|
|
83
|
+
npx ${PACKAGE_NAME} init app [--app <dir>] [--dry-run]
|
|
61
84
|
npx ${PACKAGE_NAME} init chat [--app <dir>] [--dry-run]
|
|
85
|
+
npx ${PACKAGE_NAME} init block [<name>] [--app <dir>] [--dry-run]
|
|
62
86
|
|
|
63
87
|
init
|
|
64
88
|
Fetches the agent skills for this package from ${SITE_ORIGIN} into
|
|
@@ -72,6 +96,17 @@ init
|
|
|
72
96
|
--out <dir> Deprecated: write one skill's files straight into <dir>
|
|
73
97
|
(${SKILL_NAME} unless --skill names another). Use --dir.
|
|
74
98
|
|
|
99
|
+
init app
|
|
100
|
+
Sets a Next.js app router project up for the library, under
|
|
101
|
+
components/rift/: a root wrapper that routes every link through Next's
|
|
102
|
+
Link, a component that applies the stored colour mode before first paint,
|
|
103
|
+
and the base theme's font. Prints the root layout to use; it never edits
|
|
104
|
+
the layout itself.
|
|
105
|
+
Existing files are never overwritten.
|
|
106
|
+
|
|
107
|
+
--app <dir> The app router folder (default: app/ or src/app/, whichever exists)
|
|
108
|
+
--dry-run Print what would be written and write nothing
|
|
109
|
+
|
|
75
110
|
init chat
|
|
76
111
|
Scaffolds the chat widget into a Next.js app router project: the chat,
|
|
77
112
|
follow-ups and feedback routes under app/api/chat/, the guardrails beside
|
|
@@ -81,6 +116,16 @@ init chat
|
|
|
81
116
|
--app <dir> The app router folder (default: app/ or src/app/, whichever exists)
|
|
82
117
|
--dry-run Print what would be written and write nothing
|
|
83
118
|
|
|
119
|
+
init block [<name>]
|
|
120
|
+
Copies a block into a Next.js app router project: a whole starting page
|
|
121
|
+
(an app shell, a dashboard, a sign-in) built from the library, its page
|
|
122
|
+
under app/ and any shared parts under components/blocks/. The files are
|
|
123
|
+
yours from then on. With no name, lists the blocks. Run init app first.
|
|
124
|
+
Existing files are never overwritten.
|
|
125
|
+
|
|
126
|
+
--app <dir> The app router folder (default: app/ or src/app/, whichever exists)
|
|
127
|
+
--dry-run Print what would be written and write nothing
|
|
128
|
+
|
|
84
129
|
--help Show this message
|
|
85
130
|
|
|
86
131
|
The list of skills is at ${MANIFEST_URL}`;
|
|
@@ -160,38 +205,31 @@ async function init({ only, dir, out }) {
|
|
|
160
205
|
console.log(` claude mcp add --transport http ${MCP_SERVER_NAME} ${SITE_ORIGIN}/api/mcp`);
|
|
161
206
|
}
|
|
162
207
|
|
|
163
|
-
/** Every template file
|
|
164
|
-
function templateFiles(dir =
|
|
208
|
+
/** Every template file under `base`, as a path relative to it (forward slashes). */
|
|
209
|
+
function templateFiles(base = TEMPLATES_DIR, dir = base) {
|
|
165
210
|
const out = [];
|
|
166
211
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
167
212
|
const full = join(dir, entry.name);
|
|
168
|
-
if (entry.isDirectory()) out.push(...templateFiles(full));
|
|
169
|
-
else out.push(relative(
|
|
213
|
+
if (entry.isDirectory()) out.push(...templateFiles(base, full));
|
|
214
|
+
else out.push(relative(base, full).split('\\').join('/'));
|
|
170
215
|
}
|
|
171
216
|
return out.sort();
|
|
172
217
|
}
|
|
173
218
|
|
|
174
|
-
/**
|
|
175
|
-
|
|
176
|
-
* `components/**` beside it (src/components when the app is src/app), and
|
|
177
|
-
* the env example at the project root.
|
|
178
|
-
*/
|
|
179
|
-
function destinationFor(file, appDir) {
|
|
180
|
-
if (file.startsWith('app/')) return join(appDir, ...file.slice('app/'.length).split('/'));
|
|
181
|
-
if (file.startsWith('components/')) return join(dirname(appDir), ...file.split('/'));
|
|
182
|
-
return join(process.cwd(), ...file.split('/'));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function initChat({ app, dryRun }) {
|
|
186
|
-
if (!existsSync(TEMPLATES_DIR)) {
|
|
187
|
-
fail('this copy carries no chat templates: run the published bin (npx ' + PACKAGE_NAME + ' init chat).');
|
|
188
|
-
}
|
|
219
|
+
/** The app router folder: the one passed, else app/ or src/app/, whichever exists. */
|
|
220
|
+
function findAppDir(app) {
|
|
189
221
|
const appDir = app ?? ['app', join('src', 'app')].map((d) => resolve(d)).find((d) => existsSync(d));
|
|
190
222
|
if (!appDir) {
|
|
191
223
|
fail('no app/ or src/app/ folder found. Run this inside a Next.js app router project, or pass --app <dir>.');
|
|
192
224
|
}
|
|
225
|
+
return appDir;
|
|
226
|
+
}
|
|
193
227
|
|
|
194
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Writes every template under `base` (or only `files`, paths relative to
|
|
230
|
+
* it) that does not exist yet; returns what was written and skipped.
|
|
231
|
+
*/
|
|
232
|
+
function scaffold(base, appDir, dryRun, files = templateFiles(base)) {
|
|
195
233
|
const written = [];
|
|
196
234
|
const skipped = [];
|
|
197
235
|
for (const file of files) {
|
|
@@ -202,7 +240,7 @@ function initChat({ app, dryRun }) {
|
|
|
202
240
|
}
|
|
203
241
|
if (!dryRun) {
|
|
204
242
|
mkdirSync(dirname(dest), { recursive: true });
|
|
205
|
-
writeFileSync(dest, readFileSync(join(
|
|
243
|
+
writeFileSync(dest, readFileSync(join(base, ...file.split('/'))));
|
|
206
244
|
}
|
|
207
245
|
written.push(dest);
|
|
208
246
|
console.log(`${dryRun ? '○' : '▸'} ${relative(process.cwd(), dest)}`);
|
|
@@ -210,6 +248,87 @@ function initChat({ app, dryRun }) {
|
|
|
210
248
|
for (const dest of skipped) {
|
|
211
249
|
console.log(`· ${relative(process.cwd(), dest)} exists, left as it is`);
|
|
212
250
|
}
|
|
251
|
+
return { written, skipped };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function initApp({ app, dryRun }) {
|
|
255
|
+
if (!existsSync(APP_TEMPLATES_DIR)) {
|
|
256
|
+
fail('this copy carries no app templates: run the published bin (npx ' + PACKAGE_NAME + ' init app).');
|
|
257
|
+
}
|
|
258
|
+
const appDir = findAppDir(app);
|
|
259
|
+
const { written, skipped } = scaffold(APP_TEMPLATES_DIR, appDir, dryRun);
|
|
260
|
+
console.log(
|
|
261
|
+
dryRun
|
|
262
|
+
? `\n✓ Dry run: ${written.length} files would be written, ${skipped.length} left alone.`
|
|
263
|
+
: `\n✓ App set up: ${written.length} files written, ${skipped.length} left alone.`
|
|
264
|
+
);
|
|
265
|
+
const rift = relative(appDir, join(dirname(appDir), 'components', 'rift')).split('\\').join('/');
|
|
266
|
+
const layoutFile = relative(process.cwd(), join(appDir, 'layout.tsx'));
|
|
267
|
+
console.log(`\nNext, make your root layout (${layoutFile}) this, keeping any metadata you have:\n`);
|
|
268
|
+
for (const line of [
|
|
269
|
+
`import '${PACKAGE_NAME}/tokens/tokens.css';`,
|
|
270
|
+
`import '${PACKAGE_NAME}/tokens/base.css';`,
|
|
271
|
+
`import '${rift}/font.css';`,
|
|
272
|
+
`import { riftFont } from '${rift}/font';`,
|
|
273
|
+
`import { ColorModeScript } from '${rift}/ColorModeScript';`,
|
|
274
|
+
`import { RiftRoot } from '${rift}/RiftRoot';`,
|
|
275
|
+
``,
|
|
276
|
+
`export default function RootLayout({ children }: { children: React.ReactNode }) {`,
|
|
277
|
+
` return (`,
|
|
278
|
+
` <html lang="en" className={riftFont.variable} suppressHydrationWarning>`,
|
|
279
|
+
` <head>`,
|
|
280
|
+
` <ColorModeScript />`,
|
|
281
|
+
` </head>`,
|
|
282
|
+
` <body>`,
|
|
283
|
+
` <RiftRoot>{children}</RiftRoot>`,
|
|
284
|
+
` </body>`,
|
|
285
|
+
` </html>`,
|
|
286
|
+
` );`,
|
|
287
|
+
`}`,
|
|
288
|
+
]) {
|
|
289
|
+
console.log(` ${line}`);
|
|
290
|
+
}
|
|
291
|
+
console.log(`\nIf the layout imports a globals.css from create-next-app, drop that import (or empty the file):`);
|
|
292
|
+
console.log(`its body colours and font would cover the library's.`);
|
|
293
|
+
console.log(`\nThen add a first page: npx ${PACKAGE_NAME} init block dashboard (no name lists them).`);
|
|
294
|
+
console.log(`Setup and theming: ${SITE_ORIGIN}/docs/get-started`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Where a template lands. `app/**` goes into the app router folder,
|
|
299
|
+
* `components/**` beside it (src/components when the app is src/app), and
|
|
300
|
+
* the env example at the project root.
|
|
301
|
+
*/
|
|
302
|
+
function destinationFor(file, appDir) {
|
|
303
|
+
if (file.startsWith('app/')) return join(appDir, ...file.slice('app/'.length).split('/'));
|
|
304
|
+
if (file.startsWith('components/')) return join(dirname(appDir), ...file.split('/'));
|
|
305
|
+
return join(process.cwd(), ...file.split('/'));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Whether the project's .gitignore would hide the scaffolded env example:
|
|
310
|
+
* a `.env*`-style rule with no `!.env.chat.example` exception after it.
|
|
311
|
+
* create-next-app writes `.env*`, so a fresh app's example never reaches
|
|
312
|
+
* a commit unless the scaffold says so.
|
|
313
|
+
*/
|
|
314
|
+
function ignoresEnvExample() {
|
|
315
|
+
const gitignore = resolve('.gitignore');
|
|
316
|
+
if (!existsSync(gitignore)) return false;
|
|
317
|
+
let ignored = false;
|
|
318
|
+
for (const raw of readFileSync(gitignore, 'utf8').split('\n')) {
|
|
319
|
+
const line = raw.trim();
|
|
320
|
+
if (line === '.env*' || line === '.env.*' || line === '.env.chat.example') ignored = true;
|
|
321
|
+
if (line === '!.env.chat.example' || line === '!.env*.example' || line === '!.env.*.example') ignored = false;
|
|
322
|
+
}
|
|
323
|
+
return ignored;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function initChat({ app, dryRun }) {
|
|
327
|
+
if (!existsSync(TEMPLATES_DIR)) {
|
|
328
|
+
fail('this copy carries no chat templates: run the published bin (npx ' + PACKAGE_NAME + ' init chat).');
|
|
329
|
+
}
|
|
330
|
+
const appDir = findAppDir(app);
|
|
331
|
+
const { written, skipped } = scaffold(TEMPLATES_DIR, appDir, dryRun);
|
|
213
332
|
|
|
214
333
|
console.log(
|
|
215
334
|
dryRun
|
|
@@ -226,10 +345,47 @@ function initChat({ app, dryRun }) {
|
|
|
226
345
|
` 3. Copy .env.chat.example into .env.local. With NEXT_PUBLIC_CHAT_DEMO=true the widget plays a scripted\n` +
|
|
227
346
|
` conversation and needs no key; set ANTHROPIC_API_KEY and CHAT_ENABLED=true to go live.`
|
|
228
347
|
);
|
|
348
|
+
if (ignoresEnvExample()) {
|
|
349
|
+
console.log(
|
|
350
|
+
` Your .gitignore ignores .env* (create-next-app's default), so git will not see the example.\n` +
|
|
351
|
+
` Commit it on purpose with: git add -f .env.chat.example`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
229
354
|
console.log(
|
|
230
355
|
` 4. Your layout's viewport should set interactiveWidget: "resizes-content", so the phone takeover ends above the keyboard.`
|
|
231
356
|
);
|
|
232
|
-
console.log(`\
|
|
357
|
+
console.log(`\nHow to add it, the protocol and the server options: ${SITE_ORIGIN}/docs/chat`);
|
|
358
|
+
console.log(`Every prop: ${SITE_ORIGIN}/components/${CHAT_SLUG}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function initBlock({ name, app, dryRun }) {
|
|
362
|
+
const registryFile = join(BLOCKS_DIR, 'registry.json');
|
|
363
|
+
if (!existsSync(registryFile)) {
|
|
364
|
+
fail('this copy carries no blocks: run the published bin (npx ' + PACKAGE_NAME + ' init block).');
|
|
365
|
+
}
|
|
366
|
+
const { blocks } = JSON.parse(readFileSync(registryFile, 'utf8'));
|
|
367
|
+
if (!name) {
|
|
368
|
+
console.log('Blocks:\n');
|
|
369
|
+
for (const block of blocks) console.log(` ${block.name.padEnd(12)} ${block.description}`);
|
|
370
|
+
console.log(`\nAdd one with: npx ${PACKAGE_NAME} init block <name>`);
|
|
371
|
+
console.log(`See them: ${SITE_ORIGIN}/docs/blocks`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const block = blocks.find((b) => b.name === name);
|
|
375
|
+
if (!block) {
|
|
376
|
+
fail(`no block named "${name}". The blocks: ${blocks.map((b) => b.name).join(', ')}.`);
|
|
377
|
+
}
|
|
378
|
+
const appDir = findAppDir(app);
|
|
379
|
+
const { written, skipped } = scaffold(BLOCKS_DIR, appDir, dryRun, block.files);
|
|
380
|
+
console.log(
|
|
381
|
+
dryRun
|
|
382
|
+
? `\n✓ Dry run: ${written.length} files would be written, ${skipped.length} left alone.`
|
|
383
|
+
: `\n✓ ${block.label} block added: ${written.length} files written, ${skipped.length} left alone.`
|
|
384
|
+
);
|
|
385
|
+
console.log(`\nNext:`);
|
|
386
|
+
console.log(` 1. If you have not, run npx ${PACKAGE_NAME} init app and make your root layout the one it prints,`);
|
|
387
|
+
console.log(` so the tokens, the base sheet, the font and the colour mode are in place.`);
|
|
388
|
+
console.log(` 2. Open ${block.route} in your dev server. The files are yours: rename, restyle and grow them.`);
|
|
233
389
|
}
|
|
234
390
|
|
|
235
391
|
const args = process.argv.slice(2);
|
|
@@ -252,9 +408,12 @@ function option(flag, what) {
|
|
|
252
408
|
return value;
|
|
253
409
|
}
|
|
254
410
|
|
|
255
|
-
if (args[1] === 'chat') {
|
|
411
|
+
if (args[1] === 'chat' || args[1] === 'app' || args[1] === 'block') {
|
|
256
412
|
const appFlag = option('--app', 'a directory');
|
|
257
|
-
|
|
413
|
+
const scaffoldArgs = { app: appFlag ? resolve(appFlag) : undefined, dryRun: args.includes('--dry-run') };
|
|
414
|
+
if (args[1] === 'chat') initChat(scaffoldArgs);
|
|
415
|
+
else if (args[1] === 'app') initApp(scaffoldArgs);
|
|
416
|
+
else initBlock({ ...scaffoldArgs, name: args[2] && !args[2].startsWith('--') ? args[2] : undefined });
|
|
258
417
|
} else {
|
|
259
418
|
if (args[1] && !args[1].startsWith('--')) fail(`unknown init target "${args[1]}"\n\n${USAGE}`);
|
|
260
419
|
const only = option('--skill', 'a skill name');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { colorModeScript } from 'rift-ds/components/ThemeToggle/colorMode';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies the visitor's stored colour mode before first paint, so a
|
|
5
|
+
* returning visitor never sees the wrong mode flash. Render it in your root
|
|
6
|
+
* layout's <head>. ThemeToggle reads and changes the same setting.
|
|
7
|
+
*/
|
|
8
|
+
export function ColorModeScript() {
|
|
9
|
+
return <script dangerouslySetInnerHTML={{ __html: colorModeScript() }} />;
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { LinkProvider } from 'rift-ds/behaviors/LinkProvider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The app's root for rift-ds: hands Next's Link to every rift-ds component
|
|
8
|
+
* that renders a link, so they navigate through the router instead of
|
|
9
|
+
* reloading the page. Wrap your root layout's body content in it.
|
|
10
|
+
*/
|
|
11
|
+
export function RiftRoot({ children }: { children: React.ReactNode }) {
|
|
12
|
+
return <LinkProvider linkComponent={Link}>{children}</LinkProvider>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Points the library's primary face at the one font.ts loads. The class
|
|
2
|
+
that defines --font-rift-primary sits on <html>, which is :root, so the
|
|
3
|
+
variable resolves here. A shipped theme (data-brand on <html>) sets its
|
|
4
|
+
own face and wins over this rule. */
|
|
5
|
+
:root {
|
|
6
|
+
--font-family-primary: var(--font-rift-primary), 'Nunito Sans', sans-serif;
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Nunito_Sans } from 'next/font/google';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The base theme's typeface, Nunito Sans, self-hosted by Next at build time.
|
|
5
|
+
* Put `riftFont.variable` on <html> in your root layout and import
|
|
6
|
+
* ./font.css after the tokens: together they point the library's
|
|
7
|
+
* --font-family-primary at this face. To use your own face, swap the import
|
|
8
|
+
* here and keep the variable name.
|
|
9
|
+
*/
|
|
10
|
+
export const riftFont = Nunito_Sans({
|
|
11
|
+
subsets: ['latin'],
|
|
12
|
+
weight: ['300', '400', '600', '700'],
|
|
13
|
+
variable: '--font-rift-primary',
|
|
14
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* The dashboard's grid. Four stats across on a desktop, two from the
|
|
2
|
+
laptop width down to a phone; the two panels sit side by side until the
|
|
3
|
+
tablet width, then stack. */
|
|
4
|
+
|
|
5
|
+
.stats {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
8
|
+
gap: var(--gap-500);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.panels {
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
14
|
+
gap: var(--gap-500);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The projects table: its title, then the table on the page itself. */
|
|
18
|
+
.tableSection {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--gap-300);
|
|
22
|
+
min-width: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.panelTitle {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-family: var(--font-title-body-family);
|
|
28
|
+
font-size: var(--font-title-body-size);
|
|
29
|
+
font-weight: var(--font-title-body-weight);
|
|
30
|
+
line-height: var(--font-title-body-line-height);
|
|
31
|
+
letter-spacing: var(--font-title-body-letter-spacing);
|
|
32
|
+
color: var(--color-text-primary);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.list {
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 0;
|
|
38
|
+
list-style: none;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: var(--gap-400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.progressRow {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: var(--gap-200);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.progressMeta,
|
|
51
|
+
.activityRow {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: baseline;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
gap: var(--gap-400);
|
|
56
|
+
font-size: var(--font-paragraph-sm-size);
|
|
57
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
58
|
+
color: var(--color-text-primary);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.muted {
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
color: var(--color-text-secondary);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.strong {
|
|
67
|
+
font-weight: var(--font-paragraph-sm-emphasis-weight);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media (max-width: 1279px) {
|
|
71
|
+
.stats {
|
|
72
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (max-width: 959px) {
|
|
77
|
+
.panels {
|
|
78
|
+
grid-template-columns: minmax(0, 1fr);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (max-width: 600px) {
|
|
83
|
+
.stats {
|
|
84
|
+
gap: var(--gap-400);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.panels {
|
|
88
|
+
gap: var(--gap-400);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard block: the app shell with a row of stats, two panels and a
|
|
3
|
+
* table. The numbers are placeholders; swap each array for your data.
|
|
4
|
+
*
|
|
5
|
+
* The table is a DataTable with typed columns, so it needs no render
|
|
6
|
+
* functions: the status is a Badge in its option's role, the owner a
|
|
7
|
+
* person, the due date a date, each sorting and filtering by its type.
|
|
8
|
+
* Plain column objects can pass from this server page to the client
|
|
9
|
+
* table, which a render function could not. TableToolbar is its control
|
|
10
|
+
* bar: search, filters, sort and the column panel.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Button } from 'rift-ds/components/Button/Button';
|
|
14
|
+
import { Panel } from 'rift-ds/components/Panel/Panel';
|
|
15
|
+
import { ProgressBar } from 'rift-ds/components/ProgressBar/ProgressBar';
|
|
16
|
+
import { Stat } from 'rift-ds/components/Stat/Stat';
|
|
17
|
+
import { DataTable, type DataTableColumn, type DataTableRow } from 'rift-ds/components/DataTable/DataTable';
|
|
18
|
+
import { TableToolbar } from 'rift-ds/components/TableToolbar/TableToolbar';
|
|
19
|
+
import { AppShell } from '../../components/blocks/AppShell';
|
|
20
|
+
import styles from './page.module.css';
|
|
21
|
+
|
|
22
|
+
const STATS = [
|
|
23
|
+
{ label: 'Open tasks', value: '128', delta: '+12', trend: 'up' },
|
|
24
|
+
{ label: 'Due this week', value: '24', delta: '-3', trend: 'down' },
|
|
25
|
+
{ label: 'Completed', value: '342', delta: '+18%', trend: 'up' },
|
|
26
|
+
{ label: 'Avg. cycle time', value: '3.2d', delta: '-0.4d', trend: 'up' },
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const PROGRESS = [
|
|
30
|
+
{ name: 'Website refresh', done: 72 },
|
|
31
|
+
{ name: 'Mobile app', done: 45 },
|
|
32
|
+
{ name: 'Billing migration', done: 88 },
|
|
33
|
+
{ name: 'Onboarding emails', done: 20 },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const ACTIVITY = [
|
|
37
|
+
{ who: 'Priya', what: 'closed', item: 'Fix login redirect', when: '10 min ago' },
|
|
38
|
+
{ who: 'Sam', what: 'commented on', item: 'Pricing page copy', when: '1 hr ago' },
|
|
39
|
+
{ who: 'Jordan', what: 'created', item: 'Q4 roadmap review', when: '3 hr ago' },
|
|
40
|
+
{ who: 'Lee', what: 'moved', item: 'Invoice export to Review', when: 'Yesterday' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const STATUSES = [
|
|
44
|
+
{ value: 'Planned', label: 'Planned', status: 'neutral' },
|
|
45
|
+
{ value: 'On track', label: 'On track', status: 'positive' },
|
|
46
|
+
{ value: 'At risk', label: 'At risk', status: 'warning' },
|
|
47
|
+
{ value: 'Off track', label: 'Off track', status: 'error' },
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
const COLUMNS: DataTableColumn[] = [
|
|
51
|
+
{ key: 'project', header: 'Project', rowHeader: true, sortable: true },
|
|
52
|
+
{ key: 'owner', header: 'Owner', type: 'person', sortable: true },
|
|
53
|
+
{ key: 'status', header: 'Status', type: 'status', sortable: true, options: [...STATUSES] },
|
|
54
|
+
{ key: 'due', header: 'Due', type: 'date', sortable: true },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const PROJECTS = [
|
|
58
|
+
{ project: 'Website refresh', owner: 'Priya Shah', status: 'On track', due: '2026-11-14' },
|
|
59
|
+
{ project: 'Mobile app', owner: 'Sam Rivera', status: 'At risk', due: '2026-12-02' },
|
|
60
|
+
{ project: 'Billing migration', owner: 'Jordan Lee', status: 'On track', due: '2026-11-08' },
|
|
61
|
+
{ project: 'Onboarding emails', owner: 'Lee Chen', status: 'Off track', due: '2026-10-30' },
|
|
62
|
+
{ project: 'Partner portal', owner: 'Alex Morgan', status: 'Planned', due: '2027-01-15' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const ROWS: DataTableRow[] = PROJECTS.map((p) => ({ id: p.project, values: p }));
|
|
66
|
+
|
|
67
|
+
export default function DashboardPage() {
|
|
68
|
+
return (
|
|
69
|
+
<AppShell
|
|
70
|
+
activeKey="dashboard"
|
|
71
|
+
breadcrumb={[{ label: 'Workspace', href: '#' }, { label: 'Dashboard' }]}
|
|
72
|
+
title="Dashboard"
|
|
73
|
+
actions={
|
|
74
|
+
<>
|
|
75
|
+
<Button variant="secondary" size="compact" label="Export" iconLeft="download" />
|
|
76
|
+
<Button variant="primary" size="compact" label="New task" iconLeft="add" />
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
<div className={styles.stats}>
|
|
81
|
+
{STATS.map((stat) => (
|
|
82
|
+
<Panel key={stat.label}>
|
|
83
|
+
<Stat
|
|
84
|
+
label={stat.label}
|
|
85
|
+
value={stat.value}
|
|
86
|
+
delta={stat.delta}
|
|
87
|
+
trend={stat.trend}
|
|
88
|
+
deltaPlacement="inline"
|
|
89
|
+
/>
|
|
90
|
+
</Panel>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className={styles.panels}>
|
|
95
|
+
<Panel>
|
|
96
|
+
<h2 className={styles.panelTitle}>Progress by project</h2>
|
|
97
|
+
<ul className={styles.list}>
|
|
98
|
+
{PROGRESS.map((row) => (
|
|
99
|
+
<li key={row.name} className={styles.progressRow}>
|
|
100
|
+
<div className={styles.progressMeta}>
|
|
101
|
+
<span>{row.name}</span>
|
|
102
|
+
<span className={styles.muted}>{row.done}%</span>
|
|
103
|
+
</div>
|
|
104
|
+
<ProgressBar value={row.done} size="compact" ariaLabel={`${row.name} progress`} />
|
|
105
|
+
</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
</Panel>
|
|
109
|
+
|
|
110
|
+
<Panel>
|
|
111
|
+
<h2 className={styles.panelTitle}>Recent activity</h2>
|
|
112
|
+
<ul className={styles.list}>
|
|
113
|
+
{ACTIVITY.map((row) => (
|
|
114
|
+
<li key={row.item} className={styles.activityRow}>
|
|
115
|
+
<span>
|
|
116
|
+
<strong className={styles.strong}>{row.who}</strong> {row.what} {row.item}
|
|
117
|
+
</span>
|
|
118
|
+
<span className={styles.muted}>{row.when}</span>
|
|
119
|
+
</li>
|
|
120
|
+
))}
|
|
121
|
+
</ul>
|
|
122
|
+
</Panel>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* The table sits on the page, not in a Panel: it brings its own bordered frame. */}
|
|
126
|
+
<section className={styles.tableSection} aria-labelledby="projects-title">
|
|
127
|
+
<h2 id="projects-title" className={styles.panelTitle}>
|
|
128
|
+
Projects
|
|
129
|
+
</h2>
|
|
130
|
+
<DataTable
|
|
131
|
+
columns={COLUMNS}
|
|
132
|
+
rows={ROWS}
|
|
133
|
+
caption="Projects"
|
|
134
|
+
defaultSort={{ key: 'due', direction: 'asc' }}
|
|
135
|
+
phoneLayout="stack"
|
|
136
|
+
toolbar={<TableToolbar />}
|
|
137
|
+
/>
|
|
138
|
+
</section>
|
|
139
|
+
</AppShell>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* A card centred on the page at every width: 400px wide on a desktop, the
|
|
2
|
+
full width less the gutter on a phone, where it drops its fill and sits
|
|
3
|
+
straight on the page. */
|
|
4
|
+
|
|
5
|
+
.page {
|
|
6
|
+
min-height: 100dvh;
|
|
7
|
+
display: grid;
|
|
8
|
+
place-items: center;
|
|
9
|
+
padding: var(--padding-1000) var(--padding-500);
|
|
10
|
+
background-color: var(--color-bg-page-primary);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.card {
|
|
14
|
+
width: 100%;
|
|
15
|
+
max-width: 400px;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--gap-500);
|
|
19
|
+
padding: var(--padding-1000);
|
|
20
|
+
border-radius: var(--radius-600);
|
|
21
|
+
background-color: var(--color-bg-container-primary);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.intro {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--gap-200);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.product {
|
|
31
|
+
font-family: var(--font-title-body-family);
|
|
32
|
+
font-size: var(--font-title-body-size);
|
|
33
|
+
font-weight: var(--font-title-body-weight);
|
|
34
|
+
line-height: var(--font-title-body-line-height);
|
|
35
|
+
color: var(--color-text-primary);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.title {
|
|
39
|
+
margin: 0;
|
|
40
|
+
font-family: var(--font-heading-2-family);
|
|
41
|
+
font-size: var(--font-heading-2-size);
|
|
42
|
+
font-weight: var(--font-heading-2-weight);
|
|
43
|
+
line-height: var(--font-heading-2-line-height);
|
|
44
|
+
letter-spacing: var(--font-heading-2-letter-spacing);
|
|
45
|
+
color: var(--color-text-primary);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.subhead,
|
|
49
|
+
.footer {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-size: var(--font-paragraph-sm-size);
|
|
52
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
53
|
+
color: var(--color-text-secondary);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.form {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: var(--gap-400);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.row {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: space-between;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
gap: var(--gap-200);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.full {
|
|
71
|
+
width: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.footer {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
gap: var(--gap-100);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (max-width: 600px) {
|
|
82
|
+
.page {
|
|
83
|
+
place-items: start center;
|
|
84
|
+
padding: var(--padding-1000) var(--padding-400);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.card {
|
|
88
|
+
padding: 0;
|
|
89
|
+
background-color: transparent;
|
|
90
|
+
}
|
|
91
|
+
}
|