@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.
Files changed (40) hide show
  1. package/README.md +7 -3
  2. package/ai-context.json +116 -1
  3. package/package.json +2 -2
  4. package/src/atoms/NavigationBadge.js +19 -0
  5. package/src/foundations/geometry.js +71 -0
  6. package/src/index.d.ts +214 -0
  7. package/src/index.js +14 -0
  8. package/src/molecules/ChoiceChip.js +101 -0
  9. package/src/molecules/FormMessage.js +33 -0
  10. package/src/molecules/Link.js +31 -0
  11. package/src/molecules/NavigationItem.js +55 -0
  12. package/src/molecules/Select.js +109 -0
  13. package/src/molecules/SelectionOption.js +4 -1
  14. package/src/molecules/TextField.js +174 -0
  15. package/src/molecules/Token.js +49 -0
  16. package/src/organisms/ChoicePicker.js +114 -0
  17. package/src/organisms/Combobox.js +122 -0
  18. package/src/organisms/MultiSelectField.js +138 -0
  19. package/src/organisms/SegmentedControl.js +114 -0
  20. package/src/organisms/SelectionPopup.js +73 -0
  21. package/src/organisms/TabBar.js +68 -0
  22. package/src/organisms/Tabs.js +123 -0
  23. package/src/styles/choice-chip.css +216 -0
  24. package/src/styles/choice-picker.css +100 -0
  25. package/src/styles/combobox.css +42 -0
  26. package/src/styles/form-message.css +48 -0
  27. package/src/styles/gem.css +52 -5
  28. package/src/styles/link.css +116 -0
  29. package/src/styles/multi-select-field.css +78 -0
  30. package/src/styles/navigation-badge.css +37 -0
  31. package/src/styles/navigation-item.css +155 -0
  32. package/src/styles/segmented-control.css +115 -0
  33. package/src/styles/select.css +33 -0
  34. package/src/styles/selection-indicator.css +13 -4
  35. package/src/styles/selection-popup.css +92 -0
  36. package/src/styles/tab-bar.css +47 -0
  37. package/src/styles/tabs.css +55 -0
  38. package/src/styles/text-field.css +268 -0
  39. package/src/styles/token.css +65 -0
  40. package/styles.css +18 -2
