@syncedco/flow 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/components.css CHANGED
@@ -802,6 +802,257 @@
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-data-table :where(th button, .sf-table-sort) {
889
+ align-items: center;
890
+ color: inherit;
891
+ display: inline-flex;
892
+ font: inherit;
893
+ font-weight: inherit;
894
+ gap: var(--sf-space-3xs);
895
+ padding: 0;
896
+ }
897
+
898
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort]::after {
899
+ color: var(--sf-colour-subtle);
900
+ content: "\2195";
901
+ font-size: var(--sf-step--2);
902
+ }
903
+
904
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort="ascending"]::after { content: "\2191"; }
905
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort="descending"]::after { content: "\2193"; }
906
+
907
+ .sf-data-table tr[data-clickable="true"] {
908
+ cursor: pointer;
909
+ }
910
+
911
+ .sf-data-table tr[data-clickable="true"]:focus-within {
912
+ outline: 0.125rem solid var(--sf-colour-ring);
913
+ outline-offset: -0.125rem;
914
+ }
915
+
916
+ @container (max-width: 38rem) {
917
+ .sf-table-wrap[data-view="cards"] {
918
+ border: 0;
919
+ overflow: visible;
920
+ }
921
+
922
+ .sf-table-wrap[data-view="cards"] .sf-data-table,
923
+ .sf-table-wrap[data-view="cards"] .sf-data-table :where(thead, tbody, tr, th, td) {
924
+ display: block;
925
+ min-inline-size: 0;
926
+ }
927
+
928
+ .sf-table-wrap[data-view="cards"] .sf-data-table thead {
929
+ display: none;
930
+ }
931
+
932
+ .sf-table-wrap[data-view="cards"] .sf-data-table tr {
933
+ background: var(--sf-colour-surface);
934
+ border: 1px solid var(--sf-colour-border);
935
+ border-radius: var(--sf-radius-panel);
936
+ margin-block-end: var(--sf-space-xs);
937
+ padding: var(--sf-space-xs);
938
+ }
939
+
940
+ .sf-table-wrap[data-view="cards"] .sf-data-table td {
941
+ align-items: center;
942
+ border: 0;
943
+ display: flex;
944
+ gap: var(--sf-space-s);
945
+ justify-content: space-between;
946
+ padding-block: var(--sf-space-2xs);
947
+ padding-inline: 0;
948
+ }
949
+
950
+ .sf-table-wrap[data-view="cards"] .sf-data-table td::before {
951
+ color: var(--sf-colour-muted);
952
+ content: attr(data-label);
953
+ font-size: var(--sf-step--1);
954
+ font-weight: 800;
955
+ }
956
+ }
957
+
958
+ .sf-status {
959
+ align-items: center;
960
+ background: var(--sf-status-bg, var(--sf-colour-surface-alt));
961
+ border: 1px solid var(--sf-status-border, var(--sf-colour-border));
962
+ border-radius: var(--sf-radius-full);
963
+ color: var(--sf-status-colour, var(--sf-colour-foreground));
964
+ display: inline-flex;
965
+ font-size: var(--sf-step--1);
966
+ font-weight: 700;
967
+ gap: var(--sf-space-3xs);
968
+ line-height: 1;
969
+ min-block-size: 1.75rem;
970
+ padding-inline: .7em;
971
+ }
972
+
973
+ .sf-status[data-size="sm"] {
974
+ font-size: var(--sf-step--2);
975
+ min-block-size: 1.35rem;
976
+ padding-inline: .55em;
977
+ }
978
+
979
+ .sf-status::before {
980
+ background: currentColor;
981
+ block-size: .42em;
982
+ border-radius: var(--sf-radius-full);
983
+ content: "";
984
+ inline-size: .42em;
985
+ }
986
+
987
+ .sf-status[data-tone="success"] {
988
+ --sf-status-bg: var(--sf-colour-success-soft);
989
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
990
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-success) 76%, var(--sf-colour-foreground));
991
+ }
992
+
993
+ .sf-status[data-tone="warning"] {
994
+ --sf-status-bg: var(--sf-colour-warning-soft);
995
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-warning) 38%, transparent);
996
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-warning) 70%, var(--sf-colour-foreground));
997
+ }
998
+
999
+ .sf-status[data-tone="danger"] {
1000
+ --sf-status-bg: var(--sf-colour-danger-soft);
1001
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-danger) 34%, transparent);
1002
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-danger) 76%, var(--sf-colour-foreground));
1003
+ }
1004
+
1005
+ .sf-status[data-tone="info"] {
1006
+ --sf-status-bg: var(--sf-colour-info-soft);
1007
+ --sf-status-border: color-mix(in oklch, var(--sf-colour-info) 34%, transparent);
1008
+ --sf-status-colour: color-mix(in oklch, var(--sf-colour-info) 76%, var(--sf-colour-foreground));
1009
+ }
1010
+
1011
+ .sf-data-list {
1012
+ display: grid;
1013
+ }
1014
+
1015
+ .sf-data-list > * {
1016
+ align-items: center;
1017
+ border-block-end: 1px solid var(--sf-colour-border);
1018
+ display: flex;
1019
+ flex-wrap: wrap;
1020
+ gap: var(--sf-space-xs);
1021
+ justify-content: space-between;
1022
+ padding-block: var(--sf-space-xs);
1023
+ }
1024
+
1025
+ .sf-data-list > :last-child {
1026
+ border-block-end: 0;
1027
+ }
1028
+
1029
+ .sf-empty-state {
1030
+ align-items: center;
1031
+ background: var(--sf-colour-surface-alt);
1032
+ border: 1px dashed var(--sf-colour-border-strong);
1033
+ border-radius: var(--sf-radius-panel);
1034
+ color: var(--sf-colour-muted);
1035
+ display: grid;
1036
+ gap: var(--sf-space-xs);
1037
+ justify-items: center;
1038
+ padding: var(--sf-space-l);
1039
+ text-align: center;
1040
+ }
1041
+
1042
+ .sf-settings-section,
1043
+ .sf-detail-panel {
1044
+ background: var(--sf-colour-surface);
1045
+ border: 1px solid var(--sf-colour-border);
1046
+ border-radius: var(--sf-radius-panel);
1047
+ display: grid;
1048
+ gap: var(--sf-space-s);
1049
+ padding: var(--sf-space-m);
1050
+ }
1051
+
1052
+ .sf-detail-panel {
1053
+ background: var(--sf-colour-surface-alt);
1054
+ }
1055
+
805
1056
  .sf-nav {
806
1057
  align-items: center;
807
1058
  display: flex;
@@ -880,6 +1131,13 @@
880
1131
  gap: var(--sf-form-gap, var(--sf-space-s-m));
881
1132
  }
