@syncedco/flow 0.1.4 → 0.2.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.
@@ -1025,7 +1025,7 @@ function getTokenSummary() {
1025
1025
  themePresets: presetNames.map(kebabThemeName),
1026
1026
  starterClasses: {
1027
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'],
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'],
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: 'filter-toolbar',
1244
+ name: 'Filter toolbar',
1245
+ whenToUse: 'Search, status filters, quick actions, and exports above lists, tables, queues, directories, and admin screens.',
1246
+ classes: ['sf-filter-bar', 'sf-field', 'sf-label', 'sf-input', 'sf-select', 'sf-button', 'sf-button--outline'],
1247
+ requiresJs: false,
1248
+ requiresJsNotes: 'Use native form submit, GET params, or app-owned state. Synced Flow only styles the shell and controls.',
1249
+ a11y: ['Keep every field labelled.', 'Use button type="submit" for applied filters.', 'Announce result count in app markup when filtering changes content dynamically.'],
1250
+ gotchas: ['Do not hide labels without keeping them accessible.', 'Use app logic for debounced search, saved views, and live result counts.'],
1251
+ markup: {
1252
+ html: `<form class="sf-filter-bar" role="search" action="/app/customers" method="get">
1253
+ <label class="sf-field">
1254
+ <span class="sf-label">Search</span>
1255
+ <input class="sf-input" name="q" type="search" placeholder="Search customers">
1256
+ </label>
1257
+ <label class="sf-field">
1258
+ <span class="sf-label">Status</span>
1259
+ <select class="sf-select" name="status"><option>Any status</option><option>Healthy</option><option>Needs review</option></select>
1260
+ </label>
1261
+ <button class="sf-button" type="submit">Apply</button>
1262
+ <a class="sf-button sf-button--outline" href="/app/customers/export">Export</a>
1263
+ </form>`,
1264
+ },
1265
+ keywords: ['saas', 'filter', 'toolbar', 'search', 'status', 'table', 'list', 'admin', 'crm'],
1266
+ },
1267
+ {
1268
+ id: 'date-filter-form',
1269
+ name: 'Native date filter',
1270
+ whenToUse: 'Date ranges, billing periods, export windows, audit logs, appointments, and reporting filters that can use browser-native date/time controls.',
1271
+ classes: ['sf-filter-bar', 'sf-field', 'sf-label', 'sf-input', 'sf-select', 'sf-button', 'sf-button--outline'],
1272
+ requiresJs: false,
1273
+ requiresJsNotes: 'Native date, time, month, week, and datetime-local inputs submit through normal forms. Custom calendar widgets and availability logic belong to the app.',
1274
+ 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.'],
1275
+ gotchas: ['Native picker UI varies by browser and operating system.', 'Use a dedicated calendar library only when the product needs custom scheduling behavior.'],
1276
+ markup: {
1277
+ html: `<form class="sf-filter-bar" action="/app/reports" method="get">
1278
+ <label class="sf-field"><span class="sf-label">From</span><input class="sf-input" name="from" type="date"></label>
1279
+ <label class="sf-field"><span class="sf-label">To</span><input class="sf-input" name="to" type="date"></label>
1280
+ <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>
1281
+ <button class="sf-button" type="submit">Apply</button>
1282
+ <a class="sf-button sf-button--outline" href="/app/reports">Reset</a>
1283
+ </form>`,
1284
+ },
1285
+ keywords: ['saas', 'date', 'time', 'calendar', 'filter', 'range', 'reports', 'billing', 'native'],
1286
+ },
1287
+ {
1288
+ id: 'active-filter-tags',
1289
+ name: 'Active filter tags',
1290
+ whenToUse: 'Visible selected filters below a search/filter toolbar, with clear-all and individual removal actions owned by the app.',
1291
+ classes: ['sf-cluster', 'sf-badge', 'sf-button', 'sf-button--ghost', 'sf-button--sm', 'sf-link'],
1292
+ requiresJs: false,
1293
+ requiresJsNotes: 'Links can remove filters through URL params without JavaScript. Client-side filter state belongs to the consuming app.',
1294
+ 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.'],
1295
+ gotchas: ['Use buttons only when client-side state changes in place.', 'Use links for URL-driven filters so filtered views remain shareable.'],
1296
+ markup: {
1297
+ html: `<div class="sf-cluster" aria-label="Active filters">
1298
+ <span class="sf-badge">Status: Healthy <a class="sf-link" href="?plan=team" aria-label="Remove status filter">Remove</a></span>
1299
+ <span class="sf-badge">Plan: Team <a class="sf-link" href="?status=healthy" aria-label="Remove plan filter">Remove</a></span>
1300
+ <a class="sf-button sf-button--ghost sf-button--sm" href="/app/customers">Clear filters</a>
1301
+ </div>`,
1302
+ },
1303
+ keywords: ['saas', 'filter', 'filters', 'chips', 'tags', 'badge', 'selected', 'active'],
1304
+ },
1305
+ {
1306
+ id: 'data-table',
1307
+ name: 'Data table',
1308
+ whenToUse: 'Account lists, activity logs, invoices, users, queues, directories, and operational tables that should keep native table semantics.',
1309
+ classes: ['sf-table-wrap', 'sf-data-table', 'sf-status', 'sf-avatar', 'sf-link'],
1310
+ requiresJs: false,
1311
+ requiresJsNotes: 'Sorting, pagination state, and row actions belong to the consuming app.',
1312
+ a11y: ['Use a caption or labelled section.', 'Use real table headers.', 'Do not replace table markup with divs for tabular data.'],
1313
+ gotchas: ['Wrap wide tables in sf-table-wrap.', 'Use sf-status for display only; keep actual status text in the element.'],
1314
+ markup: {
1315
+ html: `<div class="sf-table-wrap">
1316
+ <table class="sf-data-table" aria-label="Customers">
1317
+ <thead><tr><th>Customer</th><th>Status</th><th>Plan</th><th>Updated</th></tr></thead>
1318
+ <tbody>
1319
+ <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>
1320
+ <tr><td>Willow Group</td><td><span class="sf-status" data-tone="warning">Needs review</span></td><td>Operations</td><td>Yesterday</td></tr>
1321
+ </tbody>
1322
+ </table>
1323
+ </div>`,
1324
+ },
1325
+ keywords: ['saas', 'table', 'data', 'customers', 'accounts', 'admin', 'crm', 'status', 'list'],
1326
+ },
1327
+ {
1328
+ id: 'empty-state',
1329
+ name: 'Empty state',
1330
+ whenToUse: 'Empty queues, no search results, first-run screens, directories, and dashboards before data is available.',
1331
+ classes: ['sf-empty-state', 'sf-button', 'sf-button--outline', 'sf-text-muted'],
1332
+ requiresJs: false,
1333
+ requiresJsNotes: 'No JavaScript is required unless the consuming app opens a creation flow.',
1334
+ a11y: ['Use a heading that explains the missing state.', 'Keep primary action text specific.', 'Do not rely on illustration-only meaning.'],
1335
+ 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.'],
1336
+ markup: {
1337
+ html: `<div class="sf-empty-state">
1338
+ <h2 class="sf-text-h4">No matching customers</h2>
1339
+ <p class="sf-text-muted">Adjust the filters or add the first customer.</p>
1340
+ <div class="sf-cluster">
1341
+ <a class="sf-button" href="/app/customers/new">Add customer</a>
1342
+ <a class="sf-button sf-button--outline" href="/app/customers">Reset filters</a>
1343
+ </div>
1344
+ </div>`,
1345
+ },
1346
+ keywords: ['saas', 'empty', 'state', 'first-run', 'onboarding', 'no-results', 'zero'],
1347
+ },
1348
+ {
1349
+ id: 'settings-section',
1350
+ name: 'Settings section',
1351
+ whenToUse: 'Account, team, billing, security, notification, integration, profile, and site settings screens.',
1352
+ classes: ['sf-settings-section', 'sf-detail-panel', 'sf-data-list', 'sf-form', 'sf-field', 'sf-input', 'sf-check', 'sf-button'],
1353
+ requiresJs: false,
1354
+ requiresJsNotes: 'Form submission, optimistic saves, and validation belong to the consuming app.',
1355
+ a11y: ['Group related settings under a heading.', 'Use fieldsets when settings are tightly related.', 'Describe destructive settings before the action.'],
1356
+ gotchas: ['Avoid putting unrelated settings in one panel.', 'Use sf-detail-panel for read-only metadata next to editable fields.'],
1357
+ markup: {
1358
+ html: `<section class="sf-settings-section" aria-labelledby="workspace-settings-title">
1359
+ <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>
1360
+ <form class="sf-form" id="workspace-settings">
1361
+ <label class="sf-field"><span class="sf-label">Workspace name</span><input class="sf-input" name="name" value="Product Suite"></label>
1362
+ <label class="sf-check"><input type="checkbox" name="weekly_summary" checked> <span>Email weekly summary</span></label>
1363
+ </form>
1364
+ <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>
1365
+ </section>`,
1366
+ },
1367
+ keywords: ['saas', 'settings', 'section', 'billing', 'team', 'security', 'account', 'detail'],
1368
+ },
1242
1369
  {
1243
1370
  id: 'contact-form',
1244
1371
  name: 'Accessible contact form',
@@ -1362,8 +1489,8 @@ function getPublicRecipes() {
1362
1489
  id: 'saas-dashboard',
1363
1490
  name: 'SaaS dashboard',
1364
1491
  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'],
1492
+ 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'],
1493
+ 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
1494
  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
1495
  markup: `<a class="sf-skip-link" href="#dashboard-main">Skip to dashboard</a>
1369
1496
  <div class="sf-app-shell sf-theme-light">
@@ -1460,38 +1587,47 @@ function getPublicRecipes() {
1460
1587
  <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
1588
  <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
1589
  <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>
1590
+ <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
1591
  </div>
1464
1592
  </article>
1465
1593
  </section>
1466
1594
  <section class="sf-metric-grid" id="command">
1467
1595
  <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
1596
  <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>
1597
+ <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
1598
  </section>
1471
1599
  <section class="sf-panel-grid" id="customers">
1472
1600
  <article class="sf-card sf-stack">
1473
1601
  <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>
1602
+ <form class="sf-filter-bar" role="search" action="/app/activity" method="get">
1603
+ <label class="sf-field"><span class="sf-label">Search</span><input class="sf-input" name="q" type="search" placeholder="Search activity"></label>
1604
+ <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>
1605
+ <label class="sf-field"><span class="sf-label">From</span><input class="sf-input" name="from" type="date"></label>
1606
+ <button class="sf-button" type="submit">Apply</button>
1607
+ </form>
1608
+ <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
1609
  <div class="sf-table-wrap">
1475
- <table>
1610
+ <table class="sf-data-table" aria-label="Recent activity">
1476
1611
  <thead><tr><th>Item</th><th>Status</th><th>Value</th><th>Date</th></tr></thead>
1477
1612
  <tbody>
1478
- <tr><td>Enterprise onboarding</td><td><span class="sf-badge">Active</span></td><td>£7,800</td><td>Today</td></tr>
1479
- <tr><td>Usage review</td><td><span class="sf-badge">Scheduled</span></td><td>£4,050</td><td>Yesterday</td></tr>
1480
- <tr><td>Renewal workflow</td><td><span class="sf-badge">Complete</span></td><td>£3,250</td><td>17 May</td></tr>
1613
+ <tr><td>Enterprise onboarding</td><td><span class="sf-status" data-tone="success">Active</span></td><td>£7,800</td><td>Today</td></tr>
1614
+ <tr><td>Usage review</td><td><span class="sf-status" data-tone="info">Scheduled</span></td><td>£4,050</td><td>Yesterday</td></tr>
1615
+ <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
1616
  </tbody>
1482
1617
  </table>
1483
1618
  </div>
1484
1619
  </article>
1485
- <article class="sf-card sf-stack" id="activity">
1620
+ <article class="sf-card sf-stack sf-detail-panel" id="activity">
1486
1621
  <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
- <ul class="sf-list-reset sf-stack">
1488
- <li class="sf-repel"><span><strong>Northstar Labs</strong><br><span class="sf-text-muted">Growth workspace</span></span><span class="sf-text-muted">Healthy</span></li>
1489
- <li class="sf-repel"><span><strong>Willow Group</strong><br><span class="sf-text-muted">Operations workspace</span></span><span class="sf-text-muted">Needs review</span></li>
1490
- <li class="sf-repel"><span><strong>Harbour Systems</strong><br><span class="sf-text-muted">Platform workspace</span></span><span class="sf-text-muted">Healthy</span></li>
1622
+ <div class="sf-skeleton" aria-hidden="true">Loading customer details</div>
1623
+ <ul class="sf-list-reset sf-data-list">
1624
+ <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>
1625
+ <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>
1626
+ <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
1627
  </ul>
1492
1628
  </article>
1493
1629
  </section>
1494
- <section class="sf-card sf-form" aria-labelledby="signin-title">
1630
+ <section class="sf-card sf-form sf-settings-section" aria-labelledby="signin-title">
1495
1631
  <!-- Example unauthenticated state. Wire this form to your app auth provider. -->
1496
1632
  <div class="sf-repel">
1497
1633
  <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 +1820,7 @@ function runTokens() {
1684
1820
  function runCatalog() {
1685
1821
  const catalog = getPublicCatalog()
1686
1822
  if (args.includes('--json')) {
1687
- console.log(JSON.stringify(catalog, null, 2))
1823
+ writeFileSync(1, `${JSON.stringify(catalog, null, 2)}\n`)
1688
1824
  return
1689
1825
  }
1690
1826
 
@@ -4313,6 +4449,181 @@ function buildComponentCss() {
4313
4449
  overflow: hidden;
4314
4450
  }
4315
4451
 
4452
+ .sf-progress {
4453
+ accent-color: var(--progress-color, var(--color-primary));
4454
+ block-size: var(--progress-size, .625rem);
4455
+ inline-size: 100%;
4456
+ }
4457
+
4458
+ .sf-progress::-webkit-progress-bar {
4459
+ background: var(--color-surface-alt);
4460
+ border-radius: var(--radius-full);
4461
+ }
4462
+
4463
+ .sf-progress::-webkit-progress-value,
4464
+ .sf-progress::-moz-progress-bar {
4465
+ background: var(--progress-color, var(--color-primary));
4466
+ border-radius: var(--radius-full);
4467
+ }
4468
+
4469
+ .sf-skeleton {
4470
+ background: color-mix(in oklch, var(--color-surface-alt) 82%, var(--color-foreground));
4471
+ border-radius: var(--skeleton-radius, var(--radius-md));
4472
+ color: transparent;
4473
+ min-block-size: var(--skeleton-block-size, 1em);
4474
+ }
4475
+
4476
+ @media (prefers-reduced-motion: no-preference) {
4477
+ .sf-skeleton {
4478
+ animation: sf-skeleton-pulse 1.4s var(--ease-standard) infinite alternate;
4479
+ }
4480
+ }
4481
+
4482
+ @keyframes sf-skeleton-pulse {
4483
+ to { opacity: .62; }
4484
+ }
4485
+
4486
+ .sf-filter-bar {
4487
+ align-items: end;
4488
+ background: var(--color-card);
4489
+ border: 1px solid var(--color-border);
4490
+ border-radius: var(--radius-lg);
4491
+ display: flex;
4492
+ flex-wrap: wrap;
4493
+ gap: var(--space-xs);
4494
+ padding: var(--space-s);
4495
+ }
4496
+
4497
+ .sf-filter-bar > :where(.sf-field, form, label) {
4498
+ flex: 1 1 min(100%, 14rem);
4499
+ }
4500
+
4501
+ .sf-filter-bar > :where(.sf-button, button, a) {
4502
+ flex: 0 0 auto;
4503
+ }
4504
+
4505
+ .sf-data-table {
4506
+ border-collapse: collapse;
4507
+ inline-size: 100%;
4508
+ min-inline-size: var(--data-table-min, 44rem);
4509
+ }
4510
+
4511
+ .sf-data-table :where(th, td) {
4512
+ border-block-end: 1px solid var(--color-border);
4513
+ padding-block: var(--data-table-padding-block, var(--space-xs));
4514
+ padding-inline: var(--data-table-padding-inline, var(--space-s));
4515
+ text-align: start;
4516
+ vertical-align: middle;
4517
+ }
4518
+
4519
+ .sf-data-table :where(th) {
4520
+ background: var(--color-surface-alt);
4521
+ color: var(--color-muted-foreground);
4522
+ font-size: var(--step--1);
4523
+ font-weight: 800;
4524
+ }
4525
+
4526
+ .sf-data-table :where(tbody tr:hover) {
4527
+ background: color-mix(in oklch, var(--color-primary) 6%, transparent);
4528
+ }
4529
+
4530
+ .sf-data-table[data-density="compact"] {
4531
+ --data-table-padding-block: var(--space-2xs);
4532
+ --data-table-padding-inline: var(--space-xs);
4533
+ }
4534
+
4535
+ .sf-status {
4536
+ align-items: center;
4537
+ background: var(--status-bg, var(--color-surface-alt));
4538
+ border: 1px solid var(--status-border, var(--color-border));
4539
+ border-radius: var(--radius-full);
4540
+ color: var(--status-color, var(--color-foreground));
4541
+ display: inline-flex;
4542
+ font-size: var(--step--1);
4543
+ font-weight: 700;
4544
+ gap: var(--space-3xs);
4545
+ line-height: 1;
4546
+ min-block-size: 1.75rem;
4547
+ padding-inline: .7em;
4548
+ }
4549
+
4550
+ .sf-status::before {
4551
+ background: currentColor;
4552
+ block-size: .42em;
4553
+ border-radius: var(--radius-full);
4554
+ content: "";
4555
+ inline-size: .42em;
4556
+ }
4557
+
4558
+ .sf-status[data-tone="success"] {
4559
+ --status-bg: var(--sf-colour-success-soft, oklch(62% 0.13 150 / 0.12));
4560
+ --status-border: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 34%, transparent);
4561
+ --status-color: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 76%, var(--color-foreground));
4562
+ }
4563
+
4564
+ .sf-status[data-tone="warning"] {
4565
+ --status-bg: var(--sf-colour-warning-soft, oklch(78% 0.15 82 / 0.16));
4566
+ --status-border: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 38%, transparent);
4567
+ --status-color: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 70%, var(--color-foreground));
4568
+ }
4569
+
4570
+ .sf-status[data-tone="danger"] {
4571
+ --status-bg: var(--sf-colour-danger-soft, oklch(56% 0.2 28 / 0.12));
4572
+ --status-border: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 34%, transparent);
4573
+ --status-color: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 76%, var(--color-foreground));
4574
+ }
4575
+
4576
+ .sf-status[data-tone="info"] {
4577
+ --status-bg: var(--sf-colour-info-soft, oklch(70% 0.12 205 / 0.14));
4578
+ --status-border: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 34%, transparent);
4579
+ --status-color: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 76%, var(--color-foreground));
4580
+ }
4581
+
4582
+ .sf-data-list {
4583
+ display: grid;
4584
+ }
4585
+
4586
+ .sf-data-list > * {
4587
+ align-items: center;
4588
+ border-block-end: 1px solid var(--color-border);
4589
+ display: flex;
4590
+ flex-wrap: wrap;
4591
+ gap: var(--space-xs);
4592
+ justify-content: space-between;
4593
+ padding-block: var(--space-xs);
4594
+ }
4595
+
4596
+ .sf-data-list > :last-child {
4597
+ border-block-end: 0;
4598
+ }
4599
+
4600
+ .sf-empty-state {
4601
+ align-items: center;
4602
+ background: var(--color-surface-alt);
4603
+ border: 1px dashed var(--color-border);
4604
+ border-radius: var(--radius-lg);
4605
+ color: var(--color-muted-foreground);
4606
+ display: grid;
4607
+ gap: var(--space-xs);
4608
+ justify-items: center;
4609
+ padding: var(--space-l);
4610
+ text-align: center;
4611
+ }
4612
+
4613
+ .sf-settings-section,
4614
+ .sf-detail-panel {
4615
+ background: var(--color-card);
4616
+ border: 1px solid var(--color-border);
4617
+ border-radius: var(--radius-lg);
4618
+ display: grid;
4619
+ gap: var(--space-s);
4620
+ padding: var(--space-m);
4621
+ }
4622
+
4623
+ .sf-detail-panel {
4624
+ background: var(--color-surface-alt);
4625
+ }
4626
+
4316
4627
  .sf-field {
4317
4628
  display: grid;
4318
4629
  gap: var(--space-2xs);
package/components.css CHANGED
@@ -802,6 +802,181 @@
802
802
  overflow: hidden;
803
803
  }
804
804
 
805
+ .sf-progress {
806
+ accent-color: var(--sf-progress-colour, var(--sf-colour-primary));
807
+ block-size: var(--sf-progress-size, .625rem);
808
+ inline-size: 100%;
809
+ }
810
+
811
+ .sf-progress::-webkit-progress-bar {
812
+ background: var(--sf-colour-surface-alt);
813
+ border-radius: var(--sf-radius-full);
814
+ }
815
+
816
+ .sf-progress::-webkit-progress-value,
817
+ .sf-progress::-moz-progress-bar {
818
+ background: var(--sf-progress-colour, var(--sf-colour-primary));
819
+ border-radius: var(--sf-radius-full);
820
+ }
821
+
822
+ .sf-skeleton {
823
+ background: color-mix(in oklch, var(--sf-colour-surface-alt) 82%, var(--sf-colour-foreground));
824
+ border-radius: var(--sf-skeleton-radius, var(--sf-radius-md));
825
+ color: transparent;
826
+ min-block-size: var(--sf-skeleton-block-size, 1em);
827
+ }
828
+
829
+ @media (prefers-reduced-motion: no-preference) {
830
+ .sf-skeleton {
831
+ animation: sf-skeleton-pulse 1.4s var(--sf-ease-standard) infinite alternate;
832
+ }
833
+ }
834
+
835
+ @keyframes sf-skeleton-pulse {
836
+ to { opacity: .62; }
837
+ }
838
+
839
+ .sf-filter-bar {
840
+ align-items: end;
841
+ background: var(--sf-colour-surface);
842
+ border: 1px solid var(--sf-colour-border);
843
+ border-radius: var(--sf-radius-panel);
844
+ display: flex;
845
+ flex-wrap: wrap;
846
+ gap: var(--sf-space-xs);
847
+ padding: var(--sf-space-s);
848
+ }
849
+
850
+ .sf-filter-bar > :where(.sf-field, form, label) {
851
+ flex: 1 1 min(100%, 14rem);
852
+ }
853
+
854
+ .sf-filter-bar > :where(.sf-button, button, a) {
855
+ flex: 0 0 auto;
856
+ }
857
+
858
+ .sf-data-table {
859
+ border-collapse: collapse;
860
+ inline-size: 100%;
861
+ min-inline-size: var(--sf-data-table-min, 44rem);
862
+ }
863
+
864
+ .sf-data-table :where(th, td) {
865
+ border-block-end: 1px solid var(--sf-colour-border);
866
+ padding-block: var(--sf-data-table-padding-block, var(--sf-space-xs));
867
+ padding-inline: var(--sf-data-table-padding-inline, var(--sf-space-s));
868
+ text-align: start;
869
+ vertical-align: middle;
870
+ }
871
+
872
+ .sf-data-table :where(th) {
873
+ background: var(--sf-colour-surface-alt);
874
+ color: var(--sf-colour-muted);
875
+ font-size: var(--sf-step--1);
876
+ font-weight: 800;
877
+ }
878
+
879
+ .sf-data-table :where(tbody tr:hover) {
880
+ background: color-mix(in oklch, var(--sf-colour-primary) 6%, transparent);
881
+ }
882
+
883
+ .sf-data-table[data-density="compact"] {
884
+ --sf-data-table-padding-block: var(--sf-space-2xs);
885
+ --sf-data-table-padding-inline: var(--sf-space-xs);
886
+ }
887
+
888
+ .sf-status {
889
+ align-items: center;
890
+ background: var(--sf-status-bg, var(--sf-colour-surface-alt));
891
+ border: 1px solid var(--sf-status-border, var(--sf-colour-border));
892
+ border-radius: var(--sf-radius-full);
893
+ color: var(--sf-status-colour, var(--sf-colour-foreground));
894
+ display: inline-flex;
895
+ font-size: var(--sf-step--1);
896
+ font-weight: 700;
897
+ gap: var(--sf-space-3xs);
898
+ line-height: 1;
899
+ min-block-size: 1.75rem;
900
+ padding-inline: .7em;
901
+ }
902
+
903
+ .sf-status::before {
904
+ background: currentColor;
905
+ block-size: .42em;
906
+ border-radius: var(--sf-radius-full);
907
+ content: "";
908
+ inline-size: .42em;
909
+ }
910
+
911
+ .sf-status[data-tone="success"] {
912
+ --sf-status-bg: var(--sf-colour-success-soft);
913
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
914
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-success) 76%, var(--sf-colour-foreground));
915
+ }
916
+
917
+ .sf-status[data-tone="warning"] {
918
+ --sf-status-bg: var(--sf-colour-warning-soft);
919
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-warning) 38%, transparent);
920
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-warning) 70%, var(--sf-colour-foreground));
921
+ }
922
+
923
+ .sf-status[data-tone="danger"] {
924
+ --sf-status-bg: var(--sf-colour-danger-soft);
925
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-danger) 34%, transparent);
926
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-danger) 76%, var(--sf-colour-foreground));
927
+ }
928
+
929
+ .sf-status[data-tone="info"] {
930
+ --sf-status-bg: var(--sf-colour-info-soft);
931
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-info) 34%, transparent);
932
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-info) 76%, var(--sf-colour-foreground));
933
+ }
934
+
935
+ .sf-data-list {
936
+ display: grid;
937
+ }
938
+
939
+ .sf-data-list > * {
940
+ align-items: center;
941
+ border-block-end: 1px solid var(--sf-colour-border);
942
+ display: flex;
943
+ flex-wrap: wrap;
944
+ gap: var(--sf-space-xs);
945
+ justify-content: space-between;
946
+ padding-block: var(--sf-space-xs);
947
+ }
948
+
949
+ .sf-data-list > :last-child {
950
+ border-block-end: 0;
951
+ }
952
+
953
+ .sf-empty-state {
954
+ align-items: center;
955
+ background: var(--sf-colour-surface-alt);
956
+ border: 1px dashed var(--sf-colour-border-strong);
957
+ border-radius: var(--sf-radius-panel);
958
+ color: var(--sf-colour-muted);
959
+ display: grid;
960
+ gap: var(--sf-space-xs);
961
+ justify-items: center;
962
+ padding: var(--sf-space-l);
963
+ text-align: center;
964
+ }
965
+
966
+ .sf-settings-section,
967
+ .sf-detail-panel {
968
+ background: var(--sf-colour-surface);
969
+ border: 1px solid var(--sf-colour-border);
970
+ border-radius: var(--sf-radius-panel);
971
+ display: grid;
972
+ gap: var(--sf-space-s);
973
+ padding: var(--sf-space-m);
974
+ }
975
+
976
+ .sf-detail-panel {
977
+ background: var(--sf-colour-surface-alt);
978
+ }
979
+
805
980
  .sf-nav {
806
981
  align-items: center;
807
982
  display: flex;
@@ -14,7 +14,7 @@ These are intended for application code and examples.
14
14
  | Tokens | Unlayered `--sf-*` custom properties emitted by `tokens.css` |
15
15
  | Theme config | `theme.fonts`, `theme.colours`, `theme.darkColours`, `theme.radii`, `theme.layout`, `theme.components` |
16
16
  | Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-sidebar`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
17
- | Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
17
+ | Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `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-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
18
18
  | Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` |
19
19
  | Website patterns | `sf-logo-cloud`, `sf-feature`, `sf-stats`, `sf-testimonial`, `sf-pricing-grid`, `sf-price-card`, `sf-faq`, `sf-cta`, `sf-footer` |
20
20
  | Utilities | `sf-prose`, `sf-link`, `sf-link-subtle`, `sf-link-plain`, `sf-list-*`, `sf-push-*`, `sf-focus-ring`, `sf-touch-target`, `sf-skip-link`, `sr-only`, `not-sr-only` |
@@ -128,6 +128,26 @@ Use `sf-nav--mobile` with a popover drawer for mobile navigation. Use
128
128
  </nav>
129
129
  ```
130
130
 
131
+ ## Native Date And Time Inputs
132
+
133
+ Use browser-native inputs for simple date, time, month, week, and datetime
134
+ fields. Synced Flow styles the control shell with `sf-input`; validation,
135
+ range rules, and custom scheduling behavior belong to the app.
136
+
137
+ ```html
138
+ <form class="sf-filter-bar" action="/app/reports" method="get">
139
+ <label class="sf-field">
140
+ <span class="sf-label">From</span>
141
+ <input class="sf-input" name="from" type="date">
142
+ </label>
143
+ <label class="sf-field">
144
+ <span class="sf-label">To</span>
145
+ <input class="sf-input" name="to" type="date">
146
+ </label>
147
+ <button class="sf-button" type="submit">Apply</button>
148
+ </form>
149
+ ```
150
+
131
151
  ## Scroll And Sticky
132
152
 
133
153
  Use CSS scroll snap and sticky positioning for full-page sections and sticky
package/docs/patterns.md CHANGED
@@ -18,6 +18,12 @@ pnpm exec synced-flow pattern scroll-viewport-sections --json
18
18
  | `scroll-viewport-with-spy` | Scroll snap panels with optional IntersectionObserver active-state notes. |
19
19
  | `native-dialog-react` | Native `<dialog>` markup plus React/Next fallback shape. |
20
20
  | `popover-drawer-layout` | Popover-backed drawer or filter panel with `sf-drawer--stack`. |
21
+ | `filter-toolbar` | Search, filters, and actions above app lists and tables. |
22
+ | `date-filter-form` | Native date/time inputs inside filter forms. |
23
+ | `active-filter-tags` | Visible selected filter tags with remove and clear actions. |
24
+ | `data-table` | Native responsive table markup with status pills. |
25
+ | `empty-state` | No-results and first-run states with clear next actions. |
26
+ | `settings-section` | Settings/detail sections for account, team, billing, and security screens. |
21
27
 
22
28
  Pattern JSON includes:
23
29
 
package/docs/recipes.md CHANGED
@@ -160,12 +160,13 @@ The recipe includes:
160
160
  - Generic account initials using `sf-avatar`
161
161
  - Inline SVG and native meter analytics using `sf-chart` and `sf-meter`
162
162
  - traditional SaaS app shell with persistent desktop navigation and a visible stacked mobile navigation rail
163
- - KPI cards, theme-aware toolbar, filter/search form, customer table, and activity list
163
+ - KPI cards, theme-aware toolbar, filter/search form, native date input, active filter tags, data table, status pills, progress, skeleton loading, empty state, and activity list
164
164
 
165
165
  It uses Synced Flow primitives such as `sf-app-shell`, `sf-app-sidebar`,
166
166
  `sf-app-main`, `sf-toolbar`, `sf-metric-grid`, `sf-panel-grid`, `sf-icon`,
167
- `sf-chart`, `sf-chart--wide`, `sf-meter`, `sf-card`, `sf-form`, and
168
- `sf-table-wrap`.
167
+ `sf-chart`, `sf-chart--wide`, `sf-meter`, `sf-progress`, `sf-skeleton`,
168
+ `sf-card`, `sf-form`, `sf-filter-bar`, `sf-data-table`, `sf-status`,
169
+ `sf-data-list`, `sf-empty-state`, `sf-detail-panel`, and `sf-table-wrap`.
169
170
 
170
171
  ## Documentation Page
171
172
 
@@ -70,6 +70,8 @@ These cover the common UI elements needed for a simple site.
70
70
  | `sf-alert`, `sf-alert--info`, `sf-alert--success`, `sf-alert--warning`, `sf-alert--danger`, `sf-alert__title` | Notices and feedback. |
71
71
  | `sf-section-header`, `sf-kicker`, `sf-badge`, `sf-avatar` | Common marketing/content and account patterns. |
72
72
  | `sf-chart`, `sf-chart__plot`, `sf-chart__svg`, `sf-chart__legend`, `sf-meter-list`, `sf-meter` | Lightweight chart shells for app-owned SVG charts and native meter bars. |
73
+ | `sf-progress`, `sf-skeleton` | Native progress bars and CSS-only loading placeholders for async app states. |
74
+ | `sf-filter-bar`, `sf-data-table`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
73
75
 
74
76
  ## Utility Classes
75
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncedco/flow",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "AI-native fluid CSS design system for brand-consistent websites and agency builds.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.2.2",
package/styles.css CHANGED
@@ -1468,6 +1468,181 @@
1468
1468
  overflow: hidden;
1469
1469
  }
1470
1470
 
1471
+ .sf-progress {
1472
+ accent-color: var(--sf-progress-colour, var(--sf-colour-primary));
1473
+ block-size: var(--sf-progress-size, .625rem);
1474
+ inline-size: 100%;
1475
+ }
1476
+
1477
+ .sf-progress::-webkit-progress-bar {
1478
+ background: var(--sf-colour-surface-alt);
1479
+ border-radius: var(--sf-radius-full);
1480
+ }
1481
+
1482
+ .sf-progress::-webkit-progress-value,
1483
+ .sf-progress::-moz-progress-bar {
1484
+ background: var(--sf-progress-colour, var(--sf-colour-primary));
1485
+ border-radius: var(--sf-radius-full);
1486
+ }
1487
+
1488
+ .sf-skeleton {
1489
+ background: color-mix(in oklch, var(--sf-colour-surface-alt) 82%, var(--sf-colour-foreground));
1490
+ border-radius: var(--sf-skeleton-radius, var(--sf-radius-md));
1491
+ color: transparent;
1492
+ min-block-size: var(--sf-skeleton-block-size, 1em);
1493
+ }
1494
+
1495
+ @media (prefers-reduced-motion: no-preference) {
1496
+ .sf-skeleton {
1497
+ animation: sf-skeleton-pulse 1.4s var(--sf-ease-standard) infinite alternate;
1498
+ }
1499
+ }
1500
+
1501
+ @keyframes sf-skeleton-pulse {
1502
+ to { opacity: .62; }
1503
+ }
1504
+
1505
+ .sf-filter-bar {
1506
+ align-items: end;
1507
+ background: var(--sf-colour-surface);
1508
+ border: 1px solid var(--sf-colour-border);
1509
+ border-radius: var(--sf-radius-panel);
1510
+ display: flex;
1511
+ flex-wrap: wrap;
1512
+ gap: var(--sf-space-xs);
1513
+ padding: var(--sf-space-s);
1514
+ }
1515
+
1516
+ .sf-filter-bar > :where(.sf-field, form, label) {
1517
+ flex: 1 1 min(100%, 14rem);
1518
+ }
1519
+
1520
+ .sf-filter-bar > :where(.sf-button, button, a) {
1521
+ flex: 0 0 auto;
1522
+ }
1523
+
1524
+ .sf-data-table {
1525
+ border-collapse: collapse;
1526
+ inline-size: 100%;
1527
+ min-inline-size: var(--sf-data-table-min, 44rem);
1528
+ }
1529
+
1530
+ .sf-data-table :where(th, td) {
1531
+ border-block-end: 1px solid var(--sf-colour-border);
1532
+ padding-block: var(--sf-data-table-padding-block, var(--sf-space-xs));
1533
+ padding-inline: var(--sf-data-table-padding-inline, var(--sf-space-s));
1534
+ text-align: start;
1535
+ vertical-align: middle;
1536
+ }
1537
+
1538
+ .sf-data-table :where(th) {
1539
+ background: var(--sf-colour-surface-alt);
1540
+ color: var(--sf-colour-muted);
1541
+ font-size: var(--sf-step--1);
1542
+ font-weight: 800;
1543
+ }
1544
+
1545
+ .sf-data-table :where(tbody tr:hover) {
1546
+ background: color-mix(in oklch, var(--sf-colour-primary) 6%, transparent);
1547
+ }
1548
+
1549
+ .sf-data-table[data-density="compact"] {
1550
+ --sf-data-table-padding-block: var(--sf-space-2xs);
1551
+ --sf-data-table-padding-inline: var(--sf-space-xs);
1552
+ }
1553
+
1554
+ .sf-status {
1555
+ align-items: center;
1556
+ background: var(--sf-status-bg, var(--sf-colour-surface-alt));
1557
+ border: 1px solid var(--sf-status-border, var(--sf-colour-border));
1558
+ border-radius: var(--sf-radius-full);
1559
+ color: var(--sf-status-colour, var(--sf-colour-foreground));
1560
+ display: inline-flex;
1561
+ font-size: var(--sf-step--1);
1562
+ font-weight: 700;
1563
+ gap: var(--sf-space-3xs);
1564
+ line-height: 1;
1565
+ min-block-size: 1.75rem;
1566
+ padding-inline: .7em;
1567
+ }
1568
+
1569
+ .sf-status::before {
1570
+ background: currentColor;
1571
+ block-size: .42em;
1572
+ border-radius: var(--sf-radius-full);
1573
+ content: "";
1574
+ inline-size: .42em;
1575
+ }
1576
+
1577
+ .sf-status[data-tone="success"] {
1578
+ --sf-status-bg: var(--sf-colour-success-soft);
1579
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
1580
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-success) 76%, var(--sf-colour-foreground));
1581
+ }
1582
+
1583
+ .sf-status[data-tone="warning"] {
1584
+ --sf-status-bg: var(--sf-colour-warning-soft);
1585
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-warning) 38%, transparent);
1586
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-warning) 70%, var(--sf-colour-foreground));
1587
+ }
1588
+
1589
+ .sf-status[data-tone="danger"] {
1590
+ --sf-status-bg: var(--sf-colour-danger-soft);
1591
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-danger) 34%, transparent);
1592
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-danger) 76%, var(--sf-colour-foreground));
1593
+ }
1594
+
1595
+ .sf-status[data-tone="info"] {
1596
+ --sf-status-bg: var(--sf-colour-info-soft);
1597
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-info) 34%, transparent);
1598
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-info) 76%, var(--sf-colour-foreground));
1599
+ }
1600
+
1601
+ .sf-data-list {
1602
+ display: grid;
1603
+ }
1604
+
1605
+ .sf-data-list > * {
1606
+ align-items: center;
1607
+ border-block-end: 1px solid var(--sf-colour-border);
1608
+ display: flex;
1609
+ flex-wrap: wrap;
1610
+ gap: var(--sf-space-xs);
1611
+ justify-content: space-between;
1612
+ padding-block: var(--sf-space-xs);
1613
+ }
1614
+
1615
+ .sf-data-list > :last-child {
1616
+ border-block-end: 0;
1617
+ }
1618
+
1619
+ .sf-empty-state {
1620
+ align-items: center;
1621
+ background: var(--sf-colour-surface-alt);
1622
+ border: 1px dashed var(--sf-colour-border-strong);
1623
+ border-radius: var(--sf-radius-panel);
1624
+ color: var(--sf-colour-muted);
1625
+ display: grid;
1626
+ gap: var(--sf-space-xs);
1627
+ justify-items: center;
1628
+ padding: var(--sf-space-l);
1629
+ text-align: center;
1630
+ }
1631
+
1632
+ .sf-settings-section,
1633
+ .sf-detail-panel {
1634
+ background: var(--sf-colour-surface);
1635
+ border: 1px solid var(--sf-colour-border);
1636
+ border-radius: var(--sf-radius-panel);
1637
+ display: grid;
1638
+ gap: var(--sf-space-s);
1639
+ padding: var(--sf-space-m);
1640
+ }
1641
+
1642
+ .sf-detail-panel {
1643
+ background: var(--sf-colour-surface-alt);
1644
+ }
1645
+
1471
1646
  .sf-nav {
1472
1647
  align-items: center;
1473
1648
  display: flex;