@recursica/mantine-adapter 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/mantine-adapter.cjs +1 -1
  3. package/dist/mantine-adapter.cjs.map +1 -1
  4. package/dist/mantine-adapter.css +1 -1
  5. package/dist/mantine-adapter.js +1642 -1357
  6. package/dist/mantine-adapter.js.map +1 -1
  7. package/dist/src/components/Modal/Modal.d.ts +39 -2
  8. package/dist/src/components/Modal/index.d.ts +1 -0
  9. package/dist/src/components/Popover/Popover.d.ts +55 -0
  10. package/dist/src/components/Popover/index.d.ts +1 -0
  11. package/dist/src/components/Timeline/Timeline.d.ts +14 -2
  12. package/dist/src/components/Timeline/TimelineItem.d.ts +25 -0
  13. package/dist/src/components/Toast/Toast.d.ts +21 -2
  14. package/dist/src/components/Toast/index.d.ts +1 -0
  15. package/dist/src/components/index.d.ts +2 -1
  16. package/package.json +1 -1
  17. package/src/components/Modal/MODAL_IMPLEMENTATION_NOTES.md +15 -0
  18. package/src/components/Modal/Modal.module.css +127 -0
  19. package/src/components/Modal/Modal.stories.tsx +66 -4
  20. package/src/components/Modal/Modal.tsx +218 -3
  21. package/src/components/Modal/index.ts +1 -0
  22. package/src/components/Popover/IMPLEMENTATION_NOTES.md +77 -0
  23. package/src/components/Popover/Popover.module.css +84 -0
  24. package/src/components/Popover/Popover.stories.tsx +133 -0
  25. package/src/components/Popover/Popover.tsx +156 -0
  26. package/src/components/Popover/index.ts +1 -0
  27. package/src/components/Timeline/TIMELINE_IMPLEMENTATION_NOTES.md +13 -0
  28. package/src/components/Timeline/Timeline.module.css +361 -0
  29. package/src/components/Timeline/Timeline.stories.tsx +114 -3
  30. package/src/components/Timeline/Timeline.tsx +81 -4
  31. package/src/components/Timeline/TimelineItem.tsx +102 -0
  32. package/src/components/Toast/TOAST_IMPLEMENTATION_NOTES.md +39 -0
  33. package/src/components/Toast/Toast.module.css +109 -0
  34. package/src/components/Toast/Toast.stories.tsx +57 -7
  35. package/src/components/Toast/Toast.tsx +90 -4
  36. package/src/components/Toast/index.ts +1 -0
  37. package/src/components/index.ts +2 -1
