@syncedco/flow 0.2.0 → 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.
@@ -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-progress', 'sf-skeleton', 'sf-filter-bar', 'sf-data-table', '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-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,133 @@ 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
+ },
1242
1369
  {
1243
1370
  id: 'filter-toolbar',
1244
1371
  name: 'Filter toolbar',
@@ -2174,8 +2301,14 @@ function runRecipe() {
2174
2301
 
2175
2302
  function findPattern(value, patterns) {
2176
2303
  const normal = value.toLowerCase()
2304
+ const aliases = {
2305
+ form: 'contact-form',
2306
+ }
2307
+
2177
2308
  return (
2309
+ patterns.find((pattern) => pattern.id === aliases[normal]) ??
2178
2310
  patterns.find((pattern) => pattern.id === normal) ??
2311
+ patterns.find((pattern) => pattern.id.endsWith(`-${normal}`)) ??
2179
2312
  patterns.find((pattern) => pattern.keywords.some((keyword) => keyword === normal)) ??
2180
2313
  patterns.find((pattern) => pattern.name.toLowerCase().includes(normal))
2181
2314
  )
@@ -2685,7 +2818,9 @@ const semanticColours = {
2685
2818
  destructive: 'var(--color-destructive)',
2686
2819
  'destructive-foreground': 'var(--color-destructive-foreground)',
2687
2820
  border: 'var(--color-border)',
2821
+ 'border-subtle': 'var(--color-border-subtle)',
2688
2822
  input: 'var(--color-input)',
2823
+ backdrop: 'var(--color-backdrop)',
2689
2824
  ring: 'var(--color-ring)',
2690
2825
  heading: 'var(--color-heading)',
2691
2826
  'page-text': 'var(--color-page-text)',
@@ -2694,7 +2829,15 @@ const semanticColours = {
2694
2829
  subdued: 'var(--color-subdued)',
2695
2830
  surface: 'var(--color-surface)',
2696
2831
  'surface-alt': 'var(--color-surface-alt)',
2832
+ 'surface-hover': 'var(--color-surface-hover)',
2833
+ 'surface-raised-2': 'var(--color-surface-raised-2)',
2697
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)',
2698
2841
  green: 'var(--color-green)',
2699
2842
  'green-dark': 'var(--color-green-dark)',
2700
2843
  'green-muted': 'var(--color-green-muted)',
@@ -3800,6 +3943,7 @@ function buildTokensCss() {
3800
3943
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
3801
3944
 
3802
3945
  ${steps.join('\n')}
3946
+ --type-caption-sm: var(--step--2);
3803
3947
 
3804
3948
  ${spaces.join('\n')}
3805
3949
  ${pairs.join('\n')}
@@ -3850,7 +3994,9 @@ ${pairs.join('\n')}
3850
3994
  --color-destructive: var(--sf-colour-destructive, ${colours.red600});
3851
3995
  --color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
3852
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));
3853
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));
3854
4000
  --color-ring: var(--sf-colour-ring, ${colours.orange600});
3855
4001
  --color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
3856
4002
  --color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
@@ -3859,6 +4005,14 @@ ${pairs.join('\n')}
3859
4005
  --color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
3860
4006
  --color-surface: var(--sf-colour-surface, ${colours.surfaceAlt});
3861
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));
3862
4016
  --color-hero-bg: oklch(8% 0.02 260);
3863
4017
  --color-green: var(--sf-colour-green, ${colours.green600});
3864
4018
  --color-green-dark: var(--sf-colour-green-dark, ${colours.green700});
@@ -4052,11 +4206,47 @@ function buildLayoutCss() {
4052
4206
  }
4053
4207
 
4054
4208
  .sf-app-shell {
4209
+ --sf-app-header-height: 4rem;
4210
+ --sf-app-sidebar-collapsed-width: 4.75rem;
4055
4211
  background: var(--color-background);
4056
4212
  color: var(--color-foreground);
4057
4213
  display: grid;
4058
4214
  grid-template-columns: minmax(14rem, var(--sf-app-sidebar-width, 16rem)) minmax(0, 1fr);
4059
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);
4060
4250
  }
4061
4251
 
4062
4252
  .sf-app-sidebar {
@@ -4072,6 +4262,7 @@ function buildLayoutCss() {
4072
4262
  padding: var(--space-s);
4073
4263
  position: sticky;
4074
4264
  inset-block-start: 0;
4265
+ transition: transform var(--duration-normal) var(--ease-standard), inline-size var(--duration-normal) var(--ease-standard);
4075
4266
  }
4076
4267
 
4077
4268
  .sf-app-sidebar__brand {
@@ -4124,6 +4315,58 @@ function buildLayoutCss() {
4124
4315
  padding: var(--sf-app-main-padding, var(--space-m-l));
4125
4316
  }
4126
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
+
4127
4370
  .sf-grid {
4128
4371
  display: grid;
4129
4372
  gap: var(--grid-gutter);
@@ -4532,6 +4775,76 @@ function buildComponentCss() {
4532
4775
  --data-table-padding-inline: var(--space-xs);
4533
4776
  }
4534
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
+
4535
4848
  .sf-status {
4536
4849
  align-items: center;
4537
4850
  background: var(--status-bg, var(--color-surface-alt));
@@ -4547,6 +4860,12 @@ function buildComponentCss() {
4547
4860
  padding-inline: .7em;
4548
4861
  }
4549
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
+
4550
4869
  .sf-status::before {
4551
4870
  background: currentColor;
4552
4871
  block-size: .42em;
@@ -4629,6 +4948,12 @@ function buildComponentCss() {
4629
4948
  gap: var(--space-2xs);
4630
4949
  }
4631
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
+
4632
4957
  .sf-field > label {
4633
4958
  color: var(--color-muted-foreground);
4634
4959
  font-size: var(--step--1);
@@ -4641,7 +4966,41 @@ function buildComponentCss() {
4641
4966
  border-radius: var(--input-radius, var(--radius-md));
4642
4967
  color: var(--color-foreground);
4643
4968
  min-block-size: var(--input-block-size, 2.75rem);
4644
- 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);
4645
5004
  }
4646
5005
 
4647
5006
  .sf-input:focus-visible {