@syncedco/flow 0.3.2 → 0.4.1
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/CHANGELOG.md +25 -0
- package/CONTRIBUTING.md +18 -5
- package/README.md +32 -9
- package/bin/synced-flow.mjs +416 -46
- package/components.css +440 -9
- package/dist/index.d.ts +69 -0
- package/dist/index.js +69 -0
- package/docs/accessibility-css.md +14 -3
- package/docs/ai-usage.md +22 -1
- package/docs/api-contract.md +10 -2
- package/docs/audit-2026-06.md +13 -12
- package/docs/cli-reference.md +9 -6
- package/docs/config-reference.md +2 -2
- package/docs/css-optimisation.md +10 -10
- package/docs/native-components.md +121 -0
- package/docs/patterns.md +18 -0
- package/docs/quick-start.md +6 -4
- package/docs/recipes.md +12 -0
- package/docs/release-readiness.md +32 -7
- package/docs/system-primitives.md +9 -2
- package/docs/tokens.md +4 -1
- package/examples/README.md +2 -2
- package/examples/next/app/synced-flow.generated.css +5 -5
- package/examples/plain-html/synced-flow.generated.css +5 -5
- package/examples/templates/README.md +2 -0
- package/examples/templates/app-settings-workflow.html +81 -0
- package/examples/vite/src/synced-flow.generated.css +5 -5
- package/examples/wordpress/assets/css/synced-flow.css +150 -13
- package/package.json +14 -3
- package/skills/synced-flow/SKILL.md +20 -3
- package/src/index.ts +69 -0
- package/src/presets.mjs +5 -5
- package/src/tokens.mjs +10 -8
- package/styles.css +452 -19
- package/tokens.css +12 -10
package/examples/README.md
CHANGED
|
@@ -7,8 +7,8 @@ These examples show the intended starting point for new projects.
|
|
|
7
7
|
- `vite` - Vite React setup
|
|
8
8
|
- `plain-html` - no framework setup with a complete website demo using only
|
|
9
9
|
Synced Flow primitives for header, hero, content, cards, form, notice, and footer
|
|
10
|
-
- `templates` - copy-ready full-page HTML templates for SaaS,
|
|
11
|
-
and coming soon pages
|
|
10
|
+
- `templates` - copy-ready full-page HTML templates for SaaS, app settings,
|
|
11
|
+
portfolio, blog, and coming soon pages
|
|
12
12
|
- `wordpress` - block theme style setup with enqueue code, templates, parts,
|
|
13
13
|
and PHP patterns
|
|
14
14
|
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -10,6 +10,7 @@ Use the CLI for framework-specific output:
|
|
|
10
10
|
pnpm exec synced-flow recipe saas-landing --markup
|
|
11
11
|
pnpm exec synced-flow recipe saas-landing --framework next --markup
|
|
12
12
|
pnpm exec synced-flow recipe saas-dashboard --framework next --markup
|
|
13
|
+
pnpm exec synced-flow recipe app-settings-workflow --markup
|
|
13
14
|
pnpm exec synced-flow recipe portfolio-scroll --framework astro --markup
|
|
14
15
|
```
|
|
15
16
|
|
|
@@ -17,6 +18,7 @@ Templates:
|
|
|
17
18
|
|
|
18
19
|
- `saas-landing.html`
|
|
19
20
|
- `saas-dashboard.html`
|
|
21
|
+
- `app-settings-workflow.html`
|
|
20
22
|
- `portfolio-scroll.html`
|
|
21
23
|
- `blog-index.html`
|
|
22
24
|
- `coming-soon.html`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Synced Flow Application Settings Template</title>
|
|
7
|
+
<link rel="stylesheet" href="../plain-html/synced-flow.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<a class="sf-skip-link" href="#settings-main">Skip to settings</a>
|
|
11
|
+
<main class="sf-section" id="settings-main">
|
|
12
|
+
<div class="sf-container sf-split">
|
|
13
|
+
<nav aria-label="Setup progress">
|
|
14
|
+
<ol class="sf-stepper">
|
|
15
|
+
<li class="sf-stepper__step" data-complete="true"><span class="sf-stepper__marker" aria-hidden="true">✓</span><span class="sf-stepper__label"><span class="sr-only">Completed: </span>Workspace</span></li>
|
|
16
|
+
<li class="sf-stepper__step" aria-current="step"><span class="sf-stepper__marker" aria-hidden="true"></span><span class="sf-stepper__label">Preferences</span></li>
|
|
17
|
+
<li class="sf-stepper__step"><span class="sf-stepper__marker" aria-hidden="true"></span><span class="sf-stepper__label">Review</span></li>
|
|
18
|
+
</ol>
|
|
19
|
+
</nav>
|
|
20
|
+
|
|
21
|
+
<form class="sf-card sf-form" aria-labelledby="preferences-title">
|
|
22
|
+
<header class="sf-stack">
|
|
23
|
+
<p class="sf-kicker">Step 2 of 3</p>
|
|
24
|
+
<h1 id="preferences-title">Workspace preferences</h1>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<label class="sf-field">
|
|
28
|
+
<span class="sf-label">Workspace URL</span>
|
|
29
|
+
<span class="sf-input-group">
|
|
30
|
+
<span class="sf-input-group__leading" aria-hidden="true">https://</span>
|
|
31
|
+
<input class="sf-input" name="workspace" autocomplete="organization" />
|
|
32
|
+
<span class="sf-input-group__trailing" aria-hidden="true">.example.com</span>
|
|
33
|
+
</span>
|
|
34
|
+
</label>
|
|
35
|
+
|
|
36
|
+
<fieldset class="sf-fieldset">
|
|
37
|
+
<legend>Billing cycle</legend>
|
|
38
|
+
<div class="sf-segmented-control">
|
|
39
|
+
<label class="sf-segmented-control__option"><input type="radio" name="cycle" value="monthly" checked /><span class="sf-segmented-control__label">Monthly</span></label>
|
|
40
|
+
<label class="sf-segmented-control__option"><input type="radio" name="cycle" value="annual" /><span class="sf-segmented-control__label">Annual</span></label>
|
|
41
|
+
</div>
|
|
42
|
+
</fieldset>
|
|
43
|
+
|
|
44
|
+
<label class="sf-switch">
|
|
45
|
+
<input type="checkbox" role="switch" name="weekly_summary" checked />
|
|
46
|
+
<span class="sf-switch__control" aria-hidden="true"></span>
|
|
47
|
+
<span class="sf-switch__label">Email weekly summary</span>
|
|
48
|
+
</label>
|
|
49
|
+
|
|
50
|
+
<label class="sf-field">
|
|
51
|
+
<span class="sf-label" id="team-size-label">Team size: <output for="team-size">12</output></span>
|
|
52
|
+
<input class="sf-range" id="team-size" type="range" name="team_size" min="1" max="50" value="12" aria-labelledby="team-size-label" />
|
|
53
|
+
</label>
|
|
54
|
+
|
|
55
|
+
<label class="sf-field">
|
|
56
|
+
<span class="sf-label">Brand mark</span>
|
|
57
|
+
<input class="sf-file-input" type="file" name="brand_mark" accept="image/png,image/svg+xml" />
|
|
58
|
+
<span class="sf-help">PNG or SVG.</span>
|
|
59
|
+
</label>
|
|
60
|
+
|
|
61
|
+
<div class="sf-field">
|
|
62
|
+
<label class="sf-label" for="owner-search">Workspace owner</label>
|
|
63
|
+
<div class="sf-combobox">
|
|
64
|
+
<input class="sf-input" id="owner-search" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="owner-options" />
|
|
65
|
+
<ul class="sf-combobox__listbox" id="owner-options" role="listbox" hidden>
|
|
66
|
+
<li class="sf-combobox__option" id="owner-option-1" role="option" aria-selected="true">Alex Morgan</li>
|
|
67
|
+
<li class="sf-combobox__option" id="owner-option-2" role="option" aria-selected="false">Jamie Lee</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</div>
|
|
70
|
+
<span class="sf-help">This static fixture shows presentation. The consuming app must implement combobox behavior.</span>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="sf-wizard-actions">
|
|
74
|
+
<button class="sf-button sf-button--outline" type="button">Back</button>
|
|
75
|
+
<button class="sf-button" type="submit"><span class="sf-spinner" aria-hidden="true" hidden></span><span>Continue</span></button>
|
|
76
|
+
</div>
|
|
77
|
+
</form>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -915,6 +915,8 @@
|
|
|
915
915
|
|
|
916
916
|
.sf-data-table :where(th button, .sf-table-sort) {
|
|
917
917
|
align-items: center;
|
|
918
|
+
background: transparent;
|
|
919
|
+
border: 0;
|
|
918
920
|
color: inherit;
|
|
919
921
|
display: inline-flex;
|
|
920
922
|
font: inherit;
|
|
@@ -923,14 +925,14 @@
|
|
|
923
925
|
padding: 0;
|
|
924
926
|
}
|
|
925
927
|
|
|
926
|
-
.sf-data-table :where(
|
|
928
|
+
.sf-data-table th[aria-sort] :where(button, .sf-table-sort)::after {
|
|
927
929
|
color: var(--color-muted-foreground);
|
|
928
930
|
content: "\2195";
|
|
929
931
|
font-size: var(--step--2);
|
|
930
932
|
}
|
|
931
933
|
|
|
932
|
-
.sf-data-table :where(
|
|
933
|
-
.sf-data-table :where(
|
|
934
|
+
.sf-data-table th[aria-sort="ascending"] :where(button, .sf-table-sort)::after { content: "\2191"; }
|
|
935
|
+
.sf-data-table th[aria-sort="descending"] :where(button, .sf-table-sort)::after { content: "\2193"; }
|
|
934
936
|
|
|
935
937
|
.sf-data-table tr[data-clickable="true"] {
|
|
936
938
|
cursor: pointer;
|
|
@@ -1005,7 +1007,7 @@
|
|
|
1005
1007
|
}
|
|
1006
1008
|
|
|
1007
1009
|
.sf-status::before {
|
|
1008
|
-
background: currentColor;
|
|
1010
|
+
background: var(--status-dot, currentColor);
|
|
1009
1011
|
block-size: .42em;
|
|
1010
1012
|
border-radius: var(--radius-full);
|
|
1011
1013
|
content: "";
|
|
@@ -1015,25 +1017,29 @@
|
|
|
1015
1017
|
.sf-status[data-tone="success"] {
|
|
1016
1018
|
--status-bg: var(--sf-colour-success-soft, oklch(62% 0.13 150 / 0.12));
|
|
1017
1019
|
--status-border: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 34%, transparent);
|
|
1018
|
-
--status-color:
|
|
1020
|
+
--status-color: var(--color-foreground);
|
|
1021
|
+
--status-dot: var(--sf-colour-success, oklch(62% 0.13 150));
|
|
1019
1022
|
}
|
|
1020
1023
|
|
|
1021
1024
|
.sf-status[data-tone="warning"] {
|
|
1022
1025
|
--status-bg: var(--sf-colour-warning-soft, oklch(78% 0.15 82 / 0.16));
|
|
1023
1026
|
--status-border: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 38%, transparent);
|
|
1024
|
-
--status-color:
|
|
1027
|
+
--status-color: var(--color-foreground);
|
|
1028
|
+
--status-dot: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 60%, var(--color-foreground));
|
|
1025
1029
|
}
|
|
1026
1030
|
|
|
1027
1031
|
.sf-status[data-tone="danger"] {
|
|
1028
1032
|
--status-bg: var(--sf-colour-danger-soft, oklch(56% 0.2 28 / 0.12));
|
|
1029
1033
|
--status-border: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 34%, transparent);
|
|
1030
|
-
--status-color:
|
|
1034
|
+
--status-color: var(--color-foreground);
|
|
1035
|
+
--status-dot: var(--sf-colour-danger, var(--color-destructive));
|
|
1031
1036
|
}
|
|
1032
1037
|
|
|
1033
1038
|
.sf-status[data-tone="info"] {
|
|
1034
1039
|
--status-bg: var(--sf-colour-info-soft, oklch(70% 0.12 205 / 0.14));
|
|
1035
1040
|
--status-border: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 34%, transparent);
|
|
1036
|
-
--status-color:
|
|
1041
|
+
--status-color: var(--color-foreground);
|
|
1042
|
+
--status-dot: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 68%, var(--color-foreground));
|
|
1037
1043
|
}
|
|
1038
1044
|
|
|
1039
1045
|
.sf-data-list {
|
|
@@ -1081,9 +1087,16 @@
|
|
|
1081
1087
|
background: var(--color-surface-alt);
|
|
1082
1088
|
}
|
|
1083
1089
|
|
|
1090
|
+
.sf-form {
|
|
1091
|
+
display: grid;
|
|
1092
|
+
gap: var(--form-gap, var(--space-s));
|
|
1093
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1084
1096
|
.sf-field {
|
|
1085
1097
|
display: grid;
|
|
1086
1098
|
gap: var(--space-2xs);
|
|
1099
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1087
1100
|
}
|
|
1088
1101
|
|
|
1089
1102
|
.sf-form[data-density="compact"],
|
|
@@ -1146,6 +1159,130 @@
|
|
|
1146
1159
|
outline: 0.125rem solid color-mix(in oklch, var(--color-ring) 24%, transparent);
|
|
1147
1160
|
outline-offset: 0;
|
|
1148
1161
|
}
|
|
1162
|
+
|
|
1163
|
+
.sf-spinner {
|
|
1164
|
+
animation: sf-spinner-rotate .75s linear infinite;
|
|
1165
|
+
block-size: var(--spinner-size, 1em);
|
|
1166
|
+
border: .125em solid color-mix(in oklch, currentColor 28%, transparent);
|
|
1167
|
+
border-block-start-color: currentColor;
|
|
1168
|
+
border-radius: var(--radius-full);
|
|
1169
|
+
display: inline-block;
|
|
1170
|
+
flex: 0 0 auto;
|
|
1171
|
+
inline-size: var(--spinner-size, 1em);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
@keyframes sf-spinner-rotate { to { transform: rotate(1turn); } }
|
|
1175
|
+
|
|
1176
|
+
.sf-input-group {
|
|
1177
|
+
align-items: stretch;
|
|
1178
|
+
background: var(--color-card);
|
|
1179
|
+
border: 1px solid var(--color-border);
|
|
1180
|
+
border-radius: var(--input-radius, var(--radius-md));
|
|
1181
|
+
display: flex;
|
|
1182
|
+
inline-size: 100%;
|
|
1183
|
+
min-block-size: var(--input-block-size, 2.75rem);
|
|
1184
|
+
overflow: clip;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.sf-input-group:focus-within {
|
|
1188
|
+
border-color: var(--color-ring);
|
|
1189
|
+
outline: .125rem solid color-mix(in oklch, var(--color-ring) 24%, transparent);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.sf-input-group > .sf-input { background: transparent; border: 0; border-radius: 0; flex: 1 1 0%; inline-size: auto; min-inline-size: 0; outline: 0; }
|
|
1193
|
+
.sf-input-group__leading,
|
|
1194
|
+
.sf-input-group__trailing { align-items: center; color: var(--color-muted-foreground); display: inline-flex; flex: 0 0 auto; padding-inline: var(--space-xs); white-space: nowrap; }
|
|
1195
|
+
.sf-input-group__leading { border-inline-end: 1px solid var(--color-border); }
|
|
1196
|
+
.sf-input-group__trailing,
|
|
1197
|
+
.sf-input-group__action { border-inline-start: 1px solid var(--color-border); }
|
|
1198
|
+
.sf-input-group__action { background: transparent; border-block: 0; border-inline-end: 0; color: var(--color-foreground); font-weight: 700; padding-inline: var(--space-xs); }
|
|
1199
|
+
.sf-input-group[data-invalid="true"],
|
|
1200
|
+
.sf-field[data-invalid="true"] .sf-input-group { border-color: var(--color-destructive, var(--color-danger)); }
|
|
1201
|
+
.sf-input-group:has(:disabled) { background: var(--color-surface-alt); opacity: .72; }
|
|
1202
|
+
|
|
1203
|
+
.sf-file-input {
|
|
1204
|
+
background: var(--color-card);
|
|
1205
|
+
border: 1px solid var(--color-border);
|
|
1206
|
+
border-radius: var(--input-radius, var(--radius-md));
|
|
1207
|
+
color: var(--color-muted-foreground);
|
|
1208
|
+
inline-size: 100%;
|
|
1209
|
+
min-block-size: var(--input-block-size, 2.75rem);
|
|
1210
|
+
padding: var(--space-3xs);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.sf-file-input::file-selector-button { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-foreground); font: inherit; font-weight: 700; margin-inline-end: var(--space-xs); min-block-size: 2.25rem; padding-inline: var(--space-xs); }
|
|
1214
|
+
.sf-range { accent-color: var(--color-primary); inline-size: 100%; min-block-size: var(--input-block-size, 2.75rem); }
|
|
1215
|
+
.sf-range:focus-visible,
|
|
1216
|
+
.sf-file-input:focus-visible { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1217
|
+
.sf-file-input[aria-invalid="true"],
|
|
1218
|
+
.sf-field[data-invalid="true"] .sf-file-input { border-color: var(--color-destructive, var(--color-danger)); }
|
|
1219
|
+
:where(.sf-file-input, .sf-range):disabled { opacity: .55; }
|
|
1220
|
+
|
|
1221
|
+
.sf-switch { align-items: center; display: inline-grid; gap: var(--space-xs); grid-template-columns: auto 1fr; position: relative; }
|
|
1222
|
+
.sf-switch > input[type="checkbox"],
|
|
1223
|
+
.sf-segmented-control__option > input[type="radio"] { block-size: .0625rem; clip: rect(0 0 0 0); clip-path: inset(50%); inline-size: .0625rem; overflow: hidden; position: absolute; white-space: nowrap; }
|
|
1224
|
+
.sf-switch__control { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); block-size: 1.5rem; display: inline-flex; inline-size: 2.75rem; padding: .1875rem; }
|
|
1225
|
+
.sf-switch__control::before { background: var(--color-foreground); border-radius: var(--radius-full); content: ""; inline-size: 1rem; transition: transform var(--duration-fast) var(--ease-standard); }
|
|
1226
|
+
.sf-switch > input:checked + .sf-switch__control { background: var(--color-primary); border-color: var(--color-primary); }
|
|
1227
|
+
.sf-switch > input:checked + .sf-switch__control::before { background: var(--color-primary-foreground); transform: translateX(1.1875rem); }
|
|
1228
|
+
.sf-switch > input:focus-visible + .sf-switch__control { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1229
|
+
.sf-switch__label { color: var(--color-foreground); font-weight: 700; }
|
|
1230
|
+
.sf-switch:has(input:disabled) { cursor: not-allowed; opacity: .55; }
|
|
1231
|
+
|
|
1232
|
+
.sf-segmented-control { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: inline-flex; flex-wrap: wrap; gap: var(--space-3xs); padding: var(--space-3xs); }
|
|
1233
|
+
.sf-segmented-control__option { cursor: pointer; position: relative; }
|
|
1234
|
+
.sf-segmented-control__label { align-items: center; border: 1px solid transparent; border-radius: var(--radius-sm); color: var(--color-muted-foreground); display: inline-flex; font-size: var(--step--1); font-weight: 700; min-block-size: 2.25rem; padding-inline: var(--space-xs); }
|
|
1235
|
+
.sf-segmented-control__option > input:checked + .sf-segmented-control__label { background: var(--color-card); border-color: var(--color-border); color: var(--color-foreground); }
|
|
1236
|
+
.sf-segmented-control__option > input:focus + .sf-segmented-control__label { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1237
|
+
.sf-segmented-control__option:has(input:disabled) { cursor: not-allowed; opacity: .55; }
|
|
1238
|
+
|
|
1239
|
+
.sf-combobox { position: relative; }
|
|
1240
|
+
.sf-combobox__listbox { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); display: grid; gap: var(--space-3xs); inset-block-start: calc(100% + var(--space-3xs)); inset-inline: 0; list-style: none; max-block-size: min(18rem, 50dvh); overflow: auto; padding: var(--space-2xs); position: absolute; z-index: 40; }
|
|
1241
|
+
.sf-combobox__option { border-radius: var(--radius-sm); cursor: pointer; padding: var(--space-2xs) var(--space-xs); }
|
|
1242
|
+
.sf-combobox__option:hover,
|
|
1243
|
+
.sf-combobox__option[aria-selected="true"],
|
|
1244
|
+
.sf-combobox__option[data-active="true"] { background: var(--color-surface-alt); }
|
|
1245
|
+
.sf-combobox__empty { color: var(--color-muted-foreground); padding: var(--space-xs); }
|
|
1246
|
+
|
|
1247
|
+
.sf-bulk-actions { align-items: center; background: color-mix(in oklch, var(--color-primary) 12%, transparent); border: 1px solid color-mix(in oklch, var(--color-primary) 30%, var(--color-border)); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: space-between; padding: var(--space-xs); }
|
|
1248
|
+
.sf-bulk-actions__count { font-size: var(--step--1); font-weight: 800; }
|
|
1249
|
+
.sf-bulk-actions__controls,
|
|
1250
|
+
.sf-pagination__controls,
|
|
1251
|
+
.sf-pagination__pages,
|
|
1252
|
+
.sf-pagination__jump { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-2xs); }
|
|
1253
|
+
|
|
1254
|
+
.sf-pagination { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-2xs); list-style: none; padding-inline-start: 0; }
|
|
1255
|
+
.sf-pagination[data-layout="complete"] { justify-content: space-between; row-gap: var(--space-xs); }
|
|
1256
|
+
.sf-pagination__summary,
|
|
1257
|
+
.sf-pagination__jump { color: var(--color-muted-foreground); font-size: var(--step--1); }
|
|
1258
|
+
.sf-pagination__jump .sf-input { inline-size: 5rem; }
|
|
1259
|
+
|
|
1260
|
+
.sf-stepper { counter-reset: sf-step; display: grid; gap: var(--space-xs); list-style: none; padding-inline-start: 0; }
|
|
1261
|
+
.sf-stepper__step { align-items: center; color: var(--color-muted-foreground); counter-increment: sf-step; display: grid; gap: var(--space-xs); grid-template-columns: auto 1fr; }
|
|
1262
|
+
.sf-stepper__marker { align-items: center; border: 1px solid var(--color-border); border-radius: var(--radius-full); block-size: 2rem; display: inline-flex; font-size: var(--step--1); font-weight: 800; inline-size: 2rem; justify-content: center; }
|
|
1263
|
+
.sf-stepper__marker:empty::before { content: counter(sf-step); }
|
|
1264
|
+
.sf-stepper__step[aria-current="step"] { color: var(--color-foreground); }
|
|
1265
|
+
.sf-stepper__step[aria-current="step"] .sf-stepper__marker { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-foreground); }
|
|
1266
|
+
.sf-stepper__label { font-weight: 700; }
|
|
1267
|
+
.sf-wizard-actions { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: space-between; }
|
|
1268
|
+
|
|
1269
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1270
|
+
.sf-spinner { animation: none; border-color: currentColor; border-inline-end-color: transparent; }
|
|
1271
|
+
.sf-switch__control::before { transition: none; }
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
@media (forced-colors: active) {
|
|
1275
|
+
:where(.sf-input-group, .sf-file-input, .sf-combobox__listbox, .sf-bulk-actions) { border-color: ButtonText; forced-color-adjust: auto; }
|
|
1276
|
+
:where(.sf-switch__control, .sf-segmented-control__label, .sf-stepper__marker) { background: Canvas; border-color: ButtonText; color: ButtonText; forced-color-adjust: none; }
|
|
1277
|
+
.sf-switch > input:focus-visible + .sf-switch__control,
|
|
1278
|
+
.sf-segmented-control__option > input:focus + .sf-segmented-control__label { outline: .125rem solid Highlight; outline-offset: .125rem; }
|
|
1279
|
+
.sf-switch > input:checked + .sf-switch__control,
|
|
1280
|
+
.sf-segmented-control__option > input:checked + .sf-segmented-control__label,
|
|
1281
|
+
.sf-stepper__step[aria-current="step"] .sf-stepper__marker { background: Highlight; border-color: Highlight; color: HighlightText; }
|
|
1282
|
+
.sf-switch__control::before { background: ButtonText; }
|
|
1283
|
+
.sf-switch > input:checked + .sf-switch__control::before { background: HighlightText; }
|
|
1284
|
+
.sf-stepper__step[data-complete="true"] .sf-stepper__marker { background: Canvas; border-color: LinkText; color: LinkText; }
|
|
1285
|
+
}
|
|
1149
1286
|
}
|
|
1150
1287
|
|
|
1151
1288
|
@layer utilities {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncedco/flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "AI-friendly fluid CSS design system for brand-consistent websites and agency builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.2.2",
|
|
@@ -77,16 +77,27 @@
|
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
79
79
|
"build:css": "node scripts/build-css.mjs",
|
|
80
|
-
"
|
|
80
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
81
|
+
"check": "pnpm check:generated && tsc --noEmit && node scripts/guardrails.mjs",
|
|
82
|
+
"check:generated": "pnpm build:types && node scripts/check-generated-css.mjs",
|
|
81
83
|
"guardrails": "node scripts/guardrails.mjs",
|
|
82
84
|
"clean": "rm -rf dist",
|
|
83
85
|
"prepack": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
84
86
|
"prepare": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
85
|
-
"test": "pnpm build &&
|
|
87
|
+
"test": "pnpm check:generated && pnpm build && pnpm test:unit",
|
|
88
|
+
"test:unit": "node --test tests/*.test.mjs",
|
|
89
|
+
"test:browser": "playwright test --project=chromium",
|
|
90
|
+
"test:browser:all": "playwright test",
|
|
86
91
|
"type-check": "tsc --noEmit"
|
|
87
92
|
},
|
|
88
93
|
"devDependencies": {
|
|
94
|
+
"@axe-core/playwright": "^4.13.0",
|
|
95
|
+
"@playwright/test": "^1.63.0",
|
|
89
96
|
"@types/node": "^22.15.21",
|
|
97
|
+
"@types/react": "^19.2.18",
|
|
98
|
+
"@types/react-test-renderer": "19.1.0",
|
|
99
|
+
"react": "^19.2.8",
|
|
100
|
+
"react-test-renderer": "19.2.8",
|
|
90
101
|
"typescript": "^5.8.3"
|
|
91
102
|
},
|
|
92
103
|
"engines": {
|
|
@@ -91,14 +91,24 @@ intact. Leave out `utilities.css` unless static helpers such as `sf-text-*`,
|
|
|
91
91
|
`sf-app-backdrop`, `sf-frame`, and `sf-flow`.
|
|
92
92
|
- Prefer component primitives for common UI: `sf-button`, `sf-card`,
|
|
93
93
|
`sf-badge`, `sf-status`, `sf-field`, `sf-input`, `sf-search`,
|
|
94
|
-
`sf-
|
|
94
|
+
`sf-input-group`, `sf-file-input`, `sf-range`, `sf-switch`,
|
|
95
|
+
`sf-segmented-control`, `sf-spinner`, `sf-data-table`, `sf-table-sort`,
|
|
96
|
+
`sf-tab__count`, and `sf-toast-stack`.
|
|
95
97
|
- Use `saas-landing` for public SaaS marketing pages. Use `saas-dashboard` when
|
|
96
98
|
a full authenticated starter is useful, but prefer neutral app patterns for
|
|
97
99
|
reusable UI in admin panels, portals, CRMs, analytics dashboards, internal
|
|
98
100
|
tools, account areas, and product workspaces.
|
|
99
101
|
- Use app patterns before writing project CSS:
|
|
100
102
|
`app-shell-layout`, `row-action-menu`, `tabs-with-counts`,
|
|
101
|
-
`data-table-actions`, `search-field`, `modal-form`,
|
|
103
|
+
`data-table-actions`, `search-field`, `modal-form`, `toast-stack`,
|
|
104
|
+
`searchable-select`, `bulk-actions-toolbar`, `complete-pagination`, and
|
|
105
|
+
`multi-step-form`.
|
|
106
|
+
- Use `switch-control` for independent binary settings and
|
|
107
|
+
`segmented-control` for one choice from a small set. Do not use either as a
|
|
108
|
+
substitute for tabs.
|
|
109
|
+
- Treat `searchable-select`, async loading, bulk selection, and multi-step
|
|
110
|
+
navigation as app-owned behavior. Flow supplies presentation and accessible
|
|
111
|
+
markup guidance, not JavaScript state machines.
|
|
102
112
|
- Use app shell states instead of bespoke layout CSS: `data-layout="fixed-header"`
|
|
103
113
|
for fixed app headers, `data-collapsed="true"` for collapsed sidebars, and
|
|
104
114
|
`data-mobile="drawer"` with `data-sidebar-open="true"` for mobile drawer
|
|
@@ -160,9 +170,16 @@ pnpm flow:doctor
|
|
|
160
170
|
For package or repo changes, also run:
|
|
161
171
|
|
|
162
172
|
```bash
|
|
173
|
+
pnpm check:generated
|
|
163
174
|
pnpm check
|
|
164
175
|
pnpm test
|
|
176
|
+
pnpm test:browser
|
|
177
|
+
pnpm test:browser:all
|
|
165
178
|
pnpm pack --dry-run
|
|
166
179
|
```
|
|
167
180
|
|
|
168
|
-
|
|
181
|
+
Run `pnpm check:generated` before a build or lifecycle command can refresh
|
|
182
|
+
tracked CSS. It covers package CSS and the plain HTML, Vite, Next, Astro, and
|
|
183
|
+
WordPress generated outputs. Use the Chromium browser gate during development
|
|
184
|
+
and all three browser engines before a release. If `doctor` warns about stale
|
|
185
|
+
project CSS, run `pnpm flow:build`.
|
package/src/index.ts
CHANGED
|
@@ -48,10 +48,79 @@ export const fluidSystem = {
|
|
|
48
48
|
},
|
|
49
49
|
components: {
|
|
50
50
|
button: 'sf-button',
|
|
51
|
+
buttonGroup: 'sf-button-group',
|
|
52
|
+
icon: 'sf-icon',
|
|
53
|
+
iconButton: 'sf-icon-button',
|
|
51
54
|
card: 'sf-card',
|
|
55
|
+
surface: 'sf-surface',
|
|
56
|
+
hero: 'sf-hero',
|
|
57
|
+
logoCloud: 'sf-logo-cloud',
|
|
58
|
+
feature: 'sf-feature',
|
|
59
|
+
stats: 'sf-stats',
|
|
60
|
+
testimonial: 'sf-testimonial',
|
|
61
|
+
pricingGrid: 'sf-pricing-grid',
|
|
62
|
+
faq: 'sf-faq',
|
|
63
|
+
callToAction: 'sf-cta',
|
|
64
|
+
footer: 'sf-footer',
|
|
52
65
|
badge: 'sf-badge',
|
|
66
|
+
avatar: 'sf-avatar',
|
|
67
|
+
alert: 'sf-alert',
|
|
68
|
+
status: 'sf-status',
|
|
69
|
+
sectionHeader: 'sf-section-header',
|
|
70
|
+
kicker: 'sf-kicker',
|
|
71
|
+
nav: 'sf-nav',
|
|
72
|
+
form: 'sf-form',
|
|
73
|
+
fieldset: 'sf-fieldset',
|
|
53
74
|
field: 'sf-field',
|
|
75
|
+
label: 'sf-label',
|
|
76
|
+
help: 'sf-help',
|
|
77
|
+
error: 'sf-error',
|
|
54
78
|
input: 'sf-input',
|
|
79
|
+
inputGroup: 'sf-input-group',
|
|
80
|
+
select: 'sf-select',
|
|
81
|
+
textarea: 'sf-textarea',
|
|
82
|
+
check: 'sf-check',
|
|
83
|
+
switch: 'sf-switch',
|
|
84
|
+
fileInput: 'sf-file-input',
|
|
85
|
+
range: 'sf-range',
|
|
86
|
+
segmentedControl: 'sf-segmented-control',
|
|
87
|
+
spinner: 'sf-spinner',
|
|
88
|
+
search: 'sf-search',
|
|
89
|
+
combobox: 'sf-combobox',
|
|
90
|
+
filterBar: 'sf-filter-bar',
|
|
91
|
+
bulkActions: 'sf-bulk-actions',
|
|
92
|
+
dataTable: 'sf-data-table',
|
|
93
|
+
tableSort: 'sf-table-sort',
|
|
94
|
+
dataList: 'sf-data-list',
|
|
95
|
+
emptyState: 'sf-empty-state',
|
|
96
|
+
settingsSection: 'sf-settings-section',
|
|
97
|
+
detailPanel: 'sf-detail-panel',
|
|
98
|
+
dialog: 'sf-dialog',
|
|
99
|
+
popover: 'sf-popover',
|
|
100
|
+
drawer: 'sf-drawer',
|
|
101
|
+
tooltip: 'sf-tooltip',
|
|
102
|
+
toast: 'sf-toast',
|
|
103
|
+
toastStack: 'sf-toast-stack',
|
|
104
|
+
banner: 'sf-banner',
|
|
105
|
+
disclosure: 'sf-disclosure',
|
|
106
|
+
accordion: 'sf-accordion',
|
|
107
|
+
tabs: 'sf-tabs',
|
|
108
|
+
tab: 'sf-tab',
|
|
109
|
+
menu: 'sf-menu',
|
|
110
|
+
breadcrumb: 'sf-breadcrumb',
|
|
111
|
+
pagination: 'sf-pagination',
|
|
112
|
+
stepper: 'sf-stepper',
|
|
113
|
+
wizardActions: 'sf-wizard-actions',
|
|
114
|
+
progress: 'sf-progress',
|
|
115
|
+
skeleton: 'sf-skeleton',
|
|
116
|
+
chart: 'sf-chart',
|
|
117
|
+
meter: 'sf-meter',
|
|
118
|
+
codeWindow: 'sf-code-window',
|
|
119
|
+
codeLines: 'sf-code-lines',
|
|
120
|
+
tokenStrip: 'sf-token-strip',
|
|
121
|
+
marquee: 'sf-marquee',
|
|
122
|
+
commandList: 'sf-command-list',
|
|
123
|
+
platformCard: 'sf-platform-card',
|
|
55
124
|
},
|
|
56
125
|
utilities: {
|
|
57
126
|
visuallyHidden: 'sf-visually-hidden',
|
package/src/presets.mjs
CHANGED
|
@@ -12,13 +12,13 @@ export const themePresets = {
|
|
|
12
12
|
surfaceAlt: 'oklch(96.4% 0.011 80)',
|
|
13
13
|
surfaceElevated: 'var(--sf-colour-surface-raised)',
|
|
14
14
|
border: 'oklch(18% 0.026 250 / 0.12)',
|
|
15
|
-
primary: 'oklch(
|
|
16
|
-
primaryHover: 'oklch(
|
|
15
|
+
primary: 'oklch(50% 0.16 40)',
|
|
16
|
+
primaryHover: 'oklch(44% 0.14 40)',
|
|
17
17
|
primaryForeground: 'oklch(100% 0 0)',
|
|
18
18
|
accent: 'oklch(70% 0.12 205)',
|
|
19
|
-
link: 'oklch(
|
|
20
|
-
linkHover: 'oklch(
|
|
21
|
-
ring: 'oklch(
|
|
19
|
+
link: 'oklch(50% 0.16 40)',
|
|
20
|
+
linkHover: 'oklch(44% 0.14 40)',
|
|
21
|
+
ring: 'oklch(50% 0.16 40)',
|
|
22
22
|
},
|
|
23
23
|
components: {
|
|
24
24
|
button: {
|