@@ -0,0 +1,361 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ }
5
+
6
+ .item {
7
+ /* Mantine uses these vars to calculate the exact left: position of the connector line */
8
+ --tl-line-width: var(
9
+ --recursica_ui-kit_components_timeline_properties_inactive-connector-size
10
+ ) !important;
11
+
12
+ &[data-active] {
13
+ --tl-line-width: var(
14
+ --recursica_ui-kit_components_timeline_properties_active-connector-size
15
+ ) !important;
16
+ }
17
+
18
+ &[data-variant="default"] {
19
+ --tl-bullet-size: var(
20
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
21
+ ) !important;
22
+ }
23
+ &[data-variant="icon"] {
24
+ --tl-bullet-size: var(
25
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_bullet-size
26
+ ) !important;
27
+ }
28
+ &[data-variant="icon-alternative"] {
29
+ --tl-bullet-size: var(
30
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_bullet-size
31
+ ) !important;
32
+ }
33
+ &[data-variant="avatar"] {
34
+ /* Avatar sizing fallback to default to maintain connector alignment */
35
+ --tl-bullet-size: var(
36
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
37
+ ) !important;
38
+ }
39
+
40
+ /* Control connector lines via the item ::before pseudo-element */
41
+ /* Inactive connector */
42
+ &::before {
43
+ border-left-width: var(
44
+ --recursica_ui-kit_components_timeline_properties_inactive-connector-size
45
+ ) !important;
46
+ border-left-color: var(
47
+ --recursica_ui-kit_components_timeline_properties_colors_inactive-connector-color
48
+ ) !important;
49
+ }
50
+
51
+ /* Active connector */
52
+ &[data-active]::before {
53
+ border-left-width: var(
54
+ --recursica_ui-kit_components_timeline_properties_active-connector-size
55
+ ) !important;
56
+ border-left-color: var(
57
+ --recursica_ui-kit_components_timeline_properties_colors_active-connector-color
58
+ ) !important;
59
+ }
60
+ }
61
+
62
+ .itemBody {
63
+ /* Space between the bullet and the content */
64
+ padding-left: var(
65
+ --recursica_ui-kit_components_timeline_properties_bullet-content-gap
66
+ ) !important;
67
+ }
68
+
69
+ .itemBullet {
70
+ /* Default Variant */
71
+ :global(.mantine-Timeline-item)[data-variant="default"] & {
72
+ width: var(
73
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
74
+ ) !important;
75
+ height: var(
76
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_bullet-size
77
+ ) !important;
78
+ border-radius: var(
79
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_border-radius
80
+ ) !important;
81
+ border-width: var(
82
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_border-size
83
+ ) !important;
84
+
85
+ background-color: var(
86
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_colors_inactive-background
87
+ ) !important;
88
+ border-color: var(
89
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_colors_inactive-border-color
90
+ ) !important;
91
+ }
92
+ :global(.mantine-Timeline-item)[data-variant="default"][data-active] & {
93
+ background-color: var(
94
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_colors_active-background
95
+ ) !important;
96
+ border-color: var(
97
+ --recursica_ui-kit_components_timeline-bullet_variants_types_default_properties_colors_active-border-color
98
+ ) !important;
99
+ }
100
+
101
+ /* Icon Variant */
102
+ :global(.mantine-Timeline-item)[data-variant="icon"] & {
103
+ width: var(
104
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_bullet-size
105
+ ) !important;
106
+ height: var(
107
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_bullet-size
108
+ ) !important;
109
+ border-radius: var(
110
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_border-radius
111
+ ) !important;
112
+ border-width: var(
113
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_border-size
114
+ ) !important;
115
+
116
+ background-color: var(
117
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_inactive-background
118
+ ) !important;
119
+ border-color: var(
120
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_inactive-border-color
121
+ ) !important;
122
+ color: var(
123
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_inactive-icon-color
124
+ ) !important;
125
+
126
+ /* Propagate icon size */
127
+ font-size: var(
128
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_icon-size
129
+ );
130
+ & svg {
131
+ width: var(
132
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_icon-size
133
+ );
134
+ height: var(
135
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_icon-size
136
+ );
137
+ }
138
+ }
139
+ :global(.mantine-Timeline-item)[data-variant="icon"][data-active] & {
140
+ background-color: var(
141
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_active-background
142
+ ) !important;
143
+ border-color: var(
144
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_active-border-color
145
+ ) !important;
146
+ color: var(
147
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon_properties_colors_active-icon-color
148
+ ) !important;
149
+ }
150
+
151
+ /* Icon Alternative Variant */
152
+ :global(.mantine-Timeline-item)[data-variant="icon-alternative"] & {
153
+ width: var(
154
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_bullet-size
155
+ ) !important;
156
+ height: var(
157
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_bullet-size
158
+ ) !important;
159
+ border-radius: var(
160
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_border-radius
161
+ ) !important;
162
+ border-width: var(
163
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_border-size
164
+ ) !important;
165
+
166
+ background-color: var(
167
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_inactive-background
168
+ ) !important;
169
+ border-color: var(
170
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_inactive-border-color
171
+ ) !important;
172
+ color: var(
173
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_inactive-icon-color
174
+ ) !important;
175
+
176
+ font-size: var(
177
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_icon-size
178
+ );
179
+ & svg {
180
+ width: var(
181
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_icon-size
182
+ );
183
+ height: var(
184
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_icon-size
185
+ );
186
+ }
187
+ }
188
+ :global(.mantine-Timeline-item)[data-variant="icon-alternative"][data-active]
189
+ & {
190
+ background-color: var(
191
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_active-background
192
+ ) !important;
193
+ border-color: var(
194
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_active-border-color
195
+ ) !important;
196
+ color: var(
197
+ --recursica_ui-kit_components_timeline-bullet_variants_types_icon-alternative_properties_colors_active-icon-color
198
+ ) !important;
199
+ }
200
+
201
+ /* Avatar Variant */
202
+ :global(.mantine-Timeline-item)[data-variant="avatar"] & {
203
+ /* Avatar sizing is often governed by the avatar itself, but we'll apply the radius and opacity */
204
+ border-radius: var(
205
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_border-radius
206
+ ) !important;
207
+ border-width: var(
208
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_border-size
209
+ ) !important;
210
+ opacity: var(
211
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_inactive-avatar-opacity
212
+ ) !important;
213
+
214
+ background-color: var(
215
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_colors_inactive-background
216
+ ) !important;
217
+ border-color: var(
218
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_colors_inactive-border-color
219
+ ) !important;
220
+ }
221
+ :global(.mantine-Timeline-item)[data-variant="avatar"][data-active] & {
222
+ opacity: var(
223
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_active-avatar-opacity
224
+ ) !important;
225
+ background-color: var(
226
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_colors_active-background
227
+ ) !important;
228
+ border-color: var(
229
+ --recursica_ui-kit_components_timeline-bullet_variants_types_avatar_properties_colors_active-border-color
230
+ ) !important;
231
+ }
232
+ }
233
+
234
+ .itemTitle {
235
+ font-family: var(
236
+ --recursica_ui-kit_components_timeline_properties_title-text_font-family
237
+ );
238
+ font-size: var(
239
+ --recursica_ui-kit_components_timeline_properties_title-text_font-size
240
+ );
241
+ font-style: var(
242
+ --recursica_ui-kit_components_timeline_properties_title-text_font-style
243
+ );
244
+ font-weight: var(
245
+ --recursica_ui-kit_components_timeline_properties_title-text_font-weight
246
+ );
247
+ letter-spacing: var(
248
+ --recursica_ui-kit_components_timeline_properties_title-text_letter-spacing
249
+ );
250
+ line-height: var(
251
+ --recursica_ui-kit_components_timeline_properties_title-text_line-height
252
+ );
253
+ text-decoration: var(
254
+ --recursica_ui-kit_components_timeline_properties_title-text_text-decoration
255
+ );
256
+ text-transform: var(
257
+ --recursica_ui-kit_components_timeline_properties_title-text_text-transform
258
+ );
259
+
260
+ /* Title Description Gap */
261
+ margin-bottom: var(
262
+ --recursica_ui-kit_components_timeline_properties_title-description-gap
263
+ ) !important;
264
+
265
+ /* Inactive Title Color */
266
+ color: var(
267
+ --recursica_ui-kit_components_timeline_properties_colors_inactive-title-color
268
+ ) !important;
269
+
270
+ /* Active Title Color */
271
+ :global(.mantine-Timeline-item)[data-active] & {
272
+ color: var(
273
+ --recursica_ui-kit_components_timeline_properties_colors_active-title-color
274
+ ) !important;
275
+ }
276
+ }
277
+
278
+ .itemContent {
279
+ display: flex;
280
+ flex-direction: column;
281
+ gap: var(
282
+ --recursica_ui-kit_components_timeline_properties_description-timestamp-gap
283
+ );
284
+ max-width: var(
285
+ --recursica_ui-kit_components_timeline_properties_max-text-width
286
+ );
287
+ }
288
+
289
+ .description {
290
+ font-family: var(
291
+ --recursica_ui-kit_components_timeline_properties_description-text_font-family
292
+ );
293
+ font-size: var(
294
+ --recursica_ui-kit_components_timeline_properties_description-text_font-size
295
+ );
296
+ font-style: var(
297
+ --recursica_ui-kit_components_timeline_properties_description-text_font-style
298
+ );
299
+ font-weight: var(
300
+ --recursica_ui-kit_components_timeline_properties_description-text_font-weight
301
+ );
302
+ letter-spacing: var(
303
+ --recursica_ui-kit_components_timeline_properties_description-text_letter-spacing
304
+ );
305
+ line-height: var(
306
+ --recursica_ui-kit_components_timeline_properties_description-text_line-height
307
+ );
308
+ text-decoration: var(
309
+ --recursica_ui-kit_components_timeline_properties_description-text_text-decoration
310
+ );
311
+ text-transform: var(
312
+ --recursica_ui-kit_components_timeline_properties_description-text_text-transform
313
+ );
314
+
315
+ color: var(
316
+ --recursica_ui-kit_components_timeline_properties_colors_inactive-description-color
317
+ );
318
+
319
+ :global(.mantine-Timeline-item)[data-active] & {
320
+ color: var(
321
+ --recursica_ui-kit_components_timeline_properties_colors_active-description-color
322
+ );
323
+ }
324
+ }
325
+
326
+ .timestamp {
327
+ font-family: var(
328
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_font-family
329
+ );
330
+ font-size: var(
331
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_font-size
332
+ );
333
+ font-style: var(
334
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_font-style
335
+ );
336
+ font-weight: var(
337
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_font-weight
338
+ );
339
+ letter-spacing: var(
340
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_letter-spacing
341
+ );
342
+ line-height: var(
343
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_line-height
344
+ );
345
+ text-decoration: var(
346
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_text-decoration
347
+ );
348
+ text-transform: var(
349
+ --recursica_ui-kit_components_timeline_properties_timestamp-text_text-transform
350
+ );
351
+
352
+ color: var(
353
+ --recursica_ui-kit_components_timeline_properties_colors_inactive-timestamp-color
354
+ );
355
+
356
+ :global(.mantine-Timeline-item)[data-active] & {
357
+ color: var(
358
+ --recursica_ui-kit_components_timeline_properties_colors_active-timestamp-color
359
+ );
360
+ }
361
+ }
@@ -1,11 +1,13 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { Timeline } from "./Timeline";
3
- import { ComingSoon } from "@recursica/storybook-template";
4
3
 
