@flytedan/flytebot-design-system 0.36.0 → 0.38.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/index.cjs +37 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +37 -22
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/styles/components.css +53 -0
package/dist/index.d.cts
CHANGED
|
@@ -19,8 +19,18 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
19
19
|
loading?: boolean;
|
|
20
20
|
block?: boolean;
|
|
21
21
|
as?: "button" | "a";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** When `disabled` (or `loading`), stay in the tab order instead of going natively
|
|
23
|
+
* `disabled`: renders `aria-disabled="true"` rather than the `disabled` attribute,
|
|
24
|
+
* keeps the `:focus-visible` ring, and swallows click/Enter/Space without ever
|
|
25
|
+
* submitting a form. Use whenever a button's disabled state can flip while it holds
|
|
26
|
+
* focus (e.g. a controlled panel re-seeding mid-edit) — a native `disabled` drops focus
|
|
27
|
+
* to `<body>` the instant it applies, and nothing can intercept or redirect that.
|
|
28
|
+
* Also makes `aria-disabled` always explicit ("true"/"false", never absent): since the
|
|
29
|
+
* browser can no longer tell assistive tech this button's state from `disabled` itself,
|
|
30
|
+
* the ARIA state has to say so on its own at every render, not only while disabled. */
|
|
31
|
+
focusableWhenDisabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function Button({ children, variant, size, icon, trailingIcon, arrow, loading, disabled, focusableWhenDisabled, block, as, className, onClick, onKeyDown, ...rest }: ButtonProps): React.JSX.Element;
|
|
24
34
|
|
|
25
35
|
/** A square action carrying only a Phosphor glyph. Always needs a label for a11y. */
|
|
26
36
|
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -2239,12 +2249,19 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
|
|
|
2239
2249
|
error?: string;
|
|
2240
2250
|
required?: boolean;
|
|
2241
2251
|
rows?: number;
|
|
2252
|
+
/** Read-only plus `aria-busy`, with the same visible look as `disabled` (the
|
|
2253
|
+
* `.fd-input-disabled` tokens) — for a value an in-flight request is about to replace
|
|
2254
|
+
* while the field stays reachable. Unlike `disabled`, `busy` never sets the native
|
|
2255
|
+
* `disabled` attribute: `readOnly` blocks editing without removing the control from the
|
|
2256
|
+
* tab order or dropping focus that was already on it, so a keyboard user's in-progress
|
|
2257
|
+
* action (e.g. Ctrl+Enter to resolve) still reaches it while busy. */
|
|
2258
|
+
busy?: boolean;
|
|
2242
2259
|
/** Applied to the outer .fd-field element — this is where width belongs. */
|
|
2243
2260
|
style?: React.CSSProperties;
|
|
2244
2261
|
/** Applied to the outer .fd-field element, like `style` — the same rule as Input. */
|
|
2245
2262
|
className?: string;
|
|
2246
2263
|
}
|
|
2247
|
-
declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2264
|
+
declare function Textarea({ label, help, error, required, rows, disabled, busy, readOnly, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2248
2265
|
|
|
2249
2266
|
/** Numeric field with thousands formatting while unfocused, +/- steppers (press once for
|
|
2250
2267
|
* one step, press and hold to repeat with acceleration), ArrowUp/ArrowDown (Shift for
|
package/dist/index.d.ts
CHANGED
|
@@ -19,8 +19,18 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
19
19
|
loading?: boolean;
|
|
20
20
|
block?: boolean;
|
|
21
21
|
as?: "button" | "a";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** When `disabled` (or `loading`), stay in the tab order instead of going natively
|
|
23
|
+
* `disabled`: renders `aria-disabled="true"` rather than the `disabled` attribute,
|
|
24
|
+
* keeps the `:focus-visible` ring, and swallows click/Enter/Space without ever
|
|
25
|
+
* submitting a form. Use whenever a button's disabled state can flip while it holds
|
|
26
|
+
* focus (e.g. a controlled panel re-seeding mid-edit) — a native `disabled` drops focus
|
|
27
|
+
* to `<body>` the instant it applies, and nothing can intercept or redirect that.
|
|
28
|
+
* Also makes `aria-disabled` always explicit ("true"/"false", never absent): since the
|
|
29
|
+
* browser can no longer tell assistive tech this button's state from `disabled` itself,
|
|
30
|
+
* the ARIA state has to say so on its own at every render, not only while disabled. */
|
|
31
|
+
focusableWhenDisabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function Button({ children, variant, size, icon, trailingIcon, arrow, loading, disabled, focusableWhenDisabled, block, as, className, onClick, onKeyDown, ...rest }: ButtonProps): React.JSX.Element;
|
|
24
34
|
|
|
25
35
|
/** A square action carrying only a Phosphor glyph. Always needs a label for a11y. */
|
|
26
36
|
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -2239,12 +2249,19 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
|
|
|
2239
2249
|
error?: string;
|
|
2240
2250
|
required?: boolean;
|
|
2241
2251
|
rows?: number;
|
|
2252
|
+
/** Read-only plus `aria-busy`, with the same visible look as `disabled` (the
|
|
2253
|
+
* `.fd-input-disabled` tokens) — for a value an in-flight request is about to replace
|
|
2254
|
+
* while the field stays reachable. Unlike `disabled`, `busy` never sets the native
|
|
2255
|
+
* `disabled` attribute: `readOnly` blocks editing without removing the control from the
|
|
2256
|
+
* tab order or dropping focus that was already on it, so a keyboard user's in-progress
|
|
2257
|
+
* action (e.g. Ctrl+Enter to resolve) still reaches it while busy. */
|
|
2258
|
+
busy?: boolean;
|
|
2242
2259
|
/** Applied to the outer .fd-field element — this is where width belongs. */
|
|
2243
2260
|
style?: React.CSSProperties;
|
|
2244
2261
|
/** Applied to the outer .fd-field element, like `style` — the same rule as Input. */
|
|
2245
2262
|
className?: string;
|
|
2246
2263
|
}
|
|
2247
|
-
declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2264
|
+
declare function Textarea({ label, help, error, required, rows, disabled, busy, readOnly, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2248
2265
|
|
|
2249
2266
|
/** Numeric field with thousands formatting while unfocused, +/- steppers (press once for
|
|
2250
2267
|
* one step, press and hold to repeat with acceleration), ArrowUp/ArrowDown (Shift for
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ var VARIANTS = {
|
|
|
8
8
|
danger: "fd-btn-danger",
|
|
9
9
|
link: "fd-btn-link"
|
|
10
10
|
};
|
|
11
|
+
function isActivationKey(key) {
|
|
12
|
+
return key === "Enter" || key === " ";
|
|
13
|
+
}
|
|
11
14
|
function Button({
|
|
12
15
|
children,
|
|
13
16
|
variant = "primary",
|
|
@@ -17,12 +20,16 @@ function Button({
|
|
|
17
20
|
arrow = false,
|
|
18
21
|
loading = false,
|
|
19
22
|
disabled = false,
|
|
23
|
+
focusableWhenDisabled = false,
|
|
20
24
|
block = false,
|
|
21
25
|
as = "button",
|
|
22
26
|
className = "",
|
|
27
|
+
onClick,
|
|
28
|
+
onKeyDown,
|
|
23
29
|
...rest
|
|
24
30
|
}) {
|
|
25
31
|
const Tag2 = as;
|
|
32
|
+
const inert = (disabled || loading) && focusableWhenDisabled;
|
|
26
33
|
const cls = [
|
|
27
34
|
"fd-btn",
|
|
28
35
|
VARIANTS[variant] || VARIANTS.primary,
|
|
@@ -35,9 +42,16 @@ function Button({
|
|
|
35
42
|
Tag2,
|
|
36
43
|
{
|
|
37
44
|
className: cls,
|
|
38
|
-
disabled: Tag2 === "button" ? disabled || loading : void 0,
|
|
39
|
-
"aria-disabled": disabled || loading ? "true" : void 0,
|
|
45
|
+
disabled: Tag2 === "button" ? (disabled || loading) && !focusableWhenDisabled : void 0,
|
|
46
|
+
"aria-disabled": focusableWhenDisabled ? String(disabled || loading) : disabled || loading ? "true" : void 0,
|
|
40
47
|
"aria-busy": loading ? "true" : void 0,
|
|
48
|
+
onClick: inert ? (e) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
} : onClick,
|
|
51
|
+
onKeyDown: inert ? (e) => {
|
|
52
|
+
if (isActivationKey(e.key)) e.preventDefault();
|
|
53
|
+
onKeyDown?.(e);
|
|
54
|
+
} : onKeyDown,
|
|
41
55
|
...rest,
|
|
42
56
|
children: [
|
|
43
57
|
icon && !loading ? /* @__PURE__ */ jsx("i", { className: "ph ph-" + icon, "aria-hidden": "true" }) : null,
|
|
@@ -3196,12 +3210,23 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
|
|
|
3196
3210
|
|
|
3197
3211
|
// src/components/forms/Textarea.tsx
|
|
3198
3212
|
import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
3199
|
-
function Textarea({ label, help, error, required = false, rows = 4, disabled = false, id, className = "", style, ...rest }) {
|
|
3213
|
+
function Textarea({ label, help, error, required = false, rows = 4, disabled = false, busy = false, readOnly, id, className = "", style, ...rest }) {
|
|
3200
3214
|
const fieldId = useFieldId(id);
|
|
3201
|
-
const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
3215
|
+
const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled || busy ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
3202
3216
|
return /* @__PURE__ */ jsxs38("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
|
|
3203
3217
|
label ? /* @__PURE__ */ jsx44(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
|
|
3204
|
-
/* @__PURE__ */ jsx44("div", { className: box, children: /* @__PURE__ */ jsx44(
|
|
3218
|
+
/* @__PURE__ */ jsx44("div", { className: box, children: /* @__PURE__ */ jsx44(
|
|
3219
|
+
"textarea",
|
|
3220
|
+
{
|
|
3221
|
+
id: fieldId,
|
|
3222
|
+
rows,
|
|
3223
|
+
disabled,
|
|
3224
|
+
readOnly: busy || readOnly,
|
|
3225
|
+
"aria-busy": busy || void 0,
|
|
3226
|
+
"aria-invalid": error ? "true" : void 0,
|
|
3227
|
+
...rest
|
|
3228
|
+
}
|
|
3229
|
+
) }),
|
|
3205
3230
|
error ? /* @__PURE__ */ jsxs38("span", { className: "fd-field-error", children: [
|
|
3206
3231
|
/* @__PURE__ */ jsx44("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
3207
3232
|
error
|
|
@@ -3493,6 +3518,7 @@ function MultiSelectPanel({
|
|
|
3493
3518
|
}, [mode2]);
|
|
3494
3519
|
const doResolve = () => {
|
|
3495
3520
|
if (!field.resolve) return;
|
|
3521
|
+
if (pasteLoading) return;
|
|
3496
3522
|
const tokens = Array.from(new Set(pasteText.split(/[\n,]/).map((t) => t.trim()).filter(Boolean)));
|
|
3497
3523
|
if (!tokens.length) return;
|
|
3498
3524
|
setPasteLoading(true);
|
|
@@ -3575,7 +3601,7 @@ function MultiSelectPanel({
|
|
|
3575
3601
|
rows: 4,
|
|
3576
3602
|
placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
|
|
3577
3603
|
value: pasteText,
|
|
3578
|
-
|
|
3604
|
+
busy: pasteLoading,
|
|
3579
3605
|
onChange: (e) => setPasteText(e.target.value),
|
|
3580
3606
|
onKeyDown: (e) => {
|
|
3581
3607
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -3637,19 +3663,9 @@ function MultiSelectPanel({
|
|
|
3637
3663
|
{
|
|
3638
3664
|
size: "sm",
|
|
3639
3665
|
variant: "secondary",
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
e.preventDefault();
|
|
3644
|
-
return;
|
|
3645
|
-
}
|
|
3646
|
-
doResolve();
|
|
3647
|
-
},
|
|
3648
|
-
onKeyDown: (e) => {
|
|
3649
|
-
if (resolveDisabled && (e.key === "Enter" || e.key === " ")) {
|
|
3650
|
-
e.preventDefault();
|
|
3651
|
-
}
|
|
3652
|
-
},
|
|
3666
|
+
disabled: resolveDisabled,
|
|
3667
|
+
focusableWhenDisabled: true,
|
|
3668
|
+
onClick: doResolve,
|
|
3653
3669
|
children: pasteLoading ? "Resolving\u2026" : "Resolve"
|
|
3654
3670
|
}
|
|
3655
3671
|
) : void 0;
|
|
@@ -3796,7 +3812,7 @@ function RangePanel({
|
|
|
3796
3812
|
offset: 6,
|
|
3797
3813
|
width: PANEL_WIDTH.range,
|
|
3798
3814
|
label: field.label,
|
|
3799
|
-
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary",
|
|
3815
|
+
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", disabled: doneDisabled, focusableWhenDisabled: true, onClick: onClose, children: "Done" }),
|
|
3800
3816
|
children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3801
3817
|
/* @__PURE__ */ jsx45("span", { ref: minWrapRef, children: /* @__PURE__ */ jsx45(
|
|
3802
3818
|
NumberInput,
|
|
@@ -3911,7 +3927,6 @@ function DatePanel({
|
|
|
3911
3927
|
};
|
|
3912
3928
|
const doneDisabled = kind === "withinDays" && !validity.allValid();
|
|
3913
3929
|
const confirm = () => {
|
|
3914
|
-
if (doneDisabled) return;
|
|
3915
3930
|
if (kind === "withinDays") {
|
|
3916
3931
|
const shown = shownWithinDays();
|
|
3917
3932
|
if (shown) onCommit(shown);
|
|
@@ -3928,7 +3943,7 @@ function DatePanel({
|
|
|
3928
3943
|
offset: 6,
|
|
3929
3944
|
width: PANEL_WIDTH.date,
|
|
3930
3945
|
label: field.label,
|
|
3931
|
-
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary",
|
|
3946
|
+
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", disabled: doneDisabled, focusableWhenDisabled: true, onClick: confirm, children: "Done" }),
|
|
3932
3947
|
children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3933
3948
|
/* @__PURE__ */ jsx45(
|
|
3934
3949
|
SegmentedControl,
|