@syncedco/flow 0.1.3 → 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.
@@ -491,6 +491,10 @@ ${includeDefaults ? '@import "@syncedco/flow/defaults.css";\n' : ''}@import "./$
491
491
  )
492
492
  }
493
493
 
494
+ if (preset === 'wordpress') {
495
+ writeProjectFile(resolve(targetCwd, 'theme.json'), buildWordPressThemeJson(), force)
496
+ }
497
+
494
498
  if (!noScripts) addPackageScripts(targetCwd)
495
499
  if (agentsTarget !== undefined || args.includes('--agents')) {
496
500
  await installAgentGuidance({
@@ -1021,7 +1025,7 @@ function getTokenSummary() {
1021
1025
  themePresets: presetNames.map(kebabThemeName),
1022
1026
  starterClasses: {
1023
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'],
1024
- 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'],
1025
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'],
1026
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'],
1027
1031
  },
@@ -1235,6 +1239,133 @@ export default function DialogExample() {
1235
1239
  markup: '<article class="sf-container sf-prose sf-prose--blog">...</article>',
1236
1240
  keywords: ['blog', 'article', 'prose', 'legal', 'docs', 'case-study', 'table', 'figure', 'seo'],
1237
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
+ },
1238
1369
  {
1239
1370
  id: 'contact-form',
1240
1371
  name: 'Accessible contact form',
@@ -1358,8 +1489,8 @@ function getPublicRecipes() {
1358
1489
  id: 'saas-dashboard',
1359
1490
  name: 'SaaS dashboard',
1360
1491
  whenToUse: 'Authenticated SaaS apps, admin panels, customer portals, CRMs, analytics dashboards, and product workspaces.',
1361
- sections: ['sticky-navigation', 'mobile-nav-drawer', 'card-grid', 'contact-form', 'long-form-content'],
1362
- 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'],
1363
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'],
1364
1495
  markup: `<a class="sf-skip-link" href="#dashboard-main">Skip to dashboard</a>
1365
1496
  <div class="sf-app-shell sf-theme-light">
@@ -1456,38 +1587,47 @@ function getPublicRecipes() {
1456
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>
1457
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>
1458
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>
1459
1591
  </div>
1460
1592
  </article>
1461
1593
  </section>
1462
1594
  <section class="sf-metric-grid" id="command">
1463
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>
1464
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>
1465
- <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>
1466
1598
  </section>
1467
1599
  <section class="sf-panel-grid" id="customers">
1468
1600
  <article class="sf-card sf-stack">
1469
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>
1470
1609
  <div class="sf-table-wrap">
1471
- <table>
1610
+ <table class="sf-data-table" aria-label="Recent activity">
1472
1611
  <thead><tr><th>Item</th><th>Status</th><th>Value</th><th>Date</th></tr></thead>
1473
1612
  <tbody>
1474
- <tr><td>Enterprise onboarding</td><td><span class="sf-badge">Active</span></td><td>£7,800</td><td>Today</td></tr>
1475
- <tr><td>Usage review</td><td><span class="sf-badge">Scheduled</span></td><td>£4,050</td><td>Yesterday</td></tr>
1476
- <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>
1477
1616
  </tbody>
1478
1617
  </table>
1479
1618
  </div>
1480
1619
  </article>
1481
- <article class="sf-card sf-stack" id="activity">
1620
+ <article class="sf-card sf-stack sf-detail-panel" id="activity">
1482
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>
1483
- <ul class="sf-list-reset sf-stack">
1484
- <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>
1485
- <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>
1486
- <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>
1487
1627
  </ul>
1488
1628
  </article>
1489
1629
  </section>
1490
- <section class="sf-card sf-form" aria-labelledby="signin-title">
1630
+ <section class="sf-card sf-form sf-settings-section" aria-labelledby="signin-title">
1491
1631
  <!-- Example unauthenticated state. Wire this form to your app auth provider. -->
1492
1632
  <div class="sf-repel">
1493
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>
@@ -1680,7 +1820,7 @@ function runTokens() {
1680
1820
  function runCatalog() {
1681
1821
  const catalog = getPublicCatalog()
1682
1822
  if (args.includes('--json')) {
1683
- console.log(JSON.stringify(catalog, null, 2))
1823
+ writeFileSync(1, `${JSON.stringify(catalog, null, 2)}\n`)
1684
1824
  return
1685
1825
  }
1686
1826
 
@@ -2426,6 +2566,45 @@ function formatStringArray(values) {
2426
2566
  return `[${values.map((value) => `'${value}'`).join(', ')}]`
2427
2567
  }
2428
2568
 
2569
+ function buildWordPressThemeJson() {
2570
+ return `${JSON.stringify({
2571
+ $schema: 'https://schemas.wp.org/trunk/theme.json',
2572
+ version: 3,
2573
+ settings: {
2574
+ appearanceTools: true,
2575
+ color: {
2576
+ palette: [
2577
+ { slug: 'base', name: 'Base', color: 'var(--sf-colour-background)' },
2578
+ { slug: 'foreground', name: 'Foreground', color: 'var(--sf-colour-foreground)' },
2579
+ { slug: 'muted', name: 'Muted', color: 'var(--sf-colour-muted)' },
2580
+ { slug: 'surface', name: 'Surface', color: 'var(--sf-colour-surface)' },
2581
+ { slug: 'surface-alt', name: 'Surface Alt', color: 'var(--sf-colour-surface-alt)' },
2582
+ { slug: 'surface-elevated', name: 'Surface Elevated', color: 'var(--sf-colour-surface-elevated, var(--sf-colour-surface-raised))' },
2583
+ { slug: 'border', name: 'Border', color: 'var(--sf-colour-border)' },
2584
+ { slug: 'primary', name: 'Primary', color: 'var(--sf-colour-primary)' },
2585
+ { slug: 'primary-foreground', name: 'Primary Foreground', color: 'var(--sf-colour-primary-foreground)' },
2586
+ ],
2587
+ },
2588
+ layout: {
2589
+ contentSize: '72rem',
2590
+ wideSize: '90rem',
2591
+ },
2592
+ },
2593
+ styles: {
2594
+ color: {
2595
+ background: 'var(--sf-colour-background)',
2596
+ text: 'var(--sf-colour-foreground)',
2597
+ },
2598
+ spacing: {
2599
+ blockGap: 'var(--space-s-l)',
2600
+ },
2601
+ typography: {
2602
+ fontFamily: 'var(--sf-font-sans)',
2603
+ },
2604
+ },
2605
+ }, null, 2)}\n`
2606
+ }
2607
+
2429
2608
  function projectContains(targetCwd, needle) {
2430
2609
  return allProjectFiles(targetCwd).some((file) => readFileSync(file, 'utf8').includes(needle))
2431
2610
  }
@@ -4270,6 +4449,181 @@ function buildComponentCss() {
4270
4449
  overflow: hidden;
4271
4450
  }
4272
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
+
4273
4627
  .sf-field {
4274
4628
  display: grid;
4275
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` |
@@ -28,6 +28,11 @@ Options:
28
28
  | `--no-scripts` | Do not update `package.json`. |
29
29
  | `--force` | Overwrite init-managed files. |
30
30
 
31
+ The WordPress preset also writes a `theme.json` colour palette that maps
32
+ WordPress editor presets such as `base`, `surface`, `surface-elevated`, and
33
+ `primary` to Synced Flow semantic tokens. Use those presets on blocks instead of
34
+ overriding WordPress `.has-*` preset classes in CSS.
35
+
31
36
  ## agents
32
37
 
33
38
  Install or inspect project-level AI guidance.
@@ -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/docs/wordpress.md CHANGED
@@ -18,6 +18,8 @@ The WordPress preset:
18
18
  - scans PHP, HTML, JS, TS, JSX, TSX, Twig, Vue, Svelte, Astro, and MDX files
19
19
  - enables `includeCore`
20
20
  - writes `assets/css/synced-flow.css`
21
+ - writes a `theme.json` palette that maps WordPress colour presets to Synced
22
+ Flow semantic tokens
21
23
 
22
24
  See [`examples/wordpress`](../examples/wordpress) for a lean block theme style
23
25
  template with `theme.json`, template parts, PHP patterns, enqueue code, and an
@@ -54,6 +56,37 @@ wp_enqueue_style(
54
56
  For plugins, use `plugins_url()` or `plugin_dir_url()` instead of
55
57
  `get_theme_file_uri()`.
56
58
 
59
+ ## WordPress Presets
60
+
61
+ WordPress emits editor preset classes such as `.has-base-background-color`,
62
+ `.has-primary-color`, and `.has-large-font-size` with `!important`. Treat those
63
+ classes as the editor's chosen design value.
64
+
65
+ Do not override `.has-*` preset classes in project CSS. Add or choose a better
66
+ semantic preset instead, then use that preset on the block.
67
+
68
+ The generated `theme.json` maps common WordPress colour presets to Synced Flow
69
+ tokens:
70
+
71
+ ```json
72
+ {
73
+ "slug": "surface-elevated",
74
+ "name": "Surface Elevated",
75
+ "color": "var(--sf-colour-surface-elevated, var(--sf-colour-surface-raised))"
76
+ }
77
+ ```
78
+
79
+ Use those presets for any block that needs an editor-visible design role:
80
+
81
+ - `base` for page backgrounds
82
+ - `surface` and `surface-alt` for standard panels
83
+ - `surface-elevated` for chrome, mastheads, footers, sticky bars, and raised
84
+ regions
85
+ - `primary` and `primary-foreground` for branded actions
86
+
87
+ If a new visual role appears repeatedly, add it to `synced-flow.config.mjs` and
88
+ expose it in `theme.json`; avoid adding `!important` overrides.
89
+
57
90
  ## Dynamic Classes
58
91
 
59
92
  Synced Flow generates utility CSS from complete class tokens. Keep class names
@@ -168,6 +168,8 @@
168
168
  --color-surface: var(--sf-colour-surface);
169
169
  --sf-colour-surface-alt: oklch(96.4% 0.011 80);
170
170
  --color-surface-alt: var(--sf-colour-surface-alt);
171
+ --sf-colour-surface-elevated: var(--sf-colour-surface-raised);
172
+ --color-surface-elevated: var(--sf-colour-surface-elevated);
171
173
  --sf-colour-border: oklch(18% 0.026 250 / 0.12);
172
174
  --color-border: var(--sf-colour-border);
173
175
  --sf-colour-primary: oklch(68% 0.18 44);
@@ -1,5 +1,5 @@
1
- <!-- wp:group {"tagName":"footer","className":"sf-footer"} -->
2
- <footer class="wp-block-group sf-footer">
1
+ <!-- wp:group {"tagName":"footer","backgroundColor":"surface-elevated","className":"sf-footer"} -->
2
+ <footer class="wp-block-group sf-footer has-surface-elevated-background-color has-background">
3
3
  <!-- wp:group {"className":"sf-container sf-repel"} -->
4
4
  <div class="wp-block-group sf-container sf-repel">
5
5
  <!-- wp:paragraph {"className":"sf-text-muted"} -->
@@ -1,5 +1,5 @@
1
- <!-- wp:group {"tagName":"header","className":"sf-section sf-section--compact sf-sticky-top sf-bg-background"} -->
2
- <header class="wp-block-group sf-section sf-section--compact sf-sticky-top sf-bg-background">
1
+ <!-- wp:group {"tagName":"header","backgroundColor":"surface-elevated","className":"sf-section sf-section--compact sf-sticky-top"} -->
2
+ <header class="wp-block-group sf-section sf-section--compact sf-sticky-top has-surface-elevated-background-color has-background">
3
3
  <!-- wp:group {"className":"sf-container sf-nav","layout":{"type":"flex","justifyContent":"space-between"}} -->
4
4
  <div class="wp-block-group sf-container sf-nav">
5
5
  <!-- wp:site-title {"className":"sf-nav__link"} /-->
@@ -3,12 +3,65 @@
3
3
  "version": 3,
4
4
  "settings": {
5
5
  "appearanceTools": true,
6
+ "color": {
7
+ "palette": [
8
+ {
9
+ "slug": "base",
10
+ "name": "Base",
11
+ "color": "var(--sf-colour-background)"
12
+ },
13
+ {
14
+ "slug": "foreground",
15
+ "name": "Foreground",
16
+ "color": "var(--sf-colour-foreground)"
17
+ },
18
+ {
19
+ "slug": "muted",
20
+ "name": "Muted",
21
+ "color": "var(--sf-colour-muted)"
22
+ },
23
+ {
24
+ "slug": "surface",
25
+ "name": "Surface",
26
+ "color": "var(--sf-colour-surface)"
27
+ },
28
+ {
29
+ "slug": "surface-alt",
30
+ "name": "Surface Alt",
31
+ "color": "var(--sf-colour-surface-alt)"
32
+ },
33
+ {
34
+ "slug": "surface-elevated",
35
+ "name": "Surface Elevated",
36
+ "color": "var(--sf-colour-surface-elevated, var(--sf-colour-surface-raised))"
37
+ },
38
+ {
39
+ "slug": "border",
40
+ "name": "Border",
41
+ "color": "var(--sf-colour-border)"
42
+ },
43
+ {
44
+ "slug": "primary",
45
+ "name": "Primary",
46
+ "color": "var(--sf-colour-primary)"
47
+ },
48
+ {
49
+ "slug": "primary-foreground",
50
+ "name": "Primary Foreground",
51
+ "color": "var(--sf-colour-primary-foreground)"
52
+ }
53
+ ]
54
+ },
6
55
  "layout": {
7
56
  "contentSize": "72rem",
8
57
  "wideSize": "90rem"
9
58
  }
10
59
  },
11
60
  "styles": {
61
+ "color": {
62
+ "background": "var(--sf-colour-background)",
63
+ "text": "var(--sf-colour-foreground)"
64
+ },
12
65
  "spacing": {
13
66
  "blockGap": "var(--space-s-l)"
14
67
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncedco/flow",
3
- "version": "0.1.3",
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/src/presets.mjs CHANGED
@@ -10,6 +10,7 @@ export const themePresets = {
10
10
  muted: 'oklch(45% 0.018 236)',
11
11
  surface: 'oklch(100% 0 0)',
12
12
  surfaceAlt: 'oklch(96.4% 0.011 80)',
13
+ surfaceElevated: 'var(--sf-colour-surface-raised)',
13
14
  border: 'oklch(18% 0.026 250 / 0.12)',
14
15
  primary: 'oklch(68% 0.18 44)',
15
16
  primaryHover: 'oklch(60% 0.17 42)',
@@ -46,6 +47,7 @@ export const themePresets = {
46
47
  muted: 'oklch(44.6% 0.043 257)',
47
48
  surface: 'oklch(100% 0 0)',
48
49
  surfaceAlt: 'oklch(96.8% 0.007 247)',
50
+ surfaceElevated: 'var(--sf-colour-surface-raised)',
49
51
  border: 'oklch(20.8% 0.042 265 / 0.12)',
50
52
  primary: 'oklch(52% 0.14 250)',
51
53
  primaryHover: 'oklch(44% 0.13 250)',
@@ -84,6 +86,7 @@ export const themePresets = {
84
86
  muted: 'oklch(43% 0.02 65)',
85
87
  surface: 'oklch(100% 0 0)',
86
88
  surfaceAlt: 'oklch(94% 0.02 80)',
89
+ surfaceElevated: 'var(--sf-colour-surface-raised)',
87
90
  border: 'oklch(18% 0.022 55 / 0.14)',
88
91
  primary: 'oklch(58% 0.17 42)',
89
92
  primaryHover: 'oklch(50% 0.16 40)',
@@ -122,6 +125,7 @@ export const themePresets = {
122
125
  muted: 'oklch(78% 0.012 86)',
123
126
  surface: 'oklch(18% 0.026 250)',
124
127
  surfaceAlt: 'oklch(22% 0.024 248)',
128
+ surfaceElevated: 'var(--sf-colour-surface-raised)',
125
129
  border: 'oklch(100% 0 0 / 0.14)',
126
130
  primary: 'oklch(70% 0.12 205)',
127
131
  primaryHover: 'oklch(76% 0.12 205)',
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;