5
4
  const meta: Meta<typeof Timeline> = {
6
- title: "UI-Kit/🚧 Timeline",
5
+ title: "UI-Kit/Timeline",
7
6
  component: Timeline,
8
7
  tags: ["autodocs"],
8
+ argTypes: {
9
+ defaultChecked: { table: { disable: true } },
10
+ },
9
11
  };
10
12
 
11
13
  export default meta;
@@ -13,5 +15,114 @@ export default meta;
13
15
  type Story = StoryObj<typeof Timeline>;
14
16
 
15
17
  export const Default: Story = {
16
- render: () => <ComingSoon componentName="Timeline" />,
18
+ args: {
19
+ active: 1,
20
+ },
21
+ render: (args) => (
22
+ <Timeline {...args}>
23
+ <Timeline.Item
24
+ title="Commit created"
25
+ timestamp="Yesterday"
26
+ bulletVariant="default"
27
+ >
28
+ You pushed 3 new commits to the repository.
29
+ </Timeline.Item>
30
+ <Timeline.Item
31
+ title="Pull request opened"
32
+ timestamp="2 days ago"
33
+ bulletVariant="default"
34
+ >
35
+ You opened a pull request for the feature branch.
36
+ </Timeline.Item>
37
+ <Timeline.Item
38
+ title="Code review completed"
39
+ timestamp="1 week ago"
40
+ bulletVariant="default"
41
+ >
42
+ Your pull request was approved by 2 reviewers.
43
+ </Timeline.Item>
44
+ <Timeline.Item
45
+ title="Branch merged"
46
+ timestamp="2 weeks ago"
47
+ bulletVariant="default"
48
+ >
49
+ Your feature branch was successfully merged into main.
50
+ </Timeline.Item>
51
+ </Timeline>
52
+ ),
53
+ };
54
+
55
+ export const BulletVariants: Story = {
56
+ args: {
57
+ active: 1,
58
+ },
59
+ render: (args) => (
60
+ <Timeline {...args}>
61
+ <Timeline.Item
62
+ title="Default Bullet"
63
+ timestamp="Standard configuration"
64
+ bulletVariant="default"
65
+ >
66
+ The default un-configured structural dot.
67
+ </Timeline.Item>
68
+ <Timeline.Item
69
+ title="Icon Bullet"
70
+ timestamp="Standard sized icons"
71
+ bulletVariant="icon"
72
+ bullet={
73
+ <svg
74
+ viewBox="0 0 24 24"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ strokeWidth="2"
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ >
81
+ <polyline points="20 6 9 17 4 12"></polyline>
82
+ </svg>
83
+ }
84
+ >
85
+ A standard structural icon node mapping.
86
+ </Timeline.Item>
87
+ <Timeline.Item
88
+ title="Alternative Icon"
89
+ timestamp="Larger structural bounds"
90
+ bulletVariant="icon-alternative"
91
+ bullet={
92
+ <svg
93
+ viewBox="0 0 24 24"
94
+ fill="none"
95
+ stroke="currentColor"
96
+ strokeWidth="2"
97
+ strokeLinecap="round"
98
+ strokeLinejoin="round"
99
+ >
100
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
101
+ </svg>
102
+ }
103
+ >
104
+ A slightly larger alternative bounding box for specialized icons.
105
+ </Timeline.Item>
106
+ <Timeline.Item
107
+ title="Avatar Bullet"
108
+ timestamp="Profile pictures"
109
+ bulletVariant="avatar"
110
+ bullet={
111
+ <img
112
+ src="https://avatars.githubusercontent.com/u/10353856?s=460&v=4"
113
+ alt="avatar"
114
+ style={{
115
+ width: "100%",
116
+ height: "100%",
117
+ objectFit: "cover",
118
+ borderRadius: "inherit",
119
+ }}
120
+ />
121
+ }
122
+ >
123
+ Avatar mappings inherently drop borders and leverage specific opacity
124
+ states.
125
+ </Timeline.Item>
126
+ </Timeline>
127
+ ),
17
128
  };