882
1133
 
1134
+ .sf-form[data-density="compact"],
1135
+ .sf-field[data-density="compact"] {
1136
+ --sf-form-gap: var(--sf-space-xs);
1137
+ --sf-input-block-size: 2.25rem;
1138
+ --sf-input-padding-inline: var(--sf-space-xs);
1139
+ }
1140
+
883
1141
  .sf-fieldset {
884
1142
  border: 1px solid var(--sf-colour-border);
885
1143
  border-radius: var(--sf-radius-panel);
@@ -945,6 +1203,45 @@
945
1203
  transition: background-color var(--sf-duration-fast) var(--sf-ease-standard), border-color var(--sf-duration-fast) var(--sf-ease-standard), box-shadow var(--sf-duration-fast) var(--sf-ease-standard);
946
1204
  }
947
1205
 
1206
+ .sf-search {
1207
+ position: relative;
1208
+ }
1209
+
1210
+ .sf-search::before {
1211
+ block-size: .75em;
1212
+ border: 0.125em solid currentColor;
1213
+ border-radius: var(--sf-radius-full);
1214
+ color: var(--sf-colour-subtle);
1215
+ content: "";
1216
+ inline-size: .75em;
1217
+ inset-block-start: 50%;
1218
+ inset-inline-start: var(--sf-space-xs);
1219
+ line-height: 1;
1220
+ position: absolute;
1221
+ transform: translateY(-58%);
1222
+ }
1223
+
1224
+ .sf-search::after {
1225
+ background: var(--sf-colour-subtle);
1226
+ block-size: .125em;
1227
+ content: "";
1228
+ inline-size: .45em;
1229
+ inset-block-start: 50%;
1230
+ inset-inline-start: calc(var(--sf-space-xs) + .58em);
1231
+ position: absolute;
1232
+ transform: translateY(120%) rotate(45deg);
1233
+ transform-origin: inline-start center;
1234
+ }
1235
+
1236
+ .sf-search > .sf-input {
1237
+ padding-inline-start: var(--sf-space-l);
1238
+ }
1239
+
1240
+ .sf-help[data-density="compact"],
1241
+ .sf-error[data-density="compact"] {
1242
+ font-size: var(--sf-step--2);
1243
+ }
1244
+
948
1245
  .sf-textarea {
949
1246
  min-block-size: var(--sf-textarea-block-size, 8rem);
950
1247
  padding-block: var(--sf-space-xs);
@@ -1065,7 +1362,7 @@
1065
1362
  .sf-dialog::backdrop,
1066
1363
  .sf-popover::backdrop,
1067
1364
  .sf-drawer::backdrop {
1068
- background: oklch(0% 0 0 / 0.48);
1365
+ background: var(--sf-colour-backdrop);
1069
1366
  }
1070
1367
 
1071
1368
  .sf-dialog__header,
@@ -1091,6 +1388,11 @@
1091
1388
  justify-content: flex-end;
1092
1389
  }
