omniconn-design-system 1.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/CHANGELOG.md +213 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/components/ActionMenu.js +104 -0
- package/dist/components/ActionMenu.js.map +1 -0
- package/dist/components/Avatar.js +97 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/Badge.js +37 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Button.js +80 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.js +122 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Checkbox.js +68 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/DatePicker.js +624 -0
- package/dist/components/DatePicker.js.map +1 -0
- package/dist/components/Drawer.js +227 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/Field.js +29 -0
- package/dist/components/Field.js.map +1 -0
- package/dist/components/Input.js +32 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/Menu.js +293 -0
- package/dist/components/Menu.js.map +1 -0
- package/dist/components/Modal.js +195 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/PageHeader.js +207 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/Pagination.js +143 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/RadioGroup.js +114 -0
- package/dist/components/RadioGroup.js.map +1 -0
- package/dist/components/SearchBar.js +167 -0
- package/dist/components/SearchBar.js.map +1 -0
- package/dist/components/SegmentedToggle.js +56 -0
- package/dist/components/SegmentedToggle.js.map +1 -0
- package/dist/components/Select.js +338 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/Switch.js +67 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/Table.js +586 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.js +68 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Textarea.js +62 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/i18n/DesignSystemProvider.js +23 -0
- package/dist/i18n/DesignSystemProvider.js.map +1 -0
- package/dist/i18n/strings.ar.js +82 -0
- package/dist/i18n/strings.ar.js.map +1 -0
- package/dist/i18n/strings.en.js +82 -0
- package/dist/i18n/strings.en.js.map +1 -0
- package/dist/i18n/translator.js +82 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/actionVisibility.js +19 -0
- package/dist/lib/actionVisibility.js.map +1 -0
- package/dist/lib/breakpoints.js +69 -0
- package/dist/lib/breakpoints.js.map +1 -0
- package/dist/lib/cn.js +7 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/tone.js +186 -0
- package/dist/lib/tone.js.map +1 -0
- package/dist/lib/useBreakpoint.js +75 -0
- package/dist/lib/useBreakpoint.js.map +1 -0
- package/dist/lib/validation.js +78 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/portal/ModalPortal.js +44 -0
- package/dist/portal/ModalPortal.js.map +1 -0
- package/dist/styles.css +132 -0
- package/dist/utils/localeFormat.js +82 -0
- package/dist/utils/localeFormat.js.map +1 -0
- package/docs/DESIGN-SYSTEM.md +668 -0
- package/package.json +87 -0
- package/tailwind/preset.js +157 -0
- package/types/index.d.ts +645 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
const TONE_CLASSES = {
|
|
5
|
+
neutral: "bg-surface-subtle border-border text-content-muted",
|
|
6
|
+
primary: "bg-primary-50 border-primary-100 text-primary-700",
|
|
7
|
+
success: "bg-success-50 border-success/20 text-success-700",
|
|
8
|
+
warning: "bg-warning-50 border-warning/20 text-warning-700",
|
|
9
|
+
error: "bg-error-50 border-error/20 text-error-700",
|
|
10
|
+
info: "bg-info-50 border-info/20 text-info-700"
|
|
11
|
+
};
|
|
12
|
+
function Badge({
|
|
13
|
+
children,
|
|
14
|
+
tone = "neutral",
|
|
15
|
+
size = "sm",
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}) {
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"span",
|
|
21
|
+
{
|
|
22
|
+
className: cn(
|
|
23
|
+
"inline-flex items-center justify-center font-medium rounded-full border whitespace-nowrap leading-none select-none",
|
|
24
|
+
size === "sm" ? "px-2 py-0.5 text-caption" : "px-2.5 py-1 text-body",
|
|
25
|
+
TONE_CLASSES[tone] || TONE_CLASSES.neutral,
|
|
26
|
+
className
|
|
27
|
+
),
|
|
28
|
+
...props,
|
|
29
|
+
children
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
Badge,
|
|
35
|
+
Badge as default
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=Badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Badge.js","sources":["../../src/components/Badge.jsx"],"sourcesContent":["import React from \"react\";\nimport { cn } from \"../lib/cn\"; // Adjust path to your cn utility\n\nconst TONE_CLASSES = {\n neutral: \"bg-surface-subtle border-border text-content-muted\",\n primary: \"bg-primary-50 border-primary-100 text-primary-700\",\n success: \"bg-success-50 border-success/20 text-success-700\",\n warning: \"bg-warning-50 border-warning/20 text-warning-700\",\n error: \"bg-error-50 border-error/20 text-error-700\",\n info: \"bg-info-50 border-info/20 text-info-700\",\n};\n\nexport function Badge({\n children,\n tone = \"neutral\",\n size = \"sm\",\n className,\n ...props\n}) {\n return (\n <span\n className={cn(\n \"inline-flex items-center justify-center font-medium rounded-full border whitespace-nowrap leading-none select-none\",\n size === \"sm\" ? \"px-2 py-0.5 text-caption\" : \"px-2.5 py-1 text-body\",\n TONE_CLASSES[tone] || TONE_CLASSES.neutral,\n className\n )}\n {...props}\n >\n {children}\n </span>\n );\n}\n\nexport default Badge;"],"names":[],"mappings":";;;AAGA,MAAM,eAAe;AAAA,EACnB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AACR;AAEO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAG;AACD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,SAAS,OAAO,6BAA6B;AAAA,QAC7C,aAAa,IAAI,KAAK,aAAa;AAAA,QACnC;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH;AAAA,IAAA;AAAA,EAAA;AAGP;"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { CircleNotch } from "@phosphor-icons/react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { resolveTone, shiftToken } from "../lib/tone.js";
|
|
6
|
+
const VARIANTS = {
|
|
7
|
+
primary: "bg-primary text-white hover:bg-primary-hover",
|
|
8
|
+
secondary: "bg-secondary text-white hover:bg-secondary-hover",
|
|
9
|
+
outline: "bg-surface text-content border border-border-strong hover:bg-neutral-50",
|
|
10
|
+
ghost: "bg-transparent text-content hover:bg-neutral-100",
|
|
11
|
+
danger: "bg-error text-white hover:bg-error-700",
|
|
12
|
+
// For use on gradient / brand-colored surfaces (e.g. card headers).
|
|
13
|
+
inverse: "bg-transparent text-white hover:bg-white/15"
|
|
14
|
+
};
|
|
15
|
+
const SIZES = {
|
|
16
|
+
sm: "h-8 px-3 text-body gap-1.5",
|
|
17
|
+
md: "h-10 px-4 text-body-strong gap-2",
|
|
18
|
+
lg: "h-12 px-6 text-subsection gap-2"
|
|
19
|
+
};
|
|
20
|
+
const ICON_ONLY_SIZES = {
|
|
21
|
+
sm: "h-8 w-8",
|
|
22
|
+
md: "h-10 w-10",
|
|
23
|
+
lg: "h-12 w-12"
|
|
24
|
+
};
|
|
25
|
+
const ICON_PX = { sm: 16, md: 18, lg: 20 };
|
|
26
|
+
function Button({
|
|
27
|
+
variant = "primary",
|
|
28
|
+
color,
|
|
29
|
+
size = "md",
|
|
30
|
+
icon: Icon,
|
|
31
|
+
iconOnly = false,
|
|
32
|
+
loading = false,
|
|
33
|
+
disabled = false,
|
|
34
|
+
fullWidth = false,
|
|
35
|
+
type = "button",
|
|
36
|
+
className = "",
|
|
37
|
+
children,
|
|
38
|
+
...rest
|
|
39
|
+
}) {
|
|
40
|
+
const isDisabled = disabled || loading;
|
|
41
|
+
const px = ICON_PX[size];
|
|
42
|
+
const [hovered, setHovered] = useState(false);
|
|
43
|
+
const toned = color ? resolveTone(color, "primary") : null;
|
|
44
|
+
const hoverHex = color ? shiftToken(color, 1) : null;
|
|
45
|
+
const tonedStyle = toned ? {
|
|
46
|
+
backgroundColor: hovered && !isDisabled && hoverHex ? hoverHex : toned.hex,
|
|
47
|
+
color: toned.textStyle?.color
|
|
48
|
+
} : void 0;
|
|
49
|
+
return /* @__PURE__ */ jsxs(
|
|
50
|
+
"button",
|
|
51
|
+
{
|
|
52
|
+
type,
|
|
53
|
+
disabled: isDisabled,
|
|
54
|
+
"aria-busy": loading || void 0,
|
|
55
|
+
style: tonedStyle,
|
|
56
|
+
onMouseEnter: toned ? () => setHovered(true) : void 0,
|
|
57
|
+
onMouseLeave: toned ? () => setHovered(false) : void 0,
|
|
58
|
+
className: cn(
|
|
59
|
+
"inline-flex items-center justify-center rounded-md font-body font-medium",
|
|
60
|
+
"transition select-none active:scale-[.98]",
|
|
61
|
+
"focus:outline-none focus-visible:shadow-focus",
|
|
62
|
+
"disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100",
|
|
63
|
+
!toned && (VARIANTS[variant] || VARIANTS.primary),
|
|
64
|
+
iconOnly ? ICON_ONLY_SIZES[size] : SIZES[size],
|
|
65
|
+
fullWidth && "w-full",
|
|
66
|
+
className
|
|
67
|
+
),
|
|
68
|
+
...rest,
|
|
69
|
+
children: [
|
|
70
|
+
loading && /* @__PURE__ */ jsx(CircleNotch, { size: px, weight: "bold", className: "animate-spin" }),
|
|
71
|
+
!loading && Icon && /* @__PURE__ */ jsx(Icon, { size: px, weight: "regular" }),
|
|
72
|
+
!iconOnly && children
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
Button as default
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=Button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../../src/components/Button.jsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { CircleNotch } from \"@phosphor-icons/react\";\nimport { cn } from \"../lib/cn\";\nimport { resolveTone, shiftToken } from \"../lib/tone\";\n\n/**\n * Button — the one button for the whole app.\n *\n * Content shapes:\n * • text only <Button>Save</Button>\n * • icon + text (left) <Button icon={Plus}>Add unit</Button>\n * • icon only <Button icon={Plus} iconOnly aria-label=\"Add\" />\n *\n * Props:\n * variant 'primary'|'secondary'|'outline'|'ghost'|'danger'|'inverse' (default 'primary')\n * color any design-system token — 'success', 'primary-300', 'neutral-800', …\n * When set it overrides `variant`'s colors: the background is the token,\n * the label/icon auto-contrast (white on dark, near-black on light), and\n * hover steps one shade along that token's own ramp. See ./tone.js.\n * size 'sm' | 'md' | 'lg' (default 'md')\n * icon a Phosphor icon component, rendered on the leading side\n * iconOnly square icon button (pass an aria-label for accessibility)\n * loading shows a spinner and disables the button\n * disabled standard disabled state\n * fullWidth stretches to container width\n * ...rest forwarded to <button> (onClick, type, aria-*, …)\n */\n\nconst VARIANTS = {\n primary: \"bg-primary text-white hover:bg-primary-hover\",\n secondary: \"bg-secondary text-white hover:bg-secondary-hover\",\n outline: \"bg-surface text-content border border-border-strong hover:bg-neutral-50\",\n ghost: \"bg-transparent text-content hover:bg-neutral-100\",\n danger: \"bg-error text-white hover:bg-error-700\",\n // For use on gradient / brand-colored surfaces (e.g. card headers).\n inverse: \"bg-transparent text-white hover:bg-white/15\",\n};\n\nconst SIZES = {\n sm: \"h-8 px-3 text-body gap-1.5\",\n md: \"h-10 px-4 text-body-strong gap-2\",\n lg: \"h-12 px-6 text-subsection gap-2\",\n};\n\nconst ICON_ONLY_SIZES = {\n sm: \"h-8 w-8\",\n md: \"h-10 w-10\",\n lg: \"h-12 w-12\",\n};\n\nconst ICON_PX = { sm: 16, md: 18, lg: 20 };\n\nexport default function Button({\n variant = \"primary\",\n color,\n size = \"md\",\n icon: Icon,\n iconOnly = false,\n loading = false,\n disabled = false,\n fullWidth = false,\n type = \"button\",\n className = \"\",\n children,\n ...rest\n}) {\n const isDisabled = disabled || loading;\n const px = ICON_PX[size];\n const [hovered, setHovered] = useState(false);\n\n // `color` is opt-in and overrides `variant`. Hover can't be a Tailwind class here\n // (the token is only known at runtime), so it's a state-driven inline style.\n const toned = color ? resolveTone(color, \"primary\") : null;\n const hoverHex = color ? shiftToken(color, 1) : null;\n const tonedStyle = toned\n ? {\n backgroundColor: hovered && !isDisabled && hoverHex ? hoverHex : toned.hex,\n color: toned.textStyle?.color,\n }\n : undefined;\n\n return (\n <button\n type={type}\n disabled={isDisabled}\n aria-busy={loading || undefined}\n style={tonedStyle}\n onMouseEnter={toned ? () => setHovered(true) : undefined}\n onMouseLeave={toned ? () => setHovered(false) : undefined}\n className={cn(\n \"inline-flex items-center justify-center rounded-md font-body font-medium\",\n \"transition select-none active:scale-[.98]\",\n \"focus:outline-none focus-visible:shadow-focus\",\n \"disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100\",\n !toned && (VARIANTS[variant] || VARIANTS.primary),\n iconOnly ? ICON_ONLY_SIZES[size] : SIZES[size],\n fullWidth && \"w-full\",\n className\n )}\n {...rest}\n >\n {loading && <CircleNotch size={px} weight=\"bold\" className=\"animate-spin\" />}\n {!loading && Icon && <Icon size={px} weight=\"regular\" />}\n {!iconOnly && children}\n </button>\n );\n}\n"],"names":[],"mappings":";;;;;AA4BA,MAAM,WAAW;AAAA,EACf,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA;AAAA,EAER,SAAS;AACX;AAEA,MAAM,QAAQ;AAAA,EACZ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,kBAAkB;AAAA,EACtB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,UAAU,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAA;AAEtC,SAAwB,OAAO;AAAA,EAC7B,UAAU;AAAA,EACV;AAAA,EACA,OAAO;AAAA,EACP,MAAM;AAAA,EACN,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GAAG;AACD,QAAM,aAAa,YAAY;AAC/B,QAAM,KAAK,QAAQ,IAAI;AACvB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAI5C,QAAM,QAAQ,QAAQ,YAAY,OAAO,SAAS,IAAI;AACtD,QAAM,WAAW,QAAQ,WAAW,OAAO,CAAC,IAAI;AAChD,QAAM,aAAa,QACf;AAAA,IACE,iBAAiB,WAAW,CAAC,cAAc,WAAW,WAAW,MAAM;AAAA,IACvE,OAAO,MAAM,WAAW;AAAA,EAAA,IAE1B;AAEJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,UAAU;AAAA,MACV,aAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP,cAAc,QAAQ,MAAM,WAAW,IAAI,IAAI;AAAA,MAC/C,cAAc,QAAQ,MAAM,WAAW,KAAK,IAAI;AAAA,MAChD,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAAC,UAAU,SAAS,OAAO,KAAK,SAAS;AAAA,QACzC,WAAW,gBAAgB,IAAI,IAAI,MAAM,IAAI;AAAA,QAC7C,aAAa;AAAA,QACb;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,+BAAY,aAAA,EAAY,MAAM,IAAI,QAAO,QAAO,WAAU,gBAAe;AAAA,QACzE,CAAC,WAAW,QAAQ,oBAAC,QAAK,MAAM,IAAI,QAAO,WAAU;AAAA,QACrD,CAAC,YAAY;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGpB;"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
import { ArrowsOut } from "@phosphor-icons/react";
|
|
4
|
+
import Button from "./Button.js";
|
|
5
|
+
import ActionMenu from "./ActionMenu.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { resolveTone } from "../lib/tone.js";
|
|
8
|
+
import { useBreakpointTier } from "../lib/useBreakpoint.js";
|
|
9
|
+
import { resolveActionLayout } from "../lib/actionVisibility.js";
|
|
10
|
+
const MAX_INLINE_ACTIONS = 3;
|
|
11
|
+
function Card({ className = "", children, ...rest }) {
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
"div",
|
|
14
|
+
{
|
|
15
|
+
className: cn(
|
|
16
|
+
"bg-surface border border-border rounded-lg shadow-sm overflow-hidden flex flex-col",
|
|
17
|
+
className
|
|
18
|
+
),
|
|
19
|
+
...rest,
|
|
20
|
+
children
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function Header({
|
|
25
|
+
icon: Icon,
|
|
26
|
+
title,
|
|
27
|
+
actions = [],
|
|
28
|
+
subHeader,
|
|
29
|
+
onExpand,
|
|
30
|
+
tone = "gradient",
|
|
31
|
+
className = ""
|
|
32
|
+
}) {
|
|
33
|
+
const t = resolveTone(tone, "gradient");
|
|
34
|
+
const tier = useBreakpointTier();
|
|
35
|
+
const { inline: inlineActions, menu: menuActions } = resolveActionLayout(actions, tier, {
|
|
36
|
+
maxInline: MAX_INLINE_ACTIONS,
|
|
37
|
+
defaultInline: () => "tablet+",
|
|
38
|
+
overflowInline: "none"
|
|
39
|
+
});
|
|
40
|
+
const isPlain = !t.style && !t.textStyle;
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(t.className, className), style: t.style, children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
44
|
+
Icon && /* @__PURE__ */ jsx(
|
|
45
|
+
Icon,
|
|
46
|
+
{
|
|
47
|
+
size: 18,
|
|
48
|
+
weight: "regular",
|
|
49
|
+
className: cn("shrink-0", isPlain && "text-primary"),
|
|
50
|
+
style: t.textStyle
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
"h3",
|
|
55
|
+
{
|
|
56
|
+
className: cn("text-subsection font-display truncate", isPlain && "text-content"),
|
|
57
|
+
style: t.textStyle,
|
|
58
|
+
children: title
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
|
|
63
|
+
inlineActions.length > 0 && // Which actions are here vs in the ⋮ is resolved for the current tier,
|
|
64
|
+
// so nothing is rendered only to be hidden by a class.
|
|
65
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: inlineActions.map(
|
|
66
|
+
(a) => a.render ? a.render() : /* @__PURE__ */ jsx(
|
|
67
|
+
Button,
|
|
68
|
+
{
|
|
69
|
+
variant: t.isDark ? "inverse" : "ghost",
|
|
70
|
+
size: "sm",
|
|
71
|
+
iconOnly: true,
|
|
72
|
+
icon: a.icon,
|
|
73
|
+
"aria-label": a.label,
|
|
74
|
+
disabled: a.disabled,
|
|
75
|
+
onClick: a.onClick,
|
|
76
|
+
style: t.isDark ? void 0 : t.textStyle
|
|
77
|
+
},
|
|
78
|
+
a.key
|
|
79
|
+
)
|
|
80
|
+
) }),
|
|
81
|
+
/* @__PURE__ */ jsx(ActionMenu, { actions: menuActions, isDark: t.isDark }),
|
|
82
|
+
onExpand && /* @__PURE__ */ jsx(
|
|
83
|
+
Button,
|
|
84
|
+
{
|
|
85
|
+
variant: t.isDark ? "inverse" : "ghost",
|
|
86
|
+
size: "sm",
|
|
87
|
+
iconOnly: true,
|
|
88
|
+
icon: ArrowsOut,
|
|
89
|
+
"aria-label": "Expand",
|
|
90
|
+
onClick: onExpand,
|
|
91
|
+
style: t.isDark ? void 0 : t.textStyle
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
] })
|
|
95
|
+
] }),
|
|
96
|
+
subHeader && /* @__PURE__ */ jsx(
|
|
97
|
+
"div",
|
|
98
|
+
{
|
|
99
|
+
className: cn("px-4 pb-2.5 -mt-1", t.className, isPlain && "text-content-muted"),
|
|
100
|
+
style: { ...t.style, ...t.mutedTextStyle },
|
|
101
|
+
children: subHeader
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
function SubHeader({ start, end, className = "" }) {
|
|
107
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-3", className), children: [
|
|
108
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: start }),
|
|
109
|
+
end != null && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: end })
|
|
110
|
+
] });
|
|
111
|
+
}
|
|
112
|
+
Header.SubHeader = SubHeader;
|
|
113
|
+
function Body({ className = "", children }) {
|
|
114
|
+
return /* @__PURE__ */ jsx("div", { className: cn("p-4", className), children });
|
|
115
|
+
}
|
|
116
|
+
Card.Header = Header;
|
|
117
|
+
Card.SubHeader = SubHeader;
|
|
118
|
+
Card.Body = Body;
|
|
119
|
+
export {
|
|
120
|
+
Card as default
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=Card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.js","sources":["../../src/components/Card.jsx"],"sourcesContent":["import React from \"react\";\nimport { ArrowsOut } from \"@phosphor-icons/react\";\nimport Button from \"./Button\";\nimport ActionMenu from \"./ActionMenu\";\nimport { cn } from \"../lib/cn\";\nimport { resolveTone } from \"../lib/tone\";\nimport { useBreakpointTier } from \"../lib/useBreakpoint\";\nimport { resolveActionLayout } from \"../lib/actionVisibility\";\n\n/**\n * Card — surface with a mandatory header (icon + title) and a free-form body.\n *\n * Anatomy:\n * <Card>\n * <Card.Header\n * icon={Snowflake} // required\n * title=\"AC Units\" // required\n * tone=\"gradient\" // 'gradient' (default) | 'plain' | 'primary' | 'secondary'\n * // | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n * actions={[ // optional — array, not raw JSX (see below)\n * { key: \"export\", label: \"Export\", icon: DownloadSimple, onClick: () => {} },\n * ]}\n * onExpand={() => openModal()} // optional — shows the expand button; you add the modal\n * subHeader={ // optional second header row (same header area)\n * <Card.SubHeader start={…} end={…} />\n * }\n * />\n * <Card.Body> anything </Card.Body>\n * </Card>\n *\n * `actions` is a list of { key, label, icon, onClick, disabled?, on?, inline? } —\n * not raw buttons — because Card decides how to lay them out: up to 3 render as icon\n * buttons; more than 3 collapse into a single \"⋮\" overflow menu. On narrow (mobile)\n * screens, actions always collapse to the \"⋮\" menu regardless of count, so the header\n * never crowds. The menu is portaled to <body> so it isn't clipped by Card's own\n * overflow-hidden.\n *\n * `on` and `inline` override those defaults per action — same grammar and same\n * meaning as PageHeader (see ./actionVisibility.js). `on` decides where the action\n * exists at all; `inline` decides where it's a button rather than a ⋮ row:\n *\n * actions={[\n * { key: \"filter\", label: \"Filter\", icon: Funnel, on: \"desktop\" }, // desktop only\n * { key: \"info\", label: \"Info\", icon: Info, inline: \"all\" }, // never collapses\n * ]}\n *\n * Layout is logical (start/end) so it mirrors in RTL. The expand toggle always\n * sits in the outermost end corner. Default header is the brand gradient\n * (bg-gradient-header); pass tone=\"plain\" for a white header, or ANY design-system\n * token — a family ('success') or an explicit shade ('primary-300', 'neutral-800'),\n * or a status gradient ('gradient-success'). Title, icon, sub-header text and the\n * header action buttons all auto-contrast against whatever tone you pick, so light\n * shades get dark text automatically. See ./tone.js.\n */\n\nconst MAX_INLINE_ACTIONS = 3;\n\nfunction Card({ className = \"\", children, ...rest }) {\n return (\n <div\n className={cn(\n \"bg-surface border border-border rounded-lg shadow-sm overflow-hidden flex flex-col\",\n className\n )}\n {...rest}\n >\n {children}\n </div>\n );\n}\n\nfunction Header({\n icon: Icon,\n title,\n actions = [],\n subHeader,\n onExpand,\n tone = \"gradient\",\n className = \"\",\n}) {\n if (import.meta.env?.DEV && (!Icon || !title)) {\n console.warn(\"Card.Header: `icon` and `title` are required.\");\n }\n\n const t = resolveTone(tone, \"gradient\");\n const tier = useBreakpointTier();\n const { inline: inlineActions, menu: menuActions } = resolveActionLayout(actions, tier, {\n maxInline: MAX_INLINE_ACTIONS,\n defaultInline: () => \"tablet+\",\n overflowInline: \"none\",\n });\n // `plain` returns no textStyle — it sits on the app surface, so keep the semantic\n // token classes it has always used (primary icon, content-colored title).\n const isPlain = !t.style && !t.textStyle;\n\n return (\n <div className={cn(t.className, className)} style={t.style}>\n {/* Main header row */}\n <div className=\"flex items-center justify-between gap-3 px-4 py-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {Icon && (\n <Icon\n size={18}\n weight=\"regular\"\n className={cn(\"shrink-0\", isPlain && \"text-primary\")}\n style={t.textStyle}\n />\n )}\n <h3\n className={cn(\"text-subsection font-display truncate\", isPlain && \"text-content\")}\n style={t.textStyle}\n >\n {title}\n </h3>\n </div>\n\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {inlineActions.length > 0 && (\n // Which actions are here vs in the ⋮ is resolved for the current tier,\n // so nothing is rendered only to be hidden by a class.\n <div className=\"flex items-center gap-1.5\">\n {inlineActions.map((a) =>\n a.render ? (\n a.render()\n ) : (\n <Button\n key={a.key}\n variant={t.isDark ? \"inverse\" : \"ghost\"}\n size=\"sm\"\n iconOnly\n icon={a.icon}\n aria-label={a.label}\n disabled={a.disabled}\n onClick={a.onClick}\n style={t.isDark ? undefined : t.textStyle}\n />\n )\n )}\n </div>\n )}\n\n {/* A `render` action can't be re-created as a menu row, so the menu falls\n back to its label + icon — that's what keeps it reachable on mobile.\n Renders nothing when the list is empty. */}\n <ActionMenu actions={menuActions} isDark={t.isDark} />\n\n {onExpand && (\n <Button\n variant={t.isDark ? \"inverse\" : \"ghost\"}\n size=\"sm\"\n iconOnly\n icon={ArrowsOut}\n aria-label=\"Expand\"\n onClick={onExpand}\n style={t.isDark ? undefined : t.textStyle}\n />\n )}\n </div>\n </div>\n\n {/* Optional second header row — same tone background; its text color comes from\n the resolved tone so plain-text content never needs its own color class. */}\n {subHeader && (\n <div\n className={cn(\"px-4 pb-2.5 -mt-1\", t.className, isPlain && \"text-content-muted\")}\n style={{ ...t.style, ...t.mutedTextStyle }}\n >\n {subHeader}\n </div>\n )}\n </div>\n );\n}\n\nfunction SubHeader({ start, end, className = \"\" }) {\n return (\n <div className={cn(\"flex items-center justify-between gap-3\", className)}>\n <div className=\"min-w-0\">{start}</div>\n {end != null && <div className=\"shrink-0\">{end}</div>}\n </div>\n );\n}\n\nHeader.SubHeader = SubHeader;\n\nfunction Body({ className = \"\", children }) {\n return <div className={cn(\"p-4\", className)}>{children}</div>;\n}\n\nCard.Header = Header;\nCard.SubHeader = SubHeader;\nCard.Body = Body;\n\nexport default Card;"],"names":[],"mappings":";;;;;;;;;AAuDA,MAAM,qBAAqB;AAE3B,SAAS,KAAK,EAAE,YAAY,IAAI,UAAU,GAAG,QAAQ;AACnD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH;AAAA,IAAA;AAAA,EAAA;AAGP;AAEA,SAAS,OAAO;AAAA,EACd,MAAM;AAAA,EACN;AAAA,EACA,UAAU,CAAA;AAAA,EACV;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AACd,GAAG;AAKD,QAAM,IAAI,YAAY,MAAM,UAAU;AACtC,QAAM,OAAO,kBAAA;AACb,QAAM,EAAE,QAAQ,eAAe,MAAM,gBAAgB,oBAAoB,SAAS,MAAM;AAAA,IACtF,WAAW;AAAA,IACX,eAAe,MAAM;AAAA,IACrB,gBAAgB;AAAA,EAAA,CACjB;AAGD,QAAM,UAAU,CAAC,EAAE,SAAS,CAAC,EAAE;AAE/B,SACE,qBAAC,OAAA,EAAI,WAAW,GAAG,EAAE,WAAW,SAAS,GAAG,OAAO,EAAE,OAEnD,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,qDACb,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,WAAU,mCACZ,UAAA;AAAA,QAAA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAM;AAAA,YACN,QAAO;AAAA,YACP,WAAW,GAAG,YAAY,WAAW,cAAc;AAAA,YACnD,OAAO,EAAE;AAAA,UAAA;AAAA,QAAA;AAAA,QAGb;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,GAAG,yCAAyC,WAAW,cAAc;AAAA,YAChF,OAAO,EAAE;AAAA,YAER,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACH,GACF;AAAA,MAEA,qBAAC,OAAA,EAAI,WAAU,sCACZ,UAAA;AAAA,QAAA,cAAc,SAAS;AAAA;AAAA,4BAGrB,OAAA,EAAI,WAAU,6BACZ,UAAA,cAAc;AAAA,UAAI,CAAC,MAClB,EAAE,SACA,EAAE,WAEF;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,SAAS,EAAE,SAAS,YAAY;AAAA,cAChC,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,MAAM,EAAE;AAAA,cACR,cAAY,EAAE;AAAA,cACd,UAAU,EAAE;AAAA,cACZ,SAAS,EAAE;AAAA,cACX,OAAO,EAAE,SAAS,SAAY,EAAE;AAAA,YAAA;AAAA,YAR3B,EAAE;AAAA,UAAA;AAAA,QAST,GAGN;AAAA,4BAMD,YAAA,EAAW,SAAS,aAAa,QAAQ,EAAE,QAAQ;AAAA,QAEnD,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,EAAE,SAAS,YAAY;AAAA,YAChC,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,MAAM;AAAA,YACN,cAAW;AAAA,YACX,SAAS;AAAA,YACT,OAAO,EAAE,SAAS,SAAY,EAAE;AAAA,UAAA;AAAA,QAAA;AAAA,MAClC,GAEJ;AAAA,IAAA,GACF;AAAA,IAIC,aACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,GAAG,qBAAqB,EAAE,WAAW,WAAW,oBAAoB;AAAA,QAC/E,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,EAAE,eAAA;AAAA,QAEzB,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GAEJ;AAEJ;AAEA,SAAS,UAAU,EAAE,OAAO,KAAK,YAAY,MAAM;AACjD,8BACG,OAAA,EAAI,WAAW,GAAG,2CAA2C,SAAS,GACrE,UAAA;AAAA,wBAAC,OAAA,EAAI,WAAU,WAAW,UAAA,OAAM;AAAA,IAC/B,OAAO,QAAQ,oBAAC,OAAA,EAAI,WAAU,YAAY,UAAA,IAAA,CAAI;AAAA,EAAA,GACjD;AAEJ;AAEA,OAAO,YAAY;AAEnB,SAAS,KAAK,EAAE,YAAY,IAAI,YAAY;AAC1C,6BAAQ,OAAA,EAAI,WAAW,GAAG,OAAO,SAAS,GAAI,UAAS;AACzD;AAEA,KAAK,SAAS;AACd,KAAK,YAAY;AACjB,KAAK,OAAO;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect } from "react";
|
|
3
|
+
import { Check, Minus } from "@phosphor-icons/react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
const SIZES = {
|
|
6
|
+
sm: { box: "h-4 w-4", icon: 12 },
|
|
7
|
+
md: { box: "h-5 w-5", icon: 14 }
|
|
8
|
+
};
|
|
9
|
+
function Checkbox({
|
|
10
|
+
checked = false,
|
|
11
|
+
indeterminate = false,
|
|
12
|
+
onChange,
|
|
13
|
+
label,
|
|
14
|
+
size = "md",
|
|
15
|
+
disabled = false,
|
|
16
|
+
error = false,
|
|
17
|
+
className = "",
|
|
18
|
+
...rest
|
|
19
|
+
}) {
|
|
20
|
+
const inputRef = useRef(null);
|
|
21
|
+
const s = SIZES[size] || SIZES.md;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (inputRef.current) inputRef.current.indeterminate = indeterminate && !checked;
|
|
24
|
+
}, [indeterminate, checked]);
|
|
25
|
+
const active = checked || indeterminate;
|
|
26
|
+
return /* @__PURE__ */ jsxs(
|
|
27
|
+
"label",
|
|
28
|
+
{
|
|
29
|
+
className: cn(
|
|
30
|
+
"inline-flex items-center gap-2 select-none",
|
|
31
|
+
disabled ? "opacity-60 cursor-not-allowed" : "cursor-pointer",
|
|
32
|
+
className
|
|
33
|
+
),
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx(
|
|
36
|
+
"input",
|
|
37
|
+
{
|
|
38
|
+
ref: inputRef,
|
|
39
|
+
type: "checkbox",
|
|
40
|
+
className: "peer sr-only",
|
|
41
|
+
checked,
|
|
42
|
+
disabled,
|
|
43
|
+
onChange: (e) => onChange?.(e.target.checked),
|
|
44
|
+
...rest
|
|
45
|
+
}
|
|
46
|
+
),
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"span",
|
|
49
|
+
{
|
|
50
|
+
"aria-hidden": "true",
|
|
51
|
+
className: cn(
|
|
52
|
+
"inline-flex items-center justify-center rounded border transition shrink-0",
|
|
53
|
+
"peer-focus-visible:shadow-focus",
|
|
54
|
+
active ? "bg-primary border-primary text-white" : cn("bg-surface", error ? "border-error" : "border-border-strong"),
|
|
55
|
+
s.box
|
|
56
|
+
),
|
|
57
|
+
children: checked ? /* @__PURE__ */ jsx(Check, { size: s.icon, weight: "bold" }) : indeterminate ? /* @__PURE__ */ jsx(Minus, { size: s.icon, weight: "bold" }) : null
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-body text-content", children: label })
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
Checkbox as default
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sources":["../../src/components/Checkbox.jsx"],"sourcesContent":["import React, { useEffect, useRef } from \"react\";\nimport { Check, Minus } from \"@phosphor-icons/react\";\nimport { cn } from \"../lib/cn\";\n\n/**\n * Checkbox — custom box backed by a hidden native <input> (keeps keyboard,\n * form, and screen-reader behavior). Field-compatible: an injected `id` lands\n * on the input so Field's <label htmlFor> toggles it.\n *\n * Props:\n * checked boolean\n * indeterminate boolean (shows a dash; visual only)\n * onChange (next: boolean) => void\n * label optional text beside the box\n * size 'sm' | 'md' (default 'md')\n * disabled, error, id, required, aria-* (Field injects these)\n */\n\nconst SIZES = {\n sm: { box: \"h-4 w-4\", icon: 12 },\n md: { box: \"h-5 w-5\", icon: 14 },\n};\n\nexport default function Checkbox({\n checked = false,\n indeterminate = false,\n onChange,\n label,\n size = \"md\",\n disabled = false,\n error = false,\n className = \"\",\n ...rest\n}) {\n const inputRef = useRef(null);\n const s = SIZES[size] || SIZES.md;\n\n useEffect(() => {\n if (inputRef.current) inputRef.current.indeterminate = indeterminate && !checked;\n }, [indeterminate, checked]);\n\n const active = checked || indeterminate;\n\n return (\n <label\n className={cn(\n \"inline-flex items-center gap-2 select-none\",\n disabled ? \"opacity-60 cursor-not-allowed\" : \"cursor-pointer\",\n className\n )}\n >\n <input\n ref={inputRef}\n type=\"checkbox\"\n className=\"peer sr-only\"\n checked={checked}\n disabled={disabled}\n onChange={(e) => onChange?.(e.target.checked)}\n {...rest}\n />\n <span\n aria-hidden=\"true\"\n className={cn(\n \"inline-flex items-center justify-center rounded border transition shrink-0\",\n \"peer-focus-visible:shadow-focus\",\n active\n ? \"bg-primary border-primary text-white\"\n : cn(\"bg-surface\", error ? \"border-error\" : \"border-border-strong\"),\n s.box\n )}\n >\n {checked ? (\n <Check size={s.icon} weight=\"bold\" />\n ) : indeterminate ? (\n <Minus size={s.icon} weight=\"bold\" />\n ) : null}\n </span>\n {label && <span className=\"text-body text-content\">{label}</span>}\n </label>\n );\n}\n"],"names":[],"mappings":";;;;AAkBA,MAAM,QAAQ;AAAA,EACZ,IAAI,EAAE,KAAK,WAAW,MAAM,GAAA;AAAA,EAC5B,IAAI,EAAE,KAAK,WAAW,MAAM,GAAA;AAC9B;AAEA,SAAwB,SAAS;AAAA,EAC/B,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,WAAW,OAAO,IAAI;AAC5B,QAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AAE/B,YAAU,MAAM;AACd,QAAI,SAAS,QAAS,UAAS,QAAQ,gBAAgB,iBAAiB,CAAC;AAAA,EAC3E,GAAG,CAAC,eAAe,OAAO,CAAC;AAE3B,QAAM,SAAS,WAAW;AAE1B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,WAAW,kCAAkC;AAAA,QAC7C;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,WAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,OAAO;AAAA,YAC3C,GAAG;AAAA,UAAA;AAAA,QAAA;AAAA,QAEN;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA,SACI,yCACA,GAAG,cAAc,QAAQ,iBAAiB,sBAAsB;AAAA,cACpE,EAAE;AAAA,YAAA;AAAA,YAGH,oBACC,oBAAC,OAAA,EAAM,MAAM,EAAE,MAAM,QAAO,OAAA,CAAO,IACjC,oCACD,OAAA,EAAM,MAAM,EAAE,MAAM,QAAO,QAAO,IACjC;AAAA,UAAA;AAAA,QAAA;AAAA,QAEL,SAAS,oBAAC,QAAA,EAAK,WAAU,0BAA0B,UAAA,MAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhE;"}
|