@recursica/mui-adapter 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.d.ts +287 -15
- package/dist/mui-adapter.cjs +71 -71
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +9554 -8500
- package/dist/mui-adapter.js.map +1 -1
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
- package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +12 -0
- package/src/components/Button/Button.module.css +6 -5
- package/src/components/Checkbox/Checkbox.tsx +4 -1
- package/src/components/FileInput/FILEINPUT_IMPLEMENTATION_NOTES.md +148 -0
- package/src/components/FileInput/FileInput.module.css +268 -43
- package/src/components/FileInput/FileInput.stories.tsx +296 -4
- package/src/components/FileInput/FileInput.tsx +412 -6
- package/src/components/FileInput/USAGE.md +112 -4
- package/src/components/Popover/IMPLEMENTATION_NOTES.md +22 -0
- package/src/components/Popover/Popover.module.css +123 -0
- package/src/components/Popover/Popover.stories.tsx +133 -0
- package/src/components/Popover/Popover.tsx +275 -0
- package/src/components/Popover/USAGE.md +69 -0
- package/src/components/Popover/index.ts +1 -0
- package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +6 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +33 -4
- package/src/components/SegmentedControl/SegmentedControl.tsx +18 -4
- package/src/components/Slider/IMPLEMENTATION_NOTES.md +29 -0
- package/src/components/Slider/Slider.module.css +80 -17
- package/src/components/Slider/Slider.stories.tsx +1 -1
- package/src/components/Slider/Slider.tsx +36 -1
- package/src/components/Stepper/IMPLEMENTATION_NOTES.md +54 -0
- package/src/components/Stepper/Stepper.module.css +139 -106
- package/src/components/Stepper/Stepper.tsx +76 -10
- package/src/components/Stepper/USAGE.md +4 -0
- package/src/components/Tabs/IMPLEMENTATION_NOTES.md +12 -0
- package/src/components/Tabs/Tabs.module.css +107 -24
- package/src/components/Tabs/Tabs.tsx +1 -0
- package/src/components/TextArea/TextArea.module.css +20 -4
- package/src/components/TextArea/TextArea.tsx +12 -23
- package/src/components/Timeline/IMPLEMENTATION_NOTES.md +24 -3
- package/src/components/Timeline/Timeline.module.css +56 -68
- package/src/components/Timeline/Timeline.tsx +23 -27
- package/src/components/Timeline/TimelineItem.tsx +38 -35
- package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +140 -0
- package/src/components/TransferList/TransferList.module.css +179 -35
- package/src/components/TransferList/TransferList.stories.tsx +110 -6
- package/src/components/TransferList/TransferList.tsx +417 -8
- package/src/components/TransferList/USAGE.md +37 -6
- package/src/components/index.ts +1 -0
- package/src/index.ts +6 -1
|
@@ -2,10 +2,24 @@
|
|
|
2
2
|
Recursica Tabs CSS module natively binding to Figma variables.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
.root
|
|
5
|
+
/* Unlike Mantine's .root (which internally wraps List+Panel and always spans its parent),
|
|
6
|
+
MUI's .root is only the tablist — it sits beside the panel in the consumer's own flex row for
|
|
7
|
+
vertical orientation. Stretching it to 100% width there fights the panel for space and drags
|
|
8
|
+
the border-right rail/indicator away from the tab content, so only horizontal gets full width;
|
|
9
|
+
vertical sizes to its content instead, same as Mantine's vertical .list does. */
|
|
10
|
+
.root[data-orientation="horizontal"] {
|
|
6
11
|
width: 100%;
|
|
7
12
|
}
|
|
8
13
|
|
|
14
|
+
/* Mantine's vertical .list stretches to the root's full height via flex align-items, so a
|
|
15
|
+
border-right divider on it spans the whole rail. MUI's scroller (the .list's parent) is a plain
|
|
16
|
+
block for vertical, not a flex container, so .list sizes to its own content (just the stacked
|
|
17
|
+
tabs) and a divider on it stops short at the last tab instead of running the full height —
|
|
18
|
+
spell the stretch out explicitly instead. */
|
|
19
|
+
.root[data-orientation="vertical"] .list {
|
|
20
|
+
height: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
/*
|
|
10
24
|
Mantine Tabs Anatomy:
|
|
11
25
|
.root
|
|
@@ -125,6 +139,15 @@
|
|
|
125
139
|
--tabs-color: var(
|
|
126
140
|
--recursica_ui-kit_components_tabs-item_variants_styles_outline_variants_selection-states_active_properties_colors_border-color
|
|
127
141
|
) !important;
|
|
142
|
+
--tabs-active-bg: var(
|
|
143
|
+
--recursica_ui-kit_components_tabs-item_variants_styles_outline_variants_selection-states_active_properties_colors_background-color
|
|
144
|
+
);
|
|
145
|
+
/* The active tab's own background-color token above is transparent by design (it's meant to
|
|
146
|
+
let whichever ambient Layer surface sits behind the Tabs show through). That means it can't
|
|
147
|
+
double as an opaque "eraser" for the baseline-patch trick below, which needs a real paint to
|
|
148
|
+
replace, not reveal-through. Layer 0 is the default/outermost surface a Tabs instance sits
|
|
149
|
+
on absent a wrapping <Layer>; see OUTLINE SELECTION BORDER below for why this is needed. */
|
|
150
|
+
--tabs-baseline-patch-bg: var(--recursica_brand_layer_0_properties_surface);
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
.root[data-variant="outline"][data-orientation="horizontal"] {
|
|
@@ -223,18 +246,17 @@
|
|
|
223
246
|
|
|
224
247
|
/* ---------------------------------
|
|
225
248
|
SELECTION INDICATOR & BASELINE
|
|
226
|
-
Mantine draws the active-tab underline
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
Mantine draws the active-tab underline natively off the shared --tabs-color var above. MUI has
|
|
250
|
+
no equivalent built-in, so we drive its own indicator slot (classes.indicator, wired in
|
|
251
|
+
Tabs.tsx) and the list's baseline border explicitly from the same vars. Pills has no underline
|
|
252
|
+
indicator — selection shows via the pill's own background/border (see COLORS & TYPOGRAPHY
|
|
253
|
+
below). Outline repurposes the indicator as a baseline patch rather than an underline (see
|
|
254
|
+
below).
|
|
231
255
|
--------------------------------- */
|
|
232
|
-
.root[data-variant="default"] .indicator
|
|
233
|
-
.root[data-variant="outline"] .indicator {
|
|
256
|
+
.root[data-variant="default"] .indicator {
|
|
234
257
|
background-color: var(--tabs-color) !important;
|
|
235
258
|
}
|
|
236
|
-
.root[data-variant="default"][data-orientation="horizontal"] .indicator
|
|
237
|
-
.root[data-variant="outline"][data-orientation="horizontal"] .indicator {
|
|
259
|
+
.root[data-variant="default"][data-orientation="horizontal"] .indicator {
|
|
238
260
|
height: var(--tabs-active-border-size) !important;
|
|
239
261
|
}
|
|
240
262
|
/* MUI's Tabs root has no border/padding of its own, so the list's margin-bottom (the
|
|
@@ -250,27 +272,52 @@
|
|
|
250
272
|
--recursica_ui-kit_components_tabs_variants_styles_default_variants_orientation_horizontal_properties_tabs-content-gap
|
|
251
273
|
) !important;
|
|
252
274
|
}
|
|
275
|
+
/* width: same reasoning as the horizontal height above, rotated 90°.
|
|
276
|
+
right: same reasoning as the horizontal bottom offset above, rotated 90° — the list's
|
|
277
|
+
margin-right (this orientation's tabs-content-gap, spacing the panel to its right) inflates the
|
|
278
|
+
root's auto width past the list's own border-right edge. The indicator is absolutely positioned
|
|
279
|
+
against that wider box, so without this offset it renders content-gap pixels to the right of the
|
|
280
|
+
divider instead of flush against it. */
|
|
281
|
+
.root[data-variant="default"][data-orientation="vertical"] .indicator {
|
|
282
|
+
width: var(--tabs-active-border-size) !important;
|
|
283
|
+
right: var(
|
|
284
|
+
--recursica_ui-kit_components_tabs_variants_styles_default_variants_orientation_vertical_properties_tabs-content-gap
|
|
285
|
+
) !important;
|
|
286
|
+
}
|
|
287
|
+
.root[data-variant="default"][data-orientation="horizontal"][data-inverted]
|
|
288
|
+
.indicator {
|
|
289
|
+
bottom: auto !important;
|
|
290
|
+
top: 0 !important;
|
|
291
|
+
}
|
|
292
|
+
.root[data-variant="pills"] .indicator {
|
|
293
|
+
display: none !important;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Outline has no sliding underline of its own, but reuses the indicator element (already
|
|
297
|
+
positioned/sized by MUI to exactly track the selected tab, no matter which one) as a baseline
|
|
298
|
+
patch instead of hiding it — see OUTLINE SELECTION BORDER below for why a patch is needed at
|
|
299
|
+
all, and why it has to live here (in the scroller) rather than on the tab itself. */
|
|
300
|
+
.root[data-variant="outline"] .indicator {
|
|
301
|
+
background-color: var(--tabs-baseline-patch-bg) !important;
|
|
302
|
+
}
|
|
253
303
|
.root[data-variant="outline"][data-orientation="horizontal"]:not(
|
|
254
304
|
[data-inverted]
|
|
255
305
|
)
|
|
256
306
|
.indicator {
|
|
307
|
+
height: var(--tabs-inactive-border-size) !important;
|
|
257
308
|
bottom: var(
|
|
258
309
|
--recursica_ui-kit_components_tabs_variants_styles_outline_variants_orientation_horizontal_properties_tabs-content-gap
|
|
259
310
|
) !important;
|
|
260
311
|
}
|
|
261
|
-
.root[data-variant="default"][data-orientation="vertical"] .indicator,
|
|
262
|
-
.root[data-variant="outline"][data-orientation="vertical"] .indicator {
|
|
263
|
-
width: var(--tabs-active-border-size) !important;
|
|
264
|
-
}
|
|
265
|
-
.root[data-variant="default"][data-orientation="horizontal"][data-inverted]
|
|
266
|
-
.indicator,
|
|
267
312
|
.root[data-variant="outline"][data-orientation="horizontal"][data-inverted]
|
|
268
313
|
.indicator {
|
|
314
|
+
height: var(--tabs-inactive-border-size) !important;
|
|
269
315
|
bottom: auto !important;
|
|
270
|
-
top:
|
|
316
|
+
top: calc(-1 * var(--tabs-inactive-border-size)) !important;
|
|
271
317
|
}
|
|
272
|
-
.root[data-variant="
|
|
273
|
-
|
|
318
|
+
.root[data-variant="outline"][data-orientation="vertical"] .indicator {
|
|
319
|
+
width: var(--tabs-inactive-border-size) !important;
|
|
320
|
+
right: calc(-1 * var(--tabs-inactive-border-size)) !important;
|
|
274
321
|
}
|
|
275
322
|
|
|
276
323
|
.root[data-variant="default"][data-orientation="horizontal"] .list,
|
|
@@ -356,6 +403,41 @@
|
|
|
356
403
|
border-radius: var(--tabs-border-radius) 0 0 var(--tabs-border-radius);
|
|
357
404
|
}
|
|
358
405
|
|
|
406
|
+
/* ---------------------------------
|
|
407
|
+
OUTLINE SELECTION BORDER
|
|
408
|
+
Mantine's outline tab marks selection with a bordered box left open on the edge that touches
|
|
409
|
+
the list's own baseline, so the box visually connects to it instead of floating above it.
|
|
410
|
+
Every tab keeps the border geometry (transparent by default) so selecting a tab doesn't shift
|
|
411
|
+
layout; only the color changes on selection. The open edge here has 0 border-width, which
|
|
412
|
+
leaves the list's own baseline border showing straight through underneath — see the indicator
|
|
413
|
+
patch in SELECTION INDICATOR & BASELINE above, which covers just that segment. (A pseudo-element
|
|
414
|
+
on the tab itself can't do this: MUI clips tab content to its own bounds for ripple containment,
|
|
415
|
+
so anything extending past the tab's box to reach the list's border gets clipped away.)
|
|
416
|
+
--------------------------------- */
|
|
417
|
+
.root[data-variant="outline"] .tab {
|
|
418
|
+
border-style: solid;
|
|
419
|
+
border-color: transparent;
|
|
420
|
+
}
|
|
421
|
+
.root[data-variant="outline"][data-orientation="horizontal"]:not(
|
|
422
|
+
[data-inverted]
|
|
423
|
+
)
|
|
424
|
+
.tab {
|
|
425
|
+
border-width: var(--tabs-inactive-border-size)
|
|
426
|
+
var(--tabs-inactive-border-size) 0 var(--tabs-inactive-border-size);
|
|
427
|
+
}
|
|
428
|
+
.root[data-variant="outline"][data-orientation="horizontal"][data-inverted]
|
|
429
|
+
.tab {
|
|
430
|
+
border-width: 0 var(--tabs-inactive-border-size)
|
|
431
|
+
var(--tabs-inactive-border-size) var(--tabs-inactive-border-size);
|
|
432
|
+
}
|
|
433
|
+
.root[data-variant="outline"][data-orientation="vertical"] .tab {
|
|
434
|
+
border-width: var(--tabs-inactive-border-size) 0
|
|
435
|
+
var(--tabs-inactive-border-size) var(--tabs-inactive-border-size);
|
|
436
|
+
}
|
|
437
|
+
.root[data-variant="outline"] .tab:global(.Mui-selected) {
|
|
438
|
+
border-color: var(--tabs-color) !important;
|
|
439
|
+
}
|
|
440
|
+
|
|
359
441
|
/* ---------------------------------
|
|
360
442
|
COLORS & TYPOGRAPHY
|
|
361
443
|
Because colors are defined per-layer and theme via generic variable structures,
|
|
@@ -485,9 +567,7 @@
|
|
|
485
567
|
}
|
|
486
568
|
|
|
487
569
|
.root[data-variant="outline"] .tab:global(.Mui-selected) {
|
|
488
|
-
background-color: var(
|
|
489
|
-
--recursica_ui-kit_components_tabs-item_variants_styles_outline_variants_selection-states_active_properties_colors_background-color
|
|
490
|
-
) !important;
|
|
570
|
+
background-color: var(--tabs-active-bg) !important;
|
|
491
571
|
color: var(
|
|
492
572
|
--recursica_ui-kit_components_tabs-item_variants_styles_outline_variants_selection-states_active_properties_colors_text-color
|
|
493
573
|
) !important;
|
|
@@ -635,8 +715,11 @@
|
|
|
635
715
|
z-index: 0;
|
|
636
716
|
}
|
|
637
717
|
|
|
638
|
-
/* Ensure tab content sits above the hover background
|
|
639
|
-
|
|
718
|
+
/* Ensure tab content sits above the hover background. Excludes MuiTouchRipple-root: it relies on
|
|
719
|
+
its native position:absolute to stay out of flow, and forcing it to position:relative here
|
|
720
|
+
turns it into a real flex item — inserting an extra element-gap into the tab's flex row (and
|
|
721
|
+
growing the tab's width) the first time it's clicked, permanently shifting later siblings. */
|
|
722
|
+
.root .tab > *:not(:global(.MuiTouchRipple-root)) {
|
|
640
723
|
z-index: 1;
|
|
641
724
|
position: relative;
|
|
642
725
|
}
|
|
@@ -127,6 +127,19 @@
|
|
|
127
127
|
color: inherit;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/* MUI always renders multiline inputs through TextareaAutosize, which sets an inline `height`
|
|
131
|
+
that otherwise wins over the min-height above. Pin the height to the same rows-based formula
|
|
132
|
+
when autosize isn't requested, so it matches the plain (non-autosizing) textarea Mantine renders. */
|
|
133
|
+
.root:not([data-autosize]) .input {
|
|
134
|
+
height: calc(
|
|
135
|
+
var(--recursica_ui-kit_components_textarea_properties_rows) *
|
|
136
|
+
var(--recursica_ui-kit_components_textarea_properties_text_line-height) +
|
|
137
|
+
2 *
|
|
138
|
+
var(--recursica_ui-kit_components_textarea_properties_vertical-padding) +
|
|
139
|
+
2px
|
|
140
|
+
) !important;
|
|
141
|
+
}
|
|
142
|
+
|
|
130
143
|
/* -------------------------------------
|
|
131
144
|
STATE CASCADE ARCHITECTURE
|
|
132
145
|
-------------------------------------- */
|
|
@@ -144,8 +157,10 @@
|
|
|
144
157
|
var(--recursica_brand_states_focus_color);
|
|
145
158
|
}
|
|
146
159
|
|
|
147
|
-
/* Error State Mapping
|
|
148
|
-
|
|
160
|
+
/* Error State Mapping
|
|
161
|
+
MUI only stamps the global `Mui-error` state class onto the InputBase root slot (not the
|
|
162
|
+
input element itself), so the error trigger is scoped from `.root` down to `.input`. */
|
|
163
|
+
.root:global(.Mui-error) .input {
|
|
149
164
|
border-color: var(
|
|
150
165
|
--recursica_ui-kit_components_textarea_variants_states_error_properties_colors_border-color
|
|
151
166
|
) !important;
|
|
@@ -157,8 +172,9 @@
|
|
|
157
172
|
) !important;
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
/* Disabled State Mapping
|
|
161
|
-
|
|
175
|
+
/* Disabled State Mapping
|
|
176
|
+
MUI stamps `Mui-disabled` onto both the root slot and the input element directly. */
|
|
177
|
+
.input:global(.Mui-disabled) {
|
|
162
178
|
border-color: var(
|
|
163
179
|
--recursica_ui-kit_components_textarea_variants_states_disabled_properties_colors_border-color
|
|
164
180
|
) !important;
|
|
@@ -66,6 +66,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
66
66
|
emptyValueComponent,
|
|
67
67
|
value,
|
|
68
68
|
defaultValue,
|
|
69
|
+
autosize,
|
|
69
70
|
...rest
|
|
70
71
|
} = props;
|
|
71
72
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
@@ -76,27 +77,6 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
76
77
|
delete restRecord["variant"];
|
|
77
78
|
delete restRecord["radius"];
|
|
78
79
|
|
|
79
|
-
// Securely map core native blocks down ensuring nested CSS modules map precisely
|
|
80
|
-
const mergedClassNames: Partial<Record<string, string>> = {
|
|
81
|
-
wrapper: styles.root, // The nested Input internal relative wrapper bounding box
|
|
82
|
-
input: styles.input,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const classNamesProp = restRecord.classNames;
|
|
86
|
-
if (
|
|
87
|
-
classNamesProp &&
|
|
88
|
-
typeof classNamesProp === "object" &&
|
|
89
|
-
!Array.isArray(classNamesProp)
|
|
90
|
-
) {
|
|
91
|
-
const o = classNamesProp as Partial<Record<string, string>>;
|
|
92
|
-
mergedClassNames.wrapper = o.wrapper
|
|
93
|
-
? `${styles.root} ${o.wrapper}`
|
|
94
|
-
: styles.root;
|
|
95
|
-
mergedClassNames.input = o.input
|
|
96
|
-
? `${styles.input} ${o.input}`
|
|
97
|
-
: styles.input;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
80
|
const wrapperClass = className
|
|
101
81
|
? `${styles.layoutOverride} ${className}`
|
|
102
82
|
: styles.layoutOverride;
|
|
@@ -130,15 +110,24 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
130
110
|
/* Naked Input execution safely decoupled from Mui's macro Input.Wrapper DOM hooks */
|
|
131
111
|
<MuiTextarea
|
|
132
112
|
multiline
|
|
113
|
+
variant="standard"
|
|
133
114
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
115
|
inputRef={ref as any}
|
|
135
|
-
classes={mergedClassNames}
|
|
136
116
|
disabled={disabled}
|
|
137
117
|
value={value}
|
|
138
118
|
defaultValue={defaultValue}
|
|
139
119
|
label={undefined}
|
|
140
|
-
error={
|
|
120
|
+
error={!!error}
|
|
141
121
|
required={undefined}
|
|
122
|
+
slotProps={{
|
|
123
|
+
input: {
|
|
124
|
+
disableUnderline: true,
|
|
125
|
+
classes: { root: styles.root, input: styles.input },
|
|
126
|
+
...({
|
|
127
|
+
"data-autosize": autosize ? "true" : undefined,
|
|
128
|
+
} as Record<string, unknown>),
|
|
129
|
+
},
|
|
130
|
+
}}
|
|
142
131
|
{...(sanitizedProps as unknown as MuiTextareaProps)}
|
|
143
132
|
/>
|
|
144
133
|
}
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Timeline Implementation Notes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
Unlike Mantine, `@mui/lab`'s `TimelineItem` has no built-in bullet/connector — it only
|
|
6
|
+
composes whatever you nest inside it. `TimelineItem.tsx` renders `TimelineSeparator` >
|
|
7
|
+
`TimelineDot` (`.itemBullet`) + `TimelineConnector` (`.itemConnector`), and `TimelineContent`
|
|
8
|
+
(`.itemBody`) containing the `title`/description/`timestamp` nodes, to reproduce Mantine's
|
|
9
|
+
single-component bullet + connecting-line structure.
|
|
10
|
+
|
|
11
|
+
- `Timeline.tsx` computes `active`/`isLast` per item (Mui has no `active` concept at all) and
|
|
12
|
+
injects them into each `Timeline.Item` as internal `__active`/`__isLast` props, mirroring
|
|
13
|
+
Mantine's own `cloneElement` pattern for propagating active state down to children.
|
|
14
|
+
- `data-active` / `data-variant` are set directly on `TimelineItem`'s real `li` root and
|
|
15
|
+
consumed via plain descendant selectors (`.item[data-active] .itemBullet`, etc.) in
|
|
16
|
+
`Timeline.module.css` — Mui's `TimelineItem` only defines a `root` classes slot, so (unlike
|
|
17
|
+
Mantine) passing a `classes` object with `item`/`itemBullet`/`itemBody`/... keys is a no-op;
|
|
18
|
+
those class names must be applied as plain `className`s on the elements we render ourselves.
|
|
19
|
+
- The last item omits `TimelineConnector` entirely so no trailing line renders past it.
|
|
20
|
+
- `Timeline.module.css` neutralizes Mui's `TimelineItem::before` opposite-content spacer
|
|
21
|
+
(`flex: 0; padding: 0`), which otherwise pushes the separator/content right since this
|
|
22
|
+
adapter never renders `TimelineOppositeContent`.
|
|
23
|
+
|
|
24
|
+
## Limitations & Missing Tokens
|
|
25
|
+
|
|
26
|
+
- **Avatar Bullet Size**: There is no specific pixel variable provided for the Avatar bullet size in the UI kit tokens (`avatar-size` evaluates to `"default"`). To maintain exact mathematical centering with the connector line, the CSS falls back to inheriting the `default` bullet size (`20px`) for avatar nodes natively. If users supply a custom sized `img` tag, it must adhere to inline structural constraints or flex mappings.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* EXEMPTIONS:
|
|
2
2
|
- timeline-bullet_variants_types_avatar_properties_avatar-size is ignored because under the avatar timeline variant,
|
|
3
|
-
the bullet container size uses fallback bullet-size configurations to maintain vertical alignment with the
|
|
3
|
+
the bullet container size uses fallback bullet-size configurations to maintain vertical alignment with the
|
|
4
4
|
inactive/active connector line, while the nested Avatar subcomponent handles its own sizing natively. */
|
|
5
5
|
/* recursica-ignore: --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_avatar-size */
|
|
6
6
|
|
|
@@ -8,74 +8,57 @@
|
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
gap: var(--recursica_ui-kit_components_timeline_properties_item-gap);
|
|
11
|
+
margin: 0 !important;
|
|
12
|
+
padding: 0 !important;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
.item {
|
|
14
|
-
/*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
) !important;
|
|
18
|
-
|
|
19
|
-
&[data-active] {
|
|
20
|
-
--tl-line-width: var(
|
|
21
|
-
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_connector-size
|
|
22
|
-
) !important;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
&[data-variant="default"] {
|
|
26
|
-
--tl-bullet-size: var(
|
|
27
|
-
--recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
|
|
28
|
-
) !important;
|
|
29
|
-
}
|
|
30
|
-
&[data-variant="icon"] {
|
|
31
|
-
--tl-bullet-size: var(
|
|
32
|
-
--recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_bullet-size
|
|
33
|
-
) !important;
|
|
34
|
-
}
|
|
35
|
-
&[data-variant="icon-alternative"] {
|
|
36
|
-
--tl-bullet-size: var(
|
|
37
|
-
--recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_bullet-size
|
|
38
|
-
) !important;
|
|
39
|
-
}
|
|
40
|
-
&[data-variant="avatar"] {
|
|
41
|
-
/* Avatar sizing fallback to default to maintain connector alignment */
|
|
42
|
-
--tl-bullet-size: var(
|
|
43
|
-
--recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
|
|
44
|
-
) !important;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* Control connector lines via the item ::before pseudo-element */
|
|
48
|
-
/* Inactive connector */
|
|
49
|
-
&::before {
|
|
50
|
-
border-left-width: var(
|
|
51
|
-
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_connector-size
|
|
52
|
-
) !important;
|
|
53
|
-
border-left-color: var(
|
|
54
|
-
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_colors_connector-color
|
|
55
|
-
) !important;
|
|
56
|
-
}
|
|
16
|
+
/* Mui gives every item a 70px min-height by default; content should drive height instead */
|
|
17
|
+
min-height: 0 !important;
|
|
18
|
+
}
|
|
57
19
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
) !important;
|
|
66
|
-
}
|
|
20
|
+
/* Mui reserves a flex:1 spacer (via ::before) for the opposite-content column even when none is
|
|
21
|
+
rendered. Recursica's Timeline never uses TimelineOppositeContent, so neutralize it to keep the
|
|
22
|
+
bullet/connector column flush against the left edge. */
|
|
23
|
+
:global(.MuiTimelineItem-root)::before {
|
|
24
|
+
content: none !important;
|
|
25
|
+
flex: 0 !important;
|
|
26
|
+
padding: 0 !important;
|
|
67
27
|
}
|
|
68
28
|
|
|
69
29
|
.itemBody {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
30
|
+
padding: 0 0 0
|
|
31
|
+
var(--recursica_ui-kit_components_timeline_properties_bullet-content-gap) !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.itemConnector {
|
|
35
|
+
width: var(
|
|
36
|
+
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_connector-size
|
|
37
|
+
) !important;
|
|
38
|
+
background-color: var(
|
|
39
|
+
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_colors_connector-color
|
|
40
|
+
) !important;
|
|
41
|
+
}
|
|
42
|
+
.item[data-active] .itemConnector {
|
|
43
|
+
width: var(
|
|
44
|
+
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_connector-size
|
|
45
|
+
) !important;
|
|
46
|
+
background-color: var(
|
|
47
|
+
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_colors_connector-color
|
|
73
48
|
) !important;
|
|
74
49
|
}
|
|
75
50
|
|
|
76
51
|
.itemBullet {
|
|
52
|
+
display: flex !important;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
align-self: center !important;
|
|
56
|
+
margin: 0 !important;
|
|
57
|
+
padding: 0 !important;
|
|
58
|
+
box-shadow: none !important;
|
|
59
|
+
|
|
77
60
|
/* Default Variant */
|
|
78
|
-
|
|
61
|
+
.item[data-variant="default"] & {
|
|
79
62
|
width: var(
|
|
80
63
|
--recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
|
|
81
64
|
) !important;
|
|
@@ -96,7 +79,7 @@
|
|
|
96
79
|
--recursica_ui-kit_components_timeline-bullet_variants_types_default_variants_selection-states_inactive_properties_colors_border-color
|
|
97
80
|
) !important;
|
|
98
81
|
}
|
|
99
|
-
|
|
82
|
+
.item[data-variant="default"][data-active] & {
|
|
100
83
|
background-color: var(
|
|
101
84
|
--recursica_ui-kit_components_timeline-bullet_variants_types_default_variants_selection-states_active_properties_colors_background-color
|
|
102
85
|
) !important;
|
|
@@ -106,7 +89,7 @@
|
|
|
106
89
|
}
|
|
107
90
|
|
|
108
91
|
/* Icon Variant */
|
|
109
|
-
|
|
92
|
+
.item[data-variant="icon"] & {
|
|
110
93
|
width: var(
|
|
111
94
|
--recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_bullet-size
|
|
112
95
|
) !important;
|
|
@@ -143,7 +126,7 @@
|
|
|
143
126
|
);
|
|
144
127
|
}
|
|
145
128
|
}
|
|
146
|
-
|
|
129
|
+
.item[data-variant="icon"][data-active] & {
|
|
147
130
|
background-color: var(
|
|
148
131
|
--recursica_ui-kit_components_timeline-bullet_variants_types_icon_variants_selection-states_active_properties_colors_background-color
|
|
149
132
|
) !important;
|
|
@@ -156,7 +139,7 @@
|
|
|
156
139
|
}
|
|
157
140
|
|
|
158
141
|
/* Icon Alternative Variant */
|
|
159
|
-
|
|
142
|
+
.item[data-variant="icon-alternative"] & {
|
|
160
143
|
width: var(
|
|
161
144
|
--recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_bullet-size
|
|
162
145
|
) !important;
|
|
@@ -192,8 +175,7 @@
|
|
|
192
175
|
);
|
|
193
176
|
}
|
|
194
177
|
}
|
|
195
|
-
|
|
196
|
-
& {
|
|
178
|
+
.item[data-variant="icon-alternative"][data-active] & {
|
|
197
179
|
background-color: var(
|
|
198
180
|
--recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_variants_selection-states_active_properties_colors_background-color
|
|
199
181
|
) !important;
|
|
@@ -206,8 +188,14 @@
|
|
|
206
188
|
}
|
|
207
189
|
|
|
208
190
|
/* Avatar Variant */
|
|
209
|
-
|
|
191
|
+
.item[data-variant="avatar"] & {
|
|
210
192
|
/* Avatar sizing is often governed by the avatar itself, but we'll apply the radius and opacity */
|
|
193
|
+
width: var(
|
|
194
|
+
--recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
|
|
195
|
+
) !important;
|
|
196
|
+
height: var(
|
|
197
|
+
--recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
|
|
198
|
+
) !important;
|
|
211
199
|
border-radius: var(
|
|
212
200
|
--recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_border-radius
|
|
213
201
|
) !important;
|
|
@@ -225,7 +213,7 @@
|
|
|
225
213
|
--recursica_ui-kit_components_timeline-bullet_variants_types_avatar_variants_selection-states_inactive_properties_colors_border-color
|
|
226
214
|
) !important;
|
|
227
215
|
}
|
|
228
|
-
|
|
216
|
+
.item[data-variant="avatar"][data-active] & {
|
|
229
217
|
opacity: var(
|
|
230
218
|
--recursica_ui-kit_components_timeline-bullet_variants_types_avatar_variants_selection-states_active_properties_avatar-opacity
|
|
231
219
|
) !important;
|
|
@@ -275,7 +263,7 @@
|
|
|
275
263
|
) !important;
|
|
276
264
|
|
|
277
265
|
/* Active Title Color */
|
|
278
|
-
|
|
266
|
+
.item[data-active] & {
|
|
279
267
|
color: var(
|
|
280
268
|
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_colors_title-color
|
|
281
269
|
) !important;
|
|
@@ -323,7 +311,7 @@
|
|
|
323
311
|
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_colors_description-color
|
|
324
312
|
);
|
|
325
313
|
|
|
326
|
-
|
|
314
|
+
.item[data-active] & {
|
|
327
315
|
color: var(
|
|
328
316
|
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_colors_description-color
|
|
329
317
|
);
|
|
@@ -360,7 +348,7 @@
|
|
|
360
348
|
--recursica_ui-kit_components_timeline_variants_selection-states_inactive_properties_colors_timestamp-color
|
|
361
349
|
);
|
|
362
350
|
|
|
363
|
-
|
|
351
|
+
.item[data-active] & {
|
|
364
352
|
color: var(
|
|
365
353
|
--recursica_ui-kit_components_timeline_variants_selection-states_active_properties_colors_timestamp-color
|
|
366
354
|
);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type RecursicaOverStyled,
|
|
8
8
|
} from "../../utils/filterStylingProps";
|
|
9
9
|
import styles from "./Timeline.module.css";
|
|
10
|
-
import { TimelineItem } from "./TimelineItem";
|
|
10
|
+
import { TimelineItem, type TimelineItemProps } from "./TimelineItem";
|
|
11
11
|
|
|
12
12
|
import { type RecursicaTimelineProps } from "@recursica/adapter-common";
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ export type TimelineProps = RecursicaOverStyled<
|
|
|
23
23
|
|
|
24
24
|
interface TimelineComponent
|
|
25
25
|
extends React.ForwardRefExoticComponent<
|
|
26
|
-
TimelineProps & React.RefAttributes<
|
|
26
|
+
TimelineProps & React.RefAttributes<HTMLUListElement>
|
|
27
27
|
> {
|
|
28
28
|
Item: typeof TimelineItem;
|
|
29
29
|
}
|
|
@@ -32,8 +32,10 @@ interface TimelineComponent
|
|
|
32
32
|
* The `Timeline` component displays a list of events in chronological order.
|
|
33
33
|
*
|
|
34
34
|
* **Recursica Abstract:**
|
|
35
|
-
* This component acts as a structural wrapper around Mui's `<Timeline
|
|
36
|
-
*
|
|
35
|
+
* This component acts as a structural wrapper around Mui's `<Timeline>`, composing
|
|
36
|
+
* `TimelineSeparator`/`TimelineDot`/`TimelineConnector`/`TimelineContent` per item
|
|
37
|
+
* (via `Timeline.Item`) so the bullet marker and connecting line render the same way
|
|
38
|
+
* they do in the Mantine adapter.
|
|
37
39
|
*
|
|
38
40
|
* @example
|
|
39
41
|
* ```tsx
|
|
@@ -43,29 +45,21 @@ interface TimelineComponent
|
|
|
43
45
|
* ```
|
|
44
46
|
*/
|
|
45
47
|
const TimelineInner = React.forwardRef<HTMLUListElement, TimelineProps>(
|
|
46
|
-
function Timeline(
|
|
48
|
+
function Timeline(
|
|
49
|
+
{ overStyled = false, active = -1, children, ...rest },
|
|
50
|
+
ref,
|
|
51
|
+
) {
|
|
47
52
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
48
53
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
!Array.isArray(classNamesProp)
|
|
59
|
-
) {
|
|
60
|
-
const o = classNamesProp as Record<string, string>;
|
|
61
|
-
Object.keys(o).forEach((key) => {
|
|
62
|
-
if (mergedClassNames[key]) {
|
|
63
|
-
mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
|
|
64
|
-
} else {
|
|
65
|
-
mergedClassNames[key] = o[key];
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
54
|
+
const items = React.Children.toArray(children);
|
|
55
|
+
const decoratedItems = items.map((item, index) =>
|
|
56
|
+
React.isValidElement(item)
|
|
57
|
+
? React.cloneElement(item as React.ReactElement<TimelineItemProps>, {
|
|
58
|
+
__active: active >= index,
|
|
59
|
+
__isLast: index === items.length - 1,
|
|
60
|
+
})
|
|
61
|
+
: item,
|
|
62
|
+
);
|
|
69
63
|
|
|
70
64
|
return (
|
|
71
65
|
<MuiTimeline
|
|
@@ -74,8 +68,10 @@ const TimelineInner = React.forwardRef<HTMLUListElement, TimelineProps>(
|
|
|
74
68
|
MuiTimelineProps,
|
|
75
69
|
"color" | "radius" | "bulletSize" | "lineWidth"
|
|
76
70
|
>)}
|
|
77
|
-
classes={
|
|
78
|
-
|
|
71
|
+
classes={{ root: styles.root }}
|
|
72
|
+
>
|
|
73
|
+
{decoratedItems}
|
|
74
|
+
</MuiTimeline>
|
|
79
75
|
);
|
|
80
76
|
},
|
|
81
77
|
);
|