@eifi1/ui-kit 0.10.0 → 0.11.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/README.md +20 -16
- package/dist/components/alert-banner.d.ts +26 -1
- package/dist/components/alert-banner.js +10 -7
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -0
- package/dist/components/bulk-action-bar.d.ts +51 -5
- package/dist/components/bulk-action-bar.js +88 -41
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-group.d.ts +58 -3
- package/dist/components/button-group.js +56 -5
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +2 -0
- package/dist/components/calendar-heatmap.d.ts +124 -0
- package/dist/components/calendar-heatmap.js +295 -0
- package/dist/components/calendar-heatmap.js.map +1 -0
- package/dist/components/choice-card.d.ts +34 -2
- package/dist/components/choice-card.js +49 -0
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/copy-button.d.ts +1 -0
- package/dist/components/data-table.js +2 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/field.d.ts +43 -0
- package/dist/components/field.js +52 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/file-button.d.ts +1 -0
- package/dist/components/file-dropzone.d.ts +1 -0
- package/dist/components/floating-panel.d.ts +147 -4
- package/dist/components/floating-panel.js +188 -28
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/list.d.ts +14 -1
- package/dist/components/list.js +7 -1
- package/dist/components/list.js.map +1 -1
- package/dist/components/menu-item.d.ts +11 -1
- package/dist/components/menu-item.js +6 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +70 -5
- package/dist/components/mini-calendar.js +166 -62
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/nav-pills.d.ts +66 -0
- package/dist/components/nav-pills.js +65 -0
- package/dist/components/nav-pills.js.map +1 -0
- package/dist/components/number-field.d.ts +2 -0
- package/dist/components/number-input.d.ts +2 -0
- package/dist/components/numpad-sheet.d.ts +2 -0
- package/dist/components/progress-bar.d.ts +31 -2
- package/dist/components/progress-bar.js +73 -7
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/series-chart.d.ts +16 -0
- package/dist/components/series-chart.js +4 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.d.ts +1 -0
- package/dist/components/status-dot.d.ts +6 -2
- package/dist/components/status-dot.js +6 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/table.d.ts +40 -8
- package/dist/components/table.js +41 -7
- package/dist/components/table.js.map +1 -1
- package/dist/components/text.d.ts +8 -1
- package/dist/components/text.js +1 -0
- package/dist/components/text.js.map +1 -1
- package/dist/components/time-input.d.ts +1 -0
- package/dist/components/toggle-group.d.ts +15 -0
- package/dist/components/toggle-group.js +23 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/tooltip.d.ts +11 -3
- package/dist/components/tooltip.js +2 -0
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +55 -4
- package/dist/components/ui.js +84 -7
- package/dist/components/ui.js.map +1 -1
- package/dist/components/user-avatar.d.ts +25 -3
- package/dist/components/user-avatar.js +32 -3
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +1 -0
- package/dist/i18n/defaults.d.ts +2 -0
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +3 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +2 -0
- package/dist/i18n/locales/de-CH.d.ts +2 -0
- package/dist/i18n/locales/de-informal.d.ts +2 -0
- package/dist/i18n/locales/de.d.ts +2 -0
- package/dist/i18n/locales/de.js +9 -1
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +2 -0
- package/dist/i18n/locales/es.js +9 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +2 -0
- package/dist/i18n/locales/fr.js +9 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +2 -0
- package/dist/i18n/locales/hu.js +9 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +2 -0
- package/dist/i18n/locales/it.js +9 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +2 -0
- package/dist/i18n/locales/zh.js +9 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +14 -10
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/clipping.d.ts +21 -5
- package/dist/lib/clipping.js +3 -0
- package/dist/lib/clipping.js.map +1 -1
- package/dist/lib/dates.d.ts +17 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/rhf/form.d.ts +1 -0
- package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
- package/dist/rhf/use-rhf-wizard-step.js +38 -0
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
- package/dist/rhf.d.ts +2 -0
- package/dist/rhf.js +1 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +73 -3
- package/dist/shell/topbar-action-menu.js +100 -27
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +2 -1
- package/dist/wizard/stepper-nav.d.ts +1 -0
- package/dist/wizard/wizard-context.d.ts +7 -1
- package/dist/wizard/wizard-context.js +4 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/alert-banner.tsx +36 -7
- package/src/components/bulk-action-bar.tsx +163 -57
- package/src/components/button-group.tsx +124 -4
- package/src/components/calendar-heatmap.tsx +504 -0
- package/src/components/choice-card.tsx +97 -1
- package/src/components/data-table.tsx +7 -0
- package/src/components/field.tsx +137 -0
- package/src/components/floating-panel.tsx +366 -15
- package/src/components/list.tsx +28 -2
- package/src/components/menu-item.tsx +22 -1
- package/src/components/mini-calendar.tsx +245 -54
- package/src/components/nav-pills.tsx +145 -0
- package/src/components/progress-bar.tsx +129 -11
- package/src/components/series-chart.tsx +21 -0
- package/src/components/status-dot.tsx +10 -2
- package/src/components/table.tsx +85 -11
- package/src/components/text.tsx +9 -1
- package/src/components/toggle-group.tsx +48 -3
- package/src/components/tooltip.tsx +17 -3
- package/src/components/ui.tsx +149 -11
- package/src/components/user-avatar.tsx +57 -3
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/kit-labels.tsx +2 -0
- package/src/i18n/locales/de.ts +9 -0
- package/src/i18n/locales/es.ts +9 -0
- package/src/i18n/locales/fr.ts +9 -0
- package/src/i18n/locales/hu.ts +9 -0
- package/src/i18n/locales/it.ts +9 -0
- package/src/i18n/locales/zh.ts +9 -0
- package/src/index.ts +5 -0
- package/src/lib/clipping.ts +22 -4
- package/src/lib/dates.ts +37 -0
- package/src/rhf/use-rhf-wizard-step.ts +113 -0
- package/src/rhf.ts +2 -0
- package/src/shell/topbar-action-menu.tsx +239 -49
- package/src/wizard/wizard-context.tsx +9 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** What a `Field` hands its render-prop children, to spread on the control. */
|
|
5
|
+
interface FieldControlProps {
|
|
6
|
+
/** The id the label's `htmlFor` points at. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The ids of the hint and the error that are rendered, or `undefined` when
|
|
9
|
+
* neither is. */
|
|
10
|
+
"aria-describedby": string | undefined;
|
|
11
|
+
/** `true` while the field has an error, `undefined` otherwise. */
|
|
12
|
+
"aria-invalid": true | undefined;
|
|
13
|
+
/** `true` when the field is `required`. The label's star is `aria-hidden`; this
|
|
14
|
+
* is what a screen reader hears instead. */
|
|
15
|
+
"aria-required": true | undefined;
|
|
16
|
+
}
|
|
17
|
+
interface FieldProps {
|
|
18
|
+
/** The label above the control. Omit it for a group whose control is labelled
|
|
19
|
+
* some other way; the hint and error still render. */
|
|
20
|
+
label?: ReactNode;
|
|
21
|
+
/** Draws the kit's required mark after the label, and sets `aria-required` on the
|
|
22
|
+
* render-prop control. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Muted helper text below the control. */
|
|
25
|
+
hint?: ReactNode;
|
|
26
|
+
/** The error below the control. `null`, `false` and `""` are no error — what a
|
|
27
|
+
* `touched && errors.x` evaluates to on the happy path. */
|
|
28
|
+
error?: ReactNode;
|
|
29
|
+
/** The control's id. Defaults to a generated one; set it when the control carries
|
|
30
|
+
* its own id (plain children). */
|
|
31
|
+
htmlFor?: string;
|
|
32
|
+
/** Dims the label with its control. */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/** The label's size: `sm` for a dense row, `md` otherwise. */
|
|
35
|
+
labelSize?: "sm" | "md";
|
|
36
|
+
/** Wrapper className. */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** The control. A function receives {@link FieldControlProps} to spread on it. */
|
|
39
|
+
children: ReactNode | ((control: FieldControlProps) => ReactNode);
|
|
40
|
+
}
|
|
41
|
+
declare function Field({ label, required, hint, error, htmlFor, disabled, labelSize, className, children, }: FieldProps): react.JSX.Element;
|
|
42
|
+
|
|
43
|
+
export { Field, type FieldControlProps, type FieldProps };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Label } from "./ui.js";
|
|
6
|
+
const isShown = (node) => node !== void 0 && node !== null && node !== false && node !== "";
|
|
7
|
+
function Field({
|
|
8
|
+
label,
|
|
9
|
+
required,
|
|
10
|
+
hint,
|
|
11
|
+
error,
|
|
12
|
+
htmlFor,
|
|
13
|
+
disabled,
|
|
14
|
+
labelSize,
|
|
15
|
+
className,
|
|
16
|
+
children
|
|
17
|
+
}) {
|
|
18
|
+
const generated = useId();
|
|
19
|
+
const id = htmlFor ?? `${generated}-control`;
|
|
20
|
+
const hintId = `${generated}-hint`;
|
|
21
|
+
const errorId = `${generated}-error`;
|
|
22
|
+
const hasHint = isShown(hint);
|
|
23
|
+
const hasError = isShown(error);
|
|
24
|
+
const describedBy = [hasHint && hintId, hasError && errorId].filter(Boolean).join(" ");
|
|
25
|
+
const control = {
|
|
26
|
+
id,
|
|
27
|
+
"aria-describedby": describedBy || void 0,
|
|
28
|
+
"aria-invalid": hasError || void 0,
|
|
29
|
+
"aria-required": required || void 0
|
|
30
|
+
};
|
|
31
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "field", className: cn("grid gap-1.5", className), children: [
|
|
32
|
+
label !== void 0 && /* @__PURE__ */ jsx(
|
|
33
|
+
Label,
|
|
34
|
+
{
|
|
35
|
+
htmlFor: id,
|
|
36
|
+
required,
|
|
37
|
+
disabled,
|
|
38
|
+
size: labelSize,
|
|
39
|
+
"data-error": hasError || void 0,
|
|
40
|
+
className: "data-[error=true]:text-[var(--danger)]",
|
|
41
|
+
children: label
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
typeof children === "function" ? children(control) : children,
|
|
45
|
+
hasHint && /* @__PURE__ */ jsx("p", { id: hintId, className: "text-[11px] leading-tight text-[var(--text-muted)]", children: hint }),
|
|
46
|
+
hasError && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-[11px] leading-tight text-[var(--danger)]", children: error })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
Field
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/field.tsx"],"sourcesContent":["/**\n * A field group with its label ABOVE the control: label, control, hint, error.\n *\n * ```tsx\n * <Field label=\"IBAN\" required hint=\"22 characters\" error={errors.iban}>\n * {(ids) => <Input {...ids} value={iban} onChange={…} />}\n * </Field>\n * ```\n *\n * The kit's `Input` and `Select` float their label inside the field, and the pickers\n * draw theirs the same way. That is the wrong shape for a control the kit did not\n * render, a radio group, a range slider, or a form that sets its labels above the\n * fields throughout — which is what kastlan's wizards are, and why it kept its own\n * `WizardField` after the kit dropped the one it had never rendered. This is that\n * group, with the wiring the old one left to the caller: the label names the control\n * through `htmlFor`, and the control is described by the hint and the error that are\n * actually on screen.\n *\n * ## Wiring\n *\n * Pass a function as `children` and it receives {@link FieldControlProps} — `id`,\n * `aria-describedby`, `aria-invalid` and `aria-required` — to spread on the control.\n * Plain children render as they are, for a control that wires itself (a checkbox that\n * carries its own label, a group with `aria-labelledby`); `htmlFor` then points the\n * label at the control's own id, and the hint and error are on screen but describe\n * nothing — the same as the `WizardField` this replaces.\n *\n * ## Decoupled\n *\n * Deliberately knows neither the wizard nor react-hook-form: `error` is a plain\n * value. Pass `wizard.fieldErrors.iban`, a step-local error, or\n * `fieldState.error?.message` from a `FormField` render — the markup is the same\n * whichever of them gates the step, so a step can move between them without its\n * fields changing. (Inside a react-hook-form form, `FormItem` + `FormLabel` from\n * `@eifi1/ui-kit/rhf` do the same job with the ids taken from the form.)\n *\n * The error is NOT `role=\"alert\"`, for the reason the kit's `error` prop gives (see\n * `useFieldError` in components/ui.tsx): it is read when focus reaches the control.\n */\nimport { useId, type ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Label } from \"./ui\";\n\n/** What a `Field` hands its render-prop children, to spread on the control. */\nexport interface FieldControlProps {\n /** The id the label's `htmlFor` points at. */\n id: string;\n /** The ids of the hint and the error that are rendered, or `undefined` when\n * neither is. */\n \"aria-describedby\": string | undefined;\n /** `true` while the field has an error, `undefined` otherwise. */\n \"aria-invalid\": true | undefined;\n /** `true` when the field is `required`. The label's star is `aria-hidden`; this\n * is what a screen reader hears instead. */\n \"aria-required\": true | undefined;\n}\n\nexport interface FieldProps {\n /** The label above the control. Omit it for a group whose control is labelled\n * some other way; the hint and error still render. */\n label?: ReactNode;\n /** Draws the kit's required mark after the label, and sets `aria-required` on the\n * render-prop control. */\n required?: boolean;\n /** Muted helper text below the control. */\n hint?: ReactNode;\n /** The error below the control. `null`, `false` and `\"\"` are no error — what a\n * `touched && errors.x` evaluates to on the happy path. */\n error?: ReactNode;\n /** The control's id. Defaults to a generated one; set it when the control carries\n * its own id (plain children). */\n htmlFor?: string;\n /** Dims the label with its control. */\n disabled?: boolean;\n /** The label's size: `sm` for a dense row, `md` otherwise. */\n labelSize?: \"sm\" | \"md\";\n /** Wrapper className. */\n className?: string;\n /** The control. A function receives {@link FieldControlProps} to spread on it. */\n children: ReactNode | ((control: FieldControlProps) => ReactNode);\n}\n\nconst isShown = (node: ReactNode) =>\n node !== undefined && node !== null && node !== false && node !== \"\";\n\nexport function Field({\n label,\n required,\n hint,\n error,\n htmlFor,\n disabled,\n labelSize,\n className,\n children,\n}: FieldProps) {\n const generated = useId();\n const id = htmlFor ?? `${generated}-control`;\n const hintId = `${generated}-hint`;\n const errorId = `${generated}-error`;\n const hasHint = isShown(hint);\n const hasError = isShown(error);\n const describedBy = [hasHint && hintId, hasError && errorId].filter(Boolean).join(\" \");\n const control: FieldControlProps = {\n id,\n \"aria-describedby\": describedBy || undefined,\n \"aria-invalid\": hasError || undefined,\n \"aria-required\": required || undefined,\n };\n return (\n <div data-slot=\"field\" className={cn(\"grid gap-1.5\", className)}>\n {label !== undefined && (\n <Label\n htmlFor={id}\n required={required}\n disabled={disabled}\n size={labelSize}\n data-error={hasError || undefined}\n className=\"data-[error=true]:text-[var(--danger)]\"\n >\n {label}\n </Label>\n )}\n {typeof children === \"function\" ? children(control) : children}\n {hasHint && (\n <p id={hintId} className=\"text-[11px] leading-tight text-[var(--text-muted)]\">\n {hint}\n </p>\n )}\n {hasError && (\n <p id={errorId} className=\"text-[11px] leading-tight text-[var(--danger)]\">\n {error}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";AA8GI,SAEI,KAFJ;AAvEJ,SAAS,aAA6B;AACtC,SAAS,UAAU;AACnB,SAAS,aAAa;AAyCtB,MAAM,UAAU,CAAC,SACf,SAAS,UAAa,SAAS,QAAQ,SAAS,SAAS,SAAS;AAE7D,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAe;AACb,QAAM,YAAY,MAAM;AACxB,QAAM,KAAK,WAAW,GAAG,SAAS;AAClC,QAAM,SAAS,GAAG,SAAS;AAC3B,QAAM,UAAU,GAAG,SAAS;AAC5B,QAAM,UAAU,QAAQ,IAAI;AAC5B,QAAM,WAAW,QAAQ,KAAK;AAC9B,QAAM,cAAc,CAAC,WAAW,QAAQ,YAAY,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACrF,QAAM,UAA6B;AAAA,IACjC;AAAA,IACA,oBAAoB,eAAe;AAAA,IACnC,gBAAgB,YAAY;AAAA,IAC5B,iBAAiB,YAAY;AAAA,EAC/B;AACA,SACE,qBAAC,SAAI,aAAU,SAAQ,WAAW,GAAG,gBAAgB,SAAS,GAC3D;AAAA,cAAU,UACT;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,cAAY,YAAY;AAAA,QACxB,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IAED,OAAO,aAAa,aAAa,SAAS,OAAO,IAAI;AAAA,IACrD,WACC,oBAAC,OAAE,IAAI,QAAQ,WAAU,sDACtB,gBACH;AAAA,IAED,YACC,oBAAC,OAAE,IAAI,SAAS,WAAU,kDACvB,iBACH;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -3,6 +3,7 @@ import { ComponentPropsWithoutRef, ReactNode, ReactElement } from 'react';
|
|
|
3
3
|
import { Button } from './ui.js';
|
|
4
4
|
import './tooltip.js';
|
|
5
5
|
import '../hooks/use-anchored-rect.js';
|
|
6
|
+
import '../lib/clipping.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A button that opens the file picker — the shape all three apps kept writing by hand
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ButtonHTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
import { ButtonHTMLAttributes, ReactNode, MouseEvent, ReactElement, Ref, HTMLAttributes } from 'react';
|
|
3
|
+
import { TooltipSide } from './tooltip.js';
|
|
4
|
+
import '../hooks/use-anchored-rect.js';
|
|
5
|
+
import '../lib/clipping.js';
|
|
3
6
|
|
|
4
7
|
interface FloatingPanelLabels {
|
|
5
8
|
/** Accessible name of the panel header's X. */
|
|
6
9
|
close: string;
|
|
10
|
+
/**
|
|
11
|
+
* What a {@link FloatingAction}'s count badge adds to its accessible name ("3 new"),
|
|
12
|
+
* read after the label: "Invoices, 3 new". The dot itself is hidden from a reader —
|
|
13
|
+
* a bare "3" read out of context says nothing about what is counted.
|
|
14
|
+
*/
|
|
15
|
+
badge: (count: number) => string;
|
|
7
16
|
}
|
|
8
17
|
declare const DEFAULT_FLOATING_PANEL_LABELS: FloatingPanelLabels;
|
|
9
18
|
/** A LOGICAL corner: `bottom-end` is bottom-right in LTR and bottom-left in RTL. */
|
|
@@ -44,6 +53,49 @@ interface FloatingActionButtonProps extends Omit<ButtonHTMLAttributes<HTMLButton
|
|
|
44
53
|
* in both states; `aria-pressed` already says which one it is in.
|
|
45
54
|
*/
|
|
46
55
|
pressed?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Show `label` beside the icon: the EXTENDED FAB, a pill rather than a disc. For a
|
|
58
|
+
* corner control whose words are the point — kastlan's offline indicator
|
|
59
|
+
* (shared/offline/offline-indicator.tsx:39) is a pill saying "Offline — 2 photo(s)
|
|
60
|
+
* queued" / "Syncing…", hand-placed at `fixed bottom-4 left-4`, i.e. over the
|
|
61
|
+
* bottom nav and on the wrong side in RTL. Same height as the disc (3rem), so a
|
|
62
|
+
* {@link FloatingPanel} card above it still clears it.
|
|
63
|
+
*/
|
|
64
|
+
extended?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Announce `label` whenever it changes, through a polite `role="status"` region — for
|
|
67
|
+
* an extended FAB that REPORTS something (kastlan's offline/sync state), where a
|
|
68
|
+
* sighted user sees the words change and a screen reader user otherwise hears
|
|
69
|
+
* nothing until they happen to land on the button.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ A live region announces changes, not its own arrival: a FAB that mounts already
|
|
72
|
+
* saying "Offline" says it to nobody. Keep it mounted and pass `hidden` while there
|
|
73
|
+
* is nothing to report (kastlan returns `null` when online and idle); the region
|
|
74
|
+
* stays in the page, and the first real status is heard.
|
|
75
|
+
*/
|
|
76
|
+
live?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* `primary` (default): the solid brand disc of a page's main action. `surface`: the
|
|
79
|
+
* page's surface with a hairline border — for a FAB that is a status rather than a
|
|
80
|
+
* call to action, as kastlan's offline pill is (a brand pill would shout "press me"
|
|
81
|
+
* at a user who is merely offline). Ignored while `pressed` is set; a toggle has its
|
|
82
|
+
* own two looks.
|
|
83
|
+
*/
|
|
84
|
+
variant?: "primary" | "surface";
|
|
85
|
+
/**
|
|
86
|
+
* Hover and focus label in the kit {@link Tooltip}, instead of the native `title` —
|
|
87
|
+
* keksdose has ONE tooltip (dev#523) and wrapped its assistant launcher by hand,
|
|
88
|
+
* which cannot work: the tooltip's anchor is a `relative inline-flex` span, and this
|
|
89
|
+
* button is `fixed` and portalled. With `tooltip` the FAB positions a fixed wrapper
|
|
90
|
+
* instead and the button sits in the tooltip inside it. `true` shows the `label`;
|
|
91
|
+
* any other content shows that. Implies no native `title` (whatever `nativeTitle`
|
|
92
|
+
* says): one hover hint, not two. `style` stays on the button; the corner and
|
|
93
|
+
* `offset` move to the wrapper.
|
|
94
|
+
*/
|
|
95
|
+
tooltip?: ReactNode;
|
|
96
|
+
/** Where the {@link tooltip} opens. Default `top`; the bubble is portalled and kept on
|
|
97
|
+
* screen, so a corner button's label is not pushed off the edge. */
|
|
98
|
+
tooltipSide?: TooltipSide;
|
|
47
99
|
ref?: Ref<HTMLButtonElement>;
|
|
48
100
|
}
|
|
49
101
|
/**
|
|
@@ -63,7 +115,7 @@ interface FloatingActionButtonProps extends Omit<ButtonHTMLAttributes<HTMLButton
|
|
|
63
115
|
* Portalled, because `position: fixed` inside any transformed ancestor (an animated
|
|
64
116
|
* page transition) is fixed to that ancestor, not to the screen.
|
|
65
117
|
*/
|
|
66
|
-
declare function FloatingActionButton({ label, icon, corner, offset, nativeTitle, pressed, className, style, type, title, ...rest }: FloatingActionButtonProps): react.JSX.Element;
|
|
118
|
+
declare function FloatingActionButton({ label, icon, corner, offset, nativeTitle, pressed, extended, live, variant, tooltip, tooltipSide, className, style, type, title, ...rest }: FloatingActionButtonProps): react.JSX.Element;
|
|
67
119
|
interface FloatingPanelProps {
|
|
68
120
|
/** The panel's heading, and its accessible name. */
|
|
69
121
|
title: ReactNode;
|
|
@@ -84,6 +136,10 @@ interface FloatingPanelProps {
|
|
|
84
136
|
/** The FAB's native `title` — see {@link FloatingActionButtonProps.nativeTitle}.
|
|
85
137
|
* `false` for keksdose's assistant launcher, under its one-tooltip rule (dev#523). */
|
|
86
138
|
fabNativeTitle?: boolean;
|
|
139
|
+
/** The FAB's kit tooltip — see {@link FloatingActionButtonProps.tooltip}. `true` for
|
|
140
|
+
* keksdose's assistant launcher, which wanted the kit bubble and had no way to
|
|
141
|
+
* put one on a fixed, portalled button (dev#523). */
|
|
142
|
+
fabTooltip?: ReactNode;
|
|
87
143
|
/** Default: `floatingPanel.close` from the {@link UiKitProvider}, else "Close". */
|
|
88
144
|
closeLabel?: string;
|
|
89
145
|
/**
|
|
@@ -129,6 +185,93 @@ interface FloatingPanelProps {
|
|
|
129
185
|
* panel unmounts when closed; an app that must keep a draft across a close keeps it in
|
|
130
186
|
* its own state, where it also survives a reload.
|
|
131
187
|
*/
|
|
132
|
-
declare function FloatingPanel({ title, fabLabel, fabIcon, open: openProp, defaultOpen, onOpenChange, corner, offset, fabNativeTitle, closeLabel, initialFocus, className, bodyClassName, children, }: FloatingPanelProps): react.JSX.Element;
|
|
188
|
+
declare function FloatingPanel({ title, fabLabel, fabIcon, open: openProp, defaultOpen, onOpenChange, corner, offset, fabNativeTitle, fabTooltip, closeLabel, initialFocus, className, bodyClassName, children, }: FloatingPanelProps): react.JSX.Element;
|
|
189
|
+
interface FloatingActionGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "role"> {
|
|
190
|
+
/** Name the group ("Register actions"); a reader then introduces the members as one
|
|
191
|
+
* set. A group with no name is one more unlabelled wrapper. */
|
|
192
|
+
"aria-label"?: string;
|
|
193
|
+
/** Default `bottom-end`. */
|
|
194
|
+
corner?: FloatingCorner;
|
|
195
|
+
/** Distance above the nav / home indicator — see {@link FloatingActionButtonProps.offset}. */
|
|
196
|
+
offset?: string;
|
|
197
|
+
/** {@link FloatingAction}s, in reading order. */
|
|
198
|
+
children: ReactNode;
|
|
199
|
+
ref?: Ref<HTMLDivElement>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A floating pill of {@link FloatingAction}s — one to four `size-12` toggles, actions
|
|
203
|
+
* or links with hairline dividers — pinned to a corner exactly as the
|
|
204
|
+
* {@link FloatingActionButton} is: `--app-nav-h` and the home indicator cleared, a
|
|
205
|
+
* logical corner, portalled.
|
|
206
|
+
*
|
|
207
|
+
* keksdose built it twice by hand: the /transactions phone toolbar
|
|
208
|
+
* (transactions-page.tsx:644 — upcoming and pending toggles, an invoices link with a
|
|
209
|
+
* pending count, and the brand "+") and the feedback page's "awaiting only" toggle
|
|
210
|
+
* (feedback-page.tsx:854, one button in the group anyway, so the two pages look
|
|
211
|
+
* alike). Both at `right-4 bottom-20` — `right`, not `end`, so over the page's leading
|
|
212
|
+
* edge in RTL — with `divide-x`, slate hovers, and each member's `Tooltip` spelled out.
|
|
213
|
+
*
|
|
214
|
+
* `role="group"`, NOT a toolbar: every member keeps its own tab stop, as the
|
|
215
|
+
* hand-built ones did, and there is no arrow-key roving to learn. Hide it where it
|
|
216
|
+
* does not belong with a class (`md:hidden`, as both keksdose pages do).
|
|
217
|
+
*/
|
|
218
|
+
declare function FloatingActionGroup({ corner, offset, className, style, children, ...rest }: FloatingActionGroupProps): react.JSX.Element;
|
|
219
|
+
/** What {@link FloatingActionProps.renderLink} is handed. Spread it onto your router's
|
|
220
|
+
* link, mapping `href` — `renderLink={({ href, ...p }) => <Link to={href} {...p} />}`. */
|
|
221
|
+
interface FloatingActionLinkProps {
|
|
222
|
+
href: string;
|
|
223
|
+
/** The member's whole look — keep it. */
|
|
224
|
+
className: string;
|
|
225
|
+
children: ReactNode;
|
|
226
|
+
"aria-label": string;
|
|
227
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
228
|
+
[key: `aria-${string}`]: string | boolean | number | undefined;
|
|
229
|
+
[key: `data-${string}`]: unknown;
|
|
230
|
+
}
|
|
231
|
+
interface FloatingActionProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onClick"> {
|
|
232
|
+
/** The accessible name, and the tooltip's text. Keep it the same in both states of a
|
|
233
|
+
* toggle; `aria-pressed` says which one it is in. */
|
|
234
|
+
label: string;
|
|
235
|
+
icon: ReactNode;
|
|
236
|
+
/** Make it a toggle: `aria-pressed` and the brand "on" look (the brand glyph on the
|
|
237
|
+
* quiet brand fill), as {@link FloatingActionButtonProps.pressed}. Ignored on a link. */
|
|
238
|
+
pressed?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* `primary`: the brand-filled member, for the group's main action (the /transactions
|
|
241
|
+
* "+"). Default: the quiet one.
|
|
242
|
+
*/
|
|
243
|
+
variant?: "default" | "primary";
|
|
244
|
+
/**
|
|
245
|
+
* A count in a dot at the member's top end corner — the /transactions invoices link's
|
|
246
|
+
* pending count. Nothing is drawn for `0` or less; above 99 it reads `99+`. The count
|
|
247
|
+
* joins the accessible name through `floatingPanel.badge` ("Invoices, 3 new"), or
|
|
248
|
+
* `badgeLabel` when passed.
|
|
249
|
+
*/
|
|
250
|
+
badge?: number;
|
|
251
|
+
/** The badge's words for a reader, replacing `floatingPanel.badge` — "3 to review". */
|
|
252
|
+
badgeLabel?: string;
|
|
253
|
+
/** The dot's colour. Default `warning`, the pending count's. */
|
|
254
|
+
badgeTone?: "warning" | "danger" | "brand";
|
|
255
|
+
/** Show `label` in the kit {@link Tooltip} on hover and focus. Default `true`: the
|
|
256
|
+
* members are icon-only and this is their only visible label. */
|
|
257
|
+
tooltip?: boolean;
|
|
258
|
+
/** Where the tooltip opens. Default `top` — the group sits at the bottom of the screen. */
|
|
259
|
+
tooltipSide?: TooltipSide;
|
|
260
|
+
/** Make it a link — the /transactions invoices member. A plain `<a>` unless
|
|
261
|
+
* `renderLink` hands it to your router. */
|
|
262
|
+
href?: string;
|
|
263
|
+
/** Render the link through your router's `Link` — see {@link FloatingActionLinkProps}. */
|
|
264
|
+
renderLink?: (props: FloatingActionLinkProps) => ReactElement;
|
|
265
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
266
|
+
ref?: Ref<HTMLButtonElement>;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* One member of a {@link FloatingActionGroup}: a `size-12` icon button, toggle or link
|
|
270
|
+
* with its label in a styled tooltip — what each keksdose member wrapped by hand in
|
|
271
|
+
* `<Tooltip portal>`. The tooltip is portalled because the group clips (`overflow-hidden`
|
|
272
|
+
* keeps the brand member's fill inside the pill's corners), and the focus ring is inset
|
|
273
|
+
* for the same reason.
|
|
274
|
+
*/
|
|
275
|
+
declare function FloatingAction({ label, icon, pressed, variant, badge, badgeLabel, badgeTone, tooltip, tooltipSide, href, renderLink, className, type, onClick, ref, ...rest }: FloatingActionProps): react.JSX.Element;
|
|
133
276
|
|
|
134
|
-
export { DEFAULT_FLOATING_PANEL_LABELS, FloatingActionButton, type FloatingActionButtonProps, type FloatingCorner, FloatingPanel, type FloatingPanelLabels, type FloatingPanelProps };
|
|
277
|
+
export { DEFAULT_FLOATING_PANEL_LABELS, FloatingAction, FloatingActionButton, type FloatingActionButtonProps, FloatingActionGroup, type FloatingActionGroupProps, type FloatingActionLinkProps, type FloatingActionProps, type FloatingCorner, FloatingPanel, type FloatingPanelLabels, type FloatingPanelProps };
|
|
@@ -6,7 +6,11 @@ import { X } from "lucide-react";
|
|
|
6
6
|
import { cn } from "../lib/cn.js";
|
|
7
7
|
import { dirOf } from "../lib/direction.js";
|
|
8
8
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
9
|
-
|
|
9
|
+
import { Tooltip } from "./tooltip.js";
|
|
10
|
+
const DEFAULT_FLOATING_PANEL_LABELS = {
|
|
11
|
+
close: "Close",
|
|
12
|
+
badge: (count) => `${count} new`
|
|
13
|
+
};
|
|
10
14
|
const BOTTOM_CLEARANCE = "max(var(--app-nav-h, 0px), env(safe-area-inset-bottom, 0px))";
|
|
11
15
|
const SIDE_GUTTER = "max(1rem, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px))";
|
|
12
16
|
function cornerStyle(corner, lift) {
|
|
@@ -32,6 +36,11 @@ function FloatingActionButton({
|
|
|
32
36
|
offset = "1rem",
|
|
33
37
|
nativeTitle = true,
|
|
34
38
|
pressed,
|
|
39
|
+
extended = false,
|
|
40
|
+
live = false,
|
|
41
|
+
variant = "primary",
|
|
42
|
+
tooltip,
|
|
43
|
+
tooltipSide = "top",
|
|
35
44
|
className,
|
|
36
45
|
style,
|
|
37
46
|
type = "button",
|
|
@@ -40,36 +49,57 @@ function FloatingActionButton({
|
|
|
40
49
|
}) {
|
|
41
50
|
const [marker, dir] = usePortalDir();
|
|
42
51
|
const toggle = pressed !== void 0;
|
|
43
|
-
const
|
|
52
|
+
const tip = tooltip === true ? label : tooltip === false || tooltip === "" ? void 0 : tooltip;
|
|
53
|
+
const wrapped = tip !== void 0 && tip !== null;
|
|
54
|
+
const nativeTip = !wrapped && nativeTitle && title !== "" ? title ?? label : void 0;
|
|
55
|
+
const button = /* @__PURE__ */ jsxs(
|
|
56
|
+
"button",
|
|
57
|
+
{
|
|
58
|
+
...rest,
|
|
59
|
+
type,
|
|
60
|
+
dir,
|
|
61
|
+
"aria-label": label,
|
|
62
|
+
"aria-pressed": pressed ?? rest["aria-pressed"],
|
|
63
|
+
title: nativeTip,
|
|
64
|
+
style: wrapped ? style : { ...cornerStyle(corner, offset), ...style },
|
|
65
|
+
className: cn(
|
|
66
|
+
// Wrapped, the wrapper is the fixed thing and the button sits in it.
|
|
67
|
+
!wrapped && "fixed z-40",
|
|
68
|
+
"inline-flex items-center justify-center rounded-full",
|
|
69
|
+
extended ? "h-12 gap-2 px-4 text-sm font-medium [&_svg]:size-5" : "size-12 [&_svg]:size-6",
|
|
70
|
+
"shadow-lg transition-colors disabled:cursor-default",
|
|
71
|
+
!toggle && variant === "primary" && "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]",
|
|
72
|
+
!toggle && variant === "surface" && "border border-[var(--border)] bg-[var(--bg-surface)] text-[var(--text-primary)] hover:bg-[var(--bg-hover)] disabled:hover:bg-[var(--bg-surface)]",
|
|
73
|
+
// A toggle sits on the page's surface — a solid brand disc would read as
|
|
74
|
+
// "on" in both states — and says "on" in the brand family.
|
|
75
|
+
toggle && "border border-[var(--border)]",
|
|
76
|
+
pressed === false && "bg-[var(--bg-surface)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
|
|
77
|
+
pressed === true && "border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]",
|
|
78
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)] focus-visible:ring-offset-2",
|
|
79
|
+
className
|
|
80
|
+
),
|
|
81
|
+
children: [
|
|
82
|
+
icon,
|
|
83
|
+
extended && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "whitespace-nowrap", children: label })
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
);
|
|
44
87
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
45
88
|
/* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
|
|
46
89
|
typeof document !== "undefined" && createPortal(
|
|
47
|
-
/* @__PURE__ */
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
!toggle && "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]",
|
|
61
|
-
// A toggle sits on the page's surface — a solid brand disc would read as
|
|
62
|
-
// "on" in both states — and says "on" in the brand family.
|
|
63
|
-
toggle && "border border-[var(--border)]",
|
|
64
|
-
pressed === false && "bg-[var(--bg-surface)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
|
|
65
|
-
pressed === true && "border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]",
|
|
66
|
-
"outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)] focus-visible:ring-offset-2",
|
|
67
|
-
"[&_svg]:size-6",
|
|
68
|
-
className
|
|
69
|
-
),
|
|
70
|
-
children: icon
|
|
71
|
-
}
|
|
72
|
-
),
|
|
90
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
|
+
wrapped ? /* @__PURE__ */ jsx(
|
|
92
|
+
"div",
|
|
93
|
+
{
|
|
94
|
+
dir,
|
|
95
|
+
hidden: rest.hidden,
|
|
96
|
+
style: cornerStyle(corner, offset),
|
|
97
|
+
className: "fixed z-40 inline-flex",
|
|
98
|
+
children: /* @__PURE__ */ jsx(Tooltip, { label: tip, side: tooltipSide, portal: true, children: tip === label ? /* @__PURE__ */ jsx(Fragment, { children: button }) : button })
|
|
99
|
+
}
|
|
100
|
+
) : button,
|
|
101
|
+
live && /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: label })
|
|
102
|
+
] }),
|
|
73
103
|
document.body
|
|
74
104
|
)
|
|
75
105
|
] });
|
|
@@ -84,6 +114,7 @@ function FloatingPanel({
|
|
|
84
114
|
corner = "bottom-end",
|
|
85
115
|
offset = "1rem",
|
|
86
116
|
fabNativeTitle,
|
|
117
|
+
fabTooltip,
|
|
87
118
|
closeLabel,
|
|
88
119
|
initialFocus,
|
|
89
120
|
className,
|
|
@@ -143,6 +174,7 @@ function FloatingPanel({
|
|
|
143
174
|
corner,
|
|
144
175
|
offset,
|
|
145
176
|
nativeTitle: fabNativeTitle,
|
|
177
|
+
tooltip: fabTooltip,
|
|
146
178
|
"aria-expanded": open,
|
|
147
179
|
"aria-controls": open ? panelId : void 0,
|
|
148
180
|
onClick: () => {
|
|
@@ -212,9 +244,137 @@ function FloatingPanel({
|
|
|
212
244
|
)
|
|
213
245
|
] });
|
|
214
246
|
}
|
|
247
|
+
function FloatingActionGroup({
|
|
248
|
+
corner = "bottom-end",
|
|
249
|
+
offset = "1rem",
|
|
250
|
+
className,
|
|
251
|
+
style,
|
|
252
|
+
children,
|
|
253
|
+
...rest
|
|
254
|
+
}) {
|
|
255
|
+
const [marker, dir] = usePortalDir();
|
|
256
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
257
|
+
/* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
|
|
258
|
+
typeof document !== "undefined" && createPortal(
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
"div",
|
|
261
|
+
{
|
|
262
|
+
...rest,
|
|
263
|
+
role: "group",
|
|
264
|
+
dir,
|
|
265
|
+
"data-corner": corner,
|
|
266
|
+
style: { ...cornerStyle(corner, offset), ...style },
|
|
267
|
+
className: cn(
|
|
268
|
+
"fixed z-40 inline-flex flex-row overflow-hidden rounded-2xl",
|
|
269
|
+
"border border-[var(--border)] bg-[var(--bg-surface)] shadow-lg",
|
|
270
|
+
// Dividers on the logical start edge of every member but the first, so
|
|
271
|
+
// they sit between the same two members in RTL. The member is the
|
|
272
|
+
// tooltip's wrapper span, which is the direct child.
|
|
273
|
+
"[&>*:not(:first-child)]:border-s [&>*]:border-[var(--border)]",
|
|
274
|
+
className
|
|
275
|
+
),
|
|
276
|
+
children
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
document.body
|
|
280
|
+
)
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
283
|
+
const BADGE_TONE = {
|
|
284
|
+
warning: "bg-[var(--warning)] text-[var(--text-inverse)]",
|
|
285
|
+
danger: "bg-[var(--danger)] text-[var(--danger-contrast)]",
|
|
286
|
+
brand: "bg-[var(--brand)] text-[var(--brand-contrast)]"
|
|
287
|
+
};
|
|
288
|
+
function FloatingAction({
|
|
289
|
+
label,
|
|
290
|
+
icon,
|
|
291
|
+
pressed,
|
|
292
|
+
variant = "default",
|
|
293
|
+
badge,
|
|
294
|
+
badgeLabel,
|
|
295
|
+
badgeTone = "warning",
|
|
296
|
+
tooltip = true,
|
|
297
|
+
tooltipSide = "top",
|
|
298
|
+
href,
|
|
299
|
+
renderLink,
|
|
300
|
+
className,
|
|
301
|
+
type = "button",
|
|
302
|
+
onClick,
|
|
303
|
+
ref,
|
|
304
|
+
...rest
|
|
305
|
+
}) {
|
|
306
|
+
const labels = useKitLabels("floatingPanel", DEFAULT_FLOATING_PANEL_LABELS);
|
|
307
|
+
const counted = badge !== void 0 && badge > 0;
|
|
308
|
+
const name = counted ? `${label}, ${badgeLabel ?? labels.badge(badge)}` : label;
|
|
309
|
+
const isLink = href !== void 0;
|
|
310
|
+
const look = cn(
|
|
311
|
+
"relative flex size-12 items-center justify-center transition-colors",
|
|
312
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--brand)]",
|
|
313
|
+
"disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-5",
|
|
314
|
+
variant === "primary" ? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]" : pressed && !isLink ? "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]" : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
|
|
315
|
+
className
|
|
316
|
+
);
|
|
317
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
318
|
+
icon,
|
|
319
|
+
counted && /* @__PURE__ */ jsx(
|
|
320
|
+
"span",
|
|
321
|
+
{
|
|
322
|
+
"aria-hidden": true,
|
|
323
|
+
"data-badge": "",
|
|
324
|
+
className: cn(
|
|
325
|
+
"pointer-events-none absolute end-1 top-1 h-4 min-w-4 rounded-full px-1 text-center text-[10px] leading-4 font-semibold",
|
|
326
|
+
BADGE_TONE[badgeTone]
|
|
327
|
+
),
|
|
328
|
+
children: badge > 99 ? "99+" : badge
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
] });
|
|
332
|
+
let member;
|
|
333
|
+
if (isLink) {
|
|
334
|
+
const { disabled: _disabled, form: _form, value: _value, name: _name, ...anchorRest } = rest;
|
|
335
|
+
const linkProps = {
|
|
336
|
+
...anchorRest,
|
|
337
|
+
href,
|
|
338
|
+
"aria-label": name,
|
|
339
|
+
onClick,
|
|
340
|
+
className: look,
|
|
341
|
+
children: body
|
|
342
|
+
};
|
|
343
|
+
const { children: content, ...anchor } = linkProps;
|
|
344
|
+
member = renderLink ? /* @__PURE__ */ jsx(RenderedFloatingLink, { render: renderLink, ...linkProps }) : /* @__PURE__ */ jsx("a", { ...anchor, children: content });
|
|
345
|
+
} else {
|
|
346
|
+
member = /* @__PURE__ */ jsx(
|
|
347
|
+
"button",
|
|
348
|
+
{
|
|
349
|
+
...rest,
|
|
350
|
+
ref,
|
|
351
|
+
type,
|
|
352
|
+
"aria-label": name,
|
|
353
|
+
"aria-pressed": pressed ?? rest["aria-pressed"],
|
|
354
|
+
onClick,
|
|
355
|
+
className: look,
|
|
356
|
+
children: body
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
if (!tooltip) return member;
|
|
361
|
+
return (
|
|
362
|
+
// A fragment: the bubble says the member's name, and a description that repeats
|
|
363
|
+
// the name is read twice.
|
|
364
|
+
/* @__PURE__ */ jsx(Tooltip, { label, side: tooltipSide, portal: true, children: /* @__PURE__ */ jsx(Fragment, { children: member }) })
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
function RenderedFloatingLink({
|
|
368
|
+
render,
|
|
369
|
+
...props
|
|
370
|
+
}) {
|
|
371
|
+
return render(props);
|
|
372
|
+
}
|
|
215
373
|
export {
|
|
216
374
|
DEFAULT_FLOATING_PANEL_LABELS,
|
|
375
|
+
FloatingAction,
|
|
217
376
|
FloatingActionButton,
|
|
377
|
+
FloatingActionGroup,
|
|
218
378
|
FloatingPanel
|
|
219
379
|
};
|
|
220
380
|
//# sourceMappingURL=floating-panel.js.map
|