ui-foundations 0.6.0 → 0.7.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.
Files changed (35) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +43 -8
  3. package/dist/macros/ui.njk +33 -0
  4. package/dist/main.css +230 -84
  5. package/dist/react/form.js +111 -0
  6. package/dist/react/index.js +1 -0
  7. package/dist/react/input.js +7 -0
  8. package/dist/react/textarea.js +7 -0
  9. package/dist/tokens/css/appearance-modes.tokens.mode-dark.css +7 -1
  10. package/dist/tokens/css/appearance-modes.tokens.mode-light.css +7 -1
  11. package/dist/tokens/css/components-ui.tokens.css +55 -33
  12. package/dist/tokens/css/core-primitives.tokens.css +1 -1
  13. package/dist/tokens/css/semantics-roles.tokens.css +1 -1
  14. package/dist/tokens/css/themes-brands.tokens.brand-a.css +1 -1
  15. package/dist/tokens/css/themes-brands.tokens.brand-b.css +1 -1
  16. package/dist/tokens/css/themes-brands.tokens.brand-c.css +1 -1
  17. package/dist/tokens/json/appearance-modes.tokens.mode-dark.json +24 -0
  18. package/dist/tokens/json/appearance-modes.tokens.mode-light.json +24 -0
  19. package/dist/tokens/json/components-ui.tokens.json +161 -25
  20. package/dist/tokens/tokens.yaml +509 -67
  21. package/dist/tokens/ts/appearance-modes.tokens.mode-dark.ts +7 -1
  22. package/dist/tokens/ts/appearance-modes.tokens.mode-light.ts +7 -1
  23. package/dist/tokens/ts/components-ui.tokens.ts +55 -33
  24. package/dist/tokens/ts/core-primitives.tokens.ts +1 -1
  25. package/dist/tokens/ts/semantics-roles.tokens.ts +1 -1
  26. package/dist/tokens/ts/themes-brands.tokens.brand-a.ts +1 -1
  27. package/dist/tokens/ts/themes-brands.tokens.brand-b.ts +1 -1
  28. package/dist/tokens/ts/themes-brands.tokens.brand-c.ts +1 -1
  29. package/dist/ui/index.css +1 -0
  30. package/dist/ui/patterns/button.css +3 -3
  31. package/dist/ui/patterns/checkbox.css +28 -28
  32. package/dist/ui/patterns/form.css +112 -0
  33. package/dist/ui/patterns/input.css +12 -12
  34. package/dist/ui/patterns/label.css +1 -1
  35. package/package.json +14 -3
