@oneli8/react 1.0.0-beta.5 → 1.0.0-beta.6
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 +7 -3
- package/ai-context.json +116 -1
- package/package.json +2 -2
- package/src/atoms/NavigationBadge.js +19 -0
- package/src/foundations/geometry.js +71 -0
- package/src/index.d.ts +214 -0
- package/src/index.js +14 -0
- package/src/molecules/ChoiceChip.js +101 -0
- package/src/molecules/FormMessage.js +33 -0
- package/src/molecules/Link.js +31 -0
- package/src/molecules/NavigationItem.js +55 -0
- package/src/molecules/Select.js +109 -0
- package/src/molecules/SelectionOption.js +4 -1
- package/src/molecules/TextField.js +174 -0
- package/src/molecules/Token.js +49 -0
- package/src/organisms/ChoicePicker.js +114 -0
- package/src/organisms/Combobox.js +122 -0
- package/src/organisms/MultiSelectField.js +138 -0
- package/src/organisms/SegmentedControl.js +114 -0
- package/src/organisms/SelectionPopup.js +73 -0
- package/src/organisms/TabBar.js +68 -0
- package/src/organisms/Tabs.js +123 -0
- package/src/styles/choice-chip.css +216 -0
- package/src/styles/choice-picker.css +100 -0
- package/src/styles/combobox.css +42 -0
- package/src/styles/form-message.css +48 -0
- package/src/styles/gem.css +52 -5
- package/src/styles/link.css +116 -0
- package/src/styles/multi-select-field.css +78 -0
- package/src/styles/navigation-badge.css +37 -0
- package/src/styles/navigation-item.css +155 -0
- package/src/styles/segmented-control.css +115 -0
- package/src/styles/select.css +33 -0
- package/src/styles/selection-indicator.css +13 -4
- package/src/styles/selection-popup.css +92 -0
- package/src/styles/tab-bar.css +47 -0
- package/src/styles/tabs.css +55 -0
- package/src/styles/text-field.css +268 -0
- package/src/styles/token.css +65 -0
- package/styles.css +18 -2
package/README.md
CHANGED
|
@@ -19,11 +19,15 @@ export function Actions() {
|
|
|
19
19
|
|
|
20
20
|
| Tier | Components |
|
|
21
21
|
|---|---|
|
|
22
|
-
| Atom | `Icon`, `SelectionIndicator` |
|
|
23
|
-
| Molecule | `Button`, `IconButton`, `ChoiceItem`, `SelectionOption` |
|
|
24
|
-
| Organism | `RadioGroup`, `CheckboxGroup` |
|
|
22
|
+
| Atom | `Icon`, `SelectionIndicator`, `NavigationBadge` |
|
|
23
|
+
| Molecule | `Button`, `IconButton`, `Link`, `TextField`, `FormMessage`, `Select`, `ChoiceItem`, `SelectionOption`, `ChoiceChip`, `Token` |
|
|
24
|
+
| Organism | `Tabs`, `TabBar`, `SegmentedControl`, `RadioGroup`, `CheckboxGroup`, `SelectionPopup`, `Combobox`, `MultiSelectField`, `ChoicePicker` |
|
|
25
25
|
| Material | Gem, an attribute adapter rather than a tier |
|
|
26
26
|
|
|
27
|
+
Twenty one components, matching `@oneli8/core` one for one. Both packages are
|
|
28
|
+
checked against each other on every build, so the two cannot answer differently
|
|
29
|
+
about a role, a name, a relationship or a state.
|
|
30
|
+
|
|
27
31
|
## The system travels with the package
|
|
28
32
|
|
|
29
33
|
If you need a component OneLi8 does not ship, build it from these foundations
|
package/ai-context.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED by packages/sync/src/build-package-context.mjs — do not edit.",
|
|
3
3
|
"system": "OneLi8",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.6",
|
|
5
5
|
"meaning": "OneLi8 means One Light.",
|
|
6
6
|
"foundations": {
|
|
7
7
|
"package": "@oneli8/tokens",
|
|
@@ -42,6 +42,14 @@
|
|
|
42
42
|
],
|
|
43
43
|
"constraint": "Composes no other component. Native elements only."
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"tier": "atom",
|
|
47
|
+
"module": "atoms/NavigationBadge.js",
|
|
48
|
+
"exports": [
|
|
49
|
+
"NavigationBadge"
|
|
50
|
+
],
|
|
51
|
+
"constraint": "Composes no other component. Native elements only."
|
|
52
|
+
},
|
|
45
53
|
{
|
|
46
54
|
"tier": "atom",
|
|
47
55
|
"module": "atoms/SelectionIndicator.js",
|
|
@@ -58,6 +66,14 @@
|
|
|
58
66
|
],
|
|
59
67
|
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
60
68
|
},
|
|
69
|
+
{
|
|
70
|
+
"tier": "molecule",
|
|
71
|
+
"module": "molecules/ChoiceChip.js",
|
|
72
|
+
"exports": [
|
|
73
|
+
"ChoiceChip"
|
|
74
|
+
],
|
|
75
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
76
|
+
},
|
|
61
77
|
{
|
|
62
78
|
"tier": "molecule",
|
|
63
79
|
"module": "molecules/ChoiceItem.js",
|
|
@@ -66,6 +82,14 @@
|
|
|
66
82
|
],
|
|
67
83
|
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
68
84
|
},
|
|
85
|
+
{
|
|
86
|
+
"tier": "molecule",
|
|
87
|
+
"module": "molecules/FormMessage.js",
|
|
88
|
+
"exports": [
|
|
89
|
+
"FormMessage"
|
|
90
|
+
],
|
|
91
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
92
|
+
},
|
|
69
93
|
{
|
|
70
94
|
"tier": "molecule",
|
|
71
95
|
"module": "molecules/IconButton.js",
|
|
@@ -74,6 +98,23 @@
|
|
|
74
98
|
],
|
|
75
99
|
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
76
100
|
},
|
|
101
|
+
{
|
|
102
|
+
"tier": "molecule",
|
|
103
|
+
"module": "molecules/Link.js",
|
|
104
|
+
"exports": [
|
|
105
|
+
"Link"
|
|
106
|
+
],
|
|
107
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"tier": "molecule",
|
|
111
|
+
"module": "molecules/Select.js",
|
|
112
|
+
"exports": [
|
|
113
|
+
"OL8_SELECT_NATIVE_POLICIES",
|
|
114
|
+
"Select"
|
|
115
|
+
],
|
|
116
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
117
|
+
},
|
|
77
118
|
{
|
|
78
119
|
"tier": "molecule",
|
|
79
120
|
"module": "molecules/SelectionOption.js",
|
|
@@ -82,6 +123,24 @@
|
|
|
82
123
|
],
|
|
83
124
|
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
84
125
|
},
|
|
126
|
+
{
|
|
127
|
+
"tier": "molecule",
|
|
128
|
+
"module": "molecules/TextField.js",
|
|
129
|
+
"exports": [
|
|
130
|
+
"TextField",
|
|
131
|
+
"countGraphemes",
|
|
132
|
+
"clipToGraphemes"
|
|
133
|
+
],
|
|
134
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"tier": "molecule",
|
|
138
|
+
"module": "molecules/Token.js",
|
|
139
|
+
"exports": [
|
|
140
|
+
"Token"
|
|
141
|
+
],
|
|
142
|
+
"constraint": "Composed of atoms. Reusable and more specialised."
|
|
143
|
+
},
|
|
85
144
|
{
|
|
86
145
|
"tier": "organism",
|
|
87
146
|
"module": "organisms/ChoiceGroup.js",
|
|
@@ -90,6 +149,62 @@
|
|
|
90
149
|
"CheckboxGroup"
|
|
91
150
|
],
|
|
92
151
|
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"tier": "organism",
|
|
155
|
+
"module": "organisms/ChoicePicker.js",
|
|
156
|
+
"exports": [
|
|
157
|
+
"ChoicePicker"
|
|
158
|
+
],
|
|
159
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"tier": "organism",
|
|
163
|
+
"module": "organisms/Combobox.js",
|
|
164
|
+
"exports": [
|
|
165
|
+
"Combobox"
|
|
166
|
+
],
|
|
167
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"tier": "organism",
|
|
171
|
+
"module": "organisms/MultiSelectField.js",
|
|
172
|
+
"exports": [
|
|
173
|
+
"MultiSelectField"
|
|
174
|
+
],
|
|
175
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"tier": "organism",
|
|
179
|
+
"module": "organisms/SegmentedControl.js",
|
|
180
|
+
"exports": [
|
|
181
|
+
"SegmentedControl"
|
|
182
|
+
],
|
|
183
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"tier": "organism",
|
|
187
|
+
"module": "organisms/SelectionPopup.js",
|
|
188
|
+
"exports": [
|
|
189
|
+
"SelectionPopup"
|
|
190
|
+
],
|
|
191
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"tier": "organism",
|
|
195
|
+
"module": "organisms/TabBar.js",
|
|
196
|
+
"exports": [
|
|
197
|
+
"TabBar"
|
|
198
|
+
],
|
|
199
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"tier": "organism",
|
|
203
|
+
"module": "organisms/Tabs.js",
|
|
204
|
+
"exports": [
|
|
205
|
+
"Tabs"
|
|
206
|
+
],
|
|
207
|
+
"constraint": "Composed of atoms, molecules or organisms. May hold application state."
|
|
93
208
|
}
|
|
94
209
|
],
|
|
95
210
|
"buildingWhatIsNotHere": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneli8/react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "OneLi8 Design System for React. Components, design tokens, the Gem material, and the design language they follow.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"NOTICE"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@oneli8/tokens": "1.0.0-beta.
|
|
55
|
+
"@oneli8/tokens": "1.0.0-beta.6"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"react": ">=18"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createElement, forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ATOM. Figma 989:484: "Content comes from item.badge; geometry and colors are
|
|
5
|
+
* semantic token controlled." One atom serves all three navigation organisms.
|
|
6
|
+
*
|
|
7
|
+
* The count sits beside a label that already names the destination, so it is
|
|
8
|
+
* not announced twice. A product that needs it announced spells it into the
|
|
9
|
+
* item's own accessible name.
|
|
10
|
+
*/
|
|
11
|
+
export const NavigationBadge = forwardRef(function NavigationBadge({ children, className = '', ...rest }, ref) {
|
|
12
|
+
if (children === undefined || children === null || children === '') {
|
|
13
|
+
throw new Error('[ol8] a navigation badge with nothing to say should not be rendered at all.');
|
|
14
|
+
}
|
|
15
|
+
return createElement('span', {
|
|
16
|
+
...rest, ref, 'aria-hidden': 'true',
|
|
17
|
+
className: `ol8-nav-badge${className ? ` ${className}` : ''}`,
|
|
18
|
+
}, children);
|
|
19
|
+
});
|
|
@@ -28,7 +28,78 @@ export const OL8_SELECTION_STATES = {
|
|
|
28
28
|
|
|
29
29
|
export const OL8_CHOICE_KINDS = OL8_SELECTION_KINDS;
|
|
30
30
|
export const OL8_CHOICE_SIZES = OL8_BUTTON_SIZES;
|
|
31
|
+
export const OL8_LINK_FORMS = ['inline', 'standalone', 'navigation'];
|
|
32
|
+
export const OL8_LINK_SIZES = ['small', 'standard', 'large'];
|
|
33
|
+
export const OL8_LINK_MOTIONS = ['system', 'none'];
|
|
34
|
+
|
|
35
|
+
export const OL8_TEXT_FIELD_SIZES = OL8_BUTTON_SIZES;
|
|
36
|
+
export const OL8_TEXT_FIELD_APPEARANCES = ['outline', 'filled'];
|
|
37
|
+
export const OL8_TEXT_FIELD_MATERIALS = OL8_MATERIALS;
|
|
38
|
+
/** The four tones a message can carry. Pending is not one of them. */
|
|
39
|
+
export const OL8_MESSAGE_TONES = ['critical', 'caution', 'positive', 'informative'];
|
|
40
|
+
/** "Pending is a validation process status, not a fifth message tone." */
|
|
41
|
+
export const OL8_VALIDATION_STATUSES = ['idle', 'pending', 'resolved'];
|
|
42
|
+
/** Soft lets a person overshoot and then correct. Hard is for real technical limits. */
|
|
43
|
+
export const OL8_CHARACTER_LIMIT_BEHAVIORS = ['soft', 'hard'];
|
|
44
|
+
|
|
45
|
+
/** Form Message itself draws all five, because Figma 177:13 enumerates five. */
|
|
46
|
+
export const OL8_FORM_MESSAGE_TONES = [...OL8_MESSAGE_TONES, 'pending'];
|
|
47
|
+
/** Figma pairs one status icon with each tone (Form Message 177:13). */
|
|
48
|
+
export const OL8_FORM_MESSAGE_ICONS = {
|
|
49
|
+
critical: 'critical',
|
|
50
|
+
caution: 'caution',
|
|
51
|
+
positive: 'positive',
|
|
52
|
+
informative: 'informative',
|
|
53
|
+
pending: 'loading',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const OL8_NAVIGATION_SIZES = OL8_BUTTON_SIZES;
|
|
57
|
+
export const OL8_TABS_HIERARCHIES = ['primary', 'secondary', 'tertiary'];
|
|
58
|
+
export const OL8_TABS_ORIENTATIONS = ['horizontal', 'vertical'];
|
|
59
|
+
export const OL8_TABS_ACTIVATIONS = ['automatic', 'manual'];
|
|
60
|
+
/** "Declare exactly one behavior." */
|
|
61
|
+
export const OL8_SEGMENT_BEHAVIORS = ['single', 'multi', 'momentary'];
|
|
62
|
+
/** "Presentation never changes behavior." */
|
|
63
|
+
export const OL8_SEGMENT_PRESENTATIONS = [
|
|
64
|
+
'inset-fill', 'line-indicator', 'outlined-selection', 'soft-pill', 'icon-only', 'stacked-label',
|
|
65
|
+
];
|
|
66
|
+
export const OL8_TAB_BAR_PRESENTATIONS = ['bottom', 'inline', 'sidebar', 'spatial-rail'];
|
|
67
|
+
|
|
68
|
+
export const OL8_CHIP_SIZES = OL8_BUTTON_SIZES;
|
|
69
|
+
export const OL8_TOKEN_SIZES = OL8_CHIP_SIZES;
|
|
70
|
+
/** Figma draws the last three; `auto` resolves to one of them in code. */
|
|
71
|
+
export const OL8_TOKEN_MARKS = ['auto', 'none', 'check', 'remove'];
|
|
72
|
+
|
|
73
|
+
/** The contract's resolution, stated once so both packages agree. */
|
|
74
|
+
export function resolveTokenMark(mark, { removable = true, selected = false } = {}) {
|
|
75
|
+
if (mark !== 'auto') return mark;
|
|
76
|
+
if (removable) return 'remove';
|
|
77
|
+
return selected ? 'check' : 'none';
|
|
78
|
+
}
|
|
79
|
+
|
|
31
80
|
export const OL8_OPTION_SIZES = ['standard', 'large'];
|
|
81
|
+
export const OL8_POPUP_WIDTHS = ['content', 'anchor', 'wide'];
|
|
82
|
+
export const OL8_POPUP_SIZES = OL8_OPTION_SIZES;
|
|
83
|
+
export const OL8_MULTI_SELECT_SIZES = OL8_BUTTON_SIZES;
|
|
84
|
+
export const OL8_MULTI_SELECT_APPEARANCES = ['outline', 'filled'];
|
|
85
|
+
export const OL8_MULTI_SELECT_MATERIALS = OL8_MATERIALS;
|
|
86
|
+
/** Constrained resolves from a known set; suggestive may also author values. */
|
|
87
|
+
export const OL8_VALUE_POLICIES = ['constrained', 'suggestive'];
|
|
88
|
+
|
|
89
|
+
export const OL8_COMBOBOX_SIZES = OL8_BUTTON_SIZES;
|
|
90
|
+
export const OL8_COMBOBOX_APPEARANCES = ['outline', 'filled'];
|
|
91
|
+
export const OL8_COMBOBOX_MATERIALS = OL8_MATERIALS;
|
|
92
|
+
export const OL8_AUTOCOMPLETE_MODES = ['none', 'list-manual', 'list-automatic'];
|
|
93
|
+
export const OL8_FILTER_SOURCES = ['local', 'remote', 'external'];
|
|
94
|
+
|
|
95
|
+
export const OL8_PICKER_SIZES = OL8_BUTTON_SIZES;
|
|
96
|
+
export const OL8_PICKER_MATERIALS = OL8_MATERIALS;
|
|
97
|
+
export const OL8_COMMIT_BEHAVIORS = ['immediate', 'apply'];
|
|
98
|
+
export const OL8_PICKER_PRESENTATIONS = ['inline', 'popup'];
|
|
99
|
+
/** One treatment across an instance; peers never mix Check and None. */
|
|
100
|
+
export const OL8_PICKER_MARKS = ['check', 'none'];
|
|
101
|
+
|
|
102
|
+
export const OL8_POPUP_STATUSES = ['none', 'loading', 'empty', 'error'];
|
|
32
103
|
export const OL8_AGGREGATE = ['none', 'some', 'all'];
|
|
33
104
|
|
|
34
105
|
/** Compact and Standard take the 18px icon; Comfortable and Large take 24px. */
|
package/src/index.d.ts
CHANGED
|
@@ -15,6 +15,46 @@ export type Ol8Selection =
|
|
|
15
15
|
| 'unchecked' | 'checked' | 'mixed'
|
|
16
16
|
| 'unselected' | 'selected'
|
|
17
17
|
| 'off' | 'on';
|
|
18
|
+
export type Ol8LinkForm = 'inline' | 'standalone' | 'navigation';
|
|
19
|
+
export type Ol8LinkSize = 'small' | 'standard' | 'large';
|
|
20
|
+
export type Ol8LinkMotion = 'system' | 'none';
|
|
21
|
+
export declare const OL8_LINK_FORMS: readonly Ol8LinkForm[];
|
|
22
|
+
export declare const OL8_LINK_SIZES: readonly Ol8LinkSize[];
|
|
23
|
+
export declare const OL8_LINK_MOTIONS: readonly Ol8LinkMotion[];
|
|
24
|
+
|
|
25
|
+
export type Ol8TextFieldSize = 'compact' | 'standard' | 'comfortable' | 'large';
|
|
26
|
+
export type Ol8TextFieldAppearance = 'outline' | 'filled';
|
|
27
|
+
/** Pending is a validation process status, not a fifth message tone. */
|
|
28
|
+
export type Ol8MessageTone = 'critical' | 'caution' | 'positive' | 'informative';
|
|
29
|
+
export type Ol8ValidationStatus = 'idle' | 'pending' | 'resolved';
|
|
30
|
+
export type Ol8CharacterLimitBehavior = 'soft' | 'hard';
|
|
31
|
+
/** Form Message itself draws all five, because Figma 177:13 enumerates five. */
|
|
32
|
+
export type Ol8FormMessageTone = Ol8MessageTone | 'pending';
|
|
33
|
+
export declare const OL8_TEXT_FIELD_SIZES: readonly Ol8TextFieldSize[];
|
|
34
|
+
export declare const OL8_TEXT_FIELD_APPEARANCES: readonly Ol8TextFieldAppearance[];
|
|
35
|
+
export declare const OL8_TEXT_FIELD_MATERIALS: readonly Ol8Material[];
|
|
36
|
+
export declare const OL8_MESSAGE_TONES: readonly Ol8MessageTone[];
|
|
37
|
+
export declare const OL8_VALIDATION_STATUSES: readonly Ol8ValidationStatus[];
|
|
38
|
+
export declare const OL8_CHARACTER_LIMIT_BEHAVIORS: readonly Ol8CharacterLimitBehavior[];
|
|
39
|
+
export declare const OL8_FORM_MESSAGE_TONES: readonly Ol8FormMessageTone[];
|
|
40
|
+
export declare const OL8_FORM_MESSAGE_ICONS: Readonly<Record<Ol8FormMessageTone, string>>;
|
|
41
|
+
|
|
42
|
+
export type Ol8NavigationSize = 'compact' | 'standard' | 'comfortable' | 'large';
|
|
43
|
+
export type Ol8TabsHierarchy = 'primary' | 'secondary' | 'tertiary';
|
|
44
|
+
export type Ol8TabsOrientation = 'horizontal' | 'vertical';
|
|
45
|
+
export type Ol8TabsActivation = 'automatic' | 'manual';
|
|
46
|
+
export type Ol8SegmentBehavior = 'single' | 'multi' | 'momentary';
|
|
47
|
+
export type Ol8SegmentPresentation =
|
|
48
|
+
| 'inset-fill' | 'line-indicator' | 'outlined-selection'
|
|
49
|
+
| 'soft-pill' | 'icon-only' | 'stacked-label';
|
|
50
|
+
export type Ol8TabBarPresentation = 'bottom' | 'inline' | 'sidebar' | 'spatial-rail';
|
|
51
|
+
export declare const OL8_TABS_HIERARCHIES: readonly Ol8TabsHierarchy[];
|
|
52
|
+
export declare const OL8_TABS_ORIENTATIONS: readonly Ol8TabsOrientation[];
|
|
53
|
+
export declare const OL8_TABS_ACTIVATIONS: readonly Ol8TabsActivation[];
|
|
54
|
+
export declare const OL8_SEGMENT_BEHAVIORS: readonly Ol8SegmentBehavior[];
|
|
55
|
+
export declare const OL8_SEGMENT_PRESENTATIONS: readonly Ol8SegmentPresentation[];
|
|
56
|
+
export declare const OL8_TAB_BAR_PRESENTATIONS: readonly Ol8TabBarPresentation[];
|
|
57
|
+
|
|
18
58
|
export type Ol8OptionSize = 'standard' | 'large';
|
|
19
59
|
export type Ol8Aggregate = 'none' | 'some' | 'all';
|
|
20
60
|
|
|
@@ -113,6 +153,62 @@ export interface ChoiceItemProps extends Omit<ComponentPropsWithoutRef<'input'>,
|
|
|
113
153
|
}
|
|
114
154
|
export declare const ChoiceItem: ForwardRefExoticComponent<ChoiceItemProps & RefAttributes<HTMLInputElement>>;
|
|
115
155
|
|
|
156
|
+
/** MOLECULE. States are CSS, never props. Icons are intentionally parked. */
|
|
157
|
+
export interface LinkProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'> {
|
|
158
|
+
children?: ReactNode;
|
|
159
|
+
href: string;
|
|
160
|
+
form?: Ol8LinkForm;
|
|
161
|
+
size?: Ol8LinkSize;
|
|
162
|
+
/** Navigation only. `true` emits aria-current="page"; a string names the kind. */
|
|
163
|
+
current?: boolean | string;
|
|
164
|
+
motion?: Ol8LinkMotion;
|
|
165
|
+
}
|
|
166
|
+
export declare const Link: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>;
|
|
167
|
+
|
|
168
|
+
/** MOLECULE. Tone maps to semantic message tone; Pending is the validating state. */
|
|
169
|
+
export interface FormMessageProps extends ComponentPropsWithoutRef<'div'> {
|
|
170
|
+
children?: ReactNode;
|
|
171
|
+
tone?: Ol8FormMessageTone;
|
|
172
|
+
/** Figma's Show Icon. */
|
|
173
|
+
icon?: boolean;
|
|
174
|
+
}
|
|
175
|
+
export declare const FormMessage: ForwardRefExoticComponent<FormMessageProps & RefAttributes<HTMLDivElement>>;
|
|
176
|
+
|
|
177
|
+
/** MOLECULE. Conditions are CSS and native state, never props. Gem changes material only. */
|
|
178
|
+
export interface TextFieldProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size' | 'prefix'> {
|
|
179
|
+
/** Required and visible. A placeholder is never the label. */
|
|
180
|
+
label: string;
|
|
181
|
+
/** Default Comfortable, the cross platform size. */
|
|
182
|
+
size?: Ol8TextFieldSize;
|
|
183
|
+
appearance?: Ol8TextFieldAppearance;
|
|
184
|
+
material?: Ol8Material;
|
|
185
|
+
/** False keeps the label as the accessible name without showing it, which needs a documented exception. */
|
|
186
|
+
showLabel?: boolean;
|
|
187
|
+
/** Correction is required. Never set without also exposing a Critical message. */
|
|
188
|
+
invalid?: boolean;
|
|
189
|
+
leadingIcon?: string;
|
|
190
|
+
prefix?: ReactNode;
|
|
191
|
+
suffix?: ReactNode;
|
|
192
|
+
trailingAction?: { icon: string; label: string; onClick?: () => void; pressed?: boolean };
|
|
193
|
+
instruction?: ReactNode;
|
|
194
|
+
message?: ReactNode;
|
|
195
|
+
messageTone?: Ol8MessageTone;
|
|
196
|
+
messageIcon?: boolean;
|
|
197
|
+
/** Pending shows the loader; it never freezes typing. */
|
|
198
|
+
validationStatus?: Ol8ValidationStatus;
|
|
199
|
+
/** Counted in graphemes, so an emoji counts as one. */
|
|
200
|
+
characterLimit?: number;
|
|
201
|
+
/** Soft lets a person overshoot and then correct. Hard is for real technical limits. */
|
|
202
|
+
characterLimitBehavior?: Ol8CharacterLimitBehavior;
|
|
203
|
+
}
|
|
204
|
+
export declare const TextField: ForwardRefExoticComponent<TextFieldProps & RefAttributes<HTMLInputElement>>;
|
|
205
|
+
|
|
206
|
+
/** Counts what a reader would call characters. */
|
|
207
|
+
export declare function countGraphemes(value: unknown): number;
|
|
208
|
+
|
|
209
|
+
/** Keeps the first `limit` graphemes, so a cluster is never cut in half. */
|
|
210
|
+
export declare function clipToGraphemes(value: unknown, limit: number): string;
|
|
211
|
+
|
|
116
212
|
/** MOLECULE. Never focusable; a listbox moves a virtual cursor instead. */
|
|
117
213
|
export interface SelectionOptionProps extends Omit<ComponentPropsWithoutRef<'li'>, 'children'> {
|
|
118
214
|
children?: ReactNode;
|
|
@@ -165,3 +261,121 @@ export interface CheckboxGroupProps extends Ol8GroupShell {
|
|
|
165
261
|
onChange?: (state: Record<string, boolean>) => void;
|
|
166
262
|
}
|
|
167
263
|
export declare function CheckboxGroup(props: CheckboxGroupProps): ReactElement;
|
|
264
|
+
|
|
265
|
+
/** ATOM. One badge serves all three navigation organisms. */
|
|
266
|
+
export interface NavigationBadgeProps extends ComponentPropsWithoutRef<'span'> {
|
|
267
|
+
children: ReactNode;
|
|
268
|
+
}
|
|
269
|
+
export declare const NavigationBadge: ForwardRefExoticComponent<NavigationBadgeProps & RefAttributes<HTMLSpanElement>>;
|
|
270
|
+
|
|
271
|
+
export interface Ol8TabItem {
|
|
272
|
+
/** Required: exactly one panel is paired with it. */
|
|
273
|
+
id: string;
|
|
274
|
+
label?: string;
|
|
275
|
+
icon?: string;
|
|
276
|
+
badge?: string | number;
|
|
277
|
+
disabled?: boolean;
|
|
278
|
+
/** Required when the item shows an icon and no label. */
|
|
279
|
+
ariaLabel?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** ORGANISM. Emits tablist, tab and tabpanel, and keeps one enabled tab stop. */
|
|
283
|
+
export interface TabsProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {
|
|
284
|
+
/** Required. A region nobody can name is a region nobody can reach. */
|
|
285
|
+
label: string;
|
|
286
|
+
items: Ol8TabItem[];
|
|
287
|
+
value?: string;
|
|
288
|
+
defaultValue?: string;
|
|
289
|
+
onChange?: (id: string) => void;
|
|
290
|
+
hierarchy?: Ol8TabsHierarchy;
|
|
291
|
+
size?: Ol8NavigationSize;
|
|
292
|
+
material?: Ol8Material;
|
|
293
|
+
orientation?: Ol8TabsOrientation;
|
|
294
|
+
activation?: Ol8TabsActivation;
|
|
295
|
+
/** Supply it and Tabs renders the owned panel for each item. */
|
|
296
|
+
renderPanel?: (item: Ol8TabItem) => ReactNode;
|
|
297
|
+
}
|
|
298
|
+
export declare const Tabs: ForwardRefExoticComponent<TabsProps & RefAttributes<HTMLDivElement>>;
|
|
299
|
+
|
|
300
|
+
export interface Ol8SegmentItem {
|
|
301
|
+
id: string;
|
|
302
|
+
label?: string;
|
|
303
|
+
icon?: string;
|
|
304
|
+
badge?: string | number;
|
|
305
|
+
disabled?: boolean;
|
|
306
|
+
/** Required when the item shows an icon and no label. */
|
|
307
|
+
ariaLabel?: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** ORGANISM. Behaviour is declared, never inferred; presentation never changes it. */
|
|
311
|
+
export interface SegmentedControlProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {
|
|
312
|
+
/** Required accessible name. */
|
|
313
|
+
label: string;
|
|
314
|
+
/** Exactly one: single is a radio group, multi is toggle buttons, momentary stores nothing. */
|
|
315
|
+
behavior: Ol8SegmentBehavior;
|
|
316
|
+
items: Ol8SegmentItem[];
|
|
317
|
+
/** One value for single, an array for multi, nothing for momentary. */
|
|
318
|
+
value?: string | string[];
|
|
319
|
+
defaultValue?: string | string[];
|
|
320
|
+
onChange?: (value: string | string[]) => void;
|
|
321
|
+
presentation?: Ol8SegmentPresentation;
|
|
322
|
+
size?: Ol8NavigationSize;
|
|
323
|
+
material?: Ol8Material;
|
|
324
|
+
}
|
|
325
|
+
export declare const SegmentedControl: ForwardRefExoticComponent<SegmentedControlProps & RefAttributes<HTMLDivElement>>;
|
|
326
|
+
|
|
327
|
+
export interface Ol8Destination {
|
|
328
|
+
/** Required. A tab bar navigates, so it uses real links. */
|
|
329
|
+
href: string;
|
|
330
|
+
label?: string;
|
|
331
|
+
icon?: string;
|
|
332
|
+
badge?: string | number;
|
|
333
|
+
/** Required when the destination shows an icon and no label. */
|
|
334
|
+
ariaLabel?: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** ORGANISM. Native destination links, one current destination, no disabled pseudo destinations. */
|
|
338
|
+
export interface TabBarProps extends ComponentPropsWithoutRef<'nav'> {
|
|
339
|
+
/** Required navigation name, so a person can tell one landmark from another. */
|
|
340
|
+
label: string;
|
|
341
|
+
items: Ol8Destination[];
|
|
342
|
+
/** The href of the destination a person is on. */
|
|
343
|
+
current?: string;
|
|
344
|
+
/** Routes an ordinary left click while the link stays a real link. */
|
|
345
|
+
onNavigate?: (href: string, event: MouseEvent) => void;
|
|
346
|
+
presentation?: Ol8TabBarPresentation;
|
|
347
|
+
size?: Ol8NavigationSize;
|
|
348
|
+
material?: Ol8Material;
|
|
349
|
+
}
|
|
350
|
+
export declare const TabBar: ForwardRefExoticComponent<TabBarProps & RefAttributes<HTMLElement>>;
|
|
351
|
+
|
|
352
|
+
export type Ol8SelectNativePolicy = 'preferred' | 'required' | 'custom-allowed';
|
|
353
|
+
export declare const OL8_SELECT_NATIVE_POLICIES: readonly Ol8SelectNativePolicy[];
|
|
354
|
+
|
|
355
|
+
export interface Ol8SelectOption {
|
|
356
|
+
value: string;
|
|
357
|
+
label: string;
|
|
358
|
+
disabled?: boolean;
|
|
359
|
+
group?: string;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** MOLECULE. Native first: a real select, wearing the Text Field geometry Figma reuses. */
|
|
363
|
+
export interface SelectProps extends Omit<ComponentPropsWithoutRef<'select'>, 'size'> {
|
|
364
|
+
/** Required and visible. A placeholder is never the label. */
|
|
365
|
+
label: string;
|
|
366
|
+
options: Ol8SelectOption[];
|
|
367
|
+
size?: Ol8TextFieldSize;
|
|
368
|
+
appearance?: Ol8TextFieldAppearance;
|
|
369
|
+
material?: Ol8Material;
|
|
370
|
+
showLabel?: boolean;
|
|
371
|
+
invalid?: boolean;
|
|
372
|
+
/** Only when an unselected value is valid, or required validation needs a prompt. */
|
|
373
|
+
placeholderOption?: string;
|
|
374
|
+
leadingIcon?: string;
|
|
375
|
+
instruction?: ReactNode;
|
|
376
|
+
message?: ReactNode;
|
|
377
|
+
messageTone?: Ol8MessageTone;
|
|
378
|
+
/** 'custom-allowed' is refused: use Combobox, which implements the full contract. */
|
|
379
|
+
native?: Ol8SelectNativePolicy;
|
|
380
|
+
}
|
|
381
|
+
export declare const Select: ForwardRefExoticComponent<SelectProps & RefAttributes<HTMLSelectElement>>;
|
package/src/index.js
CHANGED
|
@@ -2,8 +2,22 @@ export { Icon } from './atoms/Icon.js';
|
|
|
2
2
|
export { SelectionIndicator } from './atoms/SelectionIndicator.js';
|
|
3
3
|
export { Button } from './molecules/Button.js';
|
|
4
4
|
export { IconButton } from './molecules/IconButton.js';
|
|
5
|
+
export { Link } from './molecules/Link.js';
|
|
5
6
|
export { ChoiceItem } from './molecules/ChoiceItem.js';
|
|
6
7
|
export { SelectionOption } from './molecules/SelectionOption.js';
|
|
8
|
+
export { FormMessage } from './molecules/FormMessage.js';
|
|
9
|
+
export { TextField, countGraphemes, clipToGraphemes } from './molecules/TextField.js';
|
|
10
|
+
export { Select, OL8_SELECT_NATIVE_POLICIES } from './molecules/Select.js';
|
|
11
|
+
export { ChoiceChip } from './molecules/ChoiceChip.js';
|
|
12
|
+
export { Token } from './molecules/Token.js';
|
|
7
13
|
export { RadioGroup, CheckboxGroup } from './organisms/ChoiceGroup.js';
|
|
14
|
+
export { SelectionPopup } from './organisms/SelectionPopup.js';
|
|
15
|
+
export { MultiSelectField } from './organisms/MultiSelectField.js';
|
|
16
|
+
export { ChoicePicker } from './organisms/ChoicePicker.js';
|
|
17
|
+
export { Combobox } from './organisms/Combobox.js';
|
|
18
|
+
export { NavigationBadge } from './atoms/NavigationBadge.js';
|
|
19
|
+
export { Tabs } from './organisms/Tabs.js';
|
|
20
|
+
export { SegmentedControl } from './organisms/SegmentedControl.js';
|
|
21
|
+
export { TabBar } from './organisms/TabBar.js';
|
|
8
22
|
export * from './foundations/geometry.js';
|
|
9
23
|
export { OL8_ICONS, OL8_ICON_VIEWBOX } from './foundations/icons.generated.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createElement, useId } from 'react';
|
|
2
|
+
import { IconGlyph } from '../atoms/Icon.js';
|
|
3
|
+
import { OL8_CHIP_SIZES } from '../foundations/geometry.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MOLECULE. The Soft Hexagon, built the way Figma builds it: a fixed 12 unit
|
|
7
|
+
* terminal, a rail that grows with the label, and a mirrored terminal. Only the
|
|
8
|
+
* rail grows, so peer chips of any length keep one shoulder depth and angle.
|
|
9
|
+
*
|
|
10
|
+
* The terminal paths and the shape are private, as they are in Figma, where the
|
|
11
|
+
* terminals are named "Private Geometry" and the contract says they are "not
|
|
12
|
+
* published cap atoms, layer variants, or independently swappable components".
|
|
13
|
+
* Token imports them from this module; the package index does not export them.
|
|
14
|
+
*
|
|
15
|
+
* One viewBox serves all four heights: read from Figma, every terminal path at
|
|
16
|
+
* 30, 36 and 42 shares identical X values and Y values that are the same
|
|
17
|
+
* fractions of the height, so stretching Y alone is exact.
|
|
18
|
+
*/
|
|
19
|
+
const TERMINAL_FILL =
|
|
20
|
+
'M12 0 L8.96202541 0 C7.44303812 0 6.43037944 0.83333333 5.92405035 2.5 ' +
|
|
21
|
+
'L0.45569618 12.5 C-0.15189877 13.75 -0.15189877 16.25 0.45569618 17.5 ' +
|
|
22
|
+
'L5.92405035 27.5 C6.43037944 29.16666667 7.44303812 30 8.96202541 30 L12 30 Z';
|
|
23
|
+
const TERMINAL_EDGE =
|
|
24
|
+
'M12 0 L8.96202541 0 C7.44303812 0 6.43037944 0.83333333 5.92405035 2.5 ' +
|
|
25
|
+
'L0.45569618 12.5 C-0.15189877 13.75 -0.15189877 16.25 0.45569618 17.5 ' +
|
|
26
|
+
'L5.92405035 27.5 C6.43037944 29.16666667 7.44303812 30 8.96202541 30 L12 30';
|
|
27
|
+
|
|
28
|
+
function Terminal({ uid, side }) {
|
|
29
|
+
const clip = `${uid}-clip-${side}`;
|
|
30
|
+
return createElement('svg', {
|
|
31
|
+
className: `ol8-chip__terminal ol8-chip__terminal--${side}`,
|
|
32
|
+
viewBox: '0 0 12 30', preserveAspectRatio: 'none',
|
|
33
|
+
'aria-hidden': 'true', focusable: 'false',
|
|
34
|
+
}, [
|
|
35
|
+
createElement('clipPath', { key: 'c', id: clip }, createElement('path', { d: TERMINAL_FILL })),
|
|
36
|
+
createElement('path', { key: 'fo', className: 'ol8-chip__focus-outer', d: TERMINAL_EDGE }),
|
|
37
|
+
createElement('path', { key: 'fi', className: 'ol8-chip__focus-inner', d: TERMINAL_EDGE }),
|
|
38
|
+
createElement('path', { key: 'f', className: 'ol8-chip__fill', d: TERMINAL_FILL }),
|
|
39
|
+
createElement('path', { key: 'e', className: 'ol8-chip__edge', d: TERMINAL_EDGE, clipPath: `url(#${clip})` }),
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The silhouette and its rail. Figma's "Choice Chip / Geometry Owner".
|
|
45
|
+
* The mark follows the label, which is the anatomy the contract writes down as
|
|
46
|
+
* "Label + optional Remove mark", and one position serves Check and Remove
|
|
47
|
+
* alike so peer marks never end up on opposite sides.
|
|
48
|
+
*/
|
|
49
|
+
export function ChipShape({ uid, label, mark = null }) {
|
|
50
|
+
return [
|
|
51
|
+
createElement(Terminal, { key: 'l', uid, side: 'leading' }),
|
|
52
|
+
createElement('span', { key: 'r', className: 'ol8-chip__rail' }, [
|
|
53
|
+
createElement('span', { key: 'label', className: 'ol8-chip__label' }, label),
|
|
54
|
+
mark,
|
|
55
|
+
]),
|
|
56
|
+
createElement(Terminal, { key: 't', uid, side: 'trailing' }),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A Choice Chip is a real checkbox. The contract is explicit that every picker
|
|
62
|
+
* option is "a real Checkbox rendered with the approved quiet Choice Chip
|
|
63
|
+
* appearance", with native checked state among the redundant cues, so selection
|
|
64
|
+
* never rests on colour alone.
|
|
65
|
+
*/
|
|
66
|
+
export function ChoiceChip({
|
|
67
|
+
children, size = 'standard', selected, defaultSelected, showMark = false,
|
|
68
|
+
disabled = false, name, value, id, className = '', onChange, ...rest
|
|
69
|
+
}) {
|
|
70
|
+
if (!OL8_CHIP_SIZES.includes(size)) throw new Error(`[ol8] unknown chip size "${size}"`);
|
|
71
|
+
if (!children) throw new Error('[ol8] Choice Chip requires a label');
|
|
72
|
+
const auto = useId();
|
|
73
|
+
const uid = id ?? `ol8-chip-${auto}`;
|
|
74
|
+
const isOn = selected ?? defaultSelected ?? false;
|
|
75
|
+
|
|
76
|
+
return createElement('label', {
|
|
77
|
+
className: `ol8-chip${className ? ` ${className}` : ''}`,
|
|
78
|
+
htmlFor: uid,
|
|
79
|
+
'data-ol8-size': size,
|
|
80
|
+
'data-ol8-selection': isOn ? 'selected' : 'inactive',
|
|
81
|
+
'data-ol8-availability': disabled ? 'disabled' : 'enabled',
|
|
82
|
+
}, [
|
|
83
|
+
createElement('input', {
|
|
84
|
+
key: 'input', ...rest,
|
|
85
|
+
type: 'checkbox', className: 'ol8-chip__input', id: uid, name, value, disabled, onChange,
|
|
86
|
+
// A controlled chip with nothing listening is a display of state, not a
|
|
87
|
+
// control. Saying so is what React asks for, and it is the truth: the
|
|
88
|
+
// chip cannot change anything until someone handles the change.
|
|
89
|
+
...(selected !== undefined
|
|
90
|
+
? { checked: selected, ...(onChange ? {} : { readOnly: true }) }
|
|
91
|
+
: defaultSelected !== undefined ? { defaultChecked: defaultSelected } : {}),
|
|
92
|
+
}),
|
|
93
|
+
createElement(ChipShape, {
|
|
94
|
+
key: 'shape', uid, label: children,
|
|
95
|
+
mark: showMark
|
|
96
|
+
? createElement('span', { key: 'mark', className: 'ol8-chip__mark', 'aria-hidden': 'true' },
|
|
97
|
+
createElement(IconGlyph, { name: 'check' }))
|
|
98
|
+
: null,
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
}
|