@@ -0,0 +1,216 @@
1
+ /* GENERATED from packages/core/src/molecules/choice-chip/choice-chip.css by packages/sync/src/build-styles.mjs — do not edit.
2
+ Edit the source in packages/core/src and re-run `npm run sync:styles`. */
3
+ /**
4
+ * Oneli8 · Choice Chip (MOLECULE) — the Soft Hexagon
5
+ * Figma: Select and Combobox / Choice Chip / Soft Hexagon 636:847 (8 variants)
6
+ *
7
+ * GEOMETRY, read from all four sizes in Figma:
8
+ * size target visible rail inset gap label
9
+ * compact 48 30 9 9 12 / 18
10
+ * standard 48 36 12 9 12 / 18
11
+ * comfortable 48 42 15 9 16 / 24
12
+ * large 60 42 15 9 16 / 24
13
+ * The terminal is a fixed 12 at every size and never scales with the label,
14
+ * so peer chips of different lengths keep one shoulder depth and side angle.
15
+ *
16
+ * The visible silhouette sits inside a taller protected target, which is why
17
+ * `min-block-size` is the target and the rail carries the visible height.
18
+ */
19
+
20
+ .ol8-chip {
21
+ --_height: var(--ol8-component-choice-chip-height-standard);
22
+ --_target: var(--ol8-component-choice-chip-target-standard);
23
+ --_inset: var(--ol8-component-choice-chip-rail-inset-standard);
24
+ --_label-size: var(--ol8-font-size-012);
25
+ --_label-line: var(--ol8-font-line-018);
26
+
27
+ /* Inactive is the quiet Neutral treatment; Selected is Primary Soft. */
28
+ --_surface: var(--ol8-color-surface-sunken);
29
+ --_edge: var(--ol8-color-border-subtle);
30
+ --_ink: var(--ol8-color-text-primary);
31
+
32
+ /* The ring is painted at every state and is simply invisible until focus,
33
+ so focus never changes the geometry, only the colour. */
34
+ --_focus-outer: transparent;
35
+ --_focus-inner: transparent;
36
+
37
+ box-sizing: border-box;
38
+ position: relative;
39
+ display: inline-flex;
40
+ align-items: center;
41
+ min-block-size: var(--_target);
42
+ font-family: var(--ol8-font-family-functional);
43
+ font-variation-settings: "ARRR" var(--ol8-font-axis-arrr-baseline);
44
+ color: var(--_ink);
45
+ cursor: pointer;
46
+ }
47
+ .ol8-chip[data-ol8-availability="disabled"] { cursor: not-allowed; }
48
+
49
+ /* ---- Size ------------------------------------------------------------ */
50
+ .ol8-chip[data-ol8-size="compact"] {
51
+ --_height: var(--ol8-component-choice-chip-height-compact);
52
+ --_inset: var(--ol8-component-choice-chip-rail-inset-compact);
53
+ }
54
+ .ol8-chip[data-ol8-size="standard"] {
55
+ --_height: var(--ol8-component-choice-chip-height-standard);
56
+ --_inset: var(--ol8-component-choice-chip-rail-inset-standard);
57
+ }
58
+ .ol8-chip[data-ol8-size="comfortable"] {
59
+ --_height: var(--ol8-component-choice-chip-height-comfortable);
60
+ --_inset: var(--ol8-component-choice-chip-rail-inset-comfortable);
61
+ --_label-size: var(--ol8-font-size-016);
62
+ --_label-line: var(--ol8-font-line-024);
63
+ }
64
+ .ol8-chip[data-ol8-size="large"] {
65
+ --_height: var(--ol8-component-choice-chip-height-large);
66
+ --_target: var(--ol8-component-choice-chip-target-large);
67
+ --_inset: var(--ol8-component-choice-chip-rail-inset-large);
68
+ --_label-size: var(--ol8-font-size-016);
69
+ --_label-line: var(--ol8-font-line-024);
70
+ }
71
+
72
+ /* ---- Selection ------------------------------------------------------- */
73
+ /* Never colour alone: the native checked state carries the same fact, and the
74
+ selected edge is a stronger contrast than the inactive one. */
75
+ .ol8-chip[data-ol8-selection="selected"] {
76
+ --_surface: var(--ol8-color-selection-surface);
77
+ --_edge: var(--ol8-color-primary-360);
78
+ --_ink: var(--ol8-color-selection-content);
79
+ }
80
+ .ol8-chip[data-ol8-availability="disabled"] {
81
+ --_surface: var(--ol8-color-surface-disabled);
82
+ --_edge: var(--ol8-color-border-disabled);
83
+ --_ink: var(--ol8-color-text-disabled);
84
+ }
85
+
86
+ .ol8-chip__input {
87
+ position: absolute;
88
+ inline-size: 1px;
89
+ block-size: 1px;
90
+ opacity: 0;
91
+ pointer-events: none;
92
+ }
93
+
94
+ /* ---- Terminals ------------------------------------------------------- */
95
+ /* One viewBox serves every height: Figma's terminal paths share identical X
96
+ values at 30, 36 and 42, and Y values that are the same fractions of the
97
+ height, so stretching Y alone is exact rather than approximate. */
98
+ .ol8-chip__terminal {
99
+ flex: none;
100
+ inline-size: var(--ol8-component-choice-chip-terminal);
101
+ block-size: var(--_height);
102
+ /* The point reaches 0.15 beyond the box, as it does in Figma, and the focus
103
+ ring is painted outside the silhouette. Neither may be clipped. */
104
+ overflow: visible;
105
+ }
106
+ /* The trailing terminal is the leading one mirrored, so they cannot drift. */
107
+ .ol8-chip__terminal--trailing { transform: scaleX(-1); }
108
+
109
+ .ol8-chip__fill { fill: var(--_surface); }
110
+
111
+ /* Double width, clipped to the fill: the outer half is cut away and what
112
+ remains is an inside edge of exactly one border width, matching the rail's
113
+ own inside border. A centred stroke would stand 1px proud of the rail and
114
+ show a step at the shoulder. */
115
+ .ol8-chip__edge {
116
+ fill: none;
117
+ stroke: var(--_edge);
118
+ stroke-width: calc(var(--ol8-border-width-standard) * 2);
119
+ vector-effect: non-scaling-stroke;
120
+ }
121
+
122
+ /* Both rings are painted before the fill and at double width, so the inner
123
+ half of each lands under the fill and only the outer half shows. This is how
124
+ the ring follows the hexagon instead of boxing it. */
125
+ .ol8-chip__focus-outer,
126
+ .ol8-chip__focus-inner { fill: none; vector-effect: non-scaling-stroke; }
127
+ .ol8-chip__focus-outer {
128
+ stroke: var(--_focus-outer);
129
+ stroke-width: calc((var(--ol8-focus-ring-outerwidth) + var(--ol8-focus-ring-innerwidth)) * 2);
130
+ }
131
+ .ol8-chip__focus-inner {
132
+ stroke: var(--_focus-inner);
133
+ stroke-width: calc(var(--ol8-focus-ring-innerwidth) * 2);
134
+ }
135
+
136
+ .ol8-chip:has(> .ol8-chip__input:focus-visible) {
137
+ --_focus-outer: var(--ol8-color-focus-outer);
138
+ --_focus-inner: var(--ol8-color-focus-inner);
139
+ }
140
+
141
+ /* ---- Centre rail ----------------------------------------------------- */
142
+ /* Only the rail grows with the label. It owns the top and bottom of the
143
+ perimeter; the terminals own the angled ends. */
144
+ .ol8-chip__rail {
145
+ box-sizing: border-box;
146
+ position: relative;
147
+ display: flex;
148
+ align-items: center;
149
+ gap: var(--ol8-component-choice-gap-standard);
150
+ block-size: var(--_height);
151
+ padding-inline: var(--_inset);
152
+ background: var(--_surface);
153
+ border-block: var(--ol8-border-width-standard) solid var(--_edge);
154
+ }
155
+
156
+ /* The ring's flat runs, continuing the terminals' strokes across the rail.
157
+ Two colours in one band, ordered outward from the silhouette. */
158
+ .ol8-chip__rail::before,
159
+ .ol8-chip__rail::after {
160
+ content: "";
161
+ position: absolute;
162
+ inset-inline: 0;
163
+ block-size: calc(var(--ol8-focus-ring-outerwidth) + var(--ol8-focus-ring-innerwidth));
164
+ }
165
+ .ol8-chip__rail::before {
166
+ inset-block-end: 100%;
167
+ background: linear-gradient(
168
+ var(--_focus-outer) 0 var(--ol8-focus-ring-outerwidth),
169
+ var(--_focus-inner) var(--ol8-focus-ring-outerwidth) 100%);
170
+ }
171
+ .ol8-chip__rail::after {
172
+ inset-block-start: 100%;
173
+ background: linear-gradient(
174
+ var(--_focus-inner) 0 var(--ol8-focus-ring-innerwidth),
175
+ var(--_focus-outer) var(--ol8-focus-ring-innerwidth) 100%);
176
+ }
177
+
178
+ .ol8-chip__label {
179
+ font-size: var(--_label-size);
180
+ line-height: var(--_label-line);
181
+ font-weight: var(--ol8-font-weight-regular);
182
+ color: var(--_ink);
183
+ }
184
+
185
+ /* ---- Mark ------------------------------------------------------------ */
186
+ /* Figma places the mark BEFORE the label in every variant, Remove included. */
187
+ .ol8-chip__mark {
188
+ flex: none;
189
+ display: inline-flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ inline-size: var(--ol8-component-choicechip-mark);
193
+ block-size: var(--ol8-component-choicechip-mark);
194
+ color: var(--_ink);
195
+ }
196
+
197
+ /* The Check does not scale with its slot. Figma's `Icon / Selection / Check`
198
+ is a governed small master, not an Icon Frame instance: Size=Small and
199
+ Size=Standard both carry the same 10.2 x 6.9 artwork at the same 1.8 stroke,
200
+ centred in an 18 or 24 box. So the source box stays 24 units and overflows
201
+ the smaller slot, exactly as Figma draws it. */
202
+ .ol8-chip__mark > svg {
203
+ display: block;
204
+ flex: none;
205
+ inline-size: var(--ol8-size-icon-standard);
206
+ block-size: var(--ol8-size-icon-standard);
207
+ overflow: visible;
208
+ }
209
+
210
+ /* Both marks read at the one governed optical stroke, which is what Figma
211
+ draws and what the iconography contract calls contextual stroke
212
+ normalization: peer marks in one family match apparent weight. */
213
+ .ol8-chip__mark > svg > * {
214
+ vector-effect: non-scaling-stroke;
215
+ stroke-width: var(--ol8-component-choice-optical-stroke);
216
+ }
@@ -0,0 +1,100 @@
1
+ /* GENERATED from packages/core/src/organisms/choice-picker/choice-picker.css by packages/sync/src/build-styles.mjs — do not edit.
2
+ Edit the source in packages/core/src and re-run `npm run sync:styles`. */
3
+ /**
4
+ * Oneli8 · Choice Picker (ORGANISM)
5
+ * Figma: Organism / Choice Picker / Soft Hexagon 906:2898 (16 variants)
6
+ *
7
+ * Three parts stacked twelve apart: a search field, a wrapping matrix of chips,
8
+ * and in Apply mode a commitment line. The search field is the canonical Text
9
+ * Field owner and the chips are the canonical Choice Chip owner, so neither is
10
+ * restyled here.
11
+ *
12
+ * Read from all four sizes in Figma: the root gap, the matrix gap and the
13
+ * commitment typography do NOT scale with the picker size. They are twelve,
14
+ * six, and 12/18 at Compact, Standard, Comfortable and Large alike. Only the
15
+ * search field and the chips take the size.
16
+ */
17
+
18
+ .ol8-picker {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: var(--ol8-spacing-stack-related);
22
+ inline-size: 100%;
23
+ }
24
+
25
+ /* "The matrix wraps with 6px gaps and preserves source/reading order; it is not
26
+ a masonry layout." A plain wrapping flex row is exactly that. */
27
+ .ol8-picker__matrix {
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ gap: var(--ol8-component-choice-chip-gap);
31
+ }
32
+
33
+ /* Figma: HORIZONTAL, space between, 24 tall, both texts secondary at 12/18. */
34
+ .ol8-picker__commitment {
35
+ display: flex;
36
+ align-items: flex-start;
37
+ justify-content: space-between;
38
+ gap: var(--ol8-spacing-inline-standard);
39
+ min-block-size: var(--ol8-size-icon-standard);
40
+ font-size: var(--ol8-font-size-012);
41
+ line-height: var(--ol8-font-line-018);
42
+ color: var(--ol8-color-text-secondary);
43
+ }
44
+
45
+ .ol8-picker__summary {
46
+ font-family: var(--ol8-font-family-functional);
47
+ color: inherit;
48
+ }
49
+
50
+ /* Clear all and the apply hint are text weight actions rather than Button
51
+ instances. The contract asks for Clear all to be quiet, and Figma's row is
52
+ twenty four tall, which a Button's protected forty eight target would break.
53
+ They are still real buttons, so they are reachable and announced. */
54
+ .ol8-picker__apply,
55
+ .ol8-picker__clear {
56
+ padding: 0;
57
+ border: 0;
58
+ background: none;
59
+ font-family: var(--ol8-font-family-functional);
60
+ font-size: inherit;
61
+ line-height: inherit;
62
+ color: var(--ol8-color-actionquiet-contentdefault);
63
+ cursor: pointer;
64
+ }
65
+ .ol8-picker__apply:hover,
66
+ .ol8-picker__clear:hover {
67
+ color: var(--ol8-color-actionquiet-contenthover);
68
+ text-decoration: underline;
69
+ }
70
+ .ol8-picker__apply:focus-visible,
71
+ .ol8-picker__clear:focus-visible {
72
+ outline: var(--ol8-focus-ring-innerwidth) solid var(--ol8-color-focus-inner);
73
+ outline-offset: var(--ol8-focus-ring-offset);
74
+ }
75
+
76
+ .ol8-picker[data-ol8-availability="disabled"] .ol8-picker__apply,
77
+ .ol8-picker[data-ol8-availability="disabled"] .ol8-picker__clear {
78
+ color: var(--ol8-color-actionquiet-contentdisabled);
79
+ cursor: not-allowed;
80
+ }
81
+
82
+ /* The search label stays associated while the composition hides it. */
83
+ .ol8-picker__label-row--hidden {
84
+ position: absolute;
85
+ inline-size: 1px;
86
+ block-size: 1px;
87
+ overflow: hidden;
88
+ clip-path: inset(50%);
89
+ white-space: nowrap;
90
+ }
91
+
92
+ /* Retrieval and result counts are announced rather than only drawn. */
93
+ .ol8-picker__status {
94
+ position: absolute;
95
+ inline-size: 1px;
96
+ block-size: 1px;
97
+ overflow: hidden;
98
+ clip-path: inset(50%);
99
+ white-space: nowrap;
100
+ }
@@ -0,0 +1,42 @@
1
+ /* GENERATED from packages/core/src/organisms/combobox/combobox.css by packages/sync/src/build-styles.mjs — do not edit.
2
+ Edit the source in packages/core/src and re-run `npm run sync:styles`. */
3
+ /**
4
+ * Oneli8 · Combobox (ORGANISM)
5
+ * Figma: Organism / Combobox 883:2159 — "Reuses approved Text Field, Selection
6
+ * Popup and governed icon atoms."
7
+ *
8
+ * So the field geometry is the field's, the popup is the popup's, and this
9
+ * file adds only what belongs to the combination: where the popup sits, and
10
+ * the two marks at the trailing edge.
11
+ */
12
+
13
+ .ol8-combobox { position: relative; }
14
+
15
+ /* "Closed control to popup block gap" is a token, so the popup sits where the
16
+ design says rather than wherever absolute positioning lands it. */
17
+ .ol8-combobox__popup {
18
+ position: absolute;
19
+ z-index: 1;
20
+ inset-inline: 0;
21
+ inset-block-start: calc(100% + var(--ol8-component-selectionpopup-gap));
22
+ }
23
+
24
+ .ol8-combobox__disclosure {
25
+ flex: none;
26
+ display: inline-flex;
27
+ color: var(--ol8-color-icon-secondary);
28
+ cursor: pointer;
29
+ }
30
+
31
+ .ol8-combobox__clear {
32
+ flex: none;
33
+ display: inline-flex;
34
+ border: 0;
35
+ padding: 0;
36
+ background: none;
37
+ color: var(--ol8-color-icon-secondary);
38
+ cursor: pointer;
39
+ }
40
+ .ol8-combobox__clear:disabled { color: var(--ol8-color-icon-disabled); cursor: not-allowed; }
41
+
42
+ .ol8-combobox[data-ol8-disabled] .ol8-combobox__disclosure { color: var(--ol8-color-icon-disabled); cursor: not-allowed; }
@@ -0,0 +1,48 @@
1
+ /* GENERATED from packages/core/src/molecules/form-message/form-message.css by packages/sync/src/build-styles.mjs — do not edit.
2
+ Edit the source in packages/core/src and re-run `npm run sync:styles`. */
3
+ /**
4
+ * Oneli8 · Form Message (MOLECULE)
5
+ * Figma: Form Message 177:13, five tones.
6
+ *
7
+ * Anatomy read from Figma: a horizontal row, gap Component / Text Field / Gap /
8
+ * Status, items centred, one Small status icon beside twelve over eighteen
9
+ * regular text. Tone changes exactly one thing, the inline content colour,
10
+ * which both the icon stroke and the text take.
11
+ */
12
+
13
+ .ol8-form-message {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--ol8-component-textfield-gap-status);
17
+ color: var(--_tone);
18
+ font-family: var(--ol8-font-family-functional);
19
+ font-size: var(--ol8-font-size-012);
20
+ font-weight: var(--ol8-font-weight-regular);
21
+ line-height: var(--ol8-font-line-018);
22
+ }
23
+
24
+ .ol8-form-message__icon { flex: none; color: var(--_tone); }
25
+
26
+ /* ---- Tone ------------------------------------------------------------- */
27
+ .ol8-form-message[data-ol8-tone="critical"] { --_tone: var(--ol8-color-feedbackcritical-inlinecontent); }
28
+ .ol8-form-message[data-ol8-tone="caution"] { --_tone: var(--ol8-color-feedbackcaution-inlinecontent); }
29
+ .ol8-form-message[data-ol8-tone="positive"] { --_tone: var(--ol8-color-feedbackpositive-inlinecontent); }
30
+ .ol8-form-message[data-ol8-tone="informative"] { --_tone: var(--ol8-color-feedbackinformative-inlinecontent); }
31
+
32
+ /* Pending reads as informative text, and Figma gives its loading mark the
33
+ primary icon colour rather than a feedback colour, because nothing has been
34
+ decided yet. */
35
+ .ol8-form-message[data-ol8-tone="pending"] {
36
+ --_tone: var(--ol8-color-feedbackinformative-inlinecontent);
37
+ }
38
+ .ol8-form-message[data-ol8-tone="pending"] .ol8-form-message__icon {
39
+ color: var(--ol8-color-icon-primary);
40
+ }
41
+
42
+ /* Same treatment the Button gives its busy mark: rotation is opt in and motion
43
+ guarded, and Reduced Motion keeps the static mark. */
44
+ @media (prefers-reduced-motion: no-preference) {
45
+ .ol8-form-message[data-ol8-tone="pending"] .ol8-form-message__icon > svg {
46
+ animation: ol8-spin 900ms linear infinite;
47
+ }
48
+ }
@@ -34,7 +34,8 @@
34
34
  */