@@ -0,0 +1,111 @@
1
+ import React from "react";
2
+
3
+ /**
4
+ * Form — layout container for form fields.
5
+ *
6
+ * @param {object} props
7
+ * @param {boolean} [props.borderless=false] - Remove border and background
8
+ * @param {string} [props.className=""] - Additional class names
9
+ * @param {React.ReactNode} props.children - Form content
10
+ */
11
+ export function Form({
12
+ borderless = false,
13
+ className = "",
14
+ children,
15
+ ...props
16
+ }) {
17
+ const classes = ["form"];
18
+ if (borderless) classes.push("borderless");
19
+ if (className) classes.push(className);
20
+
21
+ return React.createElement(
22
+ "form",
23
+ { className: classes.join(" "), noValidate: true, ...props },
24
+ children,
25
+ );
26
+ }
27
+
28
+ /**
29
+ * FormGroup — groups related fields with an optional title.
30
+ *
31
+ * @param {object} props
32
+ * @param {string} [props.title=""] - Group legend
33
+ * @param {string} [props.className=""] - Additional class names
34
+ * @param {React.ReactNode} props.children - Grouped fields
35
+ */
36
+ export function FormGroup({ title = "", className = "", children, ...props }) {
37
+ const classes = ["form-group"];
38
+ if (className) classes.push(className);
39
+
40
+ return React.createElement(
41
+ "fieldset",
42
+ { className: classes.join(" "), ...props },
43
+ title
44
+ ? React.createElement("legend", { className: "form-group__title" }, title)
45
+ : null,
46
+ children,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * FormField — single field wrapper with optional side label layout.
52
+ *
53
+ * @param {object} props
54
+ * @param {"top"|"side"} [props.labelPosition="top"] - Label placement
55
+ * @param {boolean} [props.invalid=false] - Show invalid state
56
+ * @param {string} [props.className=""] - Additional class names
57
+ * @param {React.ReactNode} props.children - Label + input + helper
58
+ */
59
+ export function FormField({
60
+ labelPosition = "top",
61
+ invalid = false,
62
+ className = "",
63
+ children,
64
+ ...props
65
+ }) {
66
+ const classes = ["form-field"];
67
+ if (invalid) classes.push("is-invalid");
68
+ if (className) classes.push(className);
69
+
70
+ const attrs = { className: classes.join(" "), ...props };
71
+ if (labelPosition === "side") attrs["data-label-position"] = "side";
72
+
73
+ return React.createElement("div", attrs, children);
74
+ }
75
+
76
+ /**
77
+ * FormHelper — helper or error text below a field.
78
+ *
79
+ * @param {object} props
80
+ * @param {string} props.text - Helper message
81
+ */
82
+ export function FormHelper({ text, ...props }) {
83
+ return React.createElement(
84
+ "p",
85
+ { className: "form-field__helper", ...props },
86
+ text,
87
+ );
88
+ }
89
+
90
+ /**
91
+ * FormActions — button area at the bottom of a form.
92
+ *
93
+ * @param {object} props
94
+ * @param {"start"|"end"|"stretch"} [props.align="end"] - Button alignment
95
+ * @param {string} [props.className=""] - Additional class names
96
+ * @param {React.ReactNode} props.children - Action buttons
97
+ */
98
+ export function FormActions({
99
+ align = "end",
100
+ className = "",
101
+ children,
102
+ ...props
103
+ }) {
104
+ const classes = ["form-actions"];
105
+ if (className) classes.push(className);
106
+
107
+ const attrs = { className: classes.join(" "), ...props };
108
+ if (align !== "end") attrs["data-align"] = align;
109
+
110
+ return React.createElement("div", attrs, children);
111
+ }
@@ -12,3 +12,4 @@ export { Avatar } from "./avatar.js";
12
12
  export { Accordion, AccordionItem } from "./accordion.js";
13
13
  export { TabList, Tab, TabPanel } from "./tabs.js";
14
14
  export { Tooltip } from "./tooltip.js";
15
+ export { Form, FormGroup, FormField, FormHelper, FormActions } from "./form.js";
@@ -1,9 +1,16 @@
1
1
  import React from "react";
2
+ import { warnDev } from "./warn-dev.js";
2
3
 
3
4
  export function Input({ className = "", type = "text", ...props }) {
4
5
  const classes = ["input"];
5
6
  if (className) classes.push(className);
6
7
 
8
+ if (!props["aria-label"] && !props["aria-labelledby"] && !props.id) {
9
+ warnDev(
10
+ "[ui-foundations] Input should be associated with a label via `id`, or include `aria-label`/`aria-labelledby`.",
11
+ );
12
+ }
13
+
7
14
  return React.createElement("input", {
8
15
  type,
9
16
  className: classes.join(" "),
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { warnDev } from "./warn-dev.js";
2
3
 
3
4
  /**
4
5
  * TextArea — multi-line text input.
@@ -23,6 +24,12 @@ export function TextArea({
23
24
  const classes = ["textarea"];
24
25
  if (className) classes.push(className);
25
26
 
27
+ if (!props["aria-label"] && !props["aria-labelledby"] && !props.id) {
28
+ warnDev(
29
+ "[ui-foundations] TextArea should be associated with a label via `id`, or include `aria-label`/`aria-labelledby`.",
30
+ );
31
+ }
32
+
26
33
  const elementProps = {
27
34
  className: classes.join(" "),
28
35
  placeholder,
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.603Z */
2
+ /* Generated on 2026-06-06T14:29:01.170Z */
3
3
 
4
4
  :root[data-mode="dark"] {
5
5
  --color-text-default: var(--color-neutral-800);
@@ -10,6 +10,12 @@
10
10
  --color-text-strong: var(--color-neutral-1000);
11
11
  --color-text-danger: var(--brand-color-functional-danger);
12
12
  --color-text-success: var(--brand-color-functional-success);
13
+ --color-text-on-brand: var(--color-neutral-000);
14
+ --color-text-on-danger: var(--color-neutral-000);
15
+ --color-text-on-success: var(--color-neutral-000);
16
+ --color-text-on-subtle: var(--brand-color-primary-dark);
17
+ --color-text-on-active: var(--color-neutral-000);
18
+ --color-text-on-disabled: var(--color-neutral-800);
13
19
  --color-fill-surface: var(--color-neutral-1000);
14
20
  --color-fill-disabled: var(--color-neutral-300);
15
21
  --color-fill-hover: var(--color-neutral-alpha-inverse-100);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.601Z */
2
+ /* Generated on 2026-06-06T14:29:01.166Z */
3
3
 
4
4
  :root {
5
5
  --color-text-default: var(--color-neutral-800);
@@ -10,6 +10,12 @@
10
10
  --color-text-strong: var(--color-neutral-1000);
11
11
  --color-text-danger: var(--brand-color-functional-danger);
12
12
  --color-text-success: var(--brand-color-functional-success);
13
+ --color-text-on-brand: var(--color-neutral-000);
14
+ --color-text-on-danger: var(--color-neutral-000);
15
+ --color-text-on-success: var(--color-neutral-000);
16
+ --color-text-on-subtle: var(--brand-color-primary);
17
+ --color-text-on-active: var(--color-neutral-000);
18
+ --color-text-on-disabled: var(--color-neutral-800);
13
19
  --color-fill-surface: var(--color-neutral-000);
14
20
  --color-fill-disabled: var(--color-neutral-300);
15
21
  --color-fill-hover: var(--color-neutral-alpha-500);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.611Z */
2
+ /* Generated on 2026-06-06T14:29:01.180Z */
3
3
 
4
4
  :root {
5
5
  --button-border-size-hover: var(--size-border-100);
@@ -7,13 +7,13 @@
7
7
  --button-solid-border-color-hover: var(--color-border-brand);
8
8
  --button-solid-border-color-active: var(--color-border-brand);
9
9
  --button-solid-border-color-focus: var(--color-border-brand);
10
- --button-solid-text-color-default: var(--color-text-inverse);
10
+ --button-solid-text-color-default: var(--color-text-on-brand);
11
11
  --button-solid-container-background-default: var(--color-fill-brand);
12
12
  --button-solid-container-background-hover: var(--color-fill-brand);
13
13
  --button-solid-container-background-active: var(--color-fill-brand);
14
14
  --button-solid-container-background-focus: var(--color-fill-brand);
15
- --button-solid-text-color-hover: var(--color-text-inverse);
16
- --button-solid-text-color-active: var(--color-text-inverse);
15
+ --button-solid-text-color-hover: var(--color-text-on-brand);
16
+ --button-solid-text-color-active: var(--color-text-on-brand);
17
17
  --button-outline-border-color-default: var(--color-border-brand);
18
18
  --button-outline-border-color-hover: var(--color-border-brand);
19
19
  --button-outline-border-color-active: var(--color-border-brand);
@@ -58,22 +58,22 @@
58
58
  --modal-backdrop-color: var(--color-overlay-backdrop);
59
59
  --modal-surface-color: var(--color-fill-surface);
60
60
  --modal-surface-border-radius: var(--brand-corner-modal);
61
- --input-text-text-color-default: var(--color-text-default);
62
- --input-text-text-color-hover: var(--color-text-default);
63
- --input-text-text-color-active: var(--color-text-default);
64
- --input-text-text-color-disabled: var(--color-text-disabled);
65
- --input-text-text-color-placeholder: var(--color-text-subtle);
66
- --input-border-border-color-default: var(--color-border-default);
67
- --input-border-border-color-hover: var(--color-border-brand);
68
- --input-border-border-color-active: var(--color-border-brand);
69
- --input-border-border-color-focus: var(--color-border-brand);
70
- --input-border-border-size-default: var(--size-border-100);
71
- --input-border-border-size-hover: var(--size-border-100);
72
- --input-border-border-size-active: var(--size-border-200);
73
- --input-border-border-radius: var(--brand-corner-input);
74
- --input-border-border-color-disabled: var(--color-border-disabled);
75
- --input-border-border-color-invalid: var(--color-border-danger);
76
- --input-border-border-color-valid: var(--color-border-strong);
61
+ --input-text-color-default: var(--color-text-default);
62
+ --input-text-color-hover: var(--color-text-brand);
63
+ --input-text-color-active: var(--color-text-default);
64
+ --input-text-color-disabled: var(--color-text-disabled);
65
+ --input-text-color-placeholder: var(--color-text-subtle);
66
+ --input-border-color-default: var(--color-border-default);
67
+ --input-border-color-hover: var(--color-border-brand);
68
+ --input-border-color-active: var(--color-border-brand);
69
+ --input-border-color-focus: var(--color-border-brand);
70
+ --input-border-size-default: var(--size-border-100);
71
+ --input-border-size-hover: var(--size-border-100);
72
+ --input-border-size-active: var(--size-border-200);
73
+ --input-border-radius: var(--brand-corner-input);
74
+ --input-border-color-disabled: var(--color-border-disabled);
75
+ --input-border-color-invalid: var(--color-border-danger);
76
+ --input-border-color-valid: var(--color-border-strong);
77
77
  --input-font-family: var(--brand-font-base);
78
78
  --input-font-weight: var(--typography-body-font-weight);
79
79
  --input-font-size: var(--typography-label-font-size);
@@ -91,6 +91,10 @@
91
91
  --input-overlay-active: var(--color-transparent);
92
92
  --input-height: 2.5rem;
93
93
  --input-height-min: 2.5rem;
94
+ --input-radio-text-color-active: var(--color-text-default);
95
+ --input-radio-text-color-hover: var(--color-text-strong);
96
+ --input-radio-container-background-hover: var(--color-fill-surface);
97
+ --input-radio-border-color-focus: var(--color-border-brand);
94
98
  --form-group-gap: var(--size-spacing-400);
95
99
  --form-group-title-color: var(--color-text-default);
96
100
  --form-padding-inline: var(--size-spacing-400);
@@ -104,10 +108,10 @@
104
108
  --form-container-border-color: var(--color-border-subtle);
105
109
  --form-border-size: var(--size-border-100);
106
110
  --checkbox-text-color-default: var(--color-text-default);
107
- --checkbox-text-color-hover: var(--color-text-strong);
108
- --checkbox-text-color-active: var(--color-text-inverse);
111
+ --checkbox-text-color-hover: var(--color-text-brand);
112
+ --checkbox-text-color-active: var(--color-text-on-brand);
109
113
  --checkbox-border-color-default: var(--color-border-subtle);
110
- --checkbox-border-color-hover: var(--color-border-strong);
114
+ --checkbox-border-color-hover: var(--color-border-brand);
111
115
  --checkbox-border-color-active: var(--color-border-brand);
112
116
  --checkbox-border-color-focus: var(--color-border-brand);
113
117
  --checkbox-border-color-invalid: var(--color-border-danger);
@@ -125,7 +129,7 @@
125
129
  --input-radio-text-color-default: var(--color-text-default);
126
130
  --input-radio-text-color-disabled: var(--color-text-disabled);
127
131
  --input-radio-border-color-default: var(--color-border-subtle);
128
- --input-radio-border-color-hover: var(--color-border-strong);
132
+ --input-radio-border-color-hover: var(--color-border-brand);
129
133
  --input-radio-border-color-active: var(--color-border-brand);
130
134
  --input-radio-border-color-disabled: var(--color-border-disabled);
131
135
  --input-radio-container-background-default: var(--color-fill-surface);
@@ -136,25 +140,43 @@
136
140
  --badge-default-text-color: var(--color-text-default);
137
141
  --badge-default-border-color: var(--color-transparent);
138
142
  --badge-default-container-background: var(--color-fill-subtle);
139
- --badge-brand-text-color: var(--color-text-inverse);
143
+ --badge-brand-text-color: var(--color-text-on-brand);
140
144
  --badge-brand-border-color: var(--color-transparent);
141
145
  --badge-brand-container-background: var(--color-fill-brand);
142
- --badge-success-text-color: var(--color-text-inverse);
146
+ --badge-success-text-color: var(--color-text-on-success);
143
147
  --badge-success-border-color: var(--color-transparent);
144
148
  --badge-success-container-background: var(--color-fill-success);
145
149
  --badge-font-family: var(--brand-font-lead);
146
150
  --badge-font-weight: var(--typography-label-font-weight);
147
- --badge-font-size: var(--typography-label-font-size);
148
- --badge-line-height: var(--typography-label-line-height);
149
151
  --badge-border-size-default: var(--size-border-100);
150
- --badge-border-radius: var(--brand-corner-input);
151
- --badge-padding-inline: var(--size-spacing-400);
152
+ --badge-border-radius: var(--size-radius-full);
153
+ --badge-padding-inline-md: var(--size-spacing-300);
152
154
  --badge-padding-inline-icon-only: var(--size-spacing-200);
153
- --badge-padding-block: var(--size-spacing-200);
154
- --badge-gap: var(--size-spacing-200);
155
+ --badge-padding-block-md: var(--size-spacing-100);
156
+ --badge-gap: var(--size-spacing-100);
155
157
  --badge-height-min: 2.5rem;
156
158
  --badge-width-min: 2.5rem;
157
159
  --badge-danger-container-background: var(--color-fill-danger);
158
160
  --badge-danger-border-color: var(--color-transparent);
159
- --badge-danger-text-color: var(--color-text-inverse);
161
+ --badge-danger-text-color: var(--color-text-on-danger);
162
+ --badge-font-size-sm: .75rem;
163
+ --badge-font-size-md: .875rem;
164
+ --badge-line-height-sm: 1rem;
165
+ --badge-line-height-md: 1.25rem;
166
+ --badge-padding-inline-sm: .5rem;
167
+ --badge-padding-block-sm: .125rem;
168
+ --switch-track-background-default: var(--color-fill-surface);
169
+ --switch-track-background-hover: var(--color-fill-surface);
170
+ --switch-track-background-active: var(--color-fill-brand);
171
+ --switch-track-background-disabled: var(--color-fill-disabled);
172
+ --switch-track-border-color-default: var(--color-border-default);
173
+ --switch-track-border-color-hover: var(--color-border-brand);
174
+ --switch-track-border-color-active: var(--color-border-brand);
175
+ --switch-track-border-color-disabled: var(--color-border-disabled);
176
+ --switch-thumb-background-default: var(--color-border-default);
177
+ --switch-thumb-background-hover: var(--color-border-brand);
178
+ --switch-thumb-background-active: var(--color-fill-surface);
179
+ --switch-thumb-background-disabled: var(--color-fill-surface);
180
+ --switch-text-color-default: var(--color-text-default);
181
+ --switch-text-color-disabled: var(--color-text-disabled);
160
182
  }
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.621Z */
2
+ /* Generated on 2026-06-06T14:29:01.194Z */
3
3
 
4
4
  :root {
5
5
  --color-neutral-100: rgb(230 230 230);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.624Z */
2
+ /* Generated on 2026-06-06T14:29:01.200Z */
3
3
 
4
4
  :root {
5
5
  --typography-heading-font-family: var(--brand-font-lead);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.626Z */
2
+ /* Generated on 2026-06-06T14:29:01.202Z */
3
3
 
4
4
  :root[data-brand="a"] {
5
5
  --brand-color-functional-success: var(--color-brand-c-green-30);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.628Z */
2
+ /* Generated on 2026-06-06T14:29:01.203Z */
3
3
 
4
4
  :root[data-brand="b"] {
5
5
  --brand-color-functional-success: var(--color-brand-b-green-600);
@@ -1,5 +1,5 @@
1
1
  /* Auto-generated design tokens from Figma */
2
- /* Generated on 2026-05-22T09:23:10.629Z */
2
+ /* Generated on 2026-06-06T14:29:01.205Z */
3
3
 
4
4
  :root[data-brand="c"] {
5
5
  --brand-color-functional-success: var(--color-brand-a-green-600);
@@ -33,6 +33,30 @@
33
33
  "Success": {
34
34
  "$type": "color",
35
35
  "$value": "{Brand.Color.Functional.Success}"
36
+ },
37
+ "On Brand": {
38
+ "$type": "color",
39
+ "$value": "{Color.Neutral.000}"
40
+ },
41
+ "On Danger": {
42
+ "$type": "color",
43
+ "$value": "{Color.Neutral.000}"
44
+ },
45
+ "On Success": {
46
+ "$type": "color",
47
+ "$value": "{Color.Neutral.000}"
48
+ },
49
+ "On Subtle": {
50
+ "$type": "color",
51
+ "$value": "{Brand.Color.Primary}"
52
+ },
53
+ "On Active": {
54
+ "$type": "color",
55
+ "$value": "{Color.Neutral.000}"
56
+ },
57
+ "On Disabled": {
58
+ "$type": "color",
59
+ "$value": "{Color.Neutral.800}"
36
60
  }
37
61
  },
38
62
  "Fill": {
@@ -33,6 +33,30 @@
33
33
  "Success": {
34
34
  "$type": "color",
35
35
  "$value": "{Brand.Color.Functional.Success}"
36
+ },
37
+ "On Brand": {
38
+ "$type": "color",
39
+ "$value": "{Color.Neutral.000}"
40
+ },
41
+ "On Danger": {
42
+ "$type": "color",
43
+ "$value": "{Color.Neutral.000}"
44
+ },
45
+ "On Success": {
46
+ "$type": "color",
47
+ "$value": "{Color.Neutral.000}"
48
+ },
49
+ "On Subtle": {
50
+ "$type": "color",
51
+ "$value": "{Brand.Color.Primary}"
52
+ },
53
+ "On Active": {
54
+ "$type": "color",
55
+ "$value": "{Color.Neutral.000}"
56
+ },
57
+ "On Disabled": {
58
+ "$type": "color",
59
+ "$value": "{Color.Neutral.800}"
36
60
  }
37
61
  },
38
62
  "Fill": {