1093
1390
 
1391
+ .sf-dialog[data-layout="form"] .sf-dialog__body {
1392
+ display: grid;
1393
+ gap: var(--sf-space-s);
1394
+ }
1395
+
1094
1396
  .sf-popover,
1095
1397
  .sf-menu-popover,
1096
1398
  .sf-tooltip,
@@ -1144,6 +1446,16 @@
1144
1446
  background: var(--sf-colour-surface-alt);
1145
1447
  }
1146
1448
 
1449
+ .sf-menu :where(a, button)[data-variant="danger"],
1450
+ .sf-menu-popover :where(a, button)[data-variant="danger"] {
1451
+ color: var(--sf-colour-danger);
1452
+ }
1453
+
1454
+ .sf-menu :where(a, button)[data-variant="danger"]:hover,
1455
+ .sf-menu-popover :where(a, button)[data-variant="danger"]:hover {
1456
+ background: var(--sf-colour-danger-soft);
1457
+ }
1458
+
1147
1459
  .sf-tooltip {
1148
1460
  border-radius: var(--sf-radius-md);
1149
1461
  box-shadow: var(--sf-shadow-md);
@@ -1170,6 +1482,16 @@
1170
1482
  top: var(--sf-space-s);
1171
1483
  }
1172
1484
 
