@syncedco/flow 0.1.4 → 0.3.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/bin/synced-flow.mjs +687 -17
- package/components.css +348 -1
- package/docs/api-contract.md +3 -3
- package/docs/native-components.md +20 -0
- package/docs/patterns.md +13 -0
- package/docs/recipes.md +4 -3
- package/docs/system-primitives.md +8 -4
- package/docs/tokens.md +5 -1
- package/layout.css +89 -0
- package/package.json +1 -1
- package/skills/synced-flow/SKILL.md +23 -6
- package/src/tokens.mjs +10 -0
- package/styles.css +457 -1
- package/tokens.css +19 -0
- package/utilities.css +1 -0
package/bin/synced-flow.mjs
CHANGED
|
@@ -1024,8 +1024,8 @@ function getTokenSummary() {
|
|
|
1024
1024
|
components: componentTokens,
|
|
1025
1025
|
themePresets: presetNames.map(kebabThemeName),
|
|
1026
1026
|
starterClasses: {
|
|
1027
|
-
layout: ['sf-container', 'sf-container--narrow', 'sf-container--wide', 'sf-container--full', 'sf-rail-shell', 'sf-rail-shell__rail', 'sf-rail-shell__body', 'sf-rail-shell__mark', 'sf-rail-shell__label', 'sf-section', 'sf-section--compact', 'sf-section--spacious', 'sf-stack', 'sf-flow', 'sf-cluster', 'sf-repel', 'sf-toolbar', 'sf-app-shell', 'sf-app-sidebar', 'sf-app-sidebar__brand', 'sf-app-main', 'sf-auto-grid', 'sf-auto-grid--xs', 'sf-auto-grid--sm', 'sf-auto-grid--lg', 'sf-switcher', 'sf-sidebar', 'sf-split', 'sf-split--reverse', 'sf-chapter', 'sf-chapter__copy', 'sf-frame', 'sf-cover', 'sf-panel-grid', 'sf-metric-grid', 'sf-pipeline', 'sf-scroll-viewport', 'sf-scroll-panel', 'sf-scroll-snap-y', 'sf-sticky-top', 'sf-media-object', 'sf-aside-rail'],
|
|
1028
|
-
components: ['sf-button', 'sf-button-group', 'sf-button--icon', 'sf-icon-button', 'sf-icon', 'sf-icon--xs', 'sf-icon--sm', 'sf-icon--md', 'sf-icon--lg', 'sf-icon--xl', 'sf-card', 'sf-card--flat', 'sf-card--raised', 'sf-card--compact', 'sf-card--glass', 'sf-card--technical', 'sf-card--accent', 'sf-card--center', 'sf-surface', 'sf-code-window', 'sf-code-window--glow', 'sf-code-window__bar', 'sf-window-dots', 'sf-code-lines', 'sf-code-lines--compact', 'sf-code-block', 'sf-token-strip', 'sf-marquee', 'sf-marquee__track', 'sf-marquee__item', 'sf-command-list', 'sf-command-list__prompt', 'sf-platform-card', 'sf-platform-card__logo', 'sf-hero', 'sf-logo-cloud', 'sf-feature', 'sf-stats', 'sf-stat', 'sf-testimonial', 'sf-pricing-grid', 'sf-price-card', 'sf-price', 'sf-faq', 'sf-cta', 'sf-footer', 'sf-badge', 'sf-avatar', 'sf-chart', 'sf-chart--wide', 'sf-chart__plot', 'sf-chart__svg', 'sf-chart__grid', 'sf-chart__axis', 'sf-chart__area', 'sf-chart__line', 'sf-chart__point', 'sf-chart__legend', 'sf-chart__legend-item', 'sf-chart__swatch', 'sf-meter-list', 'sf-meter-row', 'sf-meter', 'sf-nav', 'sf-nav--mobile', 'sf-nav__list', 'sf-nav__link', 'sf-menu', 'sf-breadcrumb', 'sf-pagination', 'sf-dialog', 'sf-popover', 'sf-tooltip', 'sf-tooltip-trigger', 'sf-menu-popover', 'sf-toast', 'sf-banner', 'sf-drawer', 'sf-drawer--stack', 'sf-disclosure', 'sf-accordion', 'sf-tabs', 'sf-tab-list', 'sf-tab', 'sf-tab-panel', 'sf-form', 'sf-fieldset', 'sf-field', 'sf-label', 'sf-help', 'sf-error', 'sf-input', 'sf-select', 'sf-textarea', 'sf-check', 'sf-alert', 'sf-section-header', 'sf-kicker'],
|
|
1027
|
+
layout: ['sf-container', 'sf-container--narrow', 'sf-container--wide', 'sf-container--full', 'sf-rail-shell', 'sf-rail-shell__rail', 'sf-rail-shell__body', 'sf-rail-shell__mark', 'sf-rail-shell__label', 'sf-section', 'sf-section--compact', 'sf-section--spacious', 'sf-stack', 'sf-flow', 'sf-cluster', 'sf-repel', 'sf-toolbar', 'sf-app-shell', 'sf-app-sidebar', 'sf-app-sidebar__brand', 'sf-app-header', 'sf-app-backdrop', 'sf-app-main', 'sf-auto-grid', 'sf-auto-grid--xs', 'sf-auto-grid--sm', 'sf-auto-grid--lg', 'sf-switcher', 'sf-sidebar', 'sf-split', 'sf-split--reverse', 'sf-chapter', 'sf-chapter__copy', 'sf-frame', 'sf-cover', 'sf-panel-grid', 'sf-metric-grid', 'sf-pipeline', 'sf-scroll-viewport', 'sf-scroll-panel', 'sf-scroll-snap-y', 'sf-sticky-top', 'sf-media-object', 'sf-aside-rail'],
|
|
1028
|
+
components: ['sf-button', 'sf-button-group', 'sf-button--icon', 'sf-icon-button', 'sf-icon', 'sf-icon--xs', 'sf-icon--sm', 'sf-icon--md', 'sf-icon--lg', 'sf-icon--xl', 'sf-card', 'sf-card--flat', 'sf-card--raised', 'sf-card--compact', 'sf-card--glass', 'sf-card--technical', 'sf-card--accent', 'sf-card--center', 'sf-surface', 'sf-code-window', 'sf-code-window--glow', 'sf-code-window__bar', 'sf-window-dots', 'sf-code-lines', 'sf-code-lines--compact', 'sf-code-block', 'sf-token-strip', 'sf-marquee', 'sf-marquee__track', 'sf-marquee__item', 'sf-command-list', 'sf-command-list__prompt', 'sf-platform-card', 'sf-platform-card__logo', 'sf-hero', 'sf-logo-cloud', 'sf-feature', 'sf-stats', 'sf-stat', 'sf-testimonial', 'sf-pricing-grid', 'sf-price-card', 'sf-price', 'sf-faq', 'sf-cta', 'sf-footer', 'sf-badge', 'sf-avatar', 'sf-chart', 'sf-chart--wide', 'sf-chart__plot', 'sf-chart__svg', 'sf-chart__grid', 'sf-chart__axis', 'sf-chart__area', 'sf-chart__line', 'sf-chart__point', 'sf-chart__legend', 'sf-chart__legend-item', 'sf-chart__swatch', 'sf-meter-list', 'sf-meter-row', 'sf-meter', 'sf-progress', 'sf-skeleton', 'sf-filter-bar', 'sf-search', 'sf-data-table', 'sf-table-sort', 'sf-status', 'sf-data-list', 'sf-empty-state', 'sf-settings-section', 'sf-detail-panel', 'sf-nav', 'sf-nav--mobile', 'sf-nav__list', 'sf-nav__link', 'sf-menu', 'sf-breadcrumb', 'sf-pagination', 'sf-dialog', 'sf-popover', 'sf-tooltip', 'sf-tooltip-trigger', 'sf-menu-popover', 'sf-toast', 'sf-toast-stack', 'sf-banner', 'sf-drawer', 'sf-drawer--stack', 'sf-disclosure', 'sf-accordion', 'sf-tabs', 'sf-tab-list', 'sf-tab', 'sf-tab__count', 'sf-tab-panel', 'sf-form', 'sf-fieldset', 'sf-field', 'sf-label', 'sf-help', 'sf-error', 'sf-input', 'sf-select', 'sf-textarea', 'sf-check', 'sf-alert', 'sf-section-header', 'sf-kicker'],
|
|
1029
1029
|
type: ['sf-text-caption', 'sf-text-body', 'sf-text-lead', 'sf-text-h4', 'sf-text-h3', 'sf-text-h2', 'sf-text-h1', 'sf-text-display'],
|
|
1030
1030
|
utilities: ['sr-only', 'not-sr-only', 'sf-visually-hidden', 'sf-not-visually-hidden', 'sf-skip-link', 'sf-focus-ring', 'sf-touch-target', 'sf-list-reset', 'sf-list-disc', 'sf-list-decimal', 'sf-link', 'sf-link-subtle', 'sf-link-plain', 'sf-prose', 'sf-prose--blog', 'sf-prose--legal', 'sf-meta', 'sf-figure', 'sf-caption', 'sf-table-wrap', 'sf-push-block-end', 'sf-push-block-start', 'sf-push-inline-end', 'sf-push-inline-start', 'sf-full-bleed', 'sf-text-muted', 'sf-bg-surface', 'sf-border', 'sf-rounded-panel', 'sf-shadow-md', 'sf-animate-fade', 'sf-animate-rise', 'sf-animate-scale', 'sf-animate-slide', 'sf-animate-stagger'],
|
|
1031
1031
|
},
|
|
@@ -1239,6 +1239,260 @@ export default function DialogExample() {
|
|
|
1239
1239
|
markup: '<article class="sf-container sf-prose sf-prose--blog">...</article>',
|
|
1240
1240
|
keywords: ['blog', 'article', 'prose', 'legal', 'docs', 'case-study', 'table', 'figure', 'seo'],
|
|
1241
1241
|
},
|
|
1242
|
+
{
|
|
1243
|
+
id: 'app-shell-layout',
|
|
1244
|
+
name: 'App shell layout',
|
|
1245
|
+
whenToUse: 'Product apps, portals, admin tools, and dashboards that need a header, sidebar, main workspace, optional collapsed sidebar, and mobile drawer mode.',
|
|
1246
|
+
classes: ['sf-app-shell', 'sf-app-header', 'sf-app-sidebar', 'sf-app-backdrop', 'sf-app-main', 'sf-nav', 'sf-nav__link'],
|
|
1247
|
+
requiresJs: false,
|
|
1248
|
+
requiresJsNotes: 'CSS handles layout states. The consuming app owns toggling data-collapsed and data-sidebar-open attributes.',
|
|
1249
|
+
a11y: ['Keep the sidebar inside a named nav or aside.', 'Use a real button to toggle drawer/collapsed states.', 'Move focus into the drawer when app JavaScript opens it.'],
|
|
1250
|
+
gotchas: ['Use data-mobile="drawer" only for app layouts that need overlay navigation on small screens.', 'Keep data-hide-collapsed text available through aria-labels on icon-only links.'],
|
|
1251
|
+
markup: {
|
|
1252
|
+
html: `<div class="sf-app-shell" data-layout="fixed-header" data-mobile="drawer">
|
|
1253
|
+
<header class="sf-app-header"><button class="sf-button sf-button--ghost" type="button" aria-label="Open navigation">Menu</button><strong>Workspace</strong></header>
|
|
1254
|
+
<aside class="sf-app-sidebar" aria-label="Workspace navigation"><nav class="sf-nav"><a class="sf-nav__link" href="/app" aria-current="page">Dashboard</a></nav></aside>
|
|
1255
|
+
<button class="sf-app-backdrop" type="button" aria-label="Close navigation"></button>
|
|
1256
|
+
<main class="sf-app-main">...</main>
|
|
1257
|
+
</div>`,
|
|
1258
|
+
},
|
|
1259
|
+
keywords: ['app', 'shell', 'layout', 'sidebar', 'header', 'drawer', 'collapsed', 'dashboard', 'admin', 'portal'],
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
id: 'row-action-menu',
|
|
1263
|
+
name: 'Row action menu',
|
|
1264
|
+
whenToUse: 'Kebab, overflow, and per-row action menus using native popover markup and destructive item styling.',
|
|
1265
|
+
classes: ['sf-menu-popover', 'sf-menu', 'sf-button', 'sf-button--ghost', 'sf-button--icon'],
|
|
1266
|
+
requiresJs: false,
|
|
1267
|
+
requiresJsNotes: 'Native popover handles open, backdrop dismiss, and light-dismiss where supported. App JavaScript may add keyboard roving for complex menus.',
|
|
1268
|
+
a11y: ['Use a button trigger with an accessible label.', 'Keep destructive actions visibly labelled.', 'Use links for navigation and buttons for actions.'],
|
|
1269
|
+
gotchas: ['Use popovertarget on buttons, not anchors.', 'The app owns confirmation flows for destructive actions.'],
|
|
1270
|
+
markup: {
|
|
1271
|
+
html: `<button class="sf-button sf-button--ghost sf-button--icon" type="button" popovertarget="row-actions-1" aria-label="Open row actions">...</button>
|
|
1272
|
+
<div class="sf-menu-popover" id="row-actions-1" popover="auto">
|
|
1273
|
+
<nav class="sf-menu" aria-label="Row actions">
|
|
1274
|
+
<a href="/items/1">Open</a>
|
|
1275
|
+
<button type="button">Archive</button>
|
|
1276
|
+
<button type="button" data-variant="danger">Delete</button>
|
|
1277
|
+
</nav>
|
|
1278
|
+
</div>`,
|
|
1279
|
+
},
|
|
1280
|
+
keywords: ['menu', 'row', 'actions', 'kebab', 'popover', 'destructive', 'table'],
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
id: 'tabs-with-counts',
|
|
1284
|
+
name: 'Tabs with counts',
|
|
1285
|
+
whenToUse: 'Filtered app views, inboxes, queues, and reports where each tab needs a compact count.',
|
|
1286
|
+
classes: ['sf-tabs', 'sf-tab-list', 'sf-tab', 'sf-tab__count', 'sf-tab-panel'],
|
|
1287
|
+
requiresJs: false,
|
|
1288
|
+
requiresJsNotes: 'CSS styles state. The app owns aria-selected, routing, or tab panel switching for dynamic interfaces.',
|
|
1289
|
+
a11y: ['Use role="tablist" only when implementing full keyboard tab behavior.', 'Use aria-selected on active tabs.', 'Keep counts textual and not colour-only.'],
|
|
1290
|
+
gotchas: ['Use data-variant="pill" for pill tabs.', 'Use links for route-backed tabs and buttons for in-page tab switching.'],
|
|
1291
|
+
markup: {
|
|
1292
|
+
html: `<nav class="sf-tab-list" aria-label="Views">
|
|
1293
|
+
<a class="sf-tab" data-variant="pill" href="?view=open" aria-selected="true">Open <span class="sf-tab__count">12</span></a>
|
|
1294
|
+
<a class="sf-tab" data-variant="pill" href="?view=closed">Closed <span class="sf-tab__count">8</span></a>
|
|
1295
|
+
</nav>`,
|
|
1296
|
+
},
|
|
1297
|
+
keywords: ['tabs', 'counts', 'queue', 'views', 'filters', 'pill'],
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
id: 'data-table-actions',
|
|
1301
|
+
name: 'Data table actions',
|
|
1302
|
+
whenToUse: 'Operational tables that need sortable header controls, clickable rows, and a responsive card view fallback.',
|
|
1303
|
+
classes: ['sf-table-wrap', 'sf-data-table', 'sf-table-sort', 'sf-status', 'sf-link'],
|
|
1304
|
+
requiresJs: false,
|
|
1305
|
+
requiresJsNotes: 'Sorting and row navigation are app behavior. Synced Flow only styles controls, focus state, and responsive card layout.',
|
|
1306
|
+
a11y: ['Use aria-sort on sorted columns.', 'Keep the row destination as a real link.', 'Add data-label to cells when using card fallback.'],
|
|
1307
|
+
gotchas: ['Set data-view="cards" on sf-table-wrap for the responsive card fallback.', 'Do not put interactive controls inside a stretched row link.'],
|
|
1308
|
+
markup: {
|
|
1309
|
+
html: `<div class="sf-table-wrap" data-view="cards">
|
|
1310
|
+
<table class="sf-data-table">
|
|
1311
|
+
<thead><tr><th><button class="sf-table-sort" type="button" aria-sort="ascending">Name</button></th><th>Status</th></tr></thead>
|
|
1312
|
+
<tbody><tr data-clickable="true"><td data-label="Name"><a class="sf-link" href="/items/1">Northstar</a></td><td data-label="Status"><span class="sf-status" data-size="sm" data-tone="success">Active</span></td></tr></tbody>
|
|
1313
|
+
</table>
|
|
1314
|
+
</div>`,
|
|
1315
|
+
},
|
|
1316
|
+
keywords: ['table', 'sort', 'sortable', 'responsive', 'card', 'clickable', 'data'],
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
id: 'search-field',
|
|
1320
|
+
name: 'Search field',
|
|
1321
|
+
whenToUse: 'Search inputs in filters, headers, command surfaces, and dense app interfaces.',
|
|
1322
|
+
classes: ['sf-search', 'sf-field', 'sf-label', 'sf-input'],
|
|
1323
|
+
requiresJs: false,
|
|
1324
|
+
requiresJsNotes: 'Native form submit works without JavaScript. Debounce, suggestions, and command logic belong to the app.',
|
|
1325
|
+
a11y: ['Keep a label available, visible or visually hidden.', 'Use type="search".', 'Do not use placeholder text as the only label.'],
|
|
1326
|
+
gotchas: ['Use sf-field data-density="compact" for dense interfaces.', 'Autocomplete and combobox behavior is app-owned.'],
|
|
1327
|
+
markup: {
|
|
1328
|
+
html: `<label class="sf-field sf-search" data-density="compact">
|
|
1329
|
+
<span class="sf-label">Search</span>
|
|
1330
|
+
<input class="sf-input" type="search" name="q" placeholder="Search records">
|
|
1331
|
+
</label>`,
|
|
1332
|
+
},
|
|
1333
|
+
keywords: ['search', 'input', 'filter', 'command', 'dense'],
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
id: 'modal-form',
|
|
1337
|
+
name: 'Modal form',
|
|
1338
|
+
whenToUse: 'Dialog-based create, edit, confirmation, and settings flows with form body and footer actions.',
|
|
1339
|
+
classes: ['sf-dialog', 'sf-dialog__header', 'sf-dialog__body', 'sf-dialog__footer', 'sf-form', 'sf-field', 'sf-input', 'sf-button'],
|
|
1340
|
+
requiresJs: true,
|
|
1341
|
+
requiresJsNotes: 'Native dialog provides modal behavior. Broad support still needs a tiny app handler for showModal() until commandfor is universal.',
|
|
1342
|
+
a11y: ['Use a visible dialog heading.', 'Keep footer actions inside the form or associated with form attribute.', 'Return focus to the trigger after closing.'],
|
|
1343
|
+
gotchas: ['Use data-layout="form" for form spacing.', 'Validation and persistence belong to the app.'],
|
|
1344
|
+
markup: {
|
|
1345
|
+
html: `<dialog class="sf-dialog" data-layout="form" id="edit-dialog" aria-labelledby="edit-title">
|
|
1346
|
+
<header class="sf-dialog__header"><h2 id="edit-title">Edit record</h2></header>
|
|
1347
|
+
<form class="sf-form sf-dialog__body" id="edit-form"><label class="sf-field"><span class="sf-label">Name</span><input class="sf-input" name="name"></label></form>
|
|
1348
|
+
<footer class="sf-dialog__footer"><button class="sf-button sf-button--outline" formmethod="dialog">Cancel</button><button class="sf-button" form="edit-form">Save</button></footer>
|
|
1349
|
+
</dialog>`,
|
|
1350
|
+
},
|
|
1351
|
+
keywords: ['dialog', 'modal', 'form', 'drawer', 'actions', 'footer'],
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
id: 'toast-stack',
|
|
1355
|
+
name: 'Toast stack',
|
|
1356
|
+
whenToUse: 'Non-blocking app feedback where multiple toast messages need a consistent fixed stack layout.',
|
|
1357
|
+
classes: ['sf-toast-stack', 'sf-toast', 'sf-alert', 'sf-alert__title'],
|
|
1358
|
+
requiresJs: false,
|
|
1359
|
+
requiresJsNotes: 'Stack layout is CSS. Queuing, dismissal timing, and live-region announcements belong to the app.',
|
|
1360
|
+
a11y: ['Use an aria-live region for dynamic messages.', 'Do not auto-dismiss critical errors too quickly.', 'Provide a close control when messages persist.'],
|
|
1361
|
+
gotchas: ['Use sf-toast-stack for layout and sf-toast or sf-alert for each item.', 'Keep queue state outside Synced Flow.'],
|
|
1362
|
+
markup: {
|
|
1363
|
+
html: `<div class="sf-toast-stack" aria-live="polite" aria-label="Notifications">
|
|
1364
|
+
<section class="sf-toast sf-alert" role="status"><strong class="sf-alert__title">Saved</strong><p>Changes were updated.</p></section>
|
|
1365
|
+
</div>`,
|
|
1366
|
+
},
|
|
1367
|
+
keywords: ['toast', 'notification', 'stack', 'feedback', 'alert'],
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: 'filter-toolbar',
|
|
1371
|
+
name: 'Filter toolbar',
|
|
1372
|
+
whenToUse: 'Search, status filters, quick actions, and exports above lists, tables, queues, directories, and admin screens.',
|
|
1373
|
+
classes: ['sf-filter-bar', 'sf-field', 'sf-label', 'sf-input', 'sf-select', 'sf-button', 'sf-button--outline'],
|
|
1374
|
+
requiresJs: false,
|
|
1375
|
+
requiresJsNotes: 'Use native form submit, GET params, or app-owned state. Synced Flow only styles the shell and controls.',
|
|
1376
|
+
a11y: ['Keep every field labelled.', 'Use button type="submit" for applied filters.', 'Announce result count in app markup when filtering changes content dynamically.'],
|
|
1377
|
+
gotchas: ['Do not hide labels without keeping them accessible.', 'Use app logic for debounced search, saved views, and live result counts.'],
|
|
1378
|
+
markup: {
|
|
1379
|
+
html: `<form class="sf-filter-bar" role="search" action="/app/customers" method="get">
|
|
1380
|
+
<label class="sf-field">
|
|
1381
|
+
<span class="sf-label">Search</span>
|
|
1382
|
+
<input class="sf-input" name="q" type="search" placeholder="Search customers">
|
|
1383
|
+
</label>
|
|
1384
|
+
<label class="sf-field">
|
|
1385
|
+
<span class="sf-label">Status</span>
|
|
1386
|
+
<select class="sf-select" name="status"><option>Any status</option><option>Healthy</option><option>Needs review</option></select>
|
|
1387
|
+
</label>
|
|
1388
|
+
<button class="sf-button" type="submit">Apply</button>
|
|
1389
|
+
<a class="sf-button sf-button--outline" href="/app/customers/export">Export</a>
|
|
1390
|
+
</form>`,
|
|
1391
|
+
},
|
|
1392
|
+
keywords: ['saas', 'filter', 'toolbar', 'search', 'status', 'table', 'list', 'admin', 'crm'],
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
id: 'date-filter-form',
|
|
1396
|
+
name: 'Native date filter',
|
|
1397
|
+
whenToUse: 'Date ranges, billing periods, export windows, audit logs, appointments, and reporting filters that can use browser-native date/time controls.',
|
|
1398
|
+
classes: ['sf-filter-bar', 'sf-field', 'sf-label', 'sf-input', 'sf-select', 'sf-button', 'sf-button--outline'],
|
|
1399
|
+
requiresJs: false,
|
|
1400
|
+
requiresJsNotes: 'Native date, time, month, week, and datetime-local inputs submit through normal forms. Custom calendar widgets and availability logic belong to the app.',
|
|
1401
|
+
a11y: ['Use explicit visible labels for each date or time input.', 'Prefer separate start and end fields for ranges.', 'Use server or app validation for invalid ranges.'],
|
|
1402
|
+
gotchas: ['Native picker UI varies by browser and operating system.', 'Use a dedicated calendar library only when the product needs custom scheduling behavior.'],
|
|
1403
|
+
markup: {
|
|
1404
|
+
html: `<form class="sf-filter-bar" action="/app/reports" method="get">
|
|
1405
|
+
<label class="sf-field"><span class="sf-label">From</span><input class="sf-input" name="from" type="date"></label>
|
|
1406
|
+
<label class="sf-field"><span class="sf-label">To</span><input class="sf-input" name="to" type="date"></label>
|
|
1407
|
+
<label class="sf-field"><span class="sf-label">Period</span><select class="sf-select" name="period"><option>Custom</option><option>This month</option><option>Last month</option></select></label>
|
|
1408
|
+
<button class="sf-button" type="submit">Apply</button>
|
|
1409
|
+
<a class="sf-button sf-button--outline" href="/app/reports">Reset</a>
|
|
1410
|
+
</form>`,
|
|
1411
|
+
},
|
|
1412
|
+
keywords: ['saas', 'date', 'time', 'calendar', 'filter', 'range', 'reports', 'billing', 'native'],
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
id: 'active-filter-tags',
|
|
1416
|
+
name: 'Active filter tags',
|
|
1417
|
+
whenToUse: 'Visible selected filters below a search/filter toolbar, with clear-all and individual removal actions owned by the app.',
|
|
1418
|
+
classes: ['sf-cluster', 'sf-badge', 'sf-button', 'sf-button--ghost', 'sf-button--sm', 'sf-link'],
|
|
1419
|
+
requiresJs: false,
|
|
1420
|
+
requiresJsNotes: 'Links can remove filters through URL params without JavaScript. Client-side filter state belongs to the consuming app.',
|
|
1421
|
+
a11y: ['Use clear link text that names the filter being removed.', 'Keep the active filter value visible as text.', 'Provide a clear-all action when several filters can be active.'],
|
|
1422
|
+
gotchas: ['Use buttons only when client-side state changes in place.', 'Use links for URL-driven filters so filtered views remain shareable.'],
|
|
1423
|
+
markup: {
|
|
1424
|
+
html: `<div class="sf-cluster" aria-label="Active filters">
|
|
1425
|
+
<span class="sf-badge">Status: Healthy <a class="sf-link" href="?plan=team" aria-label="Remove status filter">Remove</a></span>
|
|
1426
|
+
<span class="sf-badge">Plan: Team <a class="sf-link" href="?status=healthy" aria-label="Remove plan filter">Remove</a></span>
|
|
1427
|
+
<a class="sf-button sf-button--ghost sf-button--sm" href="/app/customers">Clear filters</a>
|
|
1428
|
+
</div>`,
|
|
1429
|
+
},
|
|
1430
|
+
keywords: ['saas', 'filter', 'filters', 'chips', 'tags', 'badge', 'selected', 'active'],
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: 'data-table',
|
|
1434
|
+
name: 'Data table',
|
|
1435
|
+
whenToUse: 'Account lists, activity logs, invoices, users, queues, directories, and operational tables that should keep native table semantics.',
|
|
1436
|
+
classes: ['sf-table-wrap', 'sf-data-table', 'sf-status', 'sf-avatar', 'sf-link'],
|
|
1437
|
+
requiresJs: false,
|
|
1438
|
+
requiresJsNotes: 'Sorting, pagination state, and row actions belong to the consuming app.',
|
|
1439
|
+
a11y: ['Use a caption or labelled section.', 'Use real table headers.', 'Do not replace table markup with divs for tabular data.'],
|
|
1440
|
+
gotchas: ['Wrap wide tables in sf-table-wrap.', 'Use sf-status for display only; keep actual status text in the element.'],
|
|
1441
|
+
markup: {
|
|
1442
|
+
html: `<div class="sf-table-wrap">
|
|
1443
|
+
<table class="sf-data-table" aria-label="Customers">
|
|
1444
|
+
<thead><tr><th>Customer</th><th>Status</th><th>Plan</th><th>Updated</th></tr></thead>
|
|
1445
|
+
<tbody>
|
|
1446
|
+
<tr><td><a class="sf-link" href="/app/customers/northstar">Northstar Labs</a></td><td><span class="sf-status" data-tone="success">Healthy</span></td><td>Growth</td><td>Today</td></tr>
|
|
1447
|
+
<tr><td>Willow Group</td><td><span class="sf-status" data-tone="warning">Needs review</span></td><td>Operations</td><td>Yesterday</td></tr>
|
|
1448
|
+
</tbody>
|
|
1449
|
+
</table>
|
|
1450
|
+
</div>`,
|
|
1451
|
+
},
|
|
1452
|
+
keywords: ['saas', 'table', 'data', 'customers', 'accounts', 'admin', 'crm', 'status', 'list'],
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
id: 'empty-state',
|
|
1456
|
+
name: 'Empty state',
|
|
1457
|
+
whenToUse: 'Empty queues, no search results, first-run screens, directories, and dashboards before data is available.',
|
|
1458
|
+
classes: ['sf-empty-state', 'sf-button', 'sf-button--outline', 'sf-text-muted'],
|
|
1459
|
+
requiresJs: false,
|
|
1460
|
+
requiresJsNotes: 'No JavaScript is required unless the consuming app opens a creation flow.',
|
|
1461
|
+
a11y: ['Use a heading that explains the missing state.', 'Keep primary action text specific.', 'Do not rely on illustration-only meaning.'],
|
|
1462
|
+
gotchas: ['Keep it short; empty states should unblock the next action.', 'Use sf-card around it only when the surrounding layout needs a card surface.'],
|
|
1463
|
+
markup: {
|
|
1464
|
+
html: `<div class="sf-empty-state">
|
|
1465
|
+
<h2 class="sf-text-h4">No matching customers</h2>
|
|
1466
|
+
<p class="sf-text-muted">Adjust the filters or add the first customer.</p>
|
|
1467
|
+
<div class="sf-cluster">
|
|
1468
|
+
<a class="sf-button" href="/app/customers/new">Add customer</a>
|
|
1469
|
+
<a class="sf-button sf-button--outline" href="/app/customers">Reset filters</a>
|
|
1470
|
+
</div>
|
|
1471
|
+
</div>`,
|
|
1472
|
+
},
|
|
1473
|
+
keywords: ['saas', 'empty', 'state', 'first-run', 'onboarding', 'no-results', 'zero'],
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
id: 'settings-section',
|
|
1477
|
+
name: 'Settings section',
|
|
1478
|
+
whenToUse: 'Account, team, billing, security, notification, integration, profile, and site settings screens.',
|
|
1479
|
+
classes: ['sf-settings-section', 'sf-detail-panel', 'sf-data-list', 'sf-form', 'sf-field', 'sf-input', 'sf-check', 'sf-button'],
|
|
1480
|
+
requiresJs: false,
|
|
1481
|
+
requiresJsNotes: 'Form submission, optimistic saves, and validation belong to the consuming app.',
|
|
1482
|
+
a11y: ['Group related settings under a heading.', 'Use fieldsets when settings are tightly related.', 'Describe destructive settings before the action.'],
|
|
1483
|
+
gotchas: ['Avoid putting unrelated settings in one panel.', 'Use sf-detail-panel for read-only metadata next to editable fields.'],
|
|
1484
|
+
markup: {
|
|
1485
|
+
html: `<section class="sf-settings-section" aria-labelledby="workspace-settings-title">
|
|
1486
|
+
<header class="sf-repel"><div><h2 id="workspace-settings-title">Workspace settings</h2><p class="sf-text-muted">Manage visible account details.</p></div><button class="sf-button" type="submit" form="workspace-settings">Save</button></header>
|
|
1487
|
+
<form class="sf-form" id="workspace-settings">
|
|
1488
|
+
<label class="sf-field"><span class="sf-label">Workspace name</span><input class="sf-input" name="name" value="Product Suite"></label>
|
|
1489
|
+
<label class="sf-check"><input type="checkbox" name="weekly_summary" checked> <span>Email weekly summary</span></label>
|
|
1490
|
+
</form>
|
|
1491
|
+
<aside class="sf-detail-panel"><dl class="sf-data-list"><div><dt>Plan</dt><dd>Team</dd></div><div><dt>Seats</dt><dd>12 active</dd></div></dl></aside>
|
|
1492
|
+
</section>`,
|
|
1493
|
+
},
|
|
1494
|
+
keywords: ['saas', 'settings', 'section', 'billing', 'team', 'security', 'account', 'detail'],
|
|
1495
|
+
},
|
|
1242
1496
|
{
|
|
1243
1497
|
id: 'contact-form',
|
|
1244
1498
|
name: 'Accessible contact form',
|
|
@@ -1362,8 +1616,8 @@ function getPublicRecipes() {
|
|
|
1362
1616
|
id: 'saas-dashboard',
|
|
1363
1617
|
name: 'SaaS dashboard',
|
|
1364
1618
|
whenToUse: 'Authenticated SaaS apps, admin panels, customer portals, CRMs, analytics dashboards, and product workspaces.',
|
|
1365
|
-
sections: ['sticky-navigation', 'mobile-nav-drawer', 'card-grid', 'contact-form', 'long-form-content'],
|
|
1366
|
-
classes: ['sf-skip-link', 'sf-theme-light', 'sf-app-shell', 'sf-app-sidebar', 'sf-app-sidebar__brand', 'sf-app-main', 'sf-toolbar', 'sf-stack', 'sf-stack--tight', 'sf-repel', 'sf-cluster', 'sf-panel-grid', 'sf-metric-grid', 'sf-nav', 'sf-nav__list', 'sf-nav__link', 'sf-icon', 'sf-card', 'sf-surface', 'sf-stat', 'sf-badge', 'sf-avatar', 'sf-chart', 'sf-chart--wide', 'sf-chart__plot', 'sf-chart__svg', 'sf-chart__grid', 'sf-chart__axis', 'sf-chart__area', 'sf-chart__line', 'sf-chart__point', 'sf-chart__legend', 'sf-chart__legend-item', 'sf-chart__swatch', 'sf-meter-list', 'sf-meter-row', 'sf-meter', 'sf-button', 'sf-button--outline', 'sf-button--icon', 'sf-form', 'sf-field', 'sf-input', 'sf-check', 'sf-table-wrap', 'sf-font-sans', 'sf-visually-hidden'],
|
|
1619
|
+
sections: ['sticky-navigation', 'mobile-nav-drawer', 'card-grid', 'contact-form', 'long-form-content', 'filter-toolbar', 'date-filter-form', 'active-filter-tags', 'data-table', 'empty-state', 'settings-section'],
|
|
1620
|
+
classes: ['sf-skip-link', 'sf-theme-light', 'sf-app-shell', 'sf-app-sidebar', 'sf-app-sidebar__brand', 'sf-app-main', 'sf-toolbar', 'sf-stack', 'sf-stack--tight', 'sf-repel', 'sf-cluster', 'sf-panel-grid', 'sf-metric-grid', 'sf-nav', 'sf-nav__list', 'sf-nav__link', 'sf-icon', 'sf-card', 'sf-surface', 'sf-stat', 'sf-badge', 'sf-avatar', 'sf-chart', 'sf-chart--wide', 'sf-chart__plot', 'sf-chart__svg', 'sf-chart__grid', 'sf-chart__axis', 'sf-chart__area', 'sf-chart__line', 'sf-chart__point', 'sf-chart__legend', 'sf-chart__legend-item', 'sf-chart__swatch', 'sf-meter-list', 'sf-meter-row', 'sf-meter', 'sf-progress', 'sf-skeleton', 'sf-filter-bar', 'sf-data-table', 'sf-status', 'sf-data-list', 'sf-empty-state', 'sf-settings-section', 'sf-detail-panel', 'sf-button', 'sf-button--outline', 'sf-button--icon', 'sf-form', 'sf-field', 'sf-input', 'sf-select', 'sf-check', 'sf-table-wrap', 'sf-font-sans', 'sf-visually-hidden'],
|
|
1367
1621
|
keywords: ['saas', 'dashboard', 'admin', 'panel', 'portal', 'crm', 'analytics', 'app', 'auth', 'login', 'signin', 'sign-in', 'user', 'account', 'metrics', 'pipeline', 'activity', 'customers', 'accounts', 'tasks', 'tickets', 'revenue'],
|
|
1368
1622
|
markup: `<a class="sf-skip-link" href="#dashboard-main">Skip to dashboard</a>
|
|
1369
1623
|
<div class="sf-app-shell sf-theme-light">
|
|
@@ -1460,38 +1714,47 @@ function getPublicRecipes() {
|
|
|
1460
1714
|
<label class="sf-meter-row"><span class="sf-repel"><strong>Accounts</strong><span class="sf-text-muted">82%</span></span><meter class="sf-meter" min="0" max="100" value="82">82%</meter></label>
|
|
1461
1715
|
<label class="sf-meter-row"><span class="sf-repel"><strong>Automation</strong><span class="sf-text-muted">64%</span></span><meter class="sf-meter" min="0" max="100" value="64">64%</meter></label>
|
|
1462
1716
|
<label class="sf-meter-row"><span class="sf-repel"><strong>Support</strong><span class="sf-text-muted">48%</span></span><meter class="sf-meter" min="0" max="100" value="48">48%</meter></label>
|
|
1717
|
+
<label class="sf-meter-row"><span class="sf-repel"><strong>Import job</strong><span class="sf-text-muted">36%</span></span><progress class="sf-progress" max="100" value="36">36%</progress></label>
|
|
1463
1718
|
</div>
|
|
1464
1719
|
</article>
|
|
1465
1720
|
</section>
|
|
1466
1721
|
<section class="sf-metric-grid" id="command">
|
|
1467
1722
|
<article class="sf-card sf-stack"><div class="sf-repel"><h2 class="sf-card__title sf-font-sans">Command centre</h2><span class="sf-badge">Priority</span></div><p class="sf-card__description">Follow-ups, approvals, and missing next actions stay visible.</p></article>
|
|
1468
1723
|
<article class="sf-card sf-stack" id="billing"><h2 class="sf-card__title sf-font-sans">Billing watch</h2><p class="sf-card__description">2 overdue payment follow-ups and 1 renewal due this week.</p></article>
|
|
1469
|
-
<article class="sf-card sf-stack"><h2 class="sf-card__title sf-font-sans">Relationship pulse</h2><p class="sf-card__description">Three customer timelines need a recent touch point.</p></article>
|
|
1724
|
+
<article class="sf-card sf-stack"><h2 class="sf-card__title sf-font-sans">Relationship pulse</h2><p class="sf-card__description">Three customer timelines need a recent touch point.</p><div class="sf-empty-state"><strong>No urgent escalations</strong><span class="sf-text-muted">New priority work will appear here.</span></div></article>
|
|
1470
1725
|
</section>
|
|
1471
1726
|
<section class="sf-panel-grid" id="customers">
|
|
1472
1727
|
<article class="sf-card sf-stack">
|
|
1473
1728
|
<div class="sf-repel"><h2 class="sf-card__title sf-font-sans">Recent activity</h2><a class="sf-link" href="/app/activity">View all</a></div>
|
|
1729
|
+
<form class="sf-filter-bar" role="search" action="/app/activity" method="get">
|
|
1730
|
+
<label class="sf-field"><span class="sf-label">Search</span><input class="sf-input" name="q" type="search" placeholder="Search activity"></label>
|
|
1731
|
+
<label class="sf-field"><span class="sf-label">Status</span><select class="sf-select" name="status"><option>Any status</option><option>Active</option><option>Scheduled</option><option>Complete</option></select></label>
|
|
1732
|
+
<label class="sf-field"><span class="sf-label">From</span><input class="sf-input" name="from" type="date"></label>
|
|
1733
|
+
<button class="sf-button" type="submit">Apply</button>
|
|
1734
|
+
</form>
|
|
1735
|
+
<div class="sf-cluster" aria-label="Active filters"><span class="sf-badge">Status: Active <a class="sf-link" href="/app/activity" aria-label="Remove status filter">Remove</a></span><a class="sf-button sf-button--ghost sf-button--sm" href="/app/activity">Clear filters</a></div>
|
|
1474
1736
|
<div class="sf-table-wrap">
|
|
1475
|
-
<table>
|
|
1737
|
+
<table class="sf-data-table" aria-label="Recent activity">
|
|
1476
1738
|
<thead><tr><th>Item</th><th>Status</th><th>Value</th><th>Date</th></tr></thead>
|
|
1477
1739
|
<tbody>
|
|
1478
|
-
<tr><td>Enterprise onboarding</td><td><span class="sf-
|
|
1479
|
-
<tr><td>Usage review</td><td><span class="sf-
|
|
1480
|
-
<tr><td>Renewal workflow</td><td><span class="sf-
|
|
1740
|
+
<tr><td>Enterprise onboarding</td><td><span class="sf-status" data-tone="success">Active</span></td><td>£7,800</td><td>Today</td></tr>
|
|
1741
|
+
<tr><td>Usage review</td><td><span class="sf-status" data-tone="info">Scheduled</span></td><td>£4,050</td><td>Yesterday</td></tr>
|
|
1742
|
+
<tr><td>Renewal workflow</td><td><span class="sf-status" data-tone="warning">Review</span></td><td>£3,250</td><td>17 May</td></tr>
|
|
1481
1743
|
</tbody>
|
|
1482
1744
|
</table>
|
|
1483
1745
|
</div>
|
|
1484
1746
|
</article>
|
|
1485
|
-
<article class="sf-card sf-stack" id="activity">
|
|
1747
|
+
<article class="sf-card sf-stack sf-detail-panel" id="activity">
|
|
1486
1748
|
<div class="sf-repel"><h2 class="sf-card__title sf-font-sans">Recent customers</h2><a class="sf-link" href="/app/customers">View all</a></div>
|
|
1487
|
-
<
|
|
1488
|
-
|
|
1489
|
-
<li
|
|
1490
|
-
<li
|
|
1749
|
+
<div class="sf-skeleton" aria-hidden="true">Loading customer details</div>
|
|
1750
|
+
<ul class="sf-list-reset sf-data-list">
|
|
1751
|
+
<li><span><strong>Northstar Labs</strong><br><span class="sf-text-muted">Growth workspace</span></span><span class="sf-status" data-tone="success">Healthy</span></li>
|
|
1752
|
+
<li><span><strong>Willow Group</strong><br><span class="sf-text-muted">Operations workspace</span></span><span class="sf-status" data-tone="warning">Needs review</span></li>
|
|
1753
|
+
<li><span><strong>Harbour Systems</strong><br><span class="sf-text-muted">Platform workspace</span></span><span class="sf-status" data-tone="success">Healthy</span></li>
|
|
1491
1754
|
</ul>
|
|
1492
1755
|
</article>
|
|
1493
1756
|
</section>
|
|
1494
|
-
<section class="sf-card sf-form" aria-labelledby="signin-title">
|
|
1757
|
+
<section class="sf-card sf-form sf-settings-section" aria-labelledby="signin-title">
|
|
1495
1758
|
<!-- Example unauthenticated state. Wire this form to your app auth provider. -->
|
|
1496
1759
|
<div class="sf-repel">
|
|
1497
1760
|
<div><p class="sf-kicker">Signed out state</p><h2 class="sf-card__title sf-font-sans" id="signin-title">Sign in to your workspace</h2></div>
|
|
@@ -1684,7 +1947,7 @@ function runTokens() {
|
|
|
1684
1947
|
function runCatalog() {
|
|
1685
1948
|
const catalog = getPublicCatalog()
|
|
1686
1949
|
if (args.includes('--json')) {
|
|
1687
|
-
|
|
1950
|
+
writeFileSync(1, `${JSON.stringify(catalog, null, 2)}\n`)
|
|
1688
1951
|
return
|
|
1689
1952
|
}
|
|
1690
1953
|
|
|
@@ -2038,8 +2301,14 @@ function runRecipe() {
|
|
|
2038
2301
|
|
|
2039
2302
|
function findPattern(value, patterns) {
|
|
2040
2303
|
const normal = value.toLowerCase()
|
|
2304
|
+
const aliases = {
|
|
2305
|
+
form: 'contact-form',
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2041
2308
|
return (
|
|
2309
|
+
patterns.find((pattern) => pattern.id === aliases[normal]) ??
|
|
2042
2310
|
patterns.find((pattern) => pattern.id === normal) ??
|
|
2311
|
+
patterns.find((pattern) => pattern.id.endsWith(`-${normal}`)) ??
|
|
2043
2312
|
patterns.find((pattern) => pattern.keywords.some((keyword) => keyword === normal)) ??
|
|
2044
2313
|
patterns.find((pattern) => pattern.name.toLowerCase().includes(normal))
|
|
2045
2314
|
)
|
|
@@ -2549,7 +2818,9 @@ const semanticColours = {
|
|
|
2549
2818
|
destructive: 'var(--color-destructive)',
|
|
2550
2819
|
'destructive-foreground': 'var(--color-destructive-foreground)',
|
|
2551
2820
|
border: 'var(--color-border)',
|
|
2821
|
+
'border-subtle': 'var(--color-border-subtle)',
|
|
2552
2822
|
input: 'var(--color-input)',
|
|
2823
|
+
backdrop: 'var(--color-backdrop)',
|
|
2553
2824
|
ring: 'var(--color-ring)',
|
|
2554
2825
|
heading: 'var(--color-heading)',
|
|
2555
2826
|
'page-text': 'var(--color-page-text)',
|
|
@@ -2558,7 +2829,15 @@ const semanticColours = {
|
|
|
2558
2829
|
subdued: 'var(--color-subdued)',
|
|
2559
2830
|
surface: 'var(--color-surface)',
|
|
2560
2831
|
'surface-alt': 'var(--color-surface-alt)',
|
|
2832
|
+
'surface-hover': 'var(--color-surface-hover)',
|
|
2833
|
+
'surface-raised-2': 'var(--color-surface-raised-2)',
|
|
2561
2834
|
'hero-bg': 'var(--color-hero-bg)',
|
|
2835
|
+
'categorical-1': 'var(--color-categorical-1)',
|
|
2836
|
+
'categorical-2': 'var(--color-categorical-2)',
|
|
2837
|
+
'categorical-3': 'var(--color-categorical-3)',
|
|
2838
|
+
'categorical-4': 'var(--color-categorical-4)',
|
|
2839
|
+
'categorical-5': 'var(--color-categorical-5)',
|
|
2840
|
+
'categorical-6': 'var(--color-categorical-6)',
|
|
2562
2841
|
green: 'var(--color-green)',
|
|
2563
2842
|
'green-dark': 'var(--color-green-dark)',
|
|
2564
2843
|
'green-muted': 'var(--color-green-muted)',
|
|
@@ -3664,6 +3943,7 @@ function buildTokensCss() {
|
|
|
3664
3943
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
3665
3944
|
|
|
3666
3945
|
${steps.join('\n')}
|
|
3946
|
+
--type-caption-sm: var(--step--2);
|
|
3667
3947
|
|
|
3668
3948
|
${spaces.join('\n')}
|
|
3669
3949
|
${pairs.join('\n')}
|
|
@@ -3714,7 +3994,9 @@ ${pairs.join('\n')}
|
|
|
3714
3994
|
--color-destructive: var(--sf-colour-destructive, ${colours.red600});
|
|
3715
3995
|
--color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
|
|
3716
3996
|
--color-border: var(--sf-colour-border, oklch(100% 0 0 / 0.12));
|
|
3997
|
+
--color-border-subtle: var(--sf-colour-border-subtle, oklch(100% 0 0 / 0.08));
|
|
3717
3998
|
--color-input: var(--sf-colour-input, oklch(100% 0 0 / 0.12));
|
|
3999
|
+
--color-backdrop: var(--sf-colour-backdrop, oklch(0% 0 0 / 0.56));
|
|
3718
4000
|
--color-ring: var(--sf-colour-ring, ${colours.orange600});
|
|
3719
4001
|
--color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
|
|
3720
4002
|
--color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
|
|
@@ -3723,6 +4005,14 @@ ${pairs.join('\n')}
|
|
|
3723
4005
|
--color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
|
|
3724
4006
|
--color-surface: var(--sf-colour-surface, ${colours.surfaceAlt});
|
|
3725
4007
|
--color-surface-alt: var(--sf-colour-surface-alt, ${colours.paperAlt});
|
|
4008
|
+
--color-surface-hover: var(--sf-colour-surface-hover, color-mix(in oklch, var(--color-surface-alt) 86%, var(--color-primary)));
|
|
4009
|
+
--color-surface-raised-2: var(--sf-colour-surface-raised-2, color-mix(in oklch, var(--color-surface) 86%, white));
|
|
4010
|
+
--color-categorical-1: var(--sf-colour-categorical-1, var(--color-primary));
|
|
4011
|
+
--color-categorical-2: var(--sf-colour-categorical-2, var(--color-green));
|
|
4012
|
+
--color-categorical-3: var(--sf-colour-categorical-3, var(--color-deco-blue));
|
|
4013
|
+
--color-categorical-4: var(--sf-colour-categorical-4, var(--color-deco-lime));
|
|
4014
|
+
--color-categorical-5: var(--sf-colour-categorical-5, var(--color-warm-icon));
|
|
4015
|
+
--color-categorical-6: var(--sf-colour-categorical-6, var(--color-purple-icon));
|
|
3726
4016
|
--color-hero-bg: oklch(8% 0.02 260);
|
|
3727
4017
|
--color-green: var(--sf-colour-green, ${colours.green600});
|
|
3728
4018
|
--color-green-dark: var(--sf-colour-green-dark, ${colours.green700});
|
|
@@ -3916,11 +4206,47 @@ function buildLayoutCss() {
|
|
|
3916
4206
|
}
|
|
3917
4207
|
|
|
3918
4208
|
.sf-app-shell {
|
|
4209
|
+
--sf-app-header-height: 4rem;
|
|
4210
|
+
--sf-app-sidebar-collapsed-width: 4.75rem;
|
|
3919
4211
|
background: var(--color-background);
|
|
3920
4212
|
color: var(--color-foreground);
|
|
3921
4213
|
display: grid;
|
|
3922
4214
|
grid-template-columns: minmax(14rem, var(--sf-app-sidebar-width, 16rem)) minmax(0, 1fr);
|
|
3923
4215
|
min-block-size: 100dvh;
|
|
4216
|
+
position: relative;
|
|
4217
|
+
transition: grid-template-columns var(--duration-normal) var(--ease-standard);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
.sf-app-header {
|
|
4221
|
+
align-items: center;
|
|
4222
|
+
background: var(--color-card);
|
|
4223
|
+
border-block-end: 1px solid var(--color-border);
|
|
4224
|
+
display: flex;
|
|
4225
|
+
gap: var(--space-s);
|
|
4226
|
+
grid-column: 1 / -1;
|
|
4227
|
+
justify-content: space-between;
|
|
4228
|
+
min-block-size: var(--sf-app-header-height);
|
|
4229
|
+
padding-inline: var(--space-s-m);
|
|
4230
|
+
z-index: 20;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
.sf-app-shell[data-layout~="fixed-header"] {
|
|
4234
|
+
grid-template-rows: var(--sf-app-header-height) minmax(0, 1fr);
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
.sf-app-shell[data-layout~="fixed-header"] .sf-app-header {
|
|
4238
|
+
inset-block-start: 0;
|
|
4239
|
+
position: sticky;
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
.sf-app-shell[data-layout~="fixed-header"] .sf-app-sidebar {
|
|
4243
|
+
inset-block-start: var(--sf-app-header-height);
|
|
4244
|
+
max-block-size: calc(100dvh - var(--sf-app-header-height));
|
|
4245
|
+
min-block-size: calc(100dvh - var(--sf-app-header-height));
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
.sf-app-shell[data-collapsed="true"] {
|
|
4249
|
+
grid-template-columns: var(--sf-app-sidebar-collapsed-width) minmax(0, 1fr);
|
|
3924
4250
|
}
|
|
3925
4251
|
|
|
3926
4252
|
.sf-app-sidebar {
|
|
@@ -3936,6 +4262,7 @@ function buildLayoutCss() {
|
|
|
3936
4262
|
padding: var(--space-s);
|
|
3937
4263
|
position: sticky;
|
|
3938
4264
|
inset-block-start: 0;
|
|
4265
|
+
transition: transform var(--duration-normal) var(--ease-standard), inline-size var(--duration-normal) var(--ease-standard);
|
|
3939
4266
|
}
|
|
3940
4267
|
|
|
3941
4268
|
.sf-app-sidebar__brand {
|
|
@@ -3988,6 +4315,58 @@ function buildLayoutCss() {
|
|
|
3988
4315
|
padding: var(--sf-app-main-padding, var(--space-m-l));
|
|
3989
4316
|
}
|
|
3990
4317
|
|
|
4318
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar {
|
|
4319
|
+
overflow-x: hidden;
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar__brand,
|
|
4323
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar .sf-nav__link {
|
|
4324
|
+
justify-content: center;
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
.sf-app-shell[data-collapsed="true"] [data-hide-collapsed="true"] {
|
|
4328
|
+
display: none;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
.sf-app-backdrop {
|
|
4332
|
+
display: none;
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
@media (max-width: 48rem) {
|
|
4336
|
+
.sf-app-shell[data-mobile="drawer"] {
|
|
4337
|
+
display: block;
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
.sf-app-shell[data-mobile="drawer"] .sf-app-header {
|
|
4341
|
+
position: sticky;
|
|
4342
|
+
inset-block-start: 0;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
.sf-app-shell[data-mobile="drawer"] .sf-app-sidebar {
|
|
4346
|
+
box-shadow: var(--shadow-xl);
|
|
4347
|
+
inline-size: min(18rem, calc(100vw - var(--space-l)));
|
|
4348
|
+
inset-block: 0;
|
|
4349
|
+
inset-inline-start: 0;
|
|
4350
|
+
max-block-size: none;
|
|
4351
|
+
min-block-size: 100dvh;
|
|
4352
|
+
position: fixed;
|
|
4353
|
+
transform: translateX(-100%);
|
|
4354
|
+
z-index: 30;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
.sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-sidebar {
|
|
4358
|
+
transform: translateX(0);
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
.sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-backdrop {
|
|
4362
|
+
background: var(--color-backdrop);
|
|
4363
|
+
display: block;
|
|
4364
|
+
inset: 0;
|
|
4365
|
+
position: fixed;
|
|
4366
|
+
z-index: 25;
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
|
|
3991
4370
|
.sf-grid {
|
|
3992
4371
|
display: grid;
|
|
3993
4372
|
gap: var(--grid-gutter);
|
|
@@ -4313,11 +4692,268 @@ function buildComponentCss() {
|
|
|
4313
4692
|
overflow: hidden;
|
|
4314
4693
|
}
|
|
4315
4694
|
|
|
4695
|
+
.sf-progress {
|
|
4696
|
+
accent-color: var(--progress-color, var(--color-primary));
|
|
4697
|
+
block-size: var(--progress-size, .625rem);
|
|
4698
|
+
inline-size: 100%;
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
.sf-progress::-webkit-progress-bar {
|
|
4702
|
+
background: var(--color-surface-alt);
|
|
4703
|
+
border-radius: var(--radius-full);
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
.sf-progress::-webkit-progress-value,
|
|
4707
|
+
.sf-progress::-moz-progress-bar {
|
|
4708
|
+
background: var(--progress-color, var(--color-primary));
|
|
4709
|
+
border-radius: var(--radius-full);
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
.sf-skeleton {
|
|
4713
|
+
background: color-mix(in oklch, var(--color-surface-alt) 82%, var(--color-foreground));
|
|
4714
|
+
border-radius: var(--skeleton-radius, var(--radius-md));
|
|
4715
|
+
color: transparent;
|
|
4716
|
+
min-block-size: var(--skeleton-block-size, 1em);
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
4720
|
+
.sf-skeleton {
|
|
4721
|
+
animation: sf-skeleton-pulse 1.4s var(--ease-standard) infinite alternate;
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
@keyframes sf-skeleton-pulse {
|
|
4726
|
+
to { opacity: .62; }
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
.sf-filter-bar {
|
|
4730
|
+
align-items: end;
|
|
4731
|
+
background: var(--color-card);
|
|
4732
|
+
border: 1px solid var(--color-border);
|
|
4733
|
+
border-radius: var(--radius-lg);
|
|
4734
|
+
display: flex;
|
|
4735
|
+
flex-wrap: wrap;
|
|
4736
|
+
gap: var(--space-xs);
|
|
4737
|
+
padding: var(--space-s);
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
.sf-filter-bar > :where(.sf-field, form, label) {
|
|
4741
|
+
flex: 1 1 min(100%, 14rem);
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
.sf-filter-bar > :where(.sf-button, button, a) {
|
|
4745
|
+
flex: 0 0 auto;
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
.sf-data-table {
|
|
4749
|
+
border-collapse: collapse;
|
|
4750
|
+
inline-size: 100%;
|
|
4751
|
+
min-inline-size: var(--data-table-min, 44rem);
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
.sf-data-table :where(th, td) {
|
|
4755
|
+
border-block-end: 1px solid var(--color-border);
|
|
4756
|
+
padding-block: var(--data-table-padding-block, var(--space-xs));
|
|
4757
|
+
padding-inline: var(--data-table-padding-inline, var(--space-s));
|
|
4758
|
+
text-align: start;
|
|
4759
|
+
vertical-align: middle;
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
.sf-data-table :where(th) {
|
|
4763
|
+
background: var(--color-surface-alt);
|
|
4764
|
+
color: var(--color-muted-foreground);
|
|
4765
|
+
font-size: var(--step--1);
|
|
4766
|
+
font-weight: 800;
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
.sf-data-table :where(tbody tr:hover) {
|
|
4770
|
+
background: color-mix(in oklch, var(--color-primary) 6%, transparent);
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
.sf-data-table[data-density="compact"] {
|
|
4774
|
+
--data-table-padding-block: var(--space-2xs);
|
|
4775
|
+
--data-table-padding-inline: var(--space-xs);
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
.sf-data-table :where(th button, .sf-table-sort) {
|
|
4779
|
+
align-items: center;
|
|
4780
|
+
color: inherit;
|
|
4781
|
+
display: inline-flex;
|
|
4782
|
+
font: inherit;
|
|
4783
|
+
font-weight: inherit;
|
|
4784
|
+
gap: var(--space-3xs);
|
|
4785
|
+
padding: 0;
|
|
4786
|
+
}
|
|
4787
|
+
|
|
4788
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort]::after {
|
|
4789
|
+
color: var(--color-muted-foreground);
|
|
4790
|
+
content: "\\2195";
|
|
4791
|
+
font-size: var(--step--2);
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort="ascending"]::after { content: "\\2191"; }
|
|
4795
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort="descending"]::after { content: "\\2193"; }
|
|
4796
|
+
|
|
4797
|
+
.sf-data-table tr[data-clickable="true"] {
|
|
4798
|
+
cursor: pointer;
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
.sf-data-table tr[data-clickable="true"]:focus-within {
|
|
4802
|
+
outline: 0.125rem solid var(--color-ring);
|
|
4803
|
+
outline-offset: -0.125rem;
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
@container (max-width: 38rem) {
|
|
4807
|
+
.sf-table-wrap[data-view="cards"] {
|
|
4808
|
+
border: 0;
|
|
4809
|
+
overflow: visible;
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4812
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table,
|
|
4813
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table :where(thead, tbody, tr, th, td) {
|
|
4814
|
+
display: block;
|
|
4815
|
+
min-inline-size: 0;
|
|
4816
|
+
}
|
|
4817
|
+
|
|
4818
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table thead {
|
|
4819
|
+
display: none;
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table tr {
|
|
4823
|
+
background: var(--color-card);
|
|
4824
|
+
border: 1px solid var(--color-border);
|
|
4825
|
+
border-radius: var(--radius-lg);
|
|
4826
|
+
margin-block-end: var(--space-xs);
|
|
4827
|
+
padding: var(--space-xs);
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table td {
|
|
4831
|
+
align-items: center;
|
|
4832
|
+
border: 0;
|
|
4833
|
+
display: flex;
|
|
4834
|
+
gap: var(--space-s);
|
|
4835
|
+
justify-content: space-between;
|
|
4836
|
+
padding-block: var(--space-2xs);
|
|
4837
|
+
padding-inline: 0;
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4840
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table td::before {
|
|
4841
|
+
color: var(--color-muted-foreground);
|
|
4842
|
+
content: attr(data-label);
|
|
4843
|
+
font-size: var(--step--1);
|
|
4844
|
+
font-weight: 800;
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
.sf-status {
|
|
4849
|
+
align-items: center;
|
|
4850
|
+
background: var(--status-bg, var(--color-surface-alt));
|
|
4851
|
+
border: 1px solid var(--status-border, var(--color-border));
|
|
4852
|
+
border-radius: var(--radius-full);
|
|
4853
|
+
color: var(--status-color, var(--color-foreground));
|
|
4854
|
+
display: inline-flex;
|
|
4855
|
+
font-size: var(--step--1);
|
|
4856
|
+
font-weight: 700;
|
|
4857
|
+
gap: var(--space-3xs);
|
|
4858
|
+
line-height: 1;
|
|
4859
|
+
min-block-size: 1.75rem;
|
|
4860
|
+
padding-inline: .7em;
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
.sf-status[data-size="sm"] {
|
|
4864
|
+
font-size: var(--step--2);
|
|
4865
|
+
min-block-size: 1.35rem;
|
|
4866
|
+
padding-inline: .55em;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
.sf-status::before {
|
|
4870
|
+
background: currentColor;
|
|
4871
|
+
block-size: .42em;
|
|
4872
|
+
border-radius: var(--radius-full);
|
|
4873
|
+
content: "";
|
|
4874
|
+
inline-size: .42em;
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
.sf-status[data-tone="success"] {
|
|
4878
|
+
--status-bg: var(--sf-colour-success-soft, oklch(62% 0.13 150 / 0.12));
|
|
4879
|
+
--status-border: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 34%, transparent);
|
|
4880
|
+
--status-color: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 76%, var(--color-foreground));
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
.sf-status[data-tone="warning"] {
|
|
4884
|
+
--status-bg: var(--sf-colour-warning-soft, oklch(78% 0.15 82 / 0.16));
|
|
4885
|
+
--status-border: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 38%, transparent);
|
|
4886
|
+
--status-color: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 70%, var(--color-foreground));
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
.sf-status[data-tone="danger"] {
|
|
4890
|
+
--status-bg: var(--sf-colour-danger-soft, oklch(56% 0.2 28 / 0.12));
|
|
4891
|
+
--status-border: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 34%, transparent);
|
|
4892
|
+
--status-color: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 76%, var(--color-foreground));
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
.sf-status[data-tone="info"] {
|
|
4896
|
+
--status-bg: var(--sf-colour-info-soft, oklch(70% 0.12 205 / 0.14));
|
|
4897
|
+
--status-border: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 34%, transparent);
|
|
4898
|
+
--status-color: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 76%, var(--color-foreground));
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
.sf-data-list {
|
|
4902
|
+
display: grid;
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
.sf-data-list > * {
|
|
4906
|
+
align-items: center;
|
|
4907
|
+
border-block-end: 1px solid var(--color-border);
|
|
4908
|
+
display: flex;
|
|
4909
|
+
flex-wrap: wrap;
|
|
4910
|
+
gap: var(--space-xs);
|
|
4911
|
+
justify-content: space-between;
|
|
4912
|
+
padding-block: var(--space-xs);
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
.sf-data-list > :last-child {
|
|
4916
|
+
border-block-end: 0;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
.sf-empty-state {
|
|
4920
|
+
align-items: center;
|
|
4921
|
+
background: var(--color-surface-alt);
|
|
4922
|
+
border: 1px dashed var(--color-border);
|
|
4923
|
+
border-radius: var(--radius-lg);
|
|
4924
|
+
color: var(--color-muted-foreground);
|
|
4925
|
+
display: grid;
|
|
4926
|
+
gap: var(--space-xs);
|
|
4927
|
+
justify-items: center;
|
|
4928
|
+
padding: var(--space-l);
|
|
4929
|
+
text-align: center;
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
.sf-settings-section,
|
|
4933
|
+
.sf-detail-panel {
|
|
4934
|
+
background: var(--color-card);
|
|
4935
|
+
border: 1px solid var(--color-border);
|
|
4936
|
+
border-radius: var(--radius-lg);
|
|
4937
|
+
display: grid;
|
|
4938
|
+
gap: var(--space-s);
|
|
4939
|
+
padding: var(--space-m);
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4942
|
+
.sf-detail-panel {
|
|
4943
|
+
background: var(--color-surface-alt);
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4316
4946
|
.sf-field {
|
|
4317
4947
|
display: grid;
|
|
4318
4948
|
gap: var(--space-2xs);
|
|
4319
4949
|
}
|
|
4320
4950
|
|
|
4951
|
+
.sf-form[data-density="compact"],
|
|
4952
|
+
.sf-field[data-density="compact"] {
|
|
4953
|
+
--input-block-size: 2.25rem;
|
|
4954
|
+
--input-padding-inline: var(--space-xs);
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4321
4957
|
.sf-field > label {
|
|
4322
4958
|
color: var(--color-muted-foreground);
|
|
4323
4959
|
font-size: var(--step--1);
|
|
@@ -4330,7 +4966,41 @@ function buildComponentCss() {
|
|
|
4330
4966
|
border-radius: var(--input-radius, var(--radius-md));
|
|
4331
4967
|
color: var(--color-foreground);
|
|
4332
4968
|
min-block-size: var(--input-block-size, 2.75rem);
|
|
4333
|
-
padding-inline: var(--space-s);
|
|
4969
|
+
padding-inline: var(--input-padding-inline, var(--space-s));
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
.sf-search {
|
|
4973
|
+
position: relative;
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4976
|
+
.sf-search::before {
|
|
4977
|
+
block-size: .75em;
|
|
4978
|
+
border: 0.125em solid currentColor;
|
|
4979
|
+
border-radius: var(--radius-full);
|
|
4980
|
+
color: var(--color-muted-foreground);
|
|
4981
|
+
content: "";
|
|
4982
|
+
inline-size: .75em;
|
|
4983
|
+
inset-block-start: 50%;
|
|
4984
|
+
inset-inline-start: var(--space-xs);
|
|
4985
|
+
line-height: 1;
|
|
4986
|
+
position: absolute;
|
|
4987
|
+
transform: translateY(-58%);
|
|
4988
|
+
}
|
|
4989
|
+
|
|
4990
|
+
.sf-search::after {
|
|
4991
|
+
background: var(--color-muted-foreground);
|
|
4992
|
+
block-size: .125em;
|
|
4993
|
+
content: "";
|
|
4994
|
+
inline-size: .45em;
|
|
4995
|
+
inset-block-start: 50%;
|
|
4996
|
+
inset-inline-start: calc(var(--space-xs) + .58em);
|
|
4997
|
+
position: absolute;
|
|
4998
|
+
transform: translateY(120%) rotate(45deg);
|
|
4999
|
+
transform-origin: inline-start center;
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
.sf-search > .sf-input {
|
|
5003
|
+
padding-inline-start: var(--space-l);
|
|
4334
5004
|
}
|
|
4335
5005
|
|
|
4336
5006
|
.sf-input:focus-visible {
|