35
35
 
36
36
  .ol8-btn[data-ol8-material="gem"],
37
- .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface {
37
+ .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface,
38
+ .ol8-field[data-ol8-material="gem"] > .ol8-field__control-stack > .ol8-field__control {
38
39
  position: relative;
39
40
  isolation: isolate;
40
41
  --_rim: var(--ol8-material-control-gem-rim-default);
@@ -43,7 +44,8 @@
43
44
  /* The regular opaque background stays underneath on purpose — it is what
44
45
  shows through when the gem layer is suppressed for Reduced Transparency. */
45
46
  .ol8-btn[data-ol8-material="gem"]::before,
46
- .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::before {
47
+ .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::before,
48
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control::before {
47
49
  content: "";
48
50
  position: absolute;
49
51
  inset: 0;
@@ -60,7 +62,8 @@
60
62
  }
61
63
 
62
64
  .ol8-btn[data-ol8-material="gem"]::after,
63
- .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::after {
65
+ .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::after,
66
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control::after {
64
67
  content: "";
65
68
  position: absolute;
66
69
  inset: 0;
@@ -145,9 +148,48 @@
145
148
  --_g3: var(--ol8-material-control-gem-secondary-loadinglow);
146
149
  }
147
150
 
151
+ /* ---- Text Field · the same Secondary body, per Figma 211:36 and 215:226 ---
152
+ Outline follows the Cut Edge, Filled sits on the Stabilized Face and follows
153
+ the Inner Thin Cut. Hover moves Filled onto the Cut Edge. Invalid keeps the
154
+ critical boundary, because a material must never hide a validation result. */
155
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control {
156
+ --_g1: var(--ol8-material-control-gem-secondary-high);
157
+ --_g2: var(--ol8-material-control-gem-secondary-mid);
158
+ --_g3: var(--ol8-material-control-gem-secondary-low);
159
+ --_surface: transparent;
160
+ --_border: var(--ol8-material-gem-color-cutedge-high);
161
+ }
162
+ .ol8-field[data-ol8-material="gem"]:hover:not([data-ol8-disabled]) .ol8-field__control {
163
+ --_g1: var(--ol8-material-control-gem-secondary-hoverhigh);
164
+ --_g2: var(--ol8-material-control-gem-secondary-hovermid);
165
+ --_g3: var(--ol8-material-control-gem-secondary-hoverlow);
166
+ --_rim: var(--ol8-material-control-gem-rim-hover);
167
+ --_border: var(--ol8-material-gem-color-cutedge-high);
168
+ }
169
+ .ol8-field[data-ol8-material="gem"][data-ol8-appearance="filled"] .ol8-field__control {
170
+ --_surface: var(--ol8-material-gem-color-quiet-stabilizedface);
171
+ --_border: var(--ol8-material-gem-color-innerthincut-high);
172
+ }
173
+ .ol8-field[data-ol8-material="gem"][data-ol8-readonly][data-ol8-appearance="filled"] .ol8-field__control {
174
+ --_border: var(--ol8-material-gem-color-innerthincut-high);
175
+ }
176
+ .ol8-field[data-ol8-material="gem"]:has(.ol8-field__input[aria-invalid="true"]) .ol8-field__control,
177
+ .ol8-field[data-ol8-material="gem"][data-ol8-status="invalid"] .ol8-field__control {
178
+ --_border: var(--ol8-color-feedbackcritical-inlinecontent);
179
+ }
180
+ /* Disabled resolves to the opaque equivalent, which is the same answer Reduced
181
+ Transparency gives. */
182
+ .ol8-field[data-ol8-material="gem"][data-ol8-disabled] .ol8-field__control {
183
+ --_surface: var(--ol8-material-gem-color-quiet-opaqueequivalent);
184
+ --_border: var(--ol8-color-border-disabled);
185
+ }
186
+ .ol8-field[data-ol8-material="gem"][data-ol8-disabled] .ol8-field__control::before,
187
+ .ol8-field[data-ol8-material="gem"][data-ol8-disabled] .ol8-field__control::after { display: none; }
188
+
148
189
  /* Content must sit above the ::before body. */
149
190
  .ol8-btn[data-ol8-material="gem"] > *,
150
- .ol8-icon-btn[data-ol8-material="gem"] .ol8-icon { position: relative; z-index: 0; }
191
+ .ol8-icon-btn[data-ol8-material="gem"] .ol8-icon,
192
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control > * { position: relative; z-index: 0; }
151
193
 
152
194
  /* ---- Disabled inherits the regular opaque treatment ------------------ */
153
195
  .ol8-btn[data-ol8-material="gem"]:disabled::before,
@@ -162,7 +204,12 @@
162
204
  .ol8-btn[data-ol8-material="gem"]::before,
163
205
  .ol8-btn[data-ol8-material="gem"]::after,
164
206
  .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::before,
165
- .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::after { display: none; }
207
+ .ol8-icon-btn[data-ol8-material="gem"] > .ol8-icon-btn__surface::after,
208
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control::before,
209
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control::after { display: none; }
210
+ .ol8-field[data-ol8-material="gem"] .ol8-field__control {
211
+ --_surface: var(--ol8-material-gem-color-quiet-opaqueequivalent);
212
+ }
166
213
  .ol8-btn--secondary[data-ol8-material="gem"] {
167
214
  --_bw: var(--ol8-component-button-secondaryboundary);
168
215
  --_bd: var(--ol8-color-actionsecondary-contentdefault);
@@ -0,0 +1,116 @@
1
+ /* GENERATED from packages/core/src/molecules/link/link.css by packages/sync/src/build-styles.mjs — do not edit.
2
+ Edit the source in packages/core/src and re-run `npm run sync:styles`. */
3
+ /**
4
+ * Oneli8 · Link (MOLECULE)
5
+ *
6
+ * Figma (138:260, 139:260, 139:453): "Runtime href, native anchor behavior,
7
+ * visited resolution, aria-current, motion preference and navigation list
8
+ * hierarchy live in code." Figma's five States are visual evidence for review,
9
+ * not classes: they are :hover, :active, :focus-visible, :visited and
10
+ * [aria-current] here.
11
+ *
12
+ * "Icons remain intentionally parked and must not be added as local artwork."
13
+ */
14
+
15
+ .ol8-link {
16
+ position: relative;
17
+ color: var(--ol8-color-text-link-default);
18
+ font-family: var(--ol8-font-family-functional);
19
+ font-weight: var(--ol8-font-weight-semibold);
20
+ text-decoration-line: underline;
21
+ text-decoration-thickness: var(--ol8-component-link-underline-default);
22
+ text-underline-offset: var(--ol8-component-link-underline-offset);
23
+ transition-property: color, text-decoration-thickness, background-color, box-shadow;
24
+ transition-duration: var(--ol8-motion-interaction-hover-duration);
25
+ transition-timing-function: var(--ol8-motion-interaction-hover-timing-function);
26
+ }
27
+
28
+ /* ---- Size ------------------------------------------------------------- */
29
+ .ol8-link[data-ol8-size="small"] {
30
+ font-size: var(--ol8-typography-body-small-font-size);
31
+ line-height: var(--ol8-typography-body-small-line-height);
32
+ letter-spacing: var(--ol8-typography-body-small-letter-spacing);
33
+ }
34
+ .ol8-link[data-ol8-size="standard"] {
35
+ font-size: var(--ol8-typography-body-medium-font-size);
36
+ line-height: var(--ol8-typography-body-medium-line-height);
37
+ letter-spacing: var(--ol8-typography-body-medium-letter-spacing);
38
+ }
39
+ .ol8-link[data-ol8-size="large"] {
40
+ font-size: var(--ol8-typography-body-large-font-size);
41
+ line-height: var(--ol8-typography-body-large-line-height);
42
+ letter-spacing: var(--ol8-typography-body-large-letter-spacing);
43
+ }
44
+ /* Not a Figma size. An inline link inside body copy that does not follow its
45
+ surrounding type is broken, and Figma cannot express "inherit" as a variant.
46
+ Flagged for the design source rather than left undocumented. */
47
+ .ol8-link[data-ol8-size="inherit"] {
48
+ font-size: inherit;
49
+ line-height: inherit;
50
+ letter-spacing: inherit;
51
+ }
52
+
53
+ /* ---- State ------------------------------------------------------------ */
54
+ .ol8-link:hover,
55
+ .ol8-link:active {
56
+ text-decoration-thickness: var(--ol8-component-link-underline-emphasis);
57
+ }
58
+ .ol8-link:hover { color: var(--ol8-color-text-link-hover); }
59
+ .ol8-link:active { color: var(--ol8-color-text-link-pressed); }
60
+
61
+ /* Navigation links live in a list where visited carries no meaning. */
62
+ .ol8-link[data-ol8-form="inline"]:visited,
63
+ .ol8-link[data-ol8-form="standalone"]:visited {
64
+ color: var(--ol8-color-text-link-visited);
65
+ }
66
+
67
+ .ol8-link:focus-visible {
68
+ outline: none;
69
+ color: var(--ol8-color-text-primary);
70
+ text-decoration-color: var(--ol8-color-text-link-default);
71
+ border-radius: var(--ol8-shape-radius-subtle);
72
+ box-shadow:
73
+ 0 0 0 var(--ol8-focus-ring-innerwidth) var(--ol8-color-focus-inner),
74
+ 0 0 0 var(--ol8-focus-ring-totalwidth) var(--ol8-color-focus-outer);
75
+ }
76
+
77
+ /* ---- Form ------------------------------------------------------------- */
78
+ /* Standalone and navigation are targets in their own right, so they take the
79
+ protected minimum. Inline sits in a sentence and must not. Figma pads both by
80
+ the minimal inline step and carries a gap for the parked icon slot. */
81
+ .ol8-link[data-ol8-form="standalone"],
82
+ .ol8-link[data-ol8-form="navigation"] {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ gap: var(--ol8-component-link-gap-standard);
86
+ min-block-size: var(--ol8-component-link-targetnavigation);
87
+ padding-inline: var(--ol8-spacing-inline-minimal);
88
+ }
89
+ .ol8-link[data-ol8-form="standalone"][data-ol8-size="small"],
90
+ .ol8-link[data-ol8-form="navigation"][data-ol8-size="small"] {
91
+ gap: var(--ol8-component-link-gap-small);
92
+ }
93
+ .ol8-link[data-ol8-form="standalone"][data-ol8-size="large"],
94
+ .ol8-link[data-ol8-form="navigation"][data-ol8-size="large"] {
95
+ gap: var(--ol8-component-link-gap-large);
96
+ }
97
+
98
+ /* Figma keeps the underline in every navigation state and marks the current
99
+ destination by thickening it to the current marker weight. */
100
+ .ol8-link[data-ol8-form="navigation"][aria-current] {
101
+ text-decoration-thickness: var(--ol8-component-link-underline-current);
102
+ }
103
+
104
+ /* ---- Motion ----------------------------------------------------------- */
105
+ .ol8-link[data-ol8-motion="none"] { transition-duration: var(--ol8-motion-duration-instant); }
106
+ @media (prefers-reduced-motion: reduce) {
107
+ .ol8-link { transition-duration: var(--ol8-motion-duration-instant); }
108
+ }
109
+ @media (forced-colors: active) {
110
+ .ol8-link { forced-color-adjust: auto; }
111
+ .ol8-link:focus-visible {
112
+ outline: var(--ol8-focus-ring-innerwidth) solid Highlight;
113
+ outline-offset: calc(var(--ol8-focus-ring-innerwidth) * -1);
114
+ box-shadow: none;
115
+ }
116
+ }