1485
+ .sf-toast-stack {
1486
+ display: grid;
1487
+ gap: var(--sf-space-xs);
1488
+ inline-size: min(100% - (var(--sf-gutter) * 2), var(--sf-toast-width, 24rem));
1489
+ inset-block-start: var(--sf-space-s);
1490
+ inset-inline-end: var(--sf-space-s);
1491
+ position: fixed;
1492
+ z-index: 50;
1493
+ }
1494
+
1173
1495
  .sf-banner {
1174
1496
  block-size: auto;
1175
1497
  border-radius: var(--sf-radius-lg);
@@ -1283,14 +1605,21 @@
1283
1605
 
1284
1606
  .sf-tab {
1285
1607
  border-block-end: 0.125rem solid transparent;
1608
+ border-radius: var(--sf-radius-full);
1286
1609
  color: var(--sf-colour-muted);
1287
1610
  display: inline-flex;
1611
+ gap: var(--sf-space-2xs);
1288
1612
  font-weight: 800;
1289
1613
  padding-block: var(--sf-space-xs);
1290
1614
  padding-inline: var(--sf-space-s);
1291
1615
  text-decoration: none;
1292
1616
  }
1293
1617
 
1618
+ .sf-tab[data-variant="pill"] {
1619
+ border: 1px solid transparent;
1620
+ padding-block: var(--sf-space-2xs);
1621
+ }
1622
+
1294
1623
  .sf-tab:hover,
1295
1624
  .sf-tab[aria-selected="true"],
1296
1625
  .sf-tab[data-state="active"] {
@@ -1298,6 +1627,24 @@
1298
1627
  color: var(--sf-colour-foreground);
1299
1628
  }
1300
1629
 
1630
+ .sf-tab[data-variant="pill"]:hover,
1631
+ .sf-tab[data-variant="pill"][aria-selected="true"],
1632
+ .sf-tab[data-variant="pill"][data-state="active"] {
1633
+ background: var(--sf-colour-primary-soft);
1634
+ border-color: var(--sf-colour-primary-soft-border);
1635
+ color: var(--sf-colour-primary);
1636
+ }
1637
+
1638
+ .sf-tab__count {
1639
+ background: var(--sf-colour-surface-alt);
1640
+ border: 1px solid var(--sf-colour-border);
1641
+ border-radius: var(--sf-radius-full);
1642
+ color: var(--sf-colour-muted);
1643
+ font-size: var(--sf-step--2);
1644
+ line-height: 1;
1645
+ padding: .25em .55em;
1646
+ }
1647
+
1301
1648
  .sf-tab-panel {
1302
1649
  background: var(--sf-colour-surface);
1303
1650
  border: 1px solid var(--sf-colour-border);
@@ -13,9 +13,9 @@ These are intended for application code and examples.
13
13
  | Imports | `@syncedco/flow/styles.css`, `tokens.css`, `reset.css`, `base.css`, `defaults.css`, `layout.css`, `components.css`, `utilities.css` |
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
- | 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` |
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` |
16
+ | Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-backdrop`, `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-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-search`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-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
+ | Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-tab__count`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-toast-stack` |
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` |
21
21
  | CLI | `synced-flow init`, `agents install`, `agents status`, `skill`, `add defaults`, `build`, `watch`, `lint`, `doctor`, `tokens`, `catalog`, `suggest`, `pattern`, `recipe`, `theme init`, `theme validate` |
@@ -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,19 @@ 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
+ | `app-shell-layout` | Fixed header, sidebar, collapsed state, and mobile drawer app shell. |
22
+ | `row-action-menu` | Native popover row actions with destructive menu item styling. |
23
+ | `tabs-with-counts` | Pill tabs with compact count chips. |
24
+ | `data-table-actions` | Sortable header controls, clickable rows, and responsive card fallback. |
25
+ | `search-field` | Labelled native search input with icon shell and compact density. |
26
+ | `modal-form` | Native dialog layout for form body and footer actions. |
27
+ | `toast-stack` | Fixed app notification stack layout. |
28
+ | `filter-toolbar` | Search, filters, and actions above app lists and tables. |
29
+ | `date-filter-form` | Native date/time inputs inside filter forms. |
30
+ | `active-filter-tags` | Visible selected filter tags with remove and clear actions. |
31
+ | `data-table` | Native responsive table markup with status pills. |
32
+ | `empty-state` | No-results and first-run states with clear next actions. |
33
+ | `settings-section` | Settings/detail sections for account, team, billing, and security screens. |
21
34
 
22
35
  Pattern JSON includes:
23
36
 
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
 
@@ -14,6 +14,8 @@ Use the `--sf-*` variables as the stable CSS foundation.
14
14
  | Space | `--sf-space-s`, `--sf-space-m-l`, `--sf-space-xl-2xl` | Fluid padding, margin, and gaps. |
15
15
  | Radius | `--sf-radius-control`, `--sf-radius-panel`, `--sf-radius-full` | Controls, panels, pills. |
16
16
  | Colour | `--sf-colour-background`, `--sf-colour-surface`, `--sf-colour-primary` | Semantic UI colour roles. |
17
+ | App colour | `--sf-colour-surface-hover`, `--sf-colour-surface-raised-2`, `--sf-colour-border-subtle`, `--sf-colour-backdrop` | App surfaces, hover states, subtle dividers, and overlays. |
18
+ | Categorical colour | `--sf-colour-categorical-1` ... `--sf-colour-categorical-6` | Charts, stages, labels, and recurring data accents. |
17
19
  | State | `--sf-colour-success`, `--sf-colour-warning`, `--sf-colour-danger`, `--sf-colour-info` | Feedback and notices. |
18
20
  | Motion | `--sf-duration-fast`, `--sf-duration-normal`, `--sf-ease-standard` | Consistent transitions. |
19
21
  | Component | `--sf-button-*`, `--sf-card-*`, `--sf-input-*`, `--sf-alert-*` | Reusable component defaults. |
@@ -36,7 +38,7 @@ These classes are the first choice for page structure.
36
38
  | `sf-cluster` | Wrapping inline groups such as actions or tags. |
37
39
  | `sf-repel` | Space-between layout that wraps safely. |
38
40
  | `sf-toolbar` | Dense app/page toolbar that wraps actions safely. |
39
- | `sf-app-shell`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-main` | Product app shell with persistent sidebar and main workspace. |
41
+ | `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-backdrop`, `sf-app-main` | Product app shell with optional fixed header, collapsed sidebar, mobile drawer, and main workspace. |
40
42
  | `sf-split` | Responsive two-column layout. |
41
43
  | `sf-split--reverse` | Reverses split layout visual order. |
42
44
  | `sf-auto-grid`, `sf-panel-grid`, `sf-metric-grid`, `sf-pipeline` | Responsive card, metric, and operational grids. |
@@ -62,14 +64,16 @@ These cover the common UI elements needed for a simple site.
62
64
  | `sf-nav`, `sf-nav__list`, `sf-nav__link` | Navigation basics. |
63
65
  | `sf-nav--mobile`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` | Mobile, menu, breadcrumb, and paginated navigation. |
64
66
  | `sf-dialog`, `sf-dialog__header`, `sf-dialog__body`, `sf-dialog__footer` | Native `<dialog>` styling. |
65
- | `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-banner`, `sf-drawer` | Popover-backed native overlays. |
67
+ | `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-toast-stack`, `sf-banner`, `sf-drawer` | Popover-backed native overlays and app feedback stacks. |
66
68
  | `sf-disclosure`, `sf-accordion` | Native `details`/`summary` disclosure patterns. |
67
- | `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab-panel` | HTML/CSS-first tab styling. |
69
+ | `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab__count`, `sf-tab-panel` | HTML/CSS-first tab styling, including counted pill tabs. |
68
70
  | `sf-form`, `sf-fieldset`, `sf-field`, `sf-label`, `sf-help`, `sf-error` | Form structure and messaging. |
69
- | `sf-input`, `sf-select`, `sf-textarea`, `sf-check` | Form controls. |
71
+ | `sf-input`, `sf-select`, `sf-textarea`, `sf-search`, `sf-check` | Form controls, including search input shell. |
70
72
  | `sf-alert`, `sf-alert--info`, `sf-alert--success`, `sf-alert--warning`, `sf-alert--danger`, `sf-alert__title` | Notices and feedback. |
71
73
  | `sf-section-header`, `sf-kicker`, `sf-badge`, `sf-avatar` | Common marketing/content and account patterns. |
72
74
  | `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. |
75
+ | `sf-progress`, `sf-skeleton` | Native progress bars and CSS-only loading placeholders for async app states. |
76
+ | `sf-filter-bar`, `sf-data-table`, `sf-table-sort`, `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
77
 
74
78
  ## Utility Classes
75
79
 
package/docs/tokens.md CHANGED
@@ -75,6 +75,7 @@ The generated core includes enough semantic variables for common website UI:
75
75
 
76
76
  - surfaces: `--sf-colour-background`, `--sf-colour-surface`,
77
77
  `--sf-colour-surface-alt`, `--sf-colour-surface-raised`,
78
+ `--sf-colour-surface-raised-2`, `--sf-colour-surface-hover`,
78
79
  `--sf-colour-surface-inset`
79
80
  - text and links: `--sf-colour-foreground`, `--sf-colour-muted`,
80
81
  `--sf-colour-subtle`, `--sf-colour-link`, `--sf-colour-link-hover`
@@ -82,8 +83,11 @@ The generated core includes enough semantic variables for common website UI:
82
83
  `--sf-colour-primary-foreground`, `--sf-colour-primary-soft`
83
84
  - state feedback: `--sf-colour-success`, `--sf-colour-warning`,
84
85
  `--sf-colour-danger`, `--sf-colour-info`, plus matching soft variants
85
- - structure: `--sf-colour-border`, `--sf-colour-border-strong`,
86
+ - structure: `--sf-colour-border-subtle`, `--sf-colour-border`,
87
+ `--sf-colour-border-strong`, `--sf-colour-backdrop`,
86
88
  `--sf-radius-control`, `--sf-radius-panel`, `--sf-shadow-*`
89
+ - data accents: `--sf-colour-categorical-1` through
90
+ `--sf-colour-categorical-6`
87
91
 
88
92
  For class-level usage, see [System primitives](system-primitives.md).
89
93
 
package/layout.css CHANGED
@@ -95,11 +95,47 @@
95
95
  }
96
96
 
97
97
  .sf-app-shell {
98
+ --sf-app-header-height: 4rem;
99
+ --sf-app-sidebar-collapsed-width: 4.75rem;
98
100
  background: var(--sf-colour-background);
99
101
  color: var(--sf-colour-foreground);
100
102
  display: grid;
101
103
  grid-template-columns: minmax(14rem, var(--sf-app-sidebar-width, 16rem)) minmax(0, 1fr);
102
104
  min-block-size: 100dvh;
105
+ position: relative;
106
+ transition: grid-template-columns var(--sf-duration-normal) var(--sf-ease-standard);
107
+ }
108
+
109
+ .sf-app-header {
110
+ align-items: center;
111
+ background: var(--sf-colour-surface);
112
+ border-block-end: 1px solid var(--sf-colour-border);
113
+ display: flex;
114
+ gap: var(--sf-space-s);
115
+ grid-column: 1 / -1;
116
+ justify-content: space-between;
117
+ min-block-size: var(--sf-app-header-height);
118
+ padding-inline: var(--sf-space-s-m);
119
+ z-index: 20;
120
+ }
121
+
122
+ .sf-app-shell[data-layout~="fixed-header"] {
123
+ grid-template-rows: var(--sf-app-header-height) minmax(0, 1fr);
124
+ }
125
+
126
+ .sf-app-shell[data-layout~="fixed-header"] .sf-app-header {
127
+ inset-block-start: 0;
128
+ position: sticky;
129
+ }
130
+
131
+ .sf-app-shell[data-layout~="fixed-header"] .sf-app-sidebar {
132
+ inset-block-start: var(--sf-app-header-height);
133
+ max-block-size: calc(100dvh - var(--sf-app-header-height));
134
+ min-block-size: calc(100dvh - var(--sf-app-header-height));
135
+ }
136
+
137
+ .sf-app-shell[data-collapsed="true"] {
138
+ grid-template-columns: var(--sf-app-sidebar-collapsed-width) minmax(0, 1fr);
103
139
  }
104
140
 
105
141
  .sf-app-sidebar {
@@ -115,6 +151,7 @@
115
151
  padding: var(--sf-space-s);
116
152
  position: sticky;
117
153
  inset-block-start: 0;
154
+ transition: transform var(--sf-duration-normal) var(--sf-ease-standard), inline-size var(--sf-duration-normal) var(--sf-ease-standard);
118
155
  }
119
156
 
120
157
  .sf-app-sidebar__brand {
@@ -134,6 +171,58 @@
134
171
  padding: var(--sf-app-main-padding, var(--sf-space-m-l));
135
172
  }
136
173
 
174
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar {
175
+ overflow-x: hidden;
176
+ }
177
+
178
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar__brand,
179
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar .sf-nav__link {
180
+ justify-content: center;
181
+ }
182
+
183
+ .sf-app-shell[data-collapsed="true"] [data-hide-collapsed="true"] {
184
+ display: none;
185
+ }
186
+
187
+ .sf-app-backdrop {
188
+ display: none;
189
+ }
190
+
191
+ @media (max-width: 48rem) {
192
+ .sf-app-shell[data-mobile="drawer"] {
193
+ display: block;
194
+ }
195
+
196
+ .sf-app-shell[data-mobile="drawer"] .sf-app-header {
197
+ position: sticky;
198
+ inset-block-start: 0;
199
+ }
200
+
201
+ .sf-app-shell[data-mobile="drawer"] .sf-app-sidebar {
202
+ box-shadow: var(--sf-shadow-xl);
203
+ inline-size: min(18rem, calc(100vw - var(--sf-space-l)));
204
+ inset-block: 0;
205
+ inset-inline-start: 0;
206
+ max-block-size: none;
207
+ min-block-size: 100dvh;
208
+ position: fixed;
209
+ transform: translateX(-100%);
210
+ z-index: 30;
211
+ }
212
+
213
+ .sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-sidebar {
214
+ transform: translateX(0);
215
+ }
216
+
217
+ .sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-backdrop {
218
+ background: var(--sf-colour-backdrop);
219
+ display: block;
220
+ inset: 0;
221
+ position: fixed;
222
+ z-index: 25;
223
+ }
224
+ }
225
+
137
226
  .sf-grid {
138
227
  display: grid;
139
228
  gap: var(--sf-grid-gap, var(--sf-gutter));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncedco/flow",
3
- "version": "0.1.4",
3
+ "version": "0.3.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",