rift-ds 1.15.0 → 1.17.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 +3 -3
- package/bin/templates/blocks/components/blocks/AppShell.tsx +1 -1
- package/chat/ChatLauncher.d.ts +11 -1
- package/chat/ChatLauncher.js +7 -1
- package/chat/ChatWidget.css +21 -0
- package/components/AppLayout/AppLayout.css +90 -2
- package/components/AppLayout/AppLayout.d.ts +10 -2
- package/components/AppLayout/AppLayout.js +20 -2
- package/components/AppSidebar/AppSidebar.css +26 -0
- package/components/AppSidebar/AppSidebar.d.ts +15 -4
- package/components/AppSidebar/AppSidebar.js +7 -2
- package/components/Checkbox/Checkbox.css +4 -2
- package/components/DataTable/DataTable.js +29 -9
- package/components/DataTable/FilterEditor.js +1 -2
- package/components/DataTable/filterModel.js +4 -3
- package/components/DataTable/views.d.ts +9 -0
- package/components/DataTable/views.js +10 -1
- package/components/Drawer/Drawer.d.ts +6 -0
- package/components/Drawer/Drawer.js +11 -1
- package/components/MegaMenu/MegaMenu.css +271 -0
- package/components/MegaMenu/MegaMenu.d.ts +72 -0
- package/components/MegaMenu/MegaMenu.js +71 -0
- package/components/Nav/Nav.d.ts +7 -2
- package/components/ProgressiveBlur/ProgressiveBlur.css +102 -0
- package/components/ProgressiveBlur/ProgressiveBlur.d.ts +30 -0
- package/components/ProgressiveBlur/ProgressiveBlur.js +27 -0
- package/components/PromptSuggestions/PromptSuggestions.css +1 -1
- package/components/QuestionCard/QuestionCard.css +3 -2
- package/components/QuestionCard/QuestionCard.js +2 -8
- package/components/RadioButton/RadioButton.css +5 -3
- package/components/SegmentedControl/SegmentedControl.css +69 -0
- package/components/SegmentedControl/SegmentedControl.d.ts +4 -3
- package/components/SegmentedControl/SegmentedControl.js +90 -48
- package/components/SiteHeader/SiteHeader.css +305 -0
- package/components/SiteHeader/SiteHeader.d.ts +117 -0
- package/components/SiteHeader/SiteHeader.js +329 -0
- package/components/TableToolbar/TableToolbar.css +6 -0
- package/components/TableToolbar/TableToolbar.js +52 -16
- package/components/ThemeToggle/ThemeToggle.css +2 -72
- package/components/ThemeToggle/ThemeToggle.d.ts +1 -1
- package/components/ThemeToggle/ThemeToggle.js +4 -40
- package/components/registry.json +26 -2
- package/components/registry.json.d.ts +26 -2
- package/components/registry.json.js +1 -1
- package/index.d.ts +3 -0
- package/index.js +6 -0
- package/package.json +1 -1
- package/tokens/registry.json +3 -0
- package/tokens/registry.json.d.ts +3 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +8 -0
- package/tokens/tokens-light.css +8 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ An open source React design system built for AI products and coding agents: comp
|
|
|
13
13
|
|
|
14
14
|
## Why Rift DS
|
|
15
15
|
|
|
16
|
-
- **<!-- component-count -->
|
|
16
|
+
- **<!-- component-count -->162<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. Every one forwards a ref and your own attributes to its element, state you can own works controlled or uncontrolled (a modal opens when your app says so, by design), and every word a component writes has a prop you can localise. The build checks the first two on every component, and the words wherever they sit in markup. React and React DOM are the only required peer dependencies.
|
|
17
17
|
- **AI patterns, the whole chat and templates, ready to use.** Composer, streaming chat thread, tool calls, reasoning and thread panel are in the box, the complete AI chat ships assembled as one widget that one command scaffolds, and the docs site has complete template screens built from them.
|
|
18
18
|
- **Fully themeable.** The shipped themes are complete looks to start from, each in light and dark. One `data-brand` attribute applies a theme, and a few CSS overrides make your own.
|
|
19
19
|
- **Ready for coding agents.** One command installs the agent skills, and a public MCP endpoint serves every component's props, so your agent builds with the library as it actually is. No key, no account.
|
|
@@ -107,13 +107,13 @@ import 'rift-ds/tokens/presets/presets.css';
|
|
|
107
107
|
|
|
108
108
|
## Components
|
|
109
109
|
|
|
110
|
-
<!-- component-count -->
|
|
110
|
+
<!-- component-count -->162<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
|
|
111
111
|
|
|
112
112
|
<details>
|
|
113
113
|
<summary>The full list</summary>
|
|
114
114
|
|
|
115
115
|
<!-- component-list:start -->
|
|
116
|
-
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
116
|
+
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bento tile · Bento tile art · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Command palette trigger · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mega menu · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Progressive blur · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Site header · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Table toolbar · Tabs · Tag input · Textarea · Theme picker · Theme toggle · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
117
117
|
<!-- component-list:end -->
|
|
118
118
|
|
|
119
119
|
</details>
|
|
@@ -78,7 +78,7 @@ export function AppShell({ activeKey, breadcrumb, title, actions, children }: Ap
|
|
|
78
78
|
expanded={expanded}
|
|
79
79
|
onExpandedChange={setExpanded}
|
|
80
80
|
logoText={PRODUCT_NAME}
|
|
81
|
-
floating
|
|
81
|
+
layout="floating"
|
|
82
82
|
/>
|
|
83
83
|
|
|
84
84
|
<div className={`${styles.main} ${expanded ? styles.mainExpanded : ''}`}>
|
package/chat/ChatLauncher.d.ts
CHANGED
|
@@ -44,6 +44,16 @@ export interface ChatLauncherProps {
|
|
|
44
44
|
widget?: Omit<ChatWidgetProps, 'fullscreenEnabled' | 'placementEnabled' | 'compact' | 'phone'>;
|
|
45
45
|
/** Rendered inside the panel after the widget: for a host's extra chrome. */
|
|
46
46
|
children?: ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* How the docked panel sits on the page. `raised` (the default) is a glass
|
|
49
|
+
* card with the floating shadow, inset by `--ds-chat-inset`. `inset` is
|
|
50
|
+
* flat: the app surface colour, no shadow, border or blur, and the
|
|
51
|
+
* `--ds-shell-inset` gap, so it matches AppLayout's `inset` content
|
|
52
|
+
* surface beside it. The overlay below the dock threshold takes the same
|
|
53
|
+
* gap but keeps its shadow, since it still covers the page; the floating
|
|
54
|
+
* card and the takeover keep their own look.
|
|
55
|
+
*/
|
|
56
|
+
surface?: 'raised' | 'inset';
|
|
47
57
|
}
|
|
48
58
|
/**
|
|
49
59
|
* The site-wide panel. Mount it once, after the page content, so it sits
|
|
@@ -68,4 +78,4 @@ export interface ChatLauncherProps {
|
|
|
68
78
|
* touch the host's body). The panel's geometry reads `--ds-chat-width`,
|
|
69
79
|
* `--ds-chat-inset` and `--ds-chat-float-height`, defaulted on `:root`.
|
|
70
80
|
*/
|
|
71
|
-
export declare function ChatLauncher({ label, icon, summary, hidden, panelId, hostAttribute, panelLabel, aiButtonLabels, widget, children, }: ChatLauncherProps): import("react").JSX.Element | null;
|
|
81
|
+
export declare function ChatLauncher({ label, icon, summary, hidden, panelId, hostAttribute, panelLabel, aiButtonLabels, widget, children, surface, }: ChatLauncherProps): import("react").JSX.Element | null;
|
package/chat/ChatLauncher.js
CHANGED
|
@@ -44,7 +44,8 @@ function ChatLauncher({
|
|
|
44
44
|
panelLabel = "Chat",
|
|
45
45
|
aiButtonLabels,
|
|
46
46
|
widget,
|
|
47
|
-
children
|
|
47
|
+
children,
|
|
48
|
+
surface = "raised"
|
|
48
49
|
}) {
|
|
49
50
|
const { open, setOpen, panelPhase, view, side, floating, returnFocusRef, send } = useChatWidget();
|
|
50
51
|
const launchSuggestion = (id) => {
|
|
@@ -270,6 +271,10 @@ function ChatLauncher({
|
|
|
270
271
|
side === "left" ? "ds-chat-launcher__panel--left" : "",
|
|
271
272
|
isFloating ? "ds-chat-launcher__panel--floating" : "",
|
|
272
273
|
isFull ? "ds-chat-launcher__panel--full" : "",
|
|
274
|
+
// Inset: the dock and the overlay both sit at the shell's gap; only the
|
|
275
|
+
// dock goes flat, since the overlay still covers the page.
|
|
276
|
+
surface === "inset" && !isFloating && !isFull ? "ds-chat-launcher__panel--inset" : "",
|
|
277
|
+
surface === "inset" && docked && !isFloating && !isFull ? "ds-chat-launcher__panel--flat" : "",
|
|
273
278
|
panelPhase === "closing" ? "ds-chat-launcher__panel--closing" : ""
|
|
274
279
|
].filter(Boolean).join(" ");
|
|
275
280
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -332,6 +337,7 @@ function ChatLauncher({
|
|
|
332
337
|
className: [
|
|
333
338
|
"ds-chat-launcher__dock-handle",
|
|
334
339
|
side === "left" ? "ds-chat-launcher__dock-handle--left" : "",
|
|
340
|
+
surface === "inset" ? "ds-chat-launcher__dock-handle--inset" : "",
|
|
335
341
|
resizing ? "ds-chat-launcher__dock-handle--resizing" : ""
|
|
336
342
|
].filter(Boolean).join(" "),
|
|
337
343
|
"aria-hidden": "true",
|
package/chat/ChatWidget.css
CHANGED
|
@@ -995,6 +995,22 @@ html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
|
995
995
|
left: var(--ds-chat-inset); /* ds-allow(physical): the left dock is the screen's left edge */
|
|
996
996
|
}
|
|
997
997
|
|
|
998
|
+
/* The inset surface (ChatLauncher's surface="inset"). Docked and as an
|
|
999
|
+
overlay, the panel is held off the edges by the shell's gap rather than
|
|
1000
|
+
the chat's own, so it lines up with AppLayout's inset content surface.
|
|
1001
|
+
Docked, it is also flat: a plane on the app surface colour with no
|
|
1002
|
+
shadow, border or blur. The overlay keeps its shadow over the page. */
|
|
1003
|
+
.ds-chat-launcher__panel--inset {
|
|
1004
|
+
--ds-chat-inset: var(--ds-shell-inset, 10px); /* AppSidebar.css declares the gap; the fallback mirrors it */
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.ds-chat-launcher__panel--flat {
|
|
1008
|
+
background: var(--color-bg-app-surface);
|
|
1009
|
+
border-color: transparent;
|
|
1010
|
+
box-shadow: none;
|
|
1011
|
+
backdrop-filter: none;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
998
1014
|
/* Fullscreen view: a true viewport takeover. The stronger blur keeps the
|
|
999
1015
|
transcript readable while the page's colours still shine through. The
|
|
1000
1016
|
border fades rather than vanishing, so the geometry never snaps mid-glide. */
|
|
@@ -1047,6 +1063,11 @@ html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
|
1047
1063
|
left: calc(var(--ds-chat-inset) + var(--ds-chat-width) - 10px); /* ds-allow(physical): the dock grip tracks the docked card's screen edge */
|
|
1048
1064
|
}
|
|
1049
1065
|
|
|
1066
|
+
/* The inset surface's dock sits at the shell's gap, so its grip does too. */
|
|
1067
|
+
.ds-chat-launcher__dock-handle--inset {
|
|
1068
|
+
--ds-chat-inset: var(--ds-shell-inset, 10px); /* AppSidebar.css declares the gap; the fallback mirrors it */
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1050
1071
|
.ds-chat-launcher__dock-handle::after {
|
|
1051
1072
|
content: "";
|
|
1052
1073
|
width: 4px;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
/* The rail's width plus, while it shows, the side panel's. */
|
|
20
20
|
margin-inline-start: calc(var(--ds-app-layout-rail, 64px) + var(--ds-app-layout-panel, 0px));
|
|
21
21
|
padding: var(--padding-1000) var(--padding-500);
|
|
22
|
-
transition: margin-
|
|
22
|
+
transition: margin-inline-start var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
23
23
|
min-width: 0;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -47,6 +47,72 @@
|
|
|
47
47
|
gap: var(--gap-1000);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/* ============================================
|
|
51
|
+
FLOATING LAYOUT — the rail floats as a glass
|
|
52
|
+
card, so the main column clears its inset on
|
|
53
|
+
both sides of it
|
|
54
|
+
============================================ */
|
|
55
|
+
.ds-app-layout--floating .ds-app-layout__main {
|
|
56
|
+
margin-inline-start: calc(
|
|
57
|
+
var(--ds-app-layout-rail, 64px) + var(--ds-app-layout-panel, 0px) +
|
|
58
|
+
var(--ds-sidebar-float-inset, 20px) * 2
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* ============================================
|
|
63
|
+
INSET LAYOUT — the rail flush on the shell
|
|
64
|
+
colour, the content full width in a surface
|
|
65
|
+
inset from the other three edges. The page
|
|
66
|
+
holds the viewport and the surface scrolls.
|
|
67
|
+
The rail's own item inset is the gap on its
|
|
68
|
+
side, so the selected item sits evenly
|
|
69
|
+
between the two edges.
|
|
70
|
+
============================================ */
|
|
71
|
+
.ds-app-layout--inset {
|
|
72
|
+
height: 100dvh;
|
|
73
|
+
min-height: 0;
|
|
74
|
+
background-color: var(--color-bg-app-shell);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ds-app-layout--inset .ds-app-layout__main {
|
|
78
|
+
padding: var(--ds-shell-inset);
|
|
79
|
+
padding-inline-start: 0;
|
|
80
|
+
min-height: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ds-app-layout__surface {
|
|
84
|
+
flex: 1;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
display: flex;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
background-color: var(--color-bg-app-surface);
|
|
89
|
+
border-radius: var(--radius-600);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ds-app-layout__scroller {
|
|
93
|
+
flex: 1;
|
|
94
|
+
min-width: 0;
|
|
95
|
+
overflow-y: auto;
|
|
96
|
+
padding: var(--padding-1000) var(--padding-1000);
|
|
97
|
+
/* The scrollbar stays invisible until the surface scrolls (data-scrolling
|
|
98
|
+
is set by the scroll handler and cleared once it settles). */
|
|
99
|
+
scrollbar-width: thin;
|
|
100
|
+
scrollbar-color: transparent transparent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ds-app-layout__scroller[data-scrolling] {
|
|
104
|
+
scrollbar-color: var(--color-bg-container-border) transparent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Full width in the surface, and a size container: content inside can
|
|
108
|
+
restack by the room the surface gives it (an open chat panel or an
|
|
109
|
+
expanded rail narrows it without the viewport changing) with
|
|
110
|
+
@container ds-app-layout-content queries. */
|
|
111
|
+
.ds-app-layout--inset .ds-app-layout__content {
|
|
112
|
+
max-width: none;
|
|
113
|
+
container: ds-app-layout-content / inline-size;
|
|
114
|
+
}
|
|
115
|
+
|
|
50
116
|
/* ============================================
|
|
51
117
|
RESPONSIVE
|
|
52
118
|
============================================ */
|
|
@@ -56,7 +122,29 @@
|
|
|
56
122
|
padding: var(--padding-500) var(--padding-400);
|
|
57
123
|
}
|
|
58
124
|
|
|
59
|
-
.ds-app-layout--expanded .ds-app-layout__main
|
|
125
|
+
.ds-app-layout--expanded .ds-app-layout__main,
|
|
126
|
+
.ds-app-layout--floating .ds-app-layout__main {
|
|
60
127
|
margin-inline-start: 0;
|
|
61
128
|
}
|
|
129
|
+
|
|
130
|
+
/* Phones: the rail is a drawer, so the inset surface runs edge to edge
|
|
131
|
+
and the page scrolls normally. */
|
|
132
|
+
.ds-app-layout--inset {
|
|
133
|
+
height: auto;
|
|
134
|
+
min-height: 100vh;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ds-app-layout--inset .ds-app-layout__main {
|
|
138
|
+
padding: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ds-app-layout__surface {
|
|
142
|
+
overflow: visible;
|
|
143
|
+
border-radius: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ds-app-layout__scroller {
|
|
147
|
+
overflow-y: visible;
|
|
148
|
+
padding: var(--padding-500) var(--padding-400);
|
|
149
|
+
}
|
|
62
150
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { AppSidebarSection, AppSidebarProfile } from '../AppSidebar/AppSidebar';
|
|
2
|
+
import { AppSidebarSection, AppSidebarProfile, AppSidebarLayout } from '../AppSidebar/AppSidebar';
|
|
3
3
|
import { LinkComponent } from '../../behaviors/LinkProvider';
|
|
4
4
|
/** Props owned by AppLayout itself — everything else falls through to the outer wrapper `<div>`. */
|
|
5
5
|
type AppLayoutOwnProps = {
|
|
@@ -23,6 +23,14 @@ type AppLayoutOwnProps = {
|
|
|
23
23
|
logo?: React.ReactNode;
|
|
24
24
|
/** Page content — centred in the main area */
|
|
25
25
|
children: React.ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* How the rail and the content meet the page. `flush` (the default) runs
|
|
28
|
+
* the rail edge to edge with the content centred beside it. `floating`
|
|
29
|
+
* floats the rail as a glass card, the content still centred. `inset` runs
|
|
30
|
+
* the rail flush on the app shell colour and sets the content full width
|
|
31
|
+
* in a surface inset from the other edges, which scrolls inside itself.
|
|
32
|
+
*/
|
|
33
|
+
layout?: AppSidebarLayout;
|
|
26
34
|
/**
|
|
27
35
|
* Colour scheme: 'dark' pins the layout to the dark theme (the historical
|
|
28
36
|
* behaviour and the default); 'inherit' drops the pin so the layout
|
|
@@ -42,7 +50,7 @@ export interface AppLayoutProps extends AppLayoutOwnProps, Omit<React.ComponentP
|
|
|
42
50
|
}
|
|
43
51
|
/**
|
|
44
52
|
* AppLayout — full-page template with a collapsible sidebar
|
|
45
|
-
* and a centred
|
|
53
|
+
* and a content area, centred or set in an inset surface (see `layout`).
|
|
46
54
|
*
|
|
47
55
|
* The forwarded ref and any unrecognised props target the outer wrapper
|
|
48
56
|
* `<div>`. With `theme="inherit"` a `data-theme` passed by the consumer
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
-
import React, { useState, useCallback } from "react";
|
|
3
|
+
import React, { useState, useCallback, useRef, useEffect } from "react";
|
|
4
4
|
import { AppSidebar } from "../AppSidebar/AppSidebar.js";
|
|
5
|
+
import { MOTION_SCROLL_SETTLE_MS } from "../../tokens/motion.js";
|
|
5
6
|
import "./AppLayout.css";
|
|
6
7
|
const AppLayout = React.forwardRef(
|
|
7
8
|
({
|
|
@@ -15,6 +16,7 @@ const AppLayout = React.forwardRef(
|
|
|
15
16
|
logoText,
|
|
16
17
|
logo,
|
|
17
18
|
children,
|
|
19
|
+
layout = "flush",
|
|
18
20
|
theme = "dark",
|
|
19
21
|
linkComponent,
|
|
20
22
|
className = "",
|
|
@@ -25,9 +27,20 @@ const AppLayout = React.forwardRef(
|
|
|
25
27
|
const handleExpandedChange = useCallback((next) => {
|
|
26
28
|
setExpanded(next);
|
|
27
29
|
}, []);
|
|
30
|
+
const scrollTimer = useRef(void 0);
|
|
31
|
+
useEffect(() => () => clearTimeout(scrollTimer.current), []);
|
|
32
|
+
const handleSurfaceScroll = useCallback((event) => {
|
|
33
|
+
const scroller = event.currentTarget;
|
|
34
|
+
scroller.dataset.scrolling = "";
|
|
35
|
+
clearTimeout(scrollTimer.current);
|
|
36
|
+
scrollTimer.current = setTimeout(() => {
|
|
37
|
+
delete scroller.dataset.scrolling;
|
|
38
|
+
}, MOTION_SCROLL_SETTLE_MS);
|
|
39
|
+
}, []);
|
|
28
40
|
const classes = [
|
|
29
41
|
baseClass,
|
|
30
42
|
expanded ? `${baseClass}--expanded` : "",
|
|
43
|
+
layout !== "flush" ? `${baseClass}--${layout}` : "",
|
|
31
44
|
className
|
|
32
45
|
].filter(Boolean).join(" ");
|
|
33
46
|
return /* @__PURE__ */ jsxs(
|
|
@@ -51,10 +64,15 @@ const AppLayout = React.forwardRef(
|
|
|
51
64
|
onExpandedChange: handleExpandedChange,
|
|
52
65
|
logoText,
|
|
53
66
|
logo,
|
|
67
|
+
layout,
|
|
54
68
|
linkComponent
|
|
55
69
|
}
|
|
56
70
|
),
|
|
57
|
-
/* @__PURE__ */ jsx("main", { className: `${baseClass}__main`, children:
|
|
71
|
+
/* @__PURE__ */ jsx("main", { className: `${baseClass}__main`, children: layout === "inset" ? (
|
|
72
|
+
// The surface clips to its rounded corners and the scroller
|
|
73
|
+
// inside it scrolls, so the scrollbar never squares a corner.
|
|
74
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__surface`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__scroller`, onScroll: handleSurfaceScroll, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) }) })
|
|
75
|
+
) : /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) })
|
|
58
76
|
]
|
|
59
77
|
}
|
|
60
78
|
);
|
|
@@ -51,6 +51,22 @@
|
|
|
51
51
|
backdrop-filter: blur(24px);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/* The inset layout's gap: between the content surface and the viewport
|
|
55
|
+
edges, and around a docked chat panel's inset surface (ChatLauncher's
|
|
56
|
+
surface="inset" falls back to the same value when this sheet is absent). */
|
|
57
|
+
:root {
|
|
58
|
+
--ds-shell-inset: 10px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ============================================
|
|
62
|
+
INSET VARIANT — the rail flush on the app
|
|
63
|
+
shell colour, beside a content surface inset
|
|
64
|
+
from the edges (AppLayout's inset layout)
|
|
65
|
+
============================================ */
|
|
66
|
+
.ds-app-sidebar--inset {
|
|
67
|
+
background-color: var(--color-bg-app-shell);
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
/* ============================================
|
|
55
71
|
TOP SECTION — logo + nav
|
|
56
72
|
============================================ */
|
|
@@ -605,6 +621,16 @@
|
|
|
605
621
|
inset-inline-start: calc(var(--ds-sidebar-float-inset, 20px) + 280px + var(--gap-300));
|
|
606
622
|
}
|
|
607
623
|
|
|
624
|
+
/* Inset: the side panel joins the rail on the shell plane, with no borders
|
|
625
|
+
between them, so rail and panel read as one column of navigation and the
|
|
626
|
+
inset content surface starts beyond both (AppLayout reserves the panel's
|
|
627
|
+
width while it shows). */
|
|
628
|
+
.ds-app-sidebar__panel--inset {
|
|
629
|
+
background-color: var(--color-bg-app-shell);
|
|
630
|
+
border-inline-start-color: transparent;
|
|
631
|
+
border-inline-end-color: transparent;
|
|
632
|
+
}
|
|
633
|
+
|
|
608
634
|
/* ============================================
|
|
609
635
|
PANEL MODE RAIL — labels under icons
|
|
610
636
|
With the second level in the panel, the
|
|
@@ -61,6 +61,8 @@ export interface AppSidebarProfile {
|
|
|
61
61
|
/** Avatar image URL */
|
|
62
62
|
avatarUrl?: string;
|
|
63
63
|
}
|
|
64
|
+
/** How the rail meets the page: edge to edge, floating as a card, or flush beside an inset content surface. */
|
|
65
|
+
export type AppSidebarLayout = 'flush' | 'floating' | 'inset';
|
|
64
66
|
/** Props owned by AppSidebar itself — everything else falls through to the `<nav>`. */
|
|
65
67
|
type AppSidebarOwnProps = {
|
|
66
68
|
/** Navigation sections */
|
|
@@ -97,10 +99,19 @@ type AppSidebarOwnProps = {
|
|
|
97
99
|
/** Callback when profile more button clicked */
|
|
98
100
|
onProfileMore?: () => void;
|
|
99
101
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* the
|
|
102
|
+
* How the rail meets the page. `flush` runs it edge to edge on the page
|
|
103
|
+
* colour. `floating` floats it off the viewport edges as a glass card:
|
|
104
|
+
* inset with rounded corners, the translucent glass fill over a backdrop
|
|
105
|
+
* blur, and the floating shadow (the inset defaults to 20px and is
|
|
106
|
+
* overridable via the --ds-sidebar-float-inset custom property). `inset`
|
|
107
|
+
* runs it flush on the app shell colour, beside a content surface inset
|
|
108
|
+
* from the edges, as AppLayout's `inset` layout draws it. Defaults to
|
|
109
|
+
* `flush` (or `floating` while the deprecated `floating` is set).
|
|
110
|
+
*/
|
|
111
|
+
layout?: AppSidebarLayout;
|
|
112
|
+
/**
|
|
113
|
+
* Floats the rail as a glass card, the same as `layout="floating"`.
|
|
114
|
+
* @deprecated Use `layout="floating"`.
|
|
104
115
|
*/
|
|
105
116
|
floating?: boolean;
|
|
106
117
|
/** Rendered under the logo row, above the nav; fades out while collapsed */
|
|
@@ -46,7 +46,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
46
46
|
expanded: controlledExpanded,
|
|
47
47
|
onExpandedChange,
|
|
48
48
|
onProfileMore,
|
|
49
|
-
|
|
49
|
+
layout: layoutProp,
|
|
50
|
+
floating: floatingProp = false,
|
|
50
51
|
topSlot,
|
|
51
52
|
footerSlot,
|
|
52
53
|
className = "",
|
|
@@ -63,6 +64,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
63
64
|
...rest
|
|
64
65
|
}, ref) {
|
|
65
66
|
const baseClass = "ds-app-sidebar";
|
|
67
|
+
const layout = layoutProp ?? (floatingProp ? "floating" : "flush");
|
|
68
|
+
const floating = layout === "floating";
|
|
66
69
|
const Link = useLinkComponent(linkComponent);
|
|
67
70
|
const avatarSeed = (item) => typeof item.pixelAvatar === "string" ? item.pixelAvatar : item.label;
|
|
68
71
|
const avatarItems = sections.flatMap(
|
|
@@ -228,6 +231,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
228
231
|
// a modal overlay would be all cost and no labels.
|
|
229
232
|
isExpanded || mobileOpen ? `${baseClass}--expanded` : "",
|
|
230
233
|
floating ? `${baseClass}--floating` : "",
|
|
234
|
+
layout === "inset" ? `${baseClass}--inset` : "",
|
|
231
235
|
mobileOpen ? `${baseClass}--mobile-open` : "",
|
|
232
236
|
isPanelMode ? `${baseClass}--panel-mode` : "",
|
|
233
237
|
className
|
|
@@ -480,7 +484,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
|
|
|
480
484
|
panelVisible ? `${baseClass}__panel--visible` : "",
|
|
481
485
|
isExpanded ? `${baseClass}__panel--beside-expanded` : "",
|
|
482
486
|
isPanelMode ? `${baseClass}__panel--panel-mode` : "",
|
|
483
|
-
floating ? `${baseClass}__panel--floating` : ""
|
|
487
|
+
floating ? `${baseClass}__panel--floating` : "",
|
|
488
|
+
layout === "inset" ? `${baseClass}__panel--inset` : ""
|
|
484
489
|
].filter(Boolean).join(" "),
|
|
485
490
|
items: panelSource.items,
|
|
486
491
|
title: panelSource.label,
|
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
width: 24px;
|
|
29
29
|
height: 24px;
|
|
30
30
|
box-sizing: border-box;
|
|
31
|
-
border: var(--border-050) solid var(--color-input-border
|
|
32
|
-
|
|
31
|
+
border: var(--border-050) solid var(--color-input-indicator-border);
|
|
32
|
+
/* Always a rounded square, whatever the theme's radius: never sharp at a
|
|
33
|
+
zero radius, never rounding towards a radio's circle at a large one. */
|
|
34
|
+
border-radius: clamp(4px, var(--radius-200), 8px); /* ds-allow(spacing): the checkbox's shape is fixed, so its corner is bounded */
|
|
33
35
|
background-color: var(--color-input-bg-primary);
|
|
34
36
|
display: inline-flex;
|
|
35
37
|
align-items: center;
|
|
@@ -22,7 +22,7 @@ import { TableContext } from "./tableContext.js";
|
|
|
22
22
|
import { Popover } from "../Popover/Popover.js";
|
|
23
23
|
import { typedComparator, TYPE_DEFAULTS, typedText, formatNumber } from "./columnTypes.js";
|
|
24
24
|
import { TypedCell } from "./TypedCell.js";
|
|
25
|
-
import { reconcileView } from "./views.js";
|
|
25
|
+
import { slotStartingView, reconcileView } from "./views.js";
|
|
26
26
|
import { calculate, TOTALS, NUMERIC_TOTALS } from "./aggregates.js";
|
|
27
27
|
import { useLinkComponent } from "../../behaviors/LinkProvider.js";
|
|
28
28
|
import { TYPED_KINDS, LIVE_KINDS, EDITOR_MIN_WIDTH } from "./editorKinds.js";
|
|
@@ -272,11 +272,21 @@ function DataTable({
|
|
|
272
272
|
const locale = useLocale(localeProp);
|
|
273
273
|
const collator = useMemo(() => textCollator(locale), [locale]);
|
|
274
274
|
const compareValues = useMemo(() => valueComparator(collator), [collator]);
|
|
275
|
-
const [
|
|
276
|
-
const
|
|
275
|
+
const [startingView] = useState(() => slotStartingView(toolbar));
|
|
276
|
+
const startsWith = (key, controlled) => controlled === void 0 && startingView?.[key] !== void 0;
|
|
277
|
+
const [size, setSize] = useControllable(
|
|
278
|
+
sizeProp,
|
|
279
|
+
(startsWith("size", sizeProp) ? startingView.size : void 0) ?? defaultSize ?? "default",
|
|
280
|
+
onSizeChange
|
|
281
|
+
);
|
|
282
|
+
const [search, setSearchState] = useControllable(
|
|
283
|
+
searchProp,
|
|
284
|
+
(startsWith("search", searchProp) ? startingView.search : void 0) ?? defaultSearch ?? "",
|
|
285
|
+
onSearchChange
|
|
286
|
+
);
|
|
277
287
|
const [filters, setFiltersState] = useControllable(
|
|
278
288
|
filtersProp,
|
|
279
|
-
defaultFilters ?? [],
|
|
289
|
+
(startsWith("filters", filtersProp) ? startingView.filters : void 0) ?? defaultFilters ?? [],
|
|
280
290
|
onFiltersChange
|
|
281
291
|
);
|
|
282
292
|
const filterLabels = {
|
|
@@ -286,7 +296,7 @@ function DataTable({
|
|
|
286
296
|
};
|
|
287
297
|
const [pageSize, setPageSize] = useControllable(
|
|
288
298
|
pageSizeProp,
|
|
289
|
-
defaultPageSize,
|
|
299
|
+
(startsWith("pageSize", pageSizeProp) ? startingView.pageSize : void 0) ?? defaultPageSize,
|
|
290
300
|
onPageSizeChange
|
|
291
301
|
);
|
|
292
302
|
const [page, setPageState] = useControllable(pageProp, defaultPage ?? 1, onPageChange);
|
|
@@ -321,14 +331,15 @@ function DataTable({
|
|
|
321
331
|
return () => clearTimeout(timer);
|
|
322
332
|
}, [announceRequest]);
|
|
323
333
|
const isSortControlled = sortsProp !== void 0 || sort !== void 0;
|
|
334
|
+
const openingSorts = defaultSorts ?? (defaultSort ? [defaultSort] : []);
|
|
324
335
|
const [uncontrolledSorts, setUncontrolledSorts] = useState(
|
|
325
|
-
|
|
336
|
+
(startsWith("sorts", sortsProp ?? sort) ? startingView.sorts : void 0) ?? openingSorts
|
|
326
337
|
);
|
|
327
338
|
const currentSorts = sortsProp ?? (sort !== void 0 ? sort ? [sort] : [] : uncontrolledSorts);
|
|
328
339
|
const sortOf = (key) => currentSorts.find((entry) => entry.key === key);
|
|
329
340
|
const [groupingState, setGroupingState] = useControllable(
|
|
330
341
|
groupingProp,
|
|
331
|
-
defaultGrouping ?? [],
|
|
342
|
+
(startsWith("grouping", groupingProp) ? startingView.grouping : void 0) ?? defaultGrouping ?? [],
|
|
332
343
|
onGroupingChange
|
|
333
344
|
);
|
|
334
345
|
const [expandedState, setExpanded] = useControllable(
|
|
@@ -391,7 +402,7 @@ function DataTable({
|
|
|
391
402
|
const canHide = (col) => !col.locked && (col.hideable ?? true);
|
|
392
403
|
const [widthsState, setWidths] = useControllable(
|
|
393
404
|
columnWidths,
|
|
394
|
-
defaultColumnWidths ?? {},
|
|
405
|
+
(startsWith("columnWidths", columnWidths) ? startingView.columnWidths : void 0) ?? defaultColumnWidths ?? {},
|
|
395
406
|
onColumnWidthsChange
|
|
396
407
|
);
|
|
397
408
|
const [orderState, setOrder] = useControllable(columnOrder, defaultColumnOrder ?? [], onColumnOrderChange);
|
|
@@ -1022,7 +1033,16 @@ function DataTable({
|
|
|
1022
1033
|
size,
|
|
1023
1034
|
...pageSize !== void 0 ? { pageSize } : {}
|
|
1024
1035
|
};
|
|
1025
|
-
const [baseView] = useState(
|
|
1036
|
+
const [baseView] = useState(() => ({
|
|
1037
|
+
...currentView,
|
|
1038
|
+
...startsWith("sorts", sortsProp ?? sort) ? { sorts: openingSorts } : {},
|
|
1039
|
+
...startsWith("filters", filtersProp) ? { filters: defaultFilters ?? [] } : {},
|
|
1040
|
+
...startsWith("search", searchProp) ? { search: defaultSearch ?? "" } : {},
|
|
1041
|
+
...startsWith("columnWidths", columnWidths) ? { columnWidths: defaultColumnWidths ?? {} } : {},
|
|
1042
|
+
...startsWith("grouping", groupingProp) ? { grouping: defaultGrouping ?? [] } : {},
|
|
1043
|
+
...startsWith("size", sizeProp) ? { size: defaultSize ?? "default" } : {},
|
|
1044
|
+
...startsWith("pageSize", pageSizeProp) ? { pageSize: defaultPageSize } : {}
|
|
1045
|
+
}));
|
|
1026
1046
|
const viewColumns = columns.map((col) => ({
|
|
1027
1047
|
key: col.key,
|
|
1028
1048
|
filterKind: col.type ? TYPE_DEFAULTS[col.type].filter : void 0,
|
|
@@ -120,9 +120,8 @@ function FilterEditor({
|
|
|
120
120
|
}
|
|
121
121
|
);
|
|
122
122
|
})();
|
|
123
|
-
const isList = column.kind === "enum" || column.kind === "tags";
|
|
124
123
|
return /* @__PURE__ */ jsxs("div", { className: "ds-data-table__filter-editor", role: "group", "aria-label": labels.editor(column.label), children: [
|
|
125
|
-
|
|
124
|
+
operators.length > 1 && /* @__PURE__ */ jsx(
|
|
126
125
|
Dropdown,
|
|
127
126
|
{
|
|
128
127
|
size,
|
|
@@ -2,9 +2,10 @@ const OPERATORS = {
|
|
|
2
2
|
text: ["contains", "notContains", "is", "isNot", "startsWith", "endsWith", "isEmpty", "isNotEmpty"],
|
|
3
3
|
number: ["eq", "neq", "lt", "lte", "gt", "gte", "between", "isEmpty", "isNotEmpty"],
|
|
4
4
|
date: ["is", "before", "after", "between", "isEmpty", "isNotEmpty"],
|
|
5
|
-
// A list filter is its checkboxes
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// A list filter is its checkboxes; the condition says what a tick means,
|
|
6
|
+
// and the first, "any of", is the one a new filter starts with.
|
|
7
|
+
enum: ["isAnyOf", "isNoneOf"],
|
|
8
|
+
tags: ["hasAnyOf", "hasAllOf", "hasNoneOf"],
|
|
8
9
|
boolean: ["isChecked", "isNotChecked"]
|
|
9
10
|
};
|
|
10
11
|
const VALUELESS = /* @__PURE__ */ new Set(["isEmpty", "isNotEmpty", "isChecked", "isNotChecked"]);
|
|
@@ -30,6 +30,15 @@ export interface TableView {
|
|
|
30
30
|
/** Rows per page. */
|
|
31
31
|
pageSize?: number;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The view a table starts on when a TableToolbar with saved views sits
|
|
35
|
+
* directly in its `toolbar` slot: the active one (`activeView`, else
|
|
36
|
+
* `defaultActiveView`, else the first). Read from the element's props, so
|
|
37
|
+
* the table's first render, the server's HTML included, is already that
|
|
38
|
+
* view; the toolbar applies it on mount too, for a bar the table cannot
|
|
39
|
+
* see into (one wrapped in another component).
|
|
40
|
+
*/
|
|
41
|
+
export declare function slotStartingView(toolbar: unknown): TableView | undefined;
|
|
33
42
|
/** What reconciling needs to know about a column. */
|
|
34
43
|
export interface ViewColumn {
|
|
35
44
|
key: string;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { OPERATORS, VALUELESS } from "./filterModel.js";
|
|
2
|
+
function slotStartingView(toolbar) {
|
|
3
|
+
if (!toolbar || typeof toolbar !== "object" || !("props" in toolbar)) return void 0;
|
|
4
|
+
const props = toolbar.props;
|
|
5
|
+
const views = props?.views ?? props?.defaultViews;
|
|
6
|
+
if (!Array.isArray(views) || views.length === 0) return void 0;
|
|
7
|
+
const id = props?.activeView ?? props?.defaultActiveView;
|
|
8
|
+
return (views.find((v) => v.id === id) ?? views[0]).view;
|
|
9
|
+
}
|
|
2
10
|
function reconcileView(view, columns) {
|
|
3
11
|
const byKey = new Map(columns.map((col) => [col.key, col]));
|
|
4
12
|
const known = (key) => byKey.has(key);
|
|
@@ -35,5 +43,6 @@ function reconcileView(view, columns) {
|
|
|
35
43
|
return next;
|
|
36
44
|
}
|
|
37
45
|
export {
|
|
38
|
-
reconcileView
|
|
46
|
+
reconcileView,
|
|
47
|
+
slotStartingView
|
|
39
48
|
};
|
|
@@ -12,6 +12,12 @@ type DrawerOwnProps = {
|
|
|
12
12
|
title: string;
|
|
13
13
|
/** Optional subtitle under the title */
|
|
14
14
|
description?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Content shown in place of the title text, such as a site's logo. The
|
|
17
|
+
* `title` still names the dialog, read by assistive tech from a visually
|
|
18
|
+
* hidden heading.
|
|
19
|
+
*/
|
|
20
|
+
heading?: React.ReactNode;
|
|
15
21
|
/** Drawer body content */
|
|
16
22
|
children?: React.ReactNode;
|
|
17
23
|
/** Optional footer slot — typically a row of Buttons */
|