@scttlnd/fintech-design-system 0.2.0 → 0.3.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/AGENTS.md +1 -1
- package/README.md +9 -3
- package/dist/components/Badge.d.mts +9 -0
- package/dist/components/Badge.mjs +18 -0
- package/dist/components/Badge.mjs.map +1 -0
- package/dist/components/Button.d.mts +6 -0
- package/dist/components/Button.mjs +14 -0
- package/dist/components/Button.mjs.map +1 -0
- package/dist/components/Field.d.mts +20 -0
- package/dist/components/Field.mjs +57 -0
- package/dist/components/Field.mjs.map +1 -0
- package/dist/components/Menu.d.mts +18 -0
- package/dist/components/Menu.mjs +109 -0
- package/dist/components/Menu.mjs.map +1 -0
- package/dist/components/Meter.d.mts +39 -0
- package/dist/components/Meter.mjs +82 -0
- package/dist/components/Meter.mjs.map +1 -0
- package/dist/components/Metric.d.mts +17 -0
- package/dist/components/Metric.mjs +34 -0
- package/dist/components/Metric.mjs.map +1 -0
- package/dist/components/Notice.d.mts +16 -0
- package/dist/components/Notice.mjs +48 -0
- package/dist/components/Notice.mjs.map +1 -0
- package/dist/components/Panel.d.mts +13 -0
- package/dist/components/Panel.mjs +42 -0
- package/dist/components/Panel.mjs.map +1 -0
- package/dist/index.d.mts +10 -125
- package/dist/index.mjs +9 -365
- package/dist/lib/cn.d.mts +2 -0
- package/dist/lib/cn.mjs +7 -0
- package/dist/lib/cn.mjs.map +1 -0
- package/package.json +18 -3
- package/styles/components/badge.css +33 -0
- package/styles/components/button.css +45 -0
- package/styles/components/empty-state.css +20 -0
- package/styles/components/eyebrow.css +11 -0
- package/styles/components/field.css +59 -0
- package/styles/components/grain.css +245 -0
- package/styles/components/menu.css +74 -0
- package/styles/components/meter.css +62 -0
- package/styles/components/metric.css +28 -0
- package/styles/components/notice.css +34 -0
- package/styles/components/panel.css +42 -0
- package/styles/components/tone.css +15 -0
- package/styles/components.css +11 -367
- package/styles/index.css +2 -43
- package/styles/themes/funder.css +76 -8
- package/styles/themes/investor.css +76 -8
- package/styles/themes/portare.css +90 -22
- package/styles/tokens.css +88 -23
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.mjs";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useId } from "react";
|
|
5
|
+
// src/components/Panel.tsx
|
|
6
|
+
/** Titled surface with optional eyebrow, help text and action. Renders a labelled `<section>`. */
|
|
7
|
+
function Panel({ title, eyebrow, help, action, headingLevel = 2, className, children, ...props }) {
|
|
8
|
+
const headingId = useId();
|
|
9
|
+
const Heading = `h${headingLevel}`;
|
|
10
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
11
|
+
"aria-labelledby": headingId,
|
|
12
|
+
className: cn("fds-panel", className),
|
|
13
|
+
...props,
|
|
14
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
15
|
+
className: "fds-panel__header",
|
|
16
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
17
|
+
className: "fds-panel__heading",
|
|
18
|
+
children: [
|
|
19
|
+
eyebrow ? /* @__PURE__ */ jsx("p", {
|
|
20
|
+
className: "fds-eyebrow",
|
|
21
|
+
children: eyebrow
|
|
22
|
+
}) : null,
|
|
23
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
24
|
+
id: headingId,
|
|
25
|
+
className: "fds-panel__title",
|
|
26
|
+
children: title
|
|
27
|
+
}),
|
|
28
|
+
help ? /* @__PURE__ */ jsx("p", {
|
|
29
|
+
className: "fds-panel__help",
|
|
30
|
+
children: help
|
|
31
|
+
}) : null
|
|
32
|
+
]
|
|
33
|
+
}), action ? /* @__PURE__ */ jsx("div", {
|
|
34
|
+
className: "fds-panel__action",
|
|
35
|
+
children: action
|
|
36
|
+
}) : null]
|
|
37
|
+
}), children]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export { Panel };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=Panel.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.mjs","names":[],"sources":["../../src/components/Panel.tsx"],"sourcesContent":["import { useId, type ElementType, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/cn.js\";\n\nexport interface PanelProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n title: ReactNode;\n eyebrow?: ReactNode;\n help?: ReactNode;\n /** Optional trailing action area (buttons, menu). */\n action?: ReactNode;\n /** Heading element for the title. Defaults to h2; pick the level that fits the page outline. */\n headingLevel?: 2 | 3 | 4;\n}\n\n/** Titled surface with optional eyebrow, help text and action. Renders a labelled `<section>`. */\nexport function Panel({\n title,\n eyebrow,\n help,\n action,\n headingLevel = 2,\n className,\n children,\n ...props\n}: PanelProps) {\n const headingId = useId();\n const Heading = `h${headingLevel}` as ElementType;\n return (\n <section aria-labelledby={headingId} className={cn(\"fds-panel\", className)} {...props}>\n <header className=\"fds-panel__header\">\n <div className=\"fds-panel__heading\">\n {eyebrow ? <p className=\"fds-eyebrow\">{eyebrow}</p> : null}\n <Heading id={headingId} className=\"fds-panel__title\">\n {title}\n </Heading>\n {help ? <p className=\"fds-panel__help\">{help}</p> : null}\n </div>\n {action ? <div className=\"fds-panel__action\">{action}</div> : null}\n </header>\n {children}\n </section>\n );\n}\n"],"mappings":";;;;;;AAcA,SAAgB,MAAM,EACpB,OACA,SACA,MACA,QACA,eAAe,GACf,WACA,UACA,GAAG,SACU;CACb,MAAM,YAAY,MAAM;CACxB,MAAM,UAAU,IAAI;CACpB,OACE,qBAAC,WAAD;EAAS,mBAAiB;EAAW,WAAW,GAAG,aAAa,SAAS;EAAG,GAAI;EAAhF,UAAA,CACE,qBAAC,UAAD;GAAQ,WAAU;GAAlB,UAAA,CACE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG,UAAU,oBAAC,KAAD;MAAG,WAAU;MAAe,UAAA;KAAW,CAAA,IAAI;KACtD,oBAAC,SAAD;MAAS,IAAI;MAAW,WAAU;MAC/B,UAAA;KACM,CAAA;KACR,OAAO,oBAAC,KAAD;MAAG,WAAU;MAAmB,UAAA;KAAQ,CAAA,IAAI;IACjD;GACJ,CAAA,GAAA,SAAS,oBAAC,OAAD;IAAK,WAAU;IAAqB,UAAA;GAAY,CAAA,IAAI,IACxD;EACP,CAAA,GAAA,QACM;;AAEb"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,125 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** Optional trailing action area (buttons, menu). */
|
|
12
|
-
action?: ReactNode;
|
|
13
|
-
/** Heading element for the title. Defaults to h2; pick the level that fits the page outline. */
|
|
14
|
-
headingLevel?: 2 | 3 | 4;
|
|
15
|
-
}
|
|
16
|
-
/** Titled surface with optional eyebrow, help text and action. Renders a labelled `<section>`. */
|
|
17
|
-
export declare function Panel({ title, eyebrow, help, action, headingLevel, className, children, ...props }: PanelProps): import("react").JSX.Element;
|
|
18
|
-
type Tone = "neutral" | "accent" | "success" | "warn" | "danger";
|
|
19
|
-
interface MetricProps extends HTMLAttributes<HTMLDivElement> {
|
|
20
|
-
label: ReactNode;
|
|
21
|
-
/** Pre-formatted value. Formatting and calculation belong to the consuming app. */
|
|
22
|
-
value: ReactNode;
|
|
23
|
-
tone?: Tone;
|
|
24
|
-
}
|
|
25
|
-
/** Label/value tile for pre-formatted figures. Does no number formatting or arithmetic. */
|
|
26
|
-
export declare function Metric({ label, value, tone, className, ...props }: MetricProps): import("react").JSX.Element;
|
|
27
|
-
interface KeyValueProps extends HTMLAttributes<HTMLDivElement> {
|
|
28
|
-
label: ReactNode;
|
|
29
|
-
value: ReactNode;
|
|
30
|
-
}
|
|
31
|
-
/** Quieter label/value pair for detail views. */
|
|
32
|
-
export declare function KeyValue({ label, value, className, ...props }: KeyValueProps): import("react").JSX.Element;
|
|
33
|
-
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
34
|
-
tone?: Tone;
|
|
35
|
-
icon?: ReactNode;
|
|
36
|
-
}
|
|
37
|
-
/** Compact status/label chip. Never rely on tone alone: include meaningful text. */
|
|
38
|
-
export declare function Badge({ tone, icon, className, children, ...props }: BadgeProps): import("react").JSX.Element;
|
|
39
|
-
interface NoticeProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
40
|
-
tone?: "info" | "success" | "warn" | "danger";
|
|
41
|
-
title?: ReactNode;
|
|
42
|
-
action?: ReactNode;
|
|
43
|
-
}
|
|
44
|
-
/** Inline message. `danger` announces as an alert; other tones as polite status. */
|
|
45
|
-
export declare function Notice({ tone, title, action, className, children, role, ...props }: NoticeProps): import("react").JSX.Element;
|
|
46
|
-
interface EmptyStateProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
47
|
-
title: ReactNode;
|
|
48
|
-
description?: ReactNode;
|
|
49
|
-
action?: ReactNode;
|
|
50
|
-
}
|
|
51
|
-
/** Placeholder for lists or panels with no data, with an optional next step. */
|
|
52
|
-
export declare function EmptyState({ title, description, action, className, ...props }: EmptyStateProps): import("react").JSX.Element;
|
|
53
|
-
interface FieldControlProps {
|
|
54
|
-
id: string;
|
|
55
|
-
"aria-describedby"?: string;
|
|
56
|
-
"aria-invalid"?: true;
|
|
57
|
-
}
|
|
58
|
-
interface FieldProps {
|
|
59
|
-
label: ReactNode;
|
|
60
|
-
hint?: ReactNode;
|
|
61
|
-
error?: ReactNode;
|
|
62
|
-
className?: string;
|
|
63
|
-
/** Render prop so the control receives the generated id and aria wiring. */
|
|
64
|
-
children: (control: FieldControlProps) => ReactNode;
|
|
65
|
-
}
|
|
66
|
-
/** Label + hint + error wrapper that wires `id`, `aria-describedby` and `aria-invalid`. */
|
|
67
|
-
export declare function Field({ label, hint, error, className, children }: FieldProps): import("react").JSX.Element;
|
|
68
|
-
export declare function Input({ className, ...props }: InputHTMLAttributes<HTMLInputElement>): import("react").JSX.Element;
|
|
69
|
-
export declare function Select({ className, ...props }: SelectHTMLAttributes<HTMLSelectElement>): import("react").JSX.Element;
|
|
70
|
-
export declare function Textarea({ className, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement>): import("react").JSX.Element;
|
|
71
|
-
interface MeterProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
72
|
-
/** Accessible name; required because the bar itself has no text. */
|
|
73
|
-
label: string;
|
|
74
|
-
/** Current value within `min`..`max`. Values outside are clamped for display. */
|
|
75
|
-
value: number;
|
|
76
|
-
min?: number;
|
|
77
|
-
max?: number;
|
|
78
|
-
/** Optional human-readable value for assistive tech, e.g. "72 of 100". */
|
|
79
|
-
valueText?: string;
|
|
80
|
-
}
|
|
81
|
-
/** Horizontal bar for a bounded score or share. Display only: no domain logic. */
|
|
82
|
-
export declare function Meter({ label, value, min, max, valueText, className, ...props }: MeterProps): import("react").JSX.Element;
|
|
83
|
-
interface SegmentedMeterProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
84
|
-
label: string;
|
|
85
|
-
/** Number of filled segments. */
|
|
86
|
-
value: number;
|
|
87
|
-
segments?: number;
|
|
88
|
-
valueText?: string;
|
|
89
|
-
}
|
|
90
|
-
/** Discrete scale (for example a 1-7 risk indicator). */
|
|
91
|
-
export declare function SegmentedMeter({ label, value, segments, valueText, className, ...props }: SegmentedMeterProps): import("react").JSX.Element;
|
|
92
|
-
interface DistributionItem {
|
|
93
|
-
label: ReactNode;
|
|
94
|
-
value: number;
|
|
95
|
-
}
|
|
96
|
-
interface DistributionListProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
97
|
-
items: readonly DistributionItem[];
|
|
98
|
-
/** Maximum rows shown; the remainder is summarised via `renderOverflow`. */
|
|
99
|
-
limit?: number;
|
|
100
|
-
/** Formats the value text. Consumer owns units and locale. */
|
|
101
|
-
formatValue?: (value: number) => ReactNode;
|
|
102
|
-
/** Shown when there is nothing positive to display. */
|
|
103
|
-
empty?: ReactNode;
|
|
104
|
-
renderOverflow?: (hidden: number) => ReactNode;
|
|
105
|
-
}
|
|
106
|
-
/** Ranked list of share bars, scaled relative to the largest row. */
|
|
107
|
-
export declare function DistributionList({ items, limit, formatValue, empty, renderOverflow, className, ...props }: DistributionListProps): import("react").JSX.Element | null;
|
|
108
|
-
interface MenuItem {
|
|
109
|
-
label: string;
|
|
110
|
-
tone?: "danger";
|
|
111
|
-
disabled?: boolean;
|
|
112
|
-
onSelect: () => void;
|
|
113
|
-
}
|
|
114
|
-
interface MenuProps {
|
|
115
|
-
/** Accessible name of the trigger button. */
|
|
116
|
-
ariaLabel: string;
|
|
117
|
-
items: readonly MenuItem[];
|
|
118
|
-
/** Trigger content. Defaults to a vertical-ellipsis glyph. */
|
|
119
|
-
trigger?: ReactNode;
|
|
120
|
-
className?: string;
|
|
121
|
-
}
|
|
122
|
-
/** Action menu: closes on outside press and Escape (returning focus), arrow-key navigation. */
|
|
123
|
-
export declare function Menu({ ariaLabel, items, trigger, className }: MenuProps): import("react").JSX.Element;
|
|
124
|
-
export type { BadgeProps, ButtonProps, DistributionItem, DistributionListProps, EmptyStateProps, FieldControlProps, FieldProps, KeyValueProps, MenuItem, MenuProps, MeterProps, MetricProps, NoticeProps, PanelProps, SegmentedMeterProps, Tone };
|
|
125
|
-
//# sourceMappingURL=index.d.mts.map
|
|
1
|
+
import { cn } from "./lib/cn.mjs";
|
|
2
|
+
import { Button, ButtonProps } from "./components/Button.mjs";
|
|
3
|
+
import { Panel, PanelProps } from "./components/Panel.mjs";
|
|
4
|
+
import { KeyValue, KeyValueProps, Metric, MetricProps, Tone } from "./components/Metric.mjs";
|
|
5
|
+
import { Badge, BadgeProps } from "./components/Badge.mjs";
|
|
6
|
+
import { EmptyState, EmptyStateProps, Notice, NoticeProps } from "./components/Notice.mjs";
|
|
7
|
+
import { Field, FieldControlProps, FieldProps, Input, Select, Textarea } from "./components/Field.mjs";
|
|
8
|
+
import { DistributionItem, DistributionList, DistributionListProps, Meter, MeterProps, SegmentedMeter, SegmentedMeterProps } from "./components/Meter.mjs";
|
|
9
|
+
import { Menu, MenuItem, MenuProps } from "./components/Menu.mjs";
|
|
10
|
+
export { Badge, type BadgeProps, Button, type ButtonProps, type DistributionItem, DistributionList, type DistributionListProps, EmptyState, type EmptyStateProps, Field, type FieldControlProps, type FieldProps, Input, KeyValue, type KeyValueProps, Menu, type MenuItem, type MenuProps, Meter, type MeterProps, Metric, type MetricProps, Notice, type NoticeProps, Panel, type PanelProps, SegmentedMeter, type SegmentedMeterProps, Select, Textarea, type Tone, cn };
|
package/dist/index.mjs
CHANGED
|
@@ -1,366 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type,
|
|
11
|
-
className: cn("fds-button", `fds-button--${variant}`, className),
|
|
12
|
-
...props
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
// src/components/Panel.tsx
|
|
16
|
-
/** Titled surface with optional eyebrow, help text and action. Renders a labelled `<section>`. */
|
|
17
|
-
function Panel({ title, eyebrow, help, action, headingLevel = 2, className, children, ...props }) {
|
|
18
|
-
const headingId = useId();
|
|
19
|
-
const Heading = `h${headingLevel}`;
|
|
20
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
21
|
-
"aria-labelledby": headingId,
|
|
22
|
-
className: cn("fds-panel", className),
|
|
23
|
-
...props,
|
|
24
|
-
children: [/* @__PURE__ */ jsxs("header", {
|
|
25
|
-
className: "fds-panel__header",
|
|
26
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
27
|
-
className: "fds-panel__heading",
|
|
28
|
-
children: [
|
|
29
|
-
eyebrow ? /* @__PURE__ */ jsx("p", {
|
|
30
|
-
className: "fds-eyebrow",
|
|
31
|
-
children: eyebrow
|
|
32
|
-
}) : null,
|
|
33
|
-
/* @__PURE__ */ jsx(Heading, {
|
|
34
|
-
id: headingId,
|
|
35
|
-
className: "fds-panel__title",
|
|
36
|
-
children: title
|
|
37
|
-
}),
|
|
38
|
-
help ? /* @__PURE__ */ jsx("p", {
|
|
39
|
-
className: "fds-panel__help",
|
|
40
|
-
children: help
|
|
41
|
-
}) : null
|
|
42
|
-
]
|
|
43
|
-
}), action ? /* @__PURE__ */ jsx("div", {
|
|
44
|
-
className: "fds-panel__action",
|
|
45
|
-
children: action
|
|
46
|
-
}) : null]
|
|
47
|
-
}), children]
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
// src/components/Metric.tsx
|
|
51
|
-
/** Label/value tile for pre-formatted figures. Does no number formatting or arithmetic. */
|
|
52
|
-
function Metric({ label, value, tone = "neutral", className, ...props }) {
|
|
53
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
54
|
-
className: cn("fds-metric", className),
|
|
55
|
-
...props,
|
|
56
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
57
|
-
className: "fds-metric__label",
|
|
58
|
-
children: label
|
|
59
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
60
|
-
className: cn("fds-metric__value", `fds-tone--${tone}`),
|
|
61
|
-
children: value
|
|
62
|
-
})]
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
/** Quieter label/value pair for detail views. */
|
|
66
|
-
function KeyValue({ label, value, className, ...props }) {
|
|
67
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
68
|
-
className: cn("fds-keyvalue", className),
|
|
69
|
-
...props,
|
|
70
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
71
|
-
className: "fds-metric__label",
|
|
72
|
-
children: label
|
|
73
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
74
|
-
className: "fds-keyvalue__value",
|
|
75
|
-
children: value
|
|
76
|
-
})]
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
// src/components/Badge.tsx
|
|
80
|
-
/** Compact status/label chip. Never rely on tone alone: include meaningful text. */
|
|
81
|
-
function Badge({ tone = "neutral", icon, className, children, ...props }) {
|
|
82
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
83
|
-
className: cn("fds-badge", `fds-badge--${tone}`, className),
|
|
84
|
-
...props,
|
|
85
|
-
children: [icon ? /* @__PURE__ */ jsx("span", {
|
|
86
|
-
"aria-hidden": "true",
|
|
87
|
-
className: "fds-badge__icon",
|
|
88
|
-
children: icon
|
|
89
|
-
}) : null, children]
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
// src/components/Notice.tsx
|
|
93
|
-
/** Inline message. `danger` announces as an alert; other tones as polite status. */
|
|
94
|
-
function Notice({ tone = "info", title, action, className, children, role, ...props }) {
|
|
95
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
96
|
-
role: role ?? (tone === "danger" ? "alert" : "status"),
|
|
97
|
-
className: cn("fds-notice", `fds-notice--${tone}`, className),
|
|
98
|
-
...props,
|
|
99
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
100
|
-
className: "fds-notice__body",
|
|
101
|
-
children: [title ? /* @__PURE__ */ jsx("p", {
|
|
102
|
-
className: "fds-notice__title",
|
|
103
|
-
children: title
|
|
104
|
-
}) : null, children ? /* @__PURE__ */ jsx("div", {
|
|
105
|
-
className: "fds-notice__text",
|
|
106
|
-
children
|
|
107
|
-
}) : null]
|
|
108
|
-
}), action ? /* @__PURE__ */ jsx("div", {
|
|
109
|
-
className: "fds-notice__action",
|
|
110
|
-
children: action
|
|
111
|
-
}) : null]
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
/** Placeholder for lists or panels with no data, with an optional next step. */
|
|
115
|
-
function EmptyState({ title, description, action, className, ...props }) {
|
|
116
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
117
|
-
className: cn("fds-empty", className),
|
|
118
|
-
...props,
|
|
119
|
-
children: [
|
|
120
|
-
/* @__PURE__ */ jsx("p", {
|
|
121
|
-
className: "fds-empty__title",
|
|
122
|
-
children: title
|
|
123
|
-
}),
|
|
124
|
-
description ? /* @__PURE__ */ jsx("p", {
|
|
125
|
-
className: "fds-empty__description",
|
|
126
|
-
children: description
|
|
127
|
-
}) : null,
|
|
128
|
-
action ? /* @__PURE__ */ jsx("div", {
|
|
129
|
-
className: "fds-empty__action",
|
|
130
|
-
children: action
|
|
131
|
-
}) : null
|
|
132
|
-
]
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
// src/components/Field.tsx
|
|
136
|
-
/** Label + hint + error wrapper that wires `id`, `aria-describedby` and `aria-invalid`. */
|
|
137
|
-
function Field({ label, hint, error, className, children }) {
|
|
138
|
-
const id = useId();
|
|
139
|
-
const hintId = `${id}-hint`;
|
|
140
|
-
const errorId = `${id}-error`;
|
|
141
|
-
const describedBy = [hint ? hintId : null, error ? errorId : null].filter(Boolean).join(" ");
|
|
142
|
-
const control = { id };
|
|
143
|
-
if (describedBy) control["aria-describedby"] = describedBy;
|
|
144
|
-
if (error) control["aria-invalid"] = true;
|
|
145
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
146
|
-
className: cn("fds-field", className),
|
|
147
|
-
children: [
|
|
148
|
-
/* @__PURE__ */ jsx("label", {
|
|
149
|
-
className: "fds-field__label",
|
|
150
|
-
htmlFor: id,
|
|
151
|
-
children: label
|
|
152
|
-
}),
|
|
153
|
-
children(control),
|
|
154
|
-
hint ? /* @__PURE__ */ jsx("p", {
|
|
155
|
-
className: "fds-field__hint",
|
|
156
|
-
id: hintId,
|
|
157
|
-
children: hint
|
|
158
|
-
}) : null,
|
|
159
|
-
error ? /* @__PURE__ */ jsx("p", {
|
|
160
|
-
className: "fds-field__error",
|
|
161
|
-
id: errorId,
|
|
162
|
-
children: error
|
|
163
|
-
}) : null
|
|
164
|
-
]
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
function Input({ className, ...props }) {
|
|
168
|
-
return /* @__PURE__ */ jsx("input", {
|
|
169
|
-
className: cn("fds-control", className),
|
|
170
|
-
...props
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function Select({ className, ...props }) {
|
|
174
|
-
return /* @__PURE__ */ jsx("select", {
|
|
175
|
-
className: cn("fds-control", className),
|
|
176
|
-
...props
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function Textarea({ className, ...props }) {
|
|
180
|
-
return /* @__PURE__ */ jsx("textarea", {
|
|
181
|
-
className: cn("fds-control fds-control--multiline", className),
|
|
182
|
-
...props
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
// src/components/Meter.tsx
|
|
186
|
-
const clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
|
|
187
|
-
/** Horizontal bar for a bounded score or share. Display only: no domain logic. */
|
|
188
|
-
function Meter({ label, value, min = 0, max = 100, valueText, className, ...props }) {
|
|
189
|
-
const span = max - min;
|
|
190
|
-
const ratio = span > 0 ? clamp((value - min) / span, 0, 1) : 0;
|
|
191
|
-
return /* @__PURE__ */ jsx("div", {
|
|
192
|
-
role: "meter",
|
|
193
|
-
"aria-label": label,
|
|
194
|
-
"aria-valuemin": min,
|
|
195
|
-
"aria-valuemax": max,
|
|
196
|
-
"aria-valuenow": clamp(value, min, max),
|
|
197
|
-
"aria-valuetext": valueText,
|
|
198
|
-
className: cn("fds-meter", className),
|
|
199
|
-
...props,
|
|
200
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
201
|
-
className: "fds-meter__fill",
|
|
202
|
-
style: { width: `${ratio * 100}%` }
|
|
203
|
-
})
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
/** Discrete scale (for example a 1-7 risk indicator). */
|
|
207
|
-
function SegmentedMeter({ label, value, segments = 7, valueText, className, ...props }) {
|
|
208
|
-
const filled = clamp(Math.round(value), 0, segments);
|
|
209
|
-
return /* @__PURE__ */ jsx("div", {
|
|
210
|
-
role: "meter",
|
|
211
|
-
"aria-label": label,
|
|
212
|
-
"aria-valuemin": 0,
|
|
213
|
-
"aria-valuemax": segments,
|
|
214
|
-
"aria-valuenow": filled,
|
|
215
|
-
"aria-valuetext": valueText,
|
|
216
|
-
className: cn("fds-segmeter", className),
|
|
217
|
-
...props,
|
|
218
|
-
children: Array.from({ length: segments }, (_, index) => /* @__PURE__ */ jsx("span", { className: cn("fds-segmeter__segment", index < filled && "fds-segmeter__segment--on") }, index))
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
/** Ranked list of share bars, scaled relative to the largest row. */
|
|
222
|
-
function DistributionList({ items, limit = 5, formatValue = (v) => String(v), empty, renderOverflow, className, ...props }) {
|
|
223
|
-
const sorted = [...items].sort((a, b) => b.value - a.value);
|
|
224
|
-
const top = sorted.slice(0, limit);
|
|
225
|
-
const max = top[0]?.value ?? 0;
|
|
226
|
-
if (max <= 0) return empty ? /* @__PURE__ */ jsx("p", {
|
|
227
|
-
className: "fds-distribution__empty",
|
|
228
|
-
children: empty
|
|
229
|
-
}) : null;
|
|
230
|
-
const hidden = sorted.length - top.length;
|
|
231
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
232
|
-
className: cn("fds-distribution", className),
|
|
233
|
-
...props,
|
|
234
|
-
children: [/* @__PURE__ */ jsx("ul", {
|
|
235
|
-
className: "fds-distribution__list",
|
|
236
|
-
children: top.map((item, index) => /* @__PURE__ */ jsxs("li", {
|
|
237
|
-
className: "fds-distribution__row",
|
|
238
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
239
|
-
className: "fds-distribution__meta",
|
|
240
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
241
|
-
className: "fds-distribution__label",
|
|
242
|
-
children: item.label
|
|
243
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
244
|
-
className: "fds-distribution__value",
|
|
245
|
-
children: formatValue(item.value)
|
|
246
|
-
})]
|
|
247
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
248
|
-
"aria-hidden": "true",
|
|
249
|
-
className: "fds-meter fds-meter--thin",
|
|
250
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
251
|
-
className: "fds-meter__fill",
|
|
252
|
-
style: { width: `${Math.max(item.value, 0) / max * 100}%` }
|
|
253
|
-
})
|
|
254
|
-
})]
|
|
255
|
-
}, index))
|
|
256
|
-
}), hidden > 0 && renderOverflow ? /* @__PURE__ */ jsx("p", {
|
|
257
|
-
className: "fds-distribution__overflow",
|
|
258
|
-
children: renderOverflow(hidden)
|
|
259
|
-
}) : null]
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
// src/components/Menu.tsx
|
|
263
|
-
/** Action menu: closes on outside press and Escape (returning focus), arrow-key navigation. */
|
|
264
|
-
function Menu({ ariaLabel, items, trigger, className }) {
|
|
265
|
-
const [open, setOpen] = useState(false);
|
|
266
|
-
const rootRef = useRef(null);
|
|
267
|
-
const triggerRef = useRef(null);
|
|
268
|
-
const menuId = useId();
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
if (!open) return;
|
|
271
|
-
const onPointerDown = (event) => {
|
|
272
|
-
if (!rootRef.current?.contains(event.target)) setOpen(false);
|
|
273
|
-
};
|
|
274
|
-
document.addEventListener("mousedown", onPointerDown);
|
|
275
|
-
document.addEventListener("touchstart", onPointerDown);
|
|
276
|
-
return () => {
|
|
277
|
-
document.removeEventListener("mousedown", onPointerDown);
|
|
278
|
-
document.removeEventListener("touchstart", onPointerDown);
|
|
279
|
-
};
|
|
280
|
-
}, [open]);
|
|
281
|
-
useEffect(() => {
|
|
282
|
-
if (open) rootRef.current?.querySelector("[role=\"menuitem\"]:not(:disabled)")?.focus();
|
|
283
|
-
}, [open]);
|
|
284
|
-
const onKeyDown = (event) => {
|
|
285
|
-
if (!open) return;
|
|
286
|
-
if (event.key === "Escape") {
|
|
287
|
-
event.stopPropagation();
|
|
288
|
-
setOpen(false);
|
|
289
|
-
triggerRef.current?.focus();
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const enabled = Array.from(rootRef.current?.querySelectorAll("[role=\"menuitem\"]:not(:disabled)") ?? []);
|
|
293
|
-
if (enabled.length === 0) return;
|
|
294
|
-
const current = enabled.indexOf(document.activeElement);
|
|
295
|
-
const move = (next) => {
|
|
296
|
-
event.preventDefault();
|
|
297
|
-
enabled[(next + enabled.length) % enabled.length]?.focus();
|
|
298
|
-
};
|
|
299
|
-
if (event.key === "ArrowDown") move(current + 1);
|
|
300
|
-
else if (event.key === "ArrowUp") move(current < 0 ? enabled.length - 1 : current - 1);
|
|
301
|
-
else if (event.key === "Home") move(0);
|
|
302
|
-
else if (event.key === "End") move(enabled.length - 1);
|
|
303
|
-
else if (event.key === "Tab") setOpen(false);
|
|
304
|
-
};
|
|
305
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
306
|
-
className: cn("fds-menu", className),
|
|
307
|
-
onKeyDown,
|
|
308
|
-
ref: rootRef,
|
|
309
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
310
|
-
"aria-controls": open ? menuId : void 0,
|
|
311
|
-
"aria-expanded": open,
|
|
312
|
-
"aria-haspopup": "menu",
|
|
313
|
-
"aria-label": ariaLabel,
|
|
314
|
-
className: "fds-menu__trigger",
|
|
315
|
-
onClick: () => setOpen((value) => !value),
|
|
316
|
-
ref: triggerRef,
|
|
317
|
-
type: "button",
|
|
318
|
-
children: trigger ?? /* @__PURE__ */ jsxs("svg", {
|
|
319
|
-
"aria-hidden": "true",
|
|
320
|
-
focusable: "false",
|
|
321
|
-
height: "16",
|
|
322
|
-
viewBox: "0 0 16 16",
|
|
323
|
-
width: "16",
|
|
324
|
-
children: [
|
|
325
|
-
/* @__PURE__ */ jsx("circle", {
|
|
326
|
-
cx: "3",
|
|
327
|
-
cy: "8",
|
|
328
|
-
fill: "currentColor",
|
|
329
|
-
r: "1.4"
|
|
330
|
-
}),
|
|
331
|
-
/* @__PURE__ */ jsx("circle", {
|
|
332
|
-
cx: "8",
|
|
333
|
-
cy: "8",
|
|
334
|
-
fill: "currentColor",
|
|
335
|
-
r: "1.4"
|
|
336
|
-
}),
|
|
337
|
-
/* @__PURE__ */ jsx("circle", {
|
|
338
|
-
cx: "13",
|
|
339
|
-
cy: "8",
|
|
340
|
-
fill: "currentColor",
|
|
341
|
-
r: "1.4"
|
|
342
|
-
})
|
|
343
|
-
]
|
|
344
|
-
})
|
|
345
|
-
}), open ? /* @__PURE__ */ jsx("div", {
|
|
346
|
-
className: "fds-menu__list",
|
|
347
|
-
id: menuId,
|
|
348
|
-
role: "menu",
|
|
349
|
-
children: items.map((item) => /* @__PURE__ */ jsx("button", {
|
|
350
|
-
className: cn("fds-menu__item", item.tone === "danger" && "fds-menu__item--danger"),
|
|
351
|
-
disabled: item.disabled,
|
|
352
|
-
onClick: () => {
|
|
353
|
-
setOpen(false);
|
|
354
|
-
triggerRef.current?.focus();
|
|
355
|
-
item.onSelect();
|
|
356
|
-
},
|
|
357
|
-
role: "menuitem",
|
|
358
|
-
type: "button",
|
|
359
|
-
children: item.label
|
|
360
|
-
}, item.label))
|
|
361
|
-
}) : null]
|
|
362
|
-
});
|
|
363
|
-
}
|
|
1
|
+
import { cn } from "./lib/cn.mjs";
|
|
2
|
+
import { Button } from "./components/Button.mjs";
|
|
3
|
+
import { Panel } from "./components/Panel.mjs";
|
|
4
|
+
import { KeyValue, Metric } from "./components/Metric.mjs";
|
|
5
|
+
import { Badge } from "./components/Badge.mjs";
|
|
6
|
+
import { EmptyState, Notice } from "./components/Notice.mjs";
|
|
7
|
+
import { Field, Input, Select, Textarea } from "./components/Field.mjs";
|
|
8
|
+
import { DistributionList, Meter, SegmentedMeter } from "./components/Meter.mjs";
|
|
9
|
+
import { Menu } from "./components/Menu.mjs";
|
|
364
10
|
export { Badge, Button, DistributionList, EmptyState, Field, Input, KeyValue, Menu, Meter, Metric, Notice, Panel, SegmentedMeter, Select, Textarea, cn };
|
|
365
|
-
|
|
366
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/lib/cn.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.mjs","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["export function cn(...classes: Array<string | false | null | undefined>): string {\n return classes.filter(Boolean).join(\" \");\n}\n"],"mappings":";AAAA,SAAgB,GAAG,GAAG,SAA2D;CAC/E,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;AACzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scttlnd/fintech-design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared design foundations for Funder, Portare and Investor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,20 +35,35 @@
|
|
|
35
35
|
"build": "vp pack",
|
|
36
36
|
"check": "vp check",
|
|
37
37
|
"test": "vp test --run",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
38
39
|
"changeset": "changeset",
|
|
39
40
|
"version": "changeset version && vp check --fix",
|
|
40
41
|
"release": "changeset publish",
|
|
41
|
-
"prepublishOnly": "npm run build"
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"showcase:dev": "vp dev --config showcase/vite.config.ts",
|
|
44
|
+
"showcase:build": "vp build --config showcase/vite.config.ts",
|
|
45
|
+
"test:browser": "playwright test",
|
|
46
|
+
"test:visual": "FDS_VISUAL=1 playwright test",
|
|
47
|
+
"test:visual:update": "FDS_VISUAL=1 playwright test --update-snapshots",
|
|
48
|
+
"test:package": "node scripts/check-build.mjs && node scripts/check-size.mjs",
|
|
49
|
+
"smoke:next": "node scripts/smoke-next.mjs",
|
|
50
|
+
"smoke:next:browser": "node scripts/verify-next.mjs"
|
|
42
51
|
},
|
|
43
52
|
"devDependencies": {
|
|
44
53
|
"@changesets/changelog-github": "^1.0.1",
|
|
45
54
|
"@changesets/cli": "^3.0.3",
|
|
55
|
+
"@playwright/test": "^1.65.0",
|
|
56
|
+
"@testing-library/react": "^16.3.3",
|
|
57
|
+
"@testing-library/user-event": "^14.6.8",
|
|
46
58
|
"@types/react": "^18.3.18",
|
|
47
59
|
"@types/react-dom": "^18.3.7",
|
|
60
|
+
"axe-core": "^4.14.0",
|
|
61
|
+
"happy-dom": "^21.0.4",
|
|
48
62
|
"react": "^18.3.1",
|
|
49
63
|
"react-dom": "^18.3.1",
|
|
50
64
|
"typescript": "^5.7.3",
|
|
51
|
-
"vite-plus": "^1.5.0"
|
|
65
|
+
"vite-plus": "^1.5.0",
|
|
66
|
+
"vitest-axe": "^0.1.0"
|
|
52
67
|
},
|
|
53
68
|
"peerDependencies": {
|
|
54
69
|
"react": "^18.3.0 || ^19.0.0"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@layer fds {
|
|
2
|
+
/* Badge */
|
|
3
|
+
.fds-badge {
|
|
4
|
+
--fds-badge-color: var(--fds-muted-strong, var(--fds-foreground));
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: 0.375rem;
|
|
8
|
+
min-height: 1.75rem;
|
|
9
|
+
padding: 0 0.5rem;
|
|
10
|
+
border: 1px solid color-mix(in srgb, var(--fds-badge-color) 30%, transparent);
|
|
11
|
+
border-radius: var(--fds-radius-sm);
|
|
12
|
+
background: color-mix(in srgb, var(--fds-badge-color) 12%, transparent);
|
|
13
|
+
color: var(--fds-badge-color);
|
|
14
|
+
font-size: 0.75rem;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
}
|
|
18
|
+
.fds-badge--accent {
|
|
19
|
+
--fds-badge-color: var(--fds-accent);
|
|
20
|
+
}
|
|
21
|
+
.fds-badge--success {
|
|
22
|
+
--fds-badge-color: var(--fds-success);
|
|
23
|
+
}
|
|
24
|
+
.fds-badge--warn {
|
|
25
|
+
--fds-badge-color: var(--fds-warn);
|
|
26
|
+
}
|
|
27
|
+
.fds-badge--danger {
|
|
28
|
+
--fds-badge-color: var(--fds-danger);
|
|
29
|
+
}
|
|
30
|
+
.fds-badge__icon {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
}
|
|
33
|
+
}
|