@usefragments/ui 1.3.3 → 1.4.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/dist/assets/ui.css +1176 -766
- package/dist/chart.cjs +60 -24
- package/dist/chart.js +38 -24
- package/dist/colorpicker.cjs +56 -9
- package/dist/colorpicker.js +34 -9
- package/dist/components/Badge/Badge.module.scss.cjs +23 -16
- package/dist/components/Badge/Badge.module.scss.js +23 -16
- package/dist/components/Badge/index.cjs +8 -1
- package/dist/components/Badge/index.d.ts +3 -0
- package/dist/components/Badge/index.d.ts.map +1 -1
- package/dist/components/Badge/index.js +8 -1
- package/dist/components/Button/Button.module.scss.cjs +16 -13
- package/dist/components/Button/Button.module.scss.js +16 -13
- package/dist/components/Button/index.d.ts +2 -1
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Chart/index.d.ts +3 -0
- package/dist/components/Chart/index.d.ts.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.module.scss.cjs +21 -21
- package/dist/components/CodeBlock/CodeBlock.module.scss.js +21 -21
- package/dist/components/ColorPicker/index.d.ts +4 -0
- package/dist/components/ColorPicker/index.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.module.scss.cjs +23 -23
- package/dist/components/Combobox/Combobox.module.scss.js +23 -23
- package/dist/components/Command/Command.module.scss.cjs +11 -11
- package/dist/components/Command/Command.module.scss.js +11 -11
- package/dist/components/ConversationList/ConversationList.module.scss.cjs +13 -10
- package/dist/components/ConversationList/ConversationList.module.scss.js +14 -11
- package/dist/components/ConversationList/index.cjs +26 -22
- package/dist/components/ConversationList/index.d.ts +5 -1
- package/dist/components/ConversationList/index.d.ts.map +1 -1
- package/dist/components/ConversationList/index.js +27 -23
- package/dist/components/DataTable/index.d.ts +4 -1
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DatePicker/index.d.ts +3 -0
- package/dist/components/DatePicker/index.d.ts.map +1 -1
- package/dist/components/Editor/index.d.ts +4 -1
- package/dist/components/Editor/index.d.ts.map +1 -1
- package/dist/components/Listbox/Listbox.module.scss.cjs +8 -8
- package/dist/components/Listbox/Listbox.module.scss.js +8 -8
- package/dist/components/Markdown/Markdown.module.scss.cjs +1 -1
- package/dist/components/Markdown/Markdown.module.scss.js +1 -1
- package/dist/components/Markdown/index.d.ts +16 -0
- package/dist/components/Markdown/index.d.ts.map +1 -1
- package/dist/components/Menu/Menu.module.scss.cjs +14 -14
- package/dist/components/Menu/Menu.module.scss.js +14 -14
- package/dist/components/Message/Message.module.scss.cjs +23 -20
- package/dist/components/Message/Message.module.scss.js +24 -21
- package/dist/components/Message/index.cjs +5 -1
- package/dist/components/Message/index.d.ts.map +1 -1
- package/dist/components/Message/index.js +5 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.scss.cjs +28 -28
- package/dist/components/NavigationMenu/NavigationMenu.module.scss.js +28 -28
- package/dist/components/Pagination/Pagination.module.scss.cjs +7 -7
- package/dist/components/Pagination/Pagination.module.scss.js +7 -7
- package/dist/components/Prompt/Prompt.module.scss.cjs +52 -22
- package/dist/components/Prompt/Prompt.module.scss.js +52 -22
- package/dist/components/Prompt/index.cjs +197 -2
- package/dist/components/Prompt/index.d.ts +98 -3
- package/dist/components/Prompt/index.d.ts.map +1 -1
- package/dist/components/Prompt/index.js +198 -3
- package/dist/components/Select/Select.module.scss.cjs +29 -17
- package/dist/components/Select/Select.module.scss.js +30 -18
- package/dist/components/Select/index.cjs +45 -8
- package/dist/components/Select/index.d.ts +17 -2
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +45 -8
- package/dist/components/Sidebar/Sidebar.module.scss.cjs +45 -42
- package/dist/components/Sidebar/Sidebar.module.scss.js +45 -42
- package/dist/components/Sidebar/index.cjs +2 -0
- package/dist/components/Sidebar/index.d.ts +6 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +2 -0
- package/dist/components/Text/Text.module.scss.cjs +63 -51
- package/dist/components/Text/Text.module.scss.js +63 -51
- package/dist/components/Text/index.d.ts +4 -2
- package/dist/components/Text/index.d.ts.map +1 -1
- package/dist/data-table.cjs +210 -28
- package/dist/data-table.js +189 -29
- package/dist/datepicker.cjs +63 -14
- package/dist/datepicker.js +41 -14
- package/dist/editor.cjs +67 -16
- package/dist/editor.js +45 -16
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/markdown.cjs +63 -19
- package/dist/markdown.js +41 -19
- package/fragments.json +1 -1
- package/package.json +2 -2
- package/src/components/Badge/Badge.contract.json +28 -5
- package/src/components/Badge/Badge.module.scss +21 -0
- package/src/components/Badge/index.tsx +10 -1
- package/src/components/Button/Button.contract.json +9 -3
- package/src/components/Button/Button.module.scss +34 -0
- package/src/components/Button/index.tsx +11 -1
- package/src/components/Chart/Chart.contract.json +7 -0
- package/src/components/Chart/index.tsx +44 -27
- package/src/components/CodeBlock/CodeBlock.contract.json +7 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +7 -2
- package/src/components/ColorPicker/ColorPicker.contract.json +9 -1
- package/src/components/ColorPicker/index.tsx +42 -12
- package/src/components/Combobox/Combobox.module.scss +4 -13
- package/src/components/ConversationList/ConversationList.contract.json +16 -1
- package/src/components/ConversationList/ConversationList.module.scss +12 -0
- package/src/components/ConversationList/index.tsx +40 -26
- package/src/components/DataTable/DataTable.contract.json +8 -1
- package/src/components/DataTable/index.tsx +282 -51
- package/src/components/DatePicker/DatePicker.contract.json +8 -1
- package/src/components/DatePicker/index.tsx +51 -22
- package/src/components/Editor/Editor.contract.json +19 -1
- package/src/components/Editor/index.tsx +67 -23
- package/src/components/Listbox/Listbox.module.scss +3 -11
- package/src/components/Markdown/Markdown.contract.json +15 -1
- package/src/components/Markdown/Markdown.module.scss +18 -1
- package/src/components/Markdown/index.tsx +58 -24
- package/src/components/Menu/Menu.module.scss +12 -2
- package/src/components/Message/Message.contract.json +3 -0
- package/src/components/Message/Message.module.scss +33 -4
- package/src/components/Message/index.tsx +7 -1
- package/src/components/Pagination/Pagination.contract.json +1 -0
- package/src/components/Pagination/Pagination.module.scss +5 -4
- package/src/components/Prompt/Prompt.contract.json +40 -1
- package/src/components/Prompt/Prompt.module.scss +351 -4
- package/src/components/Prompt/index.tsx +324 -0
- package/src/components/Select/Select.contract.json +11 -1
- package/src/components/Select/Select.module.scss +80 -13
- package/src/components/Select/index.tsx +73 -7
- package/src/components/Sidebar/Sidebar.contract.json +1 -0
- package/src/components/Sidebar/Sidebar.module.scss +10 -0
- package/src/components/Sidebar/index.tsx +7 -0
- package/src/components/Text/Text.contract.json +7 -4
- package/src/components/Text/Text.module.scss +18 -0
- package/src/components/Text/index.tsx +4 -2
- package/src/index.ts +1 -0
- package/src/tokens/_mixins.scss +39 -4
- package/src/tokens/_variables.scss +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefragments/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Customizable UI components built on Base UI headless primitives",
|
|
6
6
|
"author": "Conan McNicholl",
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
"vite": "^6.0.0",
|
|
242
242
|
"vitest": "^2.1.8",
|
|
243
243
|
"vitest-axe": "^0.1.0",
|
|
244
|
-
"@usefragments/core": "1.7.
|
|
244
|
+
"@usefragments/core": "1.7.1"
|
|
245
245
|
},
|
|
246
246
|
"files": [
|
|
247
247
|
"src",
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
"exportName": "Badge",
|
|
10
10
|
"propsSummary": [
|
|
11
11
|
"children: node (required)",
|
|
12
|
-
"variant: default|success|warning|error|info|outline (default: default)",
|
|
12
|
+
"variant: default|success|warning|error|info|outline|label|dim (default: default)",
|
|
13
13
|
"size: sm|md|lg (default: md)",
|
|
14
14
|
"dot: boolean (default: false)",
|
|
15
|
+
"dotPulse: boolean (default: false)",
|
|
16
|
+
"dotColor: string",
|
|
15
17
|
"icon: node",
|
|
18
|
+
"active: boolean (default: false)",
|
|
16
19
|
"onRemove: function",
|
|
17
20
|
"announce: boolean (default: false)",
|
|
18
21
|
"role: union"
|
|
@@ -25,10 +28,10 @@
|
|
|
25
28
|
},
|
|
26
29
|
"variant": {
|
|
27
30
|
"type": "enum",
|
|
28
|
-
"description": "Visual style indicating severity or category",
|
|
31
|
+
"description": "Visual style indicating severity or category. `label` is a hairline-bordered metadata pill (pair with dotColor); `dim` is transparent filter-pill chrome (pair with active).",
|
|
29
32
|
"default": "default",
|
|
30
33
|
"required": false,
|
|
31
|
-
"values": ["default", "success", "warning", "error", "info", "outline"]
|
|
34
|
+
"values": ["default", "success", "warning", "error", "info", "outline", "label", "dim"]
|
|
32
35
|
},
|
|
33
36
|
"size": {
|
|
34
37
|
"type": "enum",
|
|
@@ -43,11 +46,28 @@
|
|
|
43
46
|
"default": "false",
|
|
44
47
|
"required": false
|
|
45
48
|
},
|
|
49
|
+
"dotPulse": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"description": "Breathe the status dot, for a state that is still happening rather than one that has settled. Opacity only, and honours prefers-reduced-motion",
|
|
52
|
+
"default": "false",
|
|
53
|
+
"required": false
|
|
54
|
+
},
|
|
55
|
+
"dotColor": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Color for the leading status dot. Applies to variant=\"label\" (and the generic dot prop). Accepts any CSS color including CSS custom properties.",
|
|
58
|
+
"required": false
|
|
59
|
+
},
|
|
46
60
|
"icon": {
|
|
47
61
|
"type": "node",
|
|
48
62
|
"description": "Optional icon element before the text",
|
|
49
63
|
"required": false
|
|
50
64
|
},
|
|
65
|
+
"active": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"description": "Marks variant=\"dim\" as the currently selected filter. Has no effect on other variants.",
|
|
68
|
+
"default": "false",
|
|
69
|
+
"required": false
|
|
70
|
+
},
|
|
51
71
|
"onRemove": {
|
|
52
72
|
"type": "function",
|
|
53
73
|
"description": "Makes the badge removable. Called when X is clicked.",
|
|
@@ -149,9 +169,12 @@
|
|
|
149
169
|
"contract": {
|
|
150
170
|
"propsSummary": [
|
|
151
171
|
"children: ReactNode - badge label (required)",
|
|
152
|
-
"variant: default|success|warning|error|info|outline - visual style",
|
|
153
|
-
"size: sm|md - badge size",
|
|
172
|
+
"variant: default|success|warning|error|info|outline|label|dim - visual style",
|
|
173
|
+
"size: sm|md|lg - badge size",
|
|
154
174
|
"dot: boolean - show status dot indicator",
|
|
175
|
+
"dotPulse: boolean - breathe the dot for an in-progress state (respects prefers-reduced-motion)",
|
|
176
|
+
"dotColor: string - color for the leading status dot",
|
|
177
|
+
"active: boolean - marks variant=\"dim\" as the selected filter",
|
|
155
178
|
"onRemove: () => void - makes badge removable",
|
|
156
179
|
"announce: boolean - opt into role=\"status\" + auto status aria-label"
|
|
157
180
|
],
|
|
@@ -116,6 +116,27 @@ $fui-badge-dot-md: 6px !default;
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// "Still happening", as distinct from "is in this state". A live badge and a
|
|
120
|
+
// settled one are otherwise the same object, and the difference between them is
|
|
121
|
+
// the one thing a status board is scanned for — so it is carried by the only
|
|
122
|
+
// motion the component has. Opacity only: nothing reflows, and a reader who has
|
|
123
|
+
// asked for less motion loses the breathing and keeps the badge.
|
|
124
|
+
.dotPulse {
|
|
125
|
+
animation: fui-badge-dot-pulse 1.6s ease-in-out infinite;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes fui-badge-dot-pulse {
|
|
129
|
+
50% {
|
|
130
|
+
opacity: 0.35;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@media (prefers-reduced-motion: reduce) {
|
|
135
|
+
.dotPulse {
|
|
136
|
+
animation: none;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
119
140
|
.icon {
|
|
120
141
|
display: flex;
|
|
121
142
|
align-items: center;
|
|
@@ -26,6 +26,9 @@ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
|
26
26
|
size?: "sm" | "md" | "lg";
|
|
27
27
|
/** Show a status dot before the label */
|
|
28
28
|
dot?: boolean;
|
|
29
|
+
/** Breathe the status dot, for a state that is still happening rather than
|
|
30
|
+
* one that has settled. Respects `prefers-reduced-motion`. */
|
|
31
|
+
dotPulse?: boolean;
|
|
29
32
|
/** Color for the leading status dot. Applies to `variant="label"` (and the
|
|
30
33
|
* generic `dot` prop). Accepts any CSS color including CSS custom properties. */
|
|
31
34
|
dotColor?: string;
|
|
@@ -47,6 +50,7 @@ const BadgeRoot = React.forwardRef<HTMLSpanElement, BadgeProps>(function Badge(
|
|
|
47
50
|
variant = "default",
|
|
48
51
|
size = "md",
|
|
49
52
|
dot = false,
|
|
53
|
+
dotPulse = false,
|
|
50
54
|
dotColor,
|
|
51
55
|
icon,
|
|
52
56
|
active = false,
|
|
@@ -90,7 +94,12 @@ const BadgeRoot = React.forwardRef<HTMLSpanElement, BadgeProps>(function Badge(
|
|
|
90
94
|
role={role ?? (announce ? "status" : undefined)}
|
|
91
95
|
aria-label={effectiveAriaLabel}
|
|
92
96
|
>
|
|
93
|
-
{dot &&
|
|
97
|
+
{dot && (
|
|
98
|
+
<span
|
|
99
|
+
className={[styles.dot, dotPulse && styles.dotPulse].filter(Boolean).join(" ")}
|
|
100
|
+
aria-hidden="true"
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
94
103
|
{icon && (
|
|
95
104
|
<span className={styles.icon} aria-hidden="true">
|
|
96
105
|
{icon}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"propsSummary": [
|
|
16
16
|
"as: button|a",
|
|
17
17
|
"children: node (required)",
|
|
18
|
-
"variant: primary|secondary|ghost|link|danger|outlined|outline|icon (default: \"primary\")",
|
|
18
|
+
"variant: primary|secondary|ghost|link|quiet|danger|outlined|outline|icon (default: \"primary\")",
|
|
19
19
|
"size: xs|sm|md|lg (default: \"md\")",
|
|
20
20
|
"icon: boolean",
|
|
21
21
|
"fullWidth: boolean",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"variant": {
|
|
41
41
|
"type": "enum",
|
|
42
|
-
"description": "Visual style variant (\"icon\" is a convenience alias for outlined + icon-only layout, \"link\" is accent-coloured + transparent for tertiary CTAs)",
|
|
42
|
+
"description": "Visual style variant (\"icon\" is a convenience alias for outlined + icon-only layout, \"link\" is accent-coloured + transparent for tertiary CTAs, \"quiet\" is a neutral text button with no box and no height floor for meta lines and footnotes)",
|
|
43
43
|
"default": "\"primary\"",
|
|
44
44
|
"required": false,
|
|
45
45
|
"values": [
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"secondary",
|
|
48
48
|
"ghost",
|
|
49
49
|
"link",
|
|
50
|
+
"quiet",
|
|
50
51
|
"danger",
|
|
51
52
|
"outlined",
|
|
52
53
|
"outline",
|
|
@@ -134,6 +135,11 @@
|
|
|
134
135
|
"description": "Accent-coloured transparent button for tertiary CTAs",
|
|
135
136
|
"code": "<Button variant=\"link\">View all →</Button>"
|
|
136
137
|
},
|
|
138
|
+
{
|
|
139
|
+
"name": "Quiet",
|
|
140
|
+
"description": "Neutral text button with no box and no height floor, for meta lines and footnotes",
|
|
141
|
+
"code": "<Button variant=\"quiet\" size=\"xs\">main</Button>"
|
|
142
|
+
},
|
|
137
143
|
{
|
|
138
144
|
"name": "Danger",
|
|
139
145
|
"description": "Destructive action requiring attention",
|
|
@@ -184,7 +190,7 @@
|
|
|
184
190
|
],
|
|
185
191
|
"contract": {
|
|
186
192
|
"propsSummary": [
|
|
187
|
-
"variant: primary|secondary|ghost|link|danger|outlined|outline|icon (default: primary, icon = outlined + icon-only, link = accent transparent)",
|
|
193
|
+
"variant: primary|secondary|ghost|link|quiet|danger|outlined|outline|icon (default: primary, icon = outlined + icon-only, link = accent transparent, quiet = neutral text button, no box)",
|
|
188
194
|
"size: xs|sm|md|lg (default: md, xs for inline row-action controls)",
|
|
189
195
|
"disabled: boolean - disables interaction",
|
|
190
196
|
"type: button|submit|reset (default: button)",
|
|
@@ -180,6 +180,40 @@
|
|
|
180
180
|
--_button-shadow-active: none;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// Quiet: a button that reads as text. The neutral sibling of `link` — nothing
|
|
184
|
+
// but the foreground changes on hover, because in a meta line, a breadcrumb or
|
|
185
|
+
// a footnote a box would be the loudest thing on the row for the least
|
|
186
|
+
// important one. It is also the one variant that drops the interactive height
|
|
187
|
+
// floor: there is no box to hold open, so it sits on the line it is in and
|
|
188
|
+
// takes its hit target from the text and the row's own padding. That makes it
|
|
189
|
+
// the wrong choice for a standalone action — reach for `ghost` there.
|
|
190
|
+
.quiet {
|
|
191
|
+
--_button-bg: transparent;
|
|
192
|
+
--_button-bg-hover: transparent;
|
|
193
|
+
--_button-bg-active: transparent;
|
|
194
|
+
--_button-border: transparent;
|
|
195
|
+
--_button-border-hover: transparent;
|
|
196
|
+
--_button-color: var(--fui-text-tertiary, #{$fui-text-tertiary});
|
|
197
|
+
--_button-shadow: none;
|
|
198
|
+
--_button-shadow-hover: none;
|
|
199
|
+
--_button-shadow-active: none;
|
|
200
|
+
|
|
201
|
+
min-height: 0;
|
|
202
|
+
padding: 0;
|
|
203
|
+
font-weight: var(--fui-font-weight-normal, $fui-font-weight-normal);
|
|
204
|
+
|
|
205
|
+
// Only the custom property is restated: `color` reads it on .button, so the
|
|
206
|
+
// foreground follows without a second `color` declaration to keep in step.
|
|
207
|
+
&:hover:not(:disabled):not([data-disabled]) {
|
|
208
|
+
--_button-color: var(--fui-text-secondary, #{$fui-text-secondary});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
&:active:not(:disabled):not([data-disabled]),
|
|
212
|
+
&[data-pressed] {
|
|
213
|
+
--_button-color: var(--fui-text-primary, #{$fui-text-primary});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
183
217
|
.danger {
|
|
184
218
|
// Flat danger fill — no gradient. Hover/active darken the solid colour.
|
|
185
219
|
--_button-bg: var(--fui-color-danger, #{$fui-color-danger});
|
|
@@ -29,9 +29,19 @@ type ButtonBaseProps = {
|
|
|
29
29
|
* @see https://usefragments.com/components/button#variants
|
|
30
30
|
* @remarks
|
|
31
31
|
* - `"link"` — accent-coloured, transparent; for tertiary CTAs like "View all →"
|
|
32
|
+
* - `"quiet"` — neutral text button with no box and no height floor; for meta lines and footnotes
|
|
32
33
|
* - `"icon"` — convenience alias for outlined + icon-only layout
|
|
33
34
|
* - `"outline"` — alias for `"outlined"` */
|
|
34
|
-
variant?:
|
|
35
|
+
variant?:
|
|
36
|
+
| "primary"
|
|
37
|
+
| "secondary"
|
|
38
|
+
| "ghost"
|
|
39
|
+
| "link"
|
|
40
|
+
| "quiet"
|
|
41
|
+
| "danger"
|
|
42
|
+
| "outlined"
|
|
43
|
+
| "outline"
|
|
44
|
+
| "icon";
|
|
35
45
|
/** Button size. `"xs"` is for inline row-action controls (accept/dismiss, table-row icons) where `"sm"` is too tall.
|
|
36
46
|
* @default "md"
|
|
37
47
|
* @see https://usefragments.com/components/button#sizes */
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
"recharts"
|
|
11
11
|
],
|
|
12
12
|
"status": "stable",
|
|
13
|
+
"dependencies": [
|
|
14
|
+
{
|
|
15
|
+
"name": "recharts",
|
|
16
|
+
"version": ">=2.0.0 || >=3.0.0",
|
|
17
|
+
"reason": "Optional — powers Chart.Tooltip and Chart.Legend; both render nothing until it is installed."
|
|
18
|
+
}
|
|
19
|
+
],
|
|
13
20
|
"sourcePath": "src/components/Chart/index.tsx",
|
|
14
21
|
"exportName": "ChartContainer",
|
|
15
22
|
"propsSummary": [
|
|
@@ -60,20 +60,46 @@ type RechartsLegendProps = Record<string, unknown>;
|
|
|
60
60
|
|
|
61
61
|
let _RechartsTooltip: React.ComponentType<Record<string, unknown>> | null = null;
|
|
62
62
|
let _RechartsLegend: React.ComponentType<Record<string, unknown>> | null = null;
|
|
63
|
-
let
|
|
63
|
+
let _chartLoadPromise: Promise<void> | null = null;
|
|
64
64
|
let _chartFailed = false;
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// Resolved with import() rather than require(): browser ESM bundles have no
|
|
67
|
+
// `require`, so the synchronous shape disabled recharts even when installed.
|
|
68
|
+
function loadChartDeps(): Promise<void> {
|
|
69
|
+
if (!_chartLoadPromise) {
|
|
70
|
+
_chartLoadPromise = (async () => {
|
|
71
|
+
try {
|
|
72
|
+
const rc = await import('recharts');
|
|
73
|
+
_RechartsTooltip = rc.Tooltip as unknown as React.ComponentType<Record<string, unknown>>;
|
|
74
|
+
_RechartsLegend = rc.Legend as unknown as React.ComponentType<Record<string, unknown>>;
|
|
75
|
+
} catch {
|
|
76
|
+
_chartFailed = true;
|
|
77
|
+
if (process.env.NODE_ENV === 'development') {
|
|
78
|
+
console.warn(
|
|
79
|
+
'[@usefragments/ui] Chart: recharts is not installed. ' +
|
|
80
|
+
'Install it with: npm install recharts'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
76
85
|
}
|
|
86
|
+
return _chartLoadPromise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Kick off the lazy recharts load on mount and re-render once it settles. */
|
|
90
|
+
function useChartDeps(): boolean {
|
|
91
|
+
const [, rerender] = React.useReducer((n: number) => n + 1, 0);
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (_RechartsTooltip || _chartFailed) return;
|
|
94
|
+
let active = true;
|
|
95
|
+
void loadChartDeps().then(() => {
|
|
96
|
+
if (active) rerender();
|
|
97
|
+
});
|
|
98
|
+
return () => {
|
|
99
|
+
active = false;
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
return _RechartsTooltip !== null;
|
|
77
103
|
}
|
|
78
104
|
|
|
79
105
|
|
|
@@ -252,7 +278,7 @@ export function ChartTooltip({
|
|
|
252
278
|
content,
|
|
253
279
|
...props
|
|
254
280
|
}: ChartTooltipProps) {
|
|
255
|
-
|
|
281
|
+
const chartReady = useChartDeps();
|
|
256
282
|
|
|
257
283
|
const defaultContent = React.useCallback(
|
|
258
284
|
|
|
@@ -269,13 +295,8 @@ export function ChartTooltip({
|
|
|
269
295
|
[indicator, hideLabel, hideIndicator, labelFormatter, valueFormatter],
|
|
270
296
|
);
|
|
271
297
|
|
|
272
|
-
if
|
|
273
|
-
|
|
274
|
-
console.warn(
|
|
275
|
-
'[@usefragments/ui] Chart: recharts is not installed. ' +
|
|
276
|
-
'Install it with: npm install recharts'
|
|
277
|
-
);
|
|
278
|
-
}
|
|
298
|
+
// Nothing until recharts resolves; nothing at all if it is not installed.
|
|
299
|
+
if (!chartReady || !_RechartsTooltip) {
|
|
279
300
|
return null;
|
|
280
301
|
}
|
|
281
302
|
|
|
@@ -330,19 +351,13 @@ type ChartLegendProps = RechartsLegendProps & {
|
|
|
330
351
|
};
|
|
331
352
|
|
|
332
353
|
export function ChartLegend({ content, ...props }: ChartLegendProps) {
|
|
333
|
-
|
|
354
|
+
const chartReady = useChartDeps();
|
|
334
355
|
|
|
335
356
|
const defaultContent = (legendProps: Record<string, unknown>) => (
|
|
336
357
|
<ChartLegendContent {...(legendProps as ChartLegendContentProps)} />
|
|
337
358
|
);
|
|
338
359
|
|
|
339
|
-
if (
|
|
340
|
-
if (_chartFailed && process.env.NODE_ENV === 'development') {
|
|
341
|
-
console.warn(
|
|
342
|
-
'[@usefragments/ui] Chart: recharts is not installed. ' +
|
|
343
|
-
'Install it with: npm install recharts'
|
|
344
|
-
);
|
|
345
|
-
}
|
|
360
|
+
if (!chartReady || !_RechartsLegend) {
|
|
346
361
|
return null;
|
|
347
362
|
}
|
|
348
363
|
|
|
@@ -362,4 +377,6 @@ export const Chart = Object.assign(ChartContainer, {
|
|
|
362
377
|
TooltipContent: ChartTooltipContent,
|
|
363
378
|
Legend: ChartLegend,
|
|
364
379
|
LegendContent: ChartLegendContent,
|
|
380
|
+
/** Start resolving recharts before first render (optional). */
|
|
381
|
+
preload: loadChartDeps,
|
|
365
382
|
});
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
"diff"
|
|
13
13
|
],
|
|
14
14
|
"status": "stable",
|
|
15
|
+
"dependencies": [
|
|
16
|
+
{
|
|
17
|
+
"name": "shiki",
|
|
18
|
+
"version": ">=1.0.0",
|
|
19
|
+
"reason": "Optional — provides syntax highlighting; without it code renders unhighlighted."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
15
22
|
"sourcePath": "src/components/CodeBlock/index.tsx",
|
|
16
23
|
"exportName": "CodeBlock",
|
|
17
24
|
"propsSummary": [
|
|
@@ -318,9 +318,14 @@ $fui-code-scrollbar-thumb-hover: rgba(255, 255, 255, 0.2) !default;
|
|
|
318
318
|
word-break: break-word;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// `display: block` here would double-space every wrapped block: shiki emits a
|
|
322
|
+
// newline between each `.line`, and under `pre-wrap` that newline becomes a
|
|
323
|
+
// line box of its own the moment the lines stop being inline. The base rule's
|
|
324
|
+
// `inline-block` keeps it in the inline flow, and a full-width inline-block
|
|
325
|
+
// still wraps and still grows taller, which is all this variant needs.
|
|
321
326
|
:global(.line) {
|
|
322
|
-
display: block;
|
|
323
|
-
width:
|
|
327
|
+
display: inline-block;
|
|
328
|
+
width: 100%;
|
|
324
329
|
}
|
|
325
330
|
}
|
|
326
331
|
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
"theme"
|
|
13
13
|
],
|
|
14
14
|
"status": "stable",
|
|
15
|
+
"dependencies": [
|
|
16
|
+
{
|
|
17
|
+
"name": "react-colorful",
|
|
18
|
+
"version": ">=5.0.0",
|
|
19
|
+
"reason": "Optional — renders the visual picker surface; the swatch and hex input work without it."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
15
22
|
"sourcePath": "src/components/ColorPicker/index.tsx",
|
|
16
23
|
"exportName": "ColorPicker",
|
|
17
24
|
"propsSummary": [
|
|
@@ -200,7 +207,8 @@
|
|
|
200
207
|
"ai": {
|
|
201
208
|
"compositionPattern": "compound",
|
|
202
209
|
"subComponents": [
|
|
203
|
-
"Root"
|
|
210
|
+
"Root",
|
|
211
|
+
"preload"
|
|
204
212
|
]
|
|
205
213
|
},
|
|
206
214
|
"provenance": {
|
|
@@ -7,20 +7,48 @@ import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
|
7
7
|
// Lazy-loaded dependency (react-colorful)
|
|
8
8
|
// ============================================
|
|
9
9
|
|
|
10
|
-
let _HexColorPicker:
|
|
11
|
-
|
|
10
|
+
let _HexColorPicker: React.ComponentType<{
|
|
11
|
+
color: string;
|
|
12
|
+
onChange: (color: string) => void;
|
|
13
|
+
}> | null = null;
|
|
14
|
+
let _colorfulLoadPromise: Promise<void> | null = null;
|
|
12
15
|
let _colorfulFailed = false;
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// Resolved with import() rather than require(): browser ESM bundles have no
|
|
18
|
+
// `require`, so the synchronous shape hid the picker even when installed.
|
|
19
|
+
function loadColorfulDeps(): Promise<void> {
|
|
20
|
+
if (!_colorfulLoadPromise) {
|
|
21
|
+
_colorfulLoadPromise = (async () => {
|
|
22
|
+
try {
|
|
23
|
+
const rc = await import("react-colorful");
|
|
24
|
+
_HexColorPicker = rc.HexColorPicker;
|
|
25
|
+
} catch {
|
|
26
|
+
_colorfulFailed = true;
|
|
27
|
+
if (process.env.NODE_ENV === "development") {
|
|
28
|
+
console.warn(
|
|
29
|
+
"[@usefragments/ui] ColorPicker: react-colorful is not installed. " +
|
|
30
|
+
"Install it with: npm install react-colorful"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
23
35
|
}
|
|
36
|
+
return _colorfulLoadPromise;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Kick off the lazy react-colorful load on mount and re-render once it settles. */
|
|
40
|
+
function useColorfulDeps(): void {
|
|
41
|
+
const [, rerender] = React.useReducer((n: number) => n + 1, 0);
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
if (_HexColorPicker || _colorfulFailed) return;
|
|
44
|
+
let active = true;
|
|
45
|
+
void loadColorfulDeps().then(() => {
|
|
46
|
+
if (active) rerender();
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
active = false;
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
24
52
|
}
|
|
25
53
|
import { Field } from "@base-ui/react/field";
|
|
26
54
|
import { useResolvedControlSize } from "../ComponentDefaults";
|
|
@@ -75,7 +103,7 @@ const ColorPickerRoot = React.forwardRef<HTMLDivElement, ColorPickerProps>(funct
|
|
|
75
103
|
) {
|
|
76
104
|
const size = useResolvedControlSize(sizeProp);
|
|
77
105
|
const resolvedHelperText = helperText ?? description;
|
|
78
|
-
|
|
106
|
+
useColorfulDeps();
|
|
79
107
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
80
108
|
const [inputValue, setInputValue] = React.useState(value ?? defaultValue);
|
|
81
109
|
const displayValue = value !== undefined ? value : internalValue;
|
|
@@ -179,4 +207,6 @@ const ColorPickerRoot = React.forwardRef<HTMLDivElement, ColorPickerProps>(funct
|
|
|
179
207
|
|
|
180
208
|
export const ColorPicker = Object.assign(ColorPickerRoot, {
|
|
181
209
|
Root: ColorPickerRoot,
|
|
210
|
+
/** Start resolving react-colorful before first render (optional). */
|
|
211
|
+
preload: loadColorfulDeps,
|
|
182
212
|
});
|
|
@@ -194,22 +194,13 @@
|
|
|
194
194
|
@include surface-elevated;
|
|
195
195
|
@include popup-container;
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Item height derived from text-base font + vertical padding
|
|
200
|
-
--_combobox-item-h: calc(
|
|
201
|
-
var(--fui-font-size-sm, #{$fui-font-size-sm}) *
|
|
202
|
-
var(--fui-line-height-normal, #{$fui-line-height-normal}) +
|
|
203
|
-
var(--fui-space-2, #{$fui-space-2}) * 2
|
|
204
|
-
);
|
|
197
|
+
@include popup-scroll-viewport;
|
|
205
198
|
|
|
199
|
+
border-color: var(--fui-form-group-border, $fui-border);
|
|
206
200
|
min-width: var(--anchor-width);
|
|
207
|
-
// Show N items + half-peek scroll hint (default 4.5 items)
|
|
201
|
+
// Show N items + half-peek scroll hint (default 4.5 items).
|
|
208
202
|
// !important needed to override Base UI's inline max-height: 100%
|
|
209
|
-
max-height:
|
|
210
|
-
var(--_combobox-item-h) * var(--fui-select-max-items, 4.5) +
|
|
211
|
-
var(--fui-space-1, #{$fui-space-1}) * 2
|
|
212
|
-
) !important;
|
|
203
|
+
max-height: var(--_popup-scroll-max-h) !important;
|
|
213
204
|
overflow-y: auto !important;
|
|
214
205
|
|
|
215
206
|
// Animation
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"exportName": "ConversationList",
|
|
17
17
|
"propsSummary": [
|
|
18
18
|
"children: node (required)",
|
|
19
|
+
"showAvatars: boolean (default: true)",
|
|
19
20
|
"autoScroll: union (default: smart)",
|
|
20
21
|
"onScrollTop: function",
|
|
21
22
|
"loadingHistory: boolean (default: false)",
|
|
@@ -29,6 +30,12 @@
|
|
|
29
30
|
"description": "Message components",
|
|
30
31
|
"required": true
|
|
31
32
|
},
|
|
33
|
+
"showAvatars": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"description": "Show avatars for messages and typing indicators in this conversation",
|
|
36
|
+
"default": "true",
|
|
37
|
+
"required": false
|
|
38
|
+
},
|
|
32
39
|
"autoScroll": {
|
|
33
40
|
"type": "union",
|
|
34
41
|
"description": "Auto-scroll behavior: true (always), false (never), or \"smart\" (only when near bottom)",
|
|
@@ -78,6 +85,8 @@
|
|
|
78
85
|
],
|
|
79
86
|
"guidelines": [
|
|
80
87
|
"Use autoScroll=\"smart\" for best UX (only auto-scrolls when near bottom)",
|
|
88
|
+
"Set showAvatars={false} for avatarless transcripts; messages remove their outer inline inset automatically",
|
|
89
|
+
"Conversation chrome uses neutral surfaces and the body foreground; reserve brand accent for actions or semantic state",
|
|
81
90
|
"Implement onScrollTop for loading older messages",
|
|
82
91
|
"ConversationList composes your onScroll handler with internal auto-scroll/history logic",
|
|
83
92
|
"Provide an emptyState for new conversations",
|
|
@@ -85,7 +94,7 @@
|
|
|
85
94
|
],
|
|
86
95
|
"accessibility": [
|
|
87
96
|
"Uses proper ARIA roles for separators",
|
|
88
|
-
"Typing indicator
|
|
97
|
+
"Typing indicator uses a status role with an aria-label",
|
|
89
98
|
"Smooth scroll respects reduced motion preferences",
|
|
90
99
|
"Keyboard navigation works within scrollable container"
|
|
91
100
|
]
|
|
@@ -101,6 +110,11 @@
|
|
|
101
110
|
"description": "Messages grouped by date",
|
|
102
111
|
"code": "<ConversationList>\n <ConversationList.DateSeparator date={new Date(Date.now() - 86400000)} />\n <Message role=\"user\">\n <Message.Content>A message from yesterday</Message.Content>\n </Message>\n <ConversationList.DateSeparator date={new Date()} />\n <Message role=\"assistant\">\n <Message.Content>And a message from today!</Message.Content>\n </Message>\n</ConversationList>"
|
|
103
112
|
},
|
|
113
|
+
{
|
|
114
|
+
"name": "Without Avatars",
|
|
115
|
+
"description": "Compact transcript without message or typing-indicator avatars",
|
|
116
|
+
"code": "<ConversationList showAvatars={false}>\n <Message role=\"user\">\n <Message.Content>Hello!</Message.Content>\n </Message>\n <Message role=\"assistant\">\n <Message.Content>How can I help?</Message.Content>\n </Message>\n</ConversationList>"
|
|
117
|
+
},
|
|
104
118
|
{
|
|
105
119
|
"name": "With Typing Indicator",
|
|
106
120
|
"description": "Shows assistant is typing",
|
|
@@ -132,6 +146,7 @@
|
|
|
132
146
|
"contract": {
|
|
133
147
|
"propsSummary": [
|
|
134
148
|
"children: ReactNode - Message components",
|
|
149
|
+
"showAvatars: boolean - show message and typing-indicator avatars (default: true)",
|
|
135
150
|
"autoScroll: boolean | \"smart\" - scroll behavior (default: \"smart\")",
|
|
136
151
|
"onScrollTop: (event?) => void - callback for loading history (receives scroll event)",
|
|
137
152
|
"loadingHistory: boolean - show history loading spinner",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
display: flex;
|
|
10
10
|
flex-direction: column;
|
|
11
11
|
flex: 1;
|
|
12
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
12
13
|
overflow-y: auto;
|
|
13
14
|
overflow-x: hidden;
|
|
14
15
|
scroll-behavior: smooth;
|
|
@@ -25,6 +26,13 @@
|
|
|
25
26
|
gap: var(--fui-space-2, $fui-space-2);
|
|
26
27
|
padding: var(--fui-space-3, $fui-space-3);
|
|
27
28
|
min-height: 100%;
|
|
29
|
+
// Own the full height of the conversation. As a shrinkable flex item it was
|
|
30
|
+
// squashed to the height of the scroll port while its messages overflowed
|
|
31
|
+
// past it — the list still scrolled, because the overflow lands on the
|
|
32
|
+
// scroller, but this element reported a constant height no matter how much
|
|
33
|
+
// was in it. Anything measuring the conversation, auto-scroll included, was
|
|
34
|
+
// measuring the window rather than the content.
|
|
35
|
+
flex-shrink: 0;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
// ============================================
|
|
@@ -95,6 +103,10 @@
|
|
|
95
103
|
padding: var(--fui-space-2, $fui-space-2);
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
.withoutAvatar {
|
|
107
|
+
padding-inline: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
.typingAvatar {
|
|
99
111
|
flex-shrink: 0;
|
|
100
112
|
display: flex;
|