@tempoxyz/ds 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/components/Accordion.js.zyzz.json +1 -1
- package/dist/web/components/AgentLogo.js.zyzz.json +1 -1
- package/dist/web/components/Alert.css +1 -1
- package/dist/web/components/Alert.d.ts +7 -2
- package/dist/web/components/Alert.d.ts.map +1 -1
- package/dist/web/components/Alert.js +12 -7
- package/dist/web/components/Alert.js.map +1 -1
- package/dist/web/components/Alert.js.zyzz.json +1 -1
- package/dist/web/components/AlertDialog.js.zyzz.json +1 -1
- package/dist/web/components/Amount.js.zyzz.json +1 -1
- package/dist/web/components/AmountInput.js.zyzz.json +1 -1
- package/dist/web/components/AppShell.css +1 -1
- package/dist/web/components/AppShell.d.ts +108 -0
- package/dist/web/components/AppShell.d.ts.map +1 -1
- package/dist/web/components/AppShell.js +72 -24
- package/dist/web/components/AppShell.js.map +1 -1
- package/dist/web/components/AppShell.js.zyzz.json +1 -1
- package/dist/web/components/AssetChip.js.zyzz.json +1 -1
- package/dist/web/components/Autocomplete.js.zyzz.json +1 -1
- package/dist/web/components/Avatar.js.zyzz.json +1 -1
- package/dist/web/components/Badge.css +1 -1
- package/dist/web/components/Badge.d.ts +1 -1
- package/dist/web/components/Badge.d.ts.map +1 -1
- package/dist/web/components/Badge.js.map +1 -1
- package/dist/web/components/Badge.js.zyzz.json +1 -1
- package/dist/web/components/Banner.css +1 -1
- package/dist/web/components/Banner.js.zyzz.json +1 -1
- package/dist/web/components/BarChart.css +1 -1
- package/dist/web/components/BarChart.js.zyzz.json +1 -1
- package/dist/web/components/Breadcrumbs.js.zyzz.json +1 -1
- package/dist/web/components/Button.css +1 -1
- package/dist/web/components/Button.d.ts +10 -6
- package/dist/web/components/Button.d.ts.map +1 -1
- package/dist/web/components/Button.js +4 -3
- package/dist/web/components/Button.js.map +1 -1
- package/dist/web/components/Button.js.zyzz.json +1 -1
- package/dist/web/components/ButtonGroup.js.zyzz.json +1 -1
- package/dist/web/components/Calendar.css +1 -1
- package/dist/web/components/Calendar.js.zyzz.json +1 -1
- package/dist/web/components/Checkbox.js.zyzz.json +1 -1
- package/dist/web/components/CheckboxGroup.js.zyzz.json +1 -1
- package/dist/web/components/ChoiceGroup.css +1 -1
- package/dist/web/components/ChoiceGroup.js.zyzz.json +1 -1
- package/dist/web/components/Choicebox.js.zyzz.json +1 -1
- package/dist/web/components/Combobox.js.zyzz.json +1 -1
- package/dist/web/components/Container.js.zyzz.json +1 -1
- package/dist/web/components/CopyButton.js.zyzz.json +1 -1
- package/dist/web/components/DatePicker.css +1 -1
- package/dist/web/components/DatePicker.js.zyzz.json +1 -1
- package/dist/web/components/Dialog.d.ts.map +1 -1
- package/dist/web/components/Dialog.js +1 -1
- package/dist/web/components/Dialog.js.map +1 -1
- package/dist/web/components/Dialog.js.zyzz.json +1 -1
- package/dist/web/components/Drawer.js.zyzz.json +1 -1
- package/dist/web/components/Empty.css +1 -1
- package/dist/web/components/Empty.js.zyzz.json +1 -1
- package/dist/web/components/Field.js.zyzz.json +1 -1
- package/dist/web/components/Fieldset.js.zyzz.json +1 -1
- package/dist/web/components/Frame.css +1 -1
- package/dist/web/components/Frame.js.zyzz.json +1 -1
- package/dist/web/components/Gauge.js.zyzz.json +1 -1
- package/dist/web/components/Grid.js.zyzz.json +1 -1
- package/dist/web/components/Hex.css +1 -1
- package/dist/web/components/Hex.js.zyzz.json +1 -1
- package/dist/web/components/IconTile.css +1 -1
- package/dist/web/components/IconTile.js.zyzz.json +1 -1
- package/dist/web/components/Identicon.js.zyzz.json +1 -1
- package/dist/web/components/Inline.js.zyzz.json +1 -1
- package/dist/web/components/Inset.js.zyzz.json +1 -1
- package/dist/web/components/Kbd.css +1 -1
- package/dist/web/components/Kbd.js.zyzz.json +1 -1
- package/dist/web/components/ListGroup.css +1 -1
- package/dist/web/components/ListGroup.d.ts +55 -8
- package/dist/web/components/ListGroup.d.ts.map +1 -1
- package/dist/web/components/ListGroup.js +23 -19
- package/dist/web/components/ListGroup.js.map +1 -1
- package/dist/web/components/ListGroup.js.zyzz.json +1 -1
- package/dist/web/components/LoadingDots.js.zyzz.json +1 -1
- package/dist/web/components/Menu.css +1 -1
- package/dist/web/components/Menu.js.zyzz.json +1 -1
- package/dist/web/components/NetworkLogo.js.zyzz.json +1 -1
- package/dist/web/components/NumberField.js.zyzz.json +1 -1
- package/dist/web/components/OtpField.css +1 -1
- package/dist/web/components/OtpField.js.zyzz.json +1 -1
- package/dist/web/components/Pagination.css +1 -1
- package/dist/web/components/Pagination.js.zyzz.json +1 -1
- package/dist/web/components/Popover.js.zyzz.json +1 -1
- package/dist/web/components/Progress.js.zyzz.json +1 -1
- package/dist/web/components/Qr.js.zyzz.json +1 -1
- package/dist/web/components/RadioGroup.js.zyzz.json +1 -1
- package/dist/web/components/SecretValue.css +1 -1
- package/dist/web/components/SecretValue.js.zyzz.json +1 -1
- package/dist/web/components/Select.css +1 -1
- package/dist/web/components/Select.d.ts.map +1 -1
- package/dist/web/components/Select.js +2 -2
- package/dist/web/components/Select.js.map +1 -1
- package/dist/web/components/Select.js.zyzz.json +1 -1
- package/dist/web/components/Separator.js.zyzz.json +1 -1
- package/dist/web/components/SidebarMenu.css +1 -1
- package/dist/web/components/SidebarMenu.d.ts +52 -0
- package/dist/web/components/SidebarMenu.d.ts.map +1 -1
- package/dist/web/components/SidebarMenu.js +47 -16
- package/dist/web/components/SidebarMenu.js.map +1 -1
- package/dist/web/components/SidebarMenu.js.zyzz.json +1 -1
- package/dist/web/components/Skeleton.css +1 -1
- package/dist/web/components/Skeleton.js.zyzz.json +1 -1
- package/dist/web/components/Slider.js.zyzz.json +1 -1
- package/dist/web/components/Snippet.js.zyzz.json +1 -1
- package/dist/web/components/Spinner.js.zyzz.json +1 -1
- package/dist/web/components/SplitBar.js.zyzz.json +1 -1
- package/dist/web/components/SplitButton.js.zyzz.json +1 -1
- package/dist/web/components/Stack.js.zyzz.json +1 -1
- package/dist/web/components/StatusChip.css +1 -1
- package/dist/web/components/StatusChip.js.zyzz.json +1 -1
- package/dist/web/components/StatusRow.js.zyzz.json +1 -1
- package/dist/web/components/Stepper.js.zyzz.json +1 -1
- package/dist/web/components/Surface.css +1 -1
- package/dist/web/components/Surface.d.ts +85 -0
- package/dist/web/components/Surface.d.ts.map +1 -1
- package/dist/web/components/Surface.js +6 -3
- package/dist/web/components/Surface.js.map +1 -1
- package/dist/web/components/Surface.js.zyzz.json +1 -1
- package/dist/web/components/Switch.js.zyzz.json +1 -1
- package/dist/web/components/Table.css +1 -1
- package/dist/web/components/Table.js.zyzz.json +1 -1
- package/dist/web/components/Tabs.js.zyzz.json +1 -1
- package/dist/web/components/TempoLogo.js.zyzz.json +1 -1
- package/dist/web/components/TextInput.js.zyzz.json +1 -1
- package/dist/web/components/Textarea.css +1 -1
- package/dist/web/components/Textarea.js.zyzz.json +1 -1
- package/dist/web/components/Toast.css +1 -1
- package/dist/web/components/Toast.js.zyzz.json +1 -1
- package/dist/web/components/Token.js.zyzz.json +1 -1
- package/dist/web/components/TokenLogo.js.zyzz.json +1 -1
- package/dist/web/components/Tooltip.js.zyzz.json +1 -1
- package/dist/web/components/VerificationCode.css +1 -1
- package/dist/web/components/VerificationCode.js.zyzz.json +1 -1
- package/dist/web/components/Visual.js.zyzz.json +1 -1
- package/dist/web/components/internal/Field.css +1 -1
- package/dist/web/components/internal/Field.js.zyzz.json +1 -1
- package/dist/web/components/internal/InputGroup.css +1 -1
- package/dist/web/components/internal/InputGroup.js.zyzz.json +1 -1
- package/dist/web/components/internal/Listbox.css +1 -1
- package/dist/web/components/internal/Listbox.js.zyzz.json +1 -1
- package/dist/web/components/internal/TabBar.d.ts +33 -0
- package/dist/web/components/internal/TabBar.d.ts.map +1 -0
- package/dist/web/components/internal/TabBar.js +30 -0
- package/dist/web/components/internal/TabBar.js.map +1 -0
- package/dist/web/components/internal/Text.d.ts +9 -0
- package/dist/web/components/internal/Text.d.ts.map +1 -0
- package/dist/web/components/internal/Text.js +35 -0
- package/dist/web/components/internal/Text.js.map +1 -0
- package/dist/web/index.js.zyzz.json +1 -1
- package/dist/web/vars.d.ts +26 -3
- package/dist/web/vars.d.ts.map +1 -1
- package/dist/web/vars.js.map +1 -1
- package/dist/web/vars.js.zyzz.json +1 -1
- package/dist/web/zyzz.config.d.ts +130 -15
- package/dist/web/zyzz.config.d.ts.map +1 -1
- package/dist/web/zyzz.config.js.zyzz.json +1 -1
- package/package.json +1 -1
- package/src/web/components/Alert.tsx +25 -5
- package/src/web/components/AppShell.tsx +273 -8
- package/src/web/components/Badge.tsx +4 -2
- package/src/web/components/Button.tsx +14 -10
- package/src/web/components/Dialog.tsx +7 -1
- package/src/web/components/ListGroup.tsx +95 -14
- package/src/web/components/Select.tsx +2 -0
- package/src/web/components/SidebarMenu.tsx +97 -3
- package/src/web/components/Surface.tsx +105 -1
- package/src/web/components/internal/TabBar.ts +52 -0
- package/src/web/components/internal/Text.ts +39 -0
- package/src/web/vars.ts +20 -7
package/package.json
CHANGED
|
@@ -48,7 +48,8 @@ export namespace Alert {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* A text `<button>` with a 44px tall target, in the alert's strong type. Place it inside
|
|
51
|
-
* `Alert.Content`, after the message
|
|
51
|
+
* `Alert.Content`, after the message, or at the end of the row, where its label sits in line
|
|
52
|
+
* with the first line. Name the action it performs, such as Review fees.
|
|
52
53
|
*/
|
|
53
54
|
export function Action(props: Action.Props) {
|
|
54
55
|
const { className, vars, ...attributes } = props
|
|
@@ -70,7 +71,8 @@ export namespace Alert {
|
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Stacks the title, message, and action in a `<div>` that fills the row and can shrink, so long
|
|
73
|
-
* text wraps beside the indicator.
|
|
74
|
+
* text wraps beside the indicator. The message flows as text. When it sits beside a paragraph or
|
|
75
|
+
* a list, wrap it in a `p`, so the blocks keep their 4px gap.
|
|
74
76
|
*/
|
|
75
77
|
export function Content(props: Content.Props) {
|
|
76
78
|
const { className, vars, ...attributes } = props
|
|
@@ -176,13 +178,28 @@ namespace styles {
|
|
|
176
178
|
{ id: 'tds/web/alert/action' },
|
|
177
179
|
)
|
|
178
180
|
|
|
181
|
+
// The message flows as text, so inline elements such as `strong` stay on its lines. The title,
|
|
182
|
+
// the action and block children each take a line of their own, 4px from the content beside
|
|
183
|
+
// them.
|
|
179
184
|
export const content = style(
|
|
180
185
|
{
|
|
181
|
-
display: '
|
|
186
|
+
display: 'block',
|
|
182
187
|
flex: '1 !custom',
|
|
183
|
-
flexDirection: 'column',
|
|
184
188
|
minWidth: '0',
|
|
185
|
-
|
|
189
|
+
selectors: {
|
|
190
|
+
'& > [data-alert-action]': {
|
|
191
|
+
display: 'flex',
|
|
192
|
+
marginBlockStart: '4',
|
|
193
|
+
width: 'fit-content !custom',
|
|
194
|
+
},
|
|
195
|
+
'& > [data-alert-title]': { marginBlockEnd: '4' },
|
|
196
|
+
// The browser's block margins of paragraphs and lists give way to the 4px gap. `:where`
|
|
197
|
+
// keeps the reset below any class that sets a margin of its own.
|
|
198
|
+
'& > :where(p, ul, ol)': { marginBlock: '0' },
|
|
199
|
+
// A list sits 4px below the message, and a paragraph 4px below the element before it. A
|
|
200
|
+
// list follows text in practice, which CSS cannot select, so it always takes the gap.
|
|
201
|
+
'& > :is(ul, ol), & > * + :is(p, div)': { marginBlockStart: '4' },
|
|
202
|
+
},
|
|
186
203
|
},
|
|
187
204
|
{ id: 'tds/web/alert/content' },
|
|
188
205
|
)
|
|
@@ -260,6 +277,9 @@ namespace styles {
|
|
|
260
277
|
padding: '16',
|
|
261
278
|
typography: 'body.b3',
|
|
262
279
|
selectors: {
|
|
280
|
+
// An action at the end of the row centers its 44px target on the 16px first line, so its
|
|
281
|
+
// label stays in line with the message.
|
|
282
|
+
'& > [data-alert-action]': { marginBlock: 'calc((1rem - 2.75rem) / 2) !custom' },
|
|
263
283
|
'& [data-alert-action], & [data-alert-title]': { typography: 'body.b3Strong' },
|
|
264
284
|
'& [data-alert-icon]': { height: '16', width: '16' },
|
|
265
285
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as BaseUi from '@base-ui/react/drawer'
|
|
2
|
+
import { useRender } from '@base-ui/react/use-render'
|
|
2
3
|
import {
|
|
3
4
|
type ComponentPropsWithRef,
|
|
4
5
|
createContext,
|
|
@@ -9,8 +10,11 @@ import {
|
|
|
9
10
|
import type * as zyzz from 'zyzz'
|
|
10
11
|
import { keyframes } from 'zyzz/web'
|
|
11
12
|
import { style, variants, vars } from '../zyzz.config.js'
|
|
13
|
+
import * as TabBar from './internal/TabBar.js'
|
|
14
|
+
import * as Text from './internal/Text.js'
|
|
12
15
|
|
|
13
16
|
type Context = {
|
|
17
|
+
collapse: boolean
|
|
14
18
|
container: HTMLDivElement | null
|
|
15
19
|
handle: BaseUi.Drawer.Handle<unknown>
|
|
16
20
|
open: boolean
|
|
@@ -19,10 +23,11 @@ type Context = {
|
|
|
19
23
|
|
|
20
24
|
const context = createContext<Context | undefined>(undefined)
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
// The sidebar opens as a drawer below 1024px, or below 768px when it collapses to its icons from
|
|
27
|
+
// 768px to 1023px. The breakpoints are literals because media queries cannot read CSS variables.
|
|
28
|
+
function useCompact(collapse: boolean) {
|
|
29
|
+
const compactQuery = collapse ? '(max-width: 767px)' : '(max-width: 1023px)'
|
|
23
30
|
|
|
24
|
-
// The viewport breakpoint is a literal because media queries cannot read CSS variables.
|
|
25
|
-
function useCompact() {
|
|
26
31
|
return useSyncExternalStore(
|
|
27
32
|
(notify) => {
|
|
28
33
|
const query = window.matchMedia(compactQuery)
|
|
@@ -112,7 +117,9 @@ export namespace AppShell {
|
|
|
112
117
|
export function Root(props: Root.Props) {
|
|
113
118
|
const {
|
|
114
119
|
align,
|
|
120
|
+
children,
|
|
115
121
|
className,
|
|
122
|
+
collapse = false,
|
|
116
123
|
defaultOpen = false,
|
|
117
124
|
onOpenChange,
|
|
118
125
|
open,
|
|
@@ -121,6 +128,7 @@ export namespace AppShell {
|
|
|
121
128
|
...attributes
|
|
122
129
|
} = props
|
|
123
130
|
const [handle] = useState(() => BaseUi.Drawer.createHandle<unknown>())
|
|
131
|
+
const [tabBar] = useState(TabBar.create)
|
|
124
132
|
const [uncontrolled, setUncontrolled] = useState(defaultOpen)
|
|
125
133
|
const [container, setContainer] = useState<HTMLDivElement | null>(null)
|
|
126
134
|
const current = open ?? uncontrolled
|
|
@@ -128,6 +136,7 @@ export namespace AppShell {
|
|
|
128
136
|
return (
|
|
129
137
|
<context.Provider
|
|
130
138
|
value={{
|
|
139
|
+
collapse,
|
|
131
140
|
container,
|
|
132
141
|
handle,
|
|
133
142
|
open: current,
|
|
@@ -140,13 +149,19 @@ export namespace AppShell {
|
|
|
140
149
|
<div
|
|
141
150
|
{...attributes}
|
|
142
151
|
{...styles.root({ align, className, style: props.style, vars })}
|
|
152
|
+
data-app-shell-collapse={collapse ? '' : undefined}
|
|
143
153
|
data-state={current ? 'open' : 'closed'}
|
|
144
154
|
ref={(node) => {
|
|
145
155
|
setContainer(node)
|
|
146
156
|
if (typeof ref === 'function') ref(node)
|
|
147
157
|
else if (ref) ref.current = node
|
|
148
158
|
}}
|
|
149
|
-
|
|
159
|
+
>
|
|
160
|
+
<TabBar.Context.Provider value={tabBar}>
|
|
161
|
+
{children}
|
|
162
|
+
<BottomBar />
|
|
163
|
+
</TabBar.Context.Provider>
|
|
164
|
+
</div>
|
|
150
165
|
</context.Provider>
|
|
151
166
|
)
|
|
152
167
|
}
|
|
@@ -154,6 +169,14 @@ export namespace AppShell {
|
|
|
154
169
|
export namespace Root {
|
|
155
170
|
export type Props = ComponentPropsWithRef<'div'> &
|
|
156
171
|
zyzz.Props.Variants<typeof styles.root> & {
|
|
172
|
+
/**
|
|
173
|
+
* Collapses the inline sidebar to a 64px column of icons from 768px to 1023px, and opens
|
|
174
|
+
* the drawer only below 768px. Give every `SidebarMenu.Item` an icon as its first child.
|
|
175
|
+
* Absent, the sidebar opens as a drawer below 1024px.
|
|
176
|
+
* @default false
|
|
177
|
+
*/
|
|
178
|
+
collapse?: boolean | undefined
|
|
179
|
+
|
|
157
180
|
/**
|
|
158
181
|
* Initial drawer state when `open` is uncontrolled.
|
|
159
182
|
* @default false
|
|
@@ -177,10 +200,18 @@ export namespace AppShell {
|
|
|
177
200
|
export function Sidebar(props: Sidebar.Props) {
|
|
178
201
|
const { className, vars, ...attributes } = props
|
|
179
202
|
const shell = useShell()
|
|
180
|
-
const compact = useCompact()
|
|
203
|
+
const compact = useCompact(shell.collapse)
|
|
181
204
|
const sidebar = styles.sidebar({ className, style: props.style, vars })
|
|
182
205
|
|
|
183
|
-
if (!compact)
|
|
206
|
+
if (!compact)
|
|
207
|
+
return (
|
|
208
|
+
<aside
|
|
209
|
+
{...attributes}
|
|
210
|
+
{...styles.inline(sidebar)}
|
|
211
|
+
data-app-shell-rail={shell.collapse ? '' : undefined}
|
|
212
|
+
data-app-shell-sidebar="inline"
|
|
213
|
+
/>
|
|
214
|
+
)
|
|
184
215
|
|
|
185
216
|
return (
|
|
186
217
|
<BaseUi.Drawer.Root
|
|
@@ -189,7 +220,8 @@ export namespace AppShell {
|
|
|
189
220
|
open={shell.open}
|
|
190
221
|
swipeDirection="right"
|
|
191
222
|
>
|
|
192
|
-
|
|
223
|
+
{/* The closed drawer stays mounted, so its items register in the bottom bar. */}
|
|
224
|
+
<BaseUi.Drawer.Portal container={shell.container} keepMounted>
|
|
193
225
|
<BaseUi.Drawer.Backdrop {...styles.backdrop()} />
|
|
194
226
|
<BaseUi.Drawer.Viewport {...styles.viewport()}>
|
|
195
227
|
<BaseUi.Drawer.Popup
|
|
@@ -292,6 +324,54 @@ export namespace AppShell {
|
|
|
292
324
|
}
|
|
293
325
|
}
|
|
294
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Repeats the `SidebarMenu.Item`s that set `tabBar` in a bar fixed to the bottom edge below 768px:
|
|
329
|
+
* `tab` items as icons in a pill `<nav>`, then each `action` item as a floating action button.
|
|
330
|
+
* Renders nothing while no item sets `tabBar`.
|
|
331
|
+
*/
|
|
332
|
+
function BottomBar() {
|
|
333
|
+
const store = use(TabBar.Context)!
|
|
334
|
+
const entries = useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot)
|
|
335
|
+
const tabs = entries.filter(([, entry]) => entry.variant === 'tab')
|
|
336
|
+
const actions = entries.filter(([, entry]) => entry.variant === 'action')
|
|
337
|
+
|
|
338
|
+
if (entries.length === 0) return null
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div {...styles.bottomBar()} data-app-shell-bottom-bar="">
|
|
342
|
+
{tabs.length > 0 && (
|
|
343
|
+
<nav aria-label="Main" {...styles.tabBar()}>
|
|
344
|
+
{tabs.map(([id, entry]) => (
|
|
345
|
+
<BottomBarItem entry={entry} key={id} />
|
|
346
|
+
))}
|
|
347
|
+
</nav>
|
|
348
|
+
)}
|
|
349
|
+
{actions.map(([id, entry]) => (
|
|
350
|
+
<BottomBarItem entry={entry} key={id} />
|
|
351
|
+
))}
|
|
352
|
+
</div>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Renders the element of a `SidebarMenu.Item` with the attributes and `render` of the item, and
|
|
358
|
+
* shows only its first element, such as its icon. Its text stays in the accessible name.
|
|
359
|
+
*/
|
|
360
|
+
function BottomBarItem(props: { entry: TabBar.Entry }) {
|
|
361
|
+
const { entry } = props
|
|
362
|
+
|
|
363
|
+
return useRender({
|
|
364
|
+
defaultTagName: 'a',
|
|
365
|
+
props: {
|
|
366
|
+
...entry.attributes,
|
|
367
|
+
'aria-current': entry.active ? 'page' : undefined,
|
|
368
|
+
...(entry.variant === 'tab' ? styles.tab({ active: entry.active }) : styles.action()),
|
|
369
|
+
children: Text.wrap(entry.children, styles.label()),
|
|
370
|
+
},
|
|
371
|
+
render: entry.render,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
295
375
|
const fadeIn = keyframes(
|
|
296
376
|
{ from: { opacity: 0 }, to: { opacity: 1 } },
|
|
297
377
|
{ id: 'tds/web/app-shell/fade-in' },
|
|
@@ -303,6 +383,53 @@ const fadeOut = keyframes(
|
|
|
303
383
|
)
|
|
304
384
|
|
|
305
385
|
namespace styles {
|
|
386
|
+
// The 56px circle of the Platform Floating Action Button, filled like the primary button.
|
|
387
|
+
export const action = style(
|
|
388
|
+
{
|
|
389
|
+
alignItems: 'center',
|
|
390
|
+
backgroundColor: 'inverse.background.primary',
|
|
391
|
+
borderRadius: 'full',
|
|
392
|
+
boxShadow: 'large',
|
|
393
|
+
boxSizing: 'border-box',
|
|
394
|
+
color: 'inverse.content.primary',
|
|
395
|
+
cornerShape: 'round',
|
|
396
|
+
cursor: 'pointer',
|
|
397
|
+
display: 'flex',
|
|
398
|
+
flexShrink: '0 !custom',
|
|
399
|
+
height: '56',
|
|
400
|
+
justifyContent: 'center',
|
|
401
|
+
outlineColor: 'transparent !custom',
|
|
402
|
+
outlineOffset: '2',
|
|
403
|
+
outlineStyle: 'solid !custom',
|
|
404
|
+
outlineWidth: '2',
|
|
405
|
+
position: 'relative',
|
|
406
|
+
textDecoration: 'none !custom',
|
|
407
|
+
transitionDuration: '160',
|
|
408
|
+
transitionProperty: 'background-color, outline-color, scale !custom',
|
|
409
|
+
transitionTimingFunction: 'snappy',
|
|
410
|
+
width: '56',
|
|
411
|
+
selectors: {
|
|
412
|
+
'& > :first-child': { flexShrink: '0 !custom', fontSize: '24' },
|
|
413
|
+
'& > :not(:first-child)': {
|
|
414
|
+
clipPath: 'inset(50%) !custom',
|
|
415
|
+
height: '1px !custom',
|
|
416
|
+
overflow: 'hidden',
|
|
417
|
+
position: 'absolute',
|
|
418
|
+
whiteSpace: 'nowrap',
|
|
419
|
+
width: '1px !custom',
|
|
420
|
+
},
|
|
421
|
+
'&:active': { scale: 'press', transitionDuration: '100' },
|
|
422
|
+
'&:focus-visible': { outlineColor: 'border.focus' },
|
|
423
|
+
'&:hover': { backgroundColor: 'action.primary.fill.hover' },
|
|
424
|
+
},
|
|
425
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
426
|
+
transitionProperty: 'background-color, outline-color !custom',
|
|
427
|
+
selectors: { '&:active': { scale: 'none !custom' } },
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
{ id: 'tds/web/app-shell/action' },
|
|
431
|
+
)
|
|
432
|
+
|
|
306
433
|
export const backdrop = style(
|
|
307
434
|
{
|
|
308
435
|
backgroundColor: 'neutral.100',
|
|
@@ -350,6 +477,22 @@ namespace styles {
|
|
|
350
477
|
{ id: 'tds/web/app-shell/bar' },
|
|
351
478
|
)
|
|
352
479
|
|
|
480
|
+
// Floats over the content, inset by the 24px page margin of the Mobile layout and 16px from the
|
|
481
|
+
// bottom edge. It sits below the drawer and its backdrop.
|
|
482
|
+
export const bottomBar = style(
|
|
483
|
+
{
|
|
484
|
+
alignItems: 'center',
|
|
485
|
+
bottom: 'calc(1rem + env(safe-area-inset-bottom)) !custom',
|
|
486
|
+
columnGap: '12',
|
|
487
|
+
display: 'none',
|
|
488
|
+
insetInline: '24',
|
|
489
|
+
position: 'fixed',
|
|
490
|
+
zIndex: '39 !custom',
|
|
491
|
+
'@media (max-width: 767px)': { display: 'flex' },
|
|
492
|
+
},
|
|
493
|
+
{ id: 'tds/web/app-shell/bottom-bar' },
|
|
494
|
+
)
|
|
495
|
+
|
|
353
496
|
export const bars = style(
|
|
354
497
|
{ display: 'block', height: '16', position: 'relative', width: '16' },
|
|
355
498
|
{ id: 'tds/web/app-shell/bars' },
|
|
@@ -375,6 +518,9 @@ namespace styles {
|
|
|
375
518
|
},
|
|
376
519
|
'&[data-starting-style]': { transform: 'translateX(100%) !custom' },
|
|
377
520
|
'&[data-swiping]': { transitionDuration: '0', userSelect: 'none' },
|
|
521
|
+
// The closed drawer stays mounted with the `hidden` attribute, which the flex layout of
|
|
522
|
+
// the sidebar would override.
|
|
523
|
+
'&[hidden]': { display: 'none !custom' },
|
|
378
524
|
},
|
|
379
525
|
'@media (prefers-reduced-motion: reduce)': {
|
|
380
526
|
transitionProperty: 'opacity !custom',
|
|
@@ -411,8 +557,11 @@ namespace styles {
|
|
|
411
557
|
paddingInline: '16',
|
|
412
558
|
'@media (min-width: 640px)': { marginInline: '-2rem !custom', paddingInline: '24' },
|
|
413
559
|
// Below 1024px the fixed trigger covers the end of the bar, so header actions end 8px
|
|
414
|
-
// before it: its 40px plus its 16px inset.
|
|
560
|
+
// before it: its 40px plus its 16px inset. A collapsed sidebar hides it from 768px.
|
|
415
561
|
'@media (max-width: 1023px)': { paddingInlineEnd: '64' },
|
|
562
|
+
'@media (min-width: 768px) and (max-width: 1023px)': {
|
|
563
|
+
selectors: { '[data-app-shell-collapse] > main > &': { paddingInlineEnd: '24' } },
|
|
564
|
+
},
|
|
416
565
|
},
|
|
417
566
|
},
|
|
418
567
|
{ id: 'tds/web/app-shell/header' },
|
|
@@ -454,6 +603,15 @@ namespace styles {
|
|
|
454
603
|
'@media (max-width: 1023px)': {
|
|
455
604
|
selectors: { '&::after, &::before': { display: 'none !custom' } },
|
|
456
605
|
},
|
|
606
|
+
// The bottom bar covers the end of the page, so content can scroll clear of it: 16px
|
|
607
|
+
// below the bar, its 56px, and 16px above it.
|
|
608
|
+
'@media (max-width: 767px)': {
|
|
609
|
+
selectors: {
|
|
610
|
+
'&:has(> [data-app-shell-bottom-bar]) > main': {
|
|
611
|
+
paddingBottom: 'calc(5.5rem + env(safe-area-inset-bottom)) !custom',
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
},
|
|
457
615
|
},
|
|
458
616
|
defaultVariants: { align: 'center' },
|
|
459
617
|
variants: {
|
|
@@ -504,6 +662,19 @@ namespace styles {
|
|
|
504
662
|
{ id: 'tds/web/app-shell/sidebar' },
|
|
505
663
|
)
|
|
506
664
|
|
|
665
|
+
// Keeps the label of a bottom bar item in its accessible name without painting it.
|
|
666
|
+
export const label = style(
|
|
667
|
+
{
|
|
668
|
+
clipPath: 'inset(50%) !custom',
|
|
669
|
+
height: '1px !custom',
|
|
670
|
+
overflow: 'hidden',
|
|
671
|
+
position: 'absolute',
|
|
672
|
+
whiteSpace: 'nowrap',
|
|
673
|
+
width: '1px !custom',
|
|
674
|
+
},
|
|
675
|
+
{ id: 'tds/web/app-shell/label' },
|
|
676
|
+
)
|
|
677
|
+
|
|
507
678
|
// Follows `sidebar`, so its compact `display: none` overrides the sidebar's `display: flex` in
|
|
508
679
|
// server-rendered markup, which always emits the wide layout.
|
|
509
680
|
export const inline = style(
|
|
@@ -517,6 +688,12 @@ namespace styles {
|
|
|
517
688
|
position: 'sticky',
|
|
518
689
|
width: 'var(--sidebar-width, 16rem) !custom',
|
|
519
690
|
'@media (max-width: 1023px)': { display: 'none !custom' },
|
|
691
|
+
// A collapsed sidebar stays inline as a column of icons, which `SidebarMenu` centers.
|
|
692
|
+
'@media (min-width: 768px) and (max-width: 1023px)': {
|
|
693
|
+
selectors: {
|
|
694
|
+
'&[data-app-shell-rail]': { display: 'flex !custom', width: '4rem !custom' },
|
|
695
|
+
},
|
|
696
|
+
},
|
|
520
697
|
},
|
|
521
698
|
{ id: 'tds/web/app-shell/inline' },
|
|
522
699
|
)
|
|
@@ -538,6 +715,15 @@ namespace styles {
|
|
|
538
715
|
// Classic scrollbars keep their own lane instead of covering content at the edge.
|
|
539
716
|
scrollbarGutter: 'stable',
|
|
540
717
|
scrollbarWidth: 'thin',
|
|
718
|
+
// The collapsed sidebar has no room for a scrollbar lane beside its 40px icons.
|
|
719
|
+
'@media (min-width: 768px) and (max-width: 1023px)': {
|
|
720
|
+
selectors: {
|
|
721
|
+
'[data-app-shell-rail] &': {
|
|
722
|
+
scrollbarGutter: 'auto !custom',
|
|
723
|
+
scrollbarWidth: 'none !custom',
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
},
|
|
541
727
|
selectors: {
|
|
542
728
|
// Curtains fade content into the sidebar at whichever end has more to scroll.
|
|
543
729
|
'&::after, &::before': {
|
|
@@ -598,6 +784,81 @@ namespace styles {
|
|
|
598
784
|
{ id: 'tds/web/app-shell/sidebar-header' },
|
|
599
785
|
)
|
|
600
786
|
|
|
787
|
+
// An icon-only item of the Platform Tab Bar, which fills its share of the pill.
|
|
788
|
+
export const tab = variants(
|
|
789
|
+
{
|
|
790
|
+
base: {
|
|
791
|
+
alignItems: 'center',
|
|
792
|
+
borderRadius: 'full',
|
|
793
|
+
boxSizing: 'border-box',
|
|
794
|
+
color: 'content.secondary',
|
|
795
|
+
cornerShape: 'round',
|
|
796
|
+
cursor: 'pointer',
|
|
797
|
+
display: 'flex',
|
|
798
|
+
flex: '1 !custom',
|
|
799
|
+
height: '100% !custom',
|
|
800
|
+
justifyContent: 'center',
|
|
801
|
+
minWidth: '0',
|
|
802
|
+
outlineColor: 'transparent !custom',
|
|
803
|
+
outlineOffset: '-2',
|
|
804
|
+
outlineStyle: 'solid !custom',
|
|
805
|
+
outlineWidth: '2',
|
|
806
|
+
position: 'relative',
|
|
807
|
+
textDecoration: 'none !custom',
|
|
808
|
+
transitionDuration: '160',
|
|
809
|
+
transitionProperty: 'background-color, color, outline-color, scale !custom',
|
|
810
|
+
transitionTimingFunction: 'snappy',
|
|
811
|
+
selectors: {
|
|
812
|
+
'& > :first-child': { flexShrink: '0 !custom', fontSize: '24' },
|
|
813
|
+
'& > :not(:first-child)': {
|
|
814
|
+
clipPath: 'inset(50%) !custom',
|
|
815
|
+
height: '1px !custom',
|
|
816
|
+
overflow: 'hidden',
|
|
817
|
+
position: 'absolute',
|
|
818
|
+
whiteSpace: 'nowrap',
|
|
819
|
+
width: '1px !custom',
|
|
820
|
+
},
|
|
821
|
+
'&:active': { scale: 'press', transitionDuration: '100' },
|
|
822
|
+
'&:focus-visible': { outlineColor: 'border.focus' },
|
|
823
|
+
'&:hover': { color: 'content.primary' },
|
|
824
|
+
},
|
|
825
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
826
|
+
transitionProperty: 'background-color, color, outline-color !custom',
|
|
827
|
+
selectors: { '&:active': { scale: 'none !custom' } },
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
defaultVariants: { active: false },
|
|
831
|
+
variants: {
|
|
832
|
+
active: {
|
|
833
|
+
false: {},
|
|
834
|
+
true: { backgroundColor: 'container.regular', color: 'content.primary' },
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
{ id: 'tds/web/app-shell/tab' },
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
// The 56px pill of the Platform Tab Bar, which takes the width beside the action button.
|
|
842
|
+
export const tabBar = style(
|
|
843
|
+
{
|
|
844
|
+
alignItems: 'center',
|
|
845
|
+
backgroundColor: 'background.secondary',
|
|
846
|
+
borderColor: 'line.secondary',
|
|
847
|
+
borderRadius: 'full',
|
|
848
|
+
borderStyle: 'solid',
|
|
849
|
+
borderWidth: 'regular',
|
|
850
|
+
boxShadow: 'regular',
|
|
851
|
+
boxSizing: 'border-box',
|
|
852
|
+
cornerShape: 'round',
|
|
853
|
+
display: 'flex',
|
|
854
|
+
flex: '1 !custom',
|
|
855
|
+
height: '56',
|
|
856
|
+
minWidth: '0',
|
|
857
|
+
padding: '4',
|
|
858
|
+
},
|
|
859
|
+
{ id: 'tds/web/app-shell/tab-bar' },
|
|
860
|
+
)
|
|
861
|
+
|
|
601
862
|
export const title = variants(
|
|
602
863
|
{
|
|
603
864
|
base: {
|
|
@@ -654,6 +915,9 @@ namespace styles {
|
|
|
654
915
|
},
|
|
655
916
|
},
|
|
656
917
|
'@media (min-width: 1024px)': { display: 'none !custom' },
|
|
918
|
+
'@media (min-width: 768px) and (max-width: 1023px)': {
|
|
919
|
+
selectors: { '[data-app-shell-collapse] &': { display: 'none !custom' } },
|
|
920
|
+
},
|
|
657
921
|
'@media (prefers-reduced-motion: reduce)': {
|
|
658
922
|
transitionProperty: 'background-color, outline-color, outline-offset !custom',
|
|
659
923
|
selectors: { '&:active': { transform: 'none !custom' } },
|
|
@@ -670,6 +934,7 @@ namespace styles {
|
|
|
670
934
|
justifyContent: 'flex-end',
|
|
671
935
|
position: 'fixed',
|
|
672
936
|
zIndex: 'popover',
|
|
937
|
+
selectors: { '&[hidden]': { display: 'none !custom' } },
|
|
673
938
|
},
|
|
674
939
|
{ id: 'tds/web/app-shell/viewport' },
|
|
675
940
|
)
|
|
@@ -104,8 +104,10 @@ namespace styles {
|
|
|
104
104
|
// The accent text follows the inverse content color, which turns black in dark mode.
|
|
105
105
|
accent: { backgroundColor: 'accent.blue.background', color: 'inverse.content.primary' },
|
|
106
106
|
elevated: { backgroundColor: 'background.elevated', color: 'content.primary' },
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
inverse: {
|
|
108
|
+
backgroundColor: 'inverse.background.secondary',
|
|
109
|
+
color: 'inverse.content.primary',
|
|
110
|
+
},
|
|
109
111
|
outlined: {
|
|
110
112
|
backgroundColor: 'transparent !custom',
|
|
111
113
|
borderColor: 'line.secondary',
|
|
@@ -12,6 +12,7 @@ import { Spinner } from './Spinner.js'
|
|
|
12
12
|
* - Use `Button` for actions and `Button.Link` for navigation.
|
|
13
13
|
* - Label with a specific verb, such as Send payment. Give `shape="icon"` buttons, which render as circles, an `aria-label`.
|
|
14
14
|
* - Keep one `primary` button per view or section, and give the others `secondary` or `tertiary`.
|
|
15
|
+
* - Use `danger` for the action that confirms a destructive outcome, such as Revoke key.
|
|
15
16
|
* - Set `appearance="inverse"` on an inverse surface, such as a dark banner on a light page, and
|
|
16
17
|
* `appearance="overlay"` over an image or video.
|
|
17
18
|
* - Use `large` for the actions in a dialog footer. Keep `xsmall` pills for actions inside a row
|
|
@@ -162,18 +163,11 @@ namespace styles {
|
|
|
162
163
|
selectors: { '& > span:first-child': { minWidth: '0 !custom' } },
|
|
163
164
|
},
|
|
164
165
|
},
|
|
165
|
-
// The Platform Icon Button
|
|
166
|
-
//
|
|
167
|
-
// the icon takes the inverse content color over them.
|
|
166
|
+
// The Platform Icon Button fills its primary circle a step lighter than the primary
|
|
167
|
+
// Button in light mode.
|
|
168
168
|
{
|
|
169
169
|
when: { appearance: 'base', shape: 'icon', variant: 'primary' },
|
|
170
|
-
style: {
|
|
171
|
-
backgroundColor: 'inverse.background.secondary',
|
|
172
|
-
color: 'neutral.000',
|
|
173
|
-
selectors: {
|
|
174
|
-
'&:is(:hover, :active):not(:disabled)': { color: 'inverse.content.primary' },
|
|
175
|
-
},
|
|
176
|
-
},
|
|
170
|
+
style: { backgroundColor: 'inverse.background.secondary' },
|
|
177
171
|
},
|
|
178
172
|
// The focus ring of the page blends into an inverse surface, so an inverse button draws
|
|
179
173
|
// its ring in the inverse text color, 2px out, where the surface separates it from a fill
|
|
@@ -278,6 +272,16 @@ namespace styles {
|
|
|
278
272
|
// Each pressed fill follows its hover fill, so it wins while the pointer stays over the
|
|
279
273
|
// button.
|
|
280
274
|
variant: {
|
|
275
|
+
// The Platform Button has no pressed design for `danger`, so a press keeps the hover fill.
|
|
276
|
+
danger: {
|
|
277
|
+
backgroundColor: 'action.danger.fill.rest',
|
|
278
|
+
color: 'action.danger.label.rest',
|
|
279
|
+
selectors: {
|
|
280
|
+
'&:is(:hover, :active):not(:disabled)': {
|
|
281
|
+
backgroundColor: 'action.danger.fill.hover',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
281
285
|
// The Platform Button paints its primary fill and label with the inverse colors.
|
|
282
286
|
primary: {
|
|
283
287
|
backgroundColor: 'inverse.background.primary',
|
|
@@ -75,7 +75,13 @@ export namespace Dialog {
|
|
|
75
75
|
export function Body(props: Body.Props) {
|
|
76
76
|
const { className, vars, ...attributes } = props
|
|
77
77
|
|
|
78
|
-
return
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
{...attributes}
|
|
81
|
+
data-dialog-body=""
|
|
82
|
+
{...styles.body({ className, style: props.style, vars })}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
export namespace Body {
|