@@ -1,7 +1,84 @@
1
1
  import React from "react";
2
+ import {
3
+ Timeline as MantineTimeline,
4
+ type TimelineProps as MantineTimelineProps,
5
+ } from "@mantine/core";
6
+ import {
7
+ filterStylingProps,
8
+ type RecursicaOverStyled,
9
+ } from "../../utils/filterStylingProps";
10
+ import styles from "./Timeline.module.css";
11
+ import { TimelineItem } from "./TimelineItem";
2
12
 
3
- export type TimelineProps = React.HTMLAttributes<HTMLDivElement>;
13
+ /**
14
+ * Properties for the strictly-tokenized Timeline component.
15
+ * Native Mantine abstract properties like `color`, `radius`, `bulletSize`, and `lineWidth`
16
+ * have been stripped out to strictly enforce the structural mappings of the Recursica UI Kit.
17
+ */
18
+ export type TimelineProps = RecursicaOverStyled<
19
+ Omit<MantineTimelineProps, "color" | "radius" | "bulletSize" | "lineWidth">
20
+ >;
4
21
 
5
- export const Timeline: React.FC<TimelineProps> = (props) => {
6
- return <div {...props}>Timeline</div>;
7
- };
22
+ interface TimelineComponent
23
+ extends React.ForwardRefExoticComponent<
24
+ TimelineProps & React.RefAttributes<HTMLDivElement>
25
+ > {
26
+ Item: typeof TimelineItem;
27
+ }
28
+
29
+ /**
30
+ * The `Timeline` component displays a list of events in chronological order.
31
+ *
32
+ * **Recursica Abstract:**
33
+ * This component acts as a structural wrapper around Mantine's `<Timeline>`.
34
+ * It forces alignment and geometry strictly via the UI Kit's `.itemBullet` and connector definitions.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * <Timeline active={1}>
39
+ * <Timeline.Item title="Event 1" timestamp="Yesterday">Description</Timeline.Item>
40
+ * </Timeline>
41
+ * ```
42
+ */
43
+ const TimelineInner = React.forwardRef<HTMLDivElement, TimelineProps>(
44
+ function Timeline({ overStyled = false, ...rest }, ref) {
45
+ const sanitizedProps = filterStylingProps(rest, overStyled);
46
+
47
+ const mergedClassNames: Partial<Record<string, string>> = {
48
+ root: styles.root,
49
+ };
50
+
51
+ const classNamesProp = (sanitizedProps as Record<string, unknown>)
52
+ .classNames;
53
+ if (
54
+ classNamesProp &&
55
+ typeof classNamesProp === "object" &&
56
+ !Array.isArray(classNamesProp)
57
+ ) {
58
+ const o = classNamesProp as Record<string, string>;
59
+ Object.keys(o).forEach((key) => {
60
+ if (mergedClassNames[key]) {
61
+ mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
62
+ } else {
63
+ mergedClassNames[key] = o[key];
64
+ }
65
+ });
66
+ }
67
+
68
+ return (
69
+ <MantineTimeline
70
+ ref={ref}
71
+ classNames={mergedClassNames}
72
+ {...(sanitizedProps as unknown as Omit<
73
+ MantineTimelineProps,
74
+ "color" | "radius" | "bulletSize" | "lineWidth"
75
+ >)}
76
+ />
77
+ );
78
+ },
79
+ );
80
+
81
+ TimelineInner.displayName = "Timeline";
82
+
83
+ export const Timeline = TimelineInner as TimelineComponent;
84
+ Timeline.Item = TimelineItem;
@@ -0,0 +1,102 @@
1
+ import React from "react";
2
+ import {
3
+ TimelineItem as MantineTimelineItem,
4
+ type TimelineItemProps as MantineTimelineItemProps,
5
+ } from "@mantine/core";
6
+ import {
7
+ filterStylingProps,
8
+ type RecursicaOverStyled,
9
+ } from "../../utils/filterStylingProps";
10
+ import styles from "./Timeline.module.css";
11
+
12
+ export interface RecursicaTimelineItemProps {
13
+ /**
14
+ * Timestamp text displayed below the description.
15
+ */
16
+ timestamp?: React.ReactNode;
17
+ /**
18
+ * The structural styling variant of the bullet container.
19
+ * Maps to the UI Kit structural tokens.
20
+ * @default "default"
21
+ */
22
+ bulletVariant?: "default" | "avatar" | "icon" | "icon-alternative";
23
+ }
24
+
25
+ export type TimelineItemProps = RecursicaOverStyled<
26
+ Omit<MantineTimelineItemProps, "radius" | "color" | "lineVariant"> &
27
+ RecursicaTimelineItemProps
28
+ >;
29
+
30
+ /**
31
+ * The individual item component for the Timeline.
32
+ *
33
+ * **Recursica Abstract:**
34
+ * The `Timeline.Item` has been extended to support a `timestamp` string natively,
35
+ * rendering it below the body content. It also accepts a `bulletVariant` to morph
36
+ * the structural dimensions of the node circle automatically.
37
+ */
38
+ export const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(
39
+ function TimelineItem(
40
+ {
41
+ overStyled = false,
42
+ timestamp,
43
+ bulletVariant = "default",
44
+ children,
45
+ ...rest
46
+ },
47
+ ref,
48
+ ) {
49
+ const sanitizedProps = filterStylingProps(rest, overStyled);
50
+
51
+ const mergedClassNames: Partial<Record<string, string>> = {
52
+ item: styles.item,
53
+ itemBody: styles.itemBody,
54
+ itemContent: styles.itemContent,
55
+ itemBullet: styles.itemBullet,
56
+ itemTitle: styles.itemTitle,
57
+ };
58
+
59
+ const classNamesProp = (sanitizedProps as Record<string, unknown>)
60
+ .classNames;
61
+ if (
62
+ classNamesProp &&
63
+ typeof classNamesProp === "object" &&
64
+ !Array.isArray(classNamesProp)
65
+ ) {
66
+ const o = classNamesProp as Record<string, string>;
67
+ Object.keys(o).forEach((key) => {
68
+ if (mergedClassNames[key]) {
69
+ mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
70
+ } else {
71
+ mergedClassNames[key] = o[key];
72
+ }
73
+ });
74
+ }
75
+
76
+ // Embed timestamp inside children if provided, wrapped in a specific class
77
+ const content = timestamp ? (
78
+ <>
79
+ {children && <div className={styles.description}>{children}</div>}
80
+ <div className={styles.timestamp}>{timestamp}</div>
81
+ </>
82
+ ) : (
83
+ children
84
+ );
85
+
86
+ return (
87
+ <MantineTimelineItem
88
+ ref={ref}
89
+ classNames={mergedClassNames}
90
+ data-variant={bulletVariant}
91
+ {...(sanitizedProps as unknown as Omit<
92
+ MantineTimelineItemProps,
93
+ "radius" | "color" | "lineVariant"
94
+ >)}
95
+ >
96
+ {content}
97
+ </MantineTimelineItem>
98
+ );
99
+ },
100
+ );
101
+
102
+ TimelineItem.displayName = "TimelineItem";