@recursica/mantine-adapter 0.36.0 → 0.38.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 (63) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -3
  3. package/dist/mantine-adapter.cjs +2 -2
  4. package/dist/mantine-adapter.cjs.map +1 -1
  5. package/dist/mantine-adapter.css +1 -1
  6. package/dist/mantine-adapter.js +2352 -2099
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Dropdown/BareDropdown.d.ts +19 -0
  9. package/dist/src/components/TimePicker/TimePicker.d.ts +9 -3
  10. package/dist/src/components/Tree/Tree.d.ts +5 -0
  11. package/dist/src/index.d.ts +1 -1
  12. package/docs/PHILOSOPHY.md +39 -0
  13. package/package.json +3 -2
  14. package/src/components/Accordion/USAGE.md +2 -41
  15. package/src/components/AutoComplete/USAGE.md +2 -18
  16. package/src/components/Avatar/USAGE.md +0 -27
  17. package/src/components/Badge/USAGE.md +3 -6
  18. package/src/components/Breadcrumb/USAGE.md +1 -5
  19. package/src/components/Button/Button.tsx +5 -0
  20. package/src/components/Button/IMPLEMENTATION_NOTES.md +8 -0
  21. package/src/components/Button/USAGE.md +5 -25
  22. package/src/components/Card/USAGE.md +4 -12
  23. package/src/components/Checkbox/USAGE.md +4 -20
  24. package/src/components/Chip/USAGE.md +3 -32
  25. package/src/components/DatePicker/USAGE.md +2 -12
  26. package/src/components/Dropdown/BareDropdown.tsx +85 -0
  27. package/src/components/Dropdown/Dropdown.tsx +12 -3
  28. package/src/components/Dropdown/USAGE.md +2 -6
  29. package/src/components/Flex/USAGE.md +1 -1
  30. package/src/components/FormControlWrapper/USAGE.md +3 -31
  31. package/src/components/Grid/USAGE.md +1 -1
  32. package/src/components/Group/USAGE.md +1 -1
  33. package/src/components/HoverCard/USAGE.md +3 -72
  34. package/src/components/Label/USAGE.md +8 -48
  35. package/src/components/Link/USAGE.md +4 -10
  36. package/src/components/Loader/USAGE.md +4 -23
  37. package/src/components/Menu/USAGE.md +3 -73
  38. package/src/components/Modal/USAGE.md +3 -3
  39. package/src/components/NumberInput/USAGE.md +5 -8
  40. package/src/components/Pagination/USAGE.md +0 -19
  41. package/src/components/Panel/USAGE.md +6 -95
  42. package/src/components/Popover/USAGE.md +6 -66
  43. package/src/components/ReadOnlyField/USAGE.md +2 -10
  44. package/src/components/SegmentedControl/USAGE.md +1 -15
  45. package/src/components/Slider/USAGE.md +1 -45
  46. package/src/components/Stack/USAGE.md +1 -1
  47. package/src/components/Switch/USAGE.md +1 -24
  48. package/src/components/TextArea/USAGE.md +2 -2
  49. package/src/components/TextField/USAGE.md +1 -17
  50. package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +72 -0
  51. package/src/components/TimePicker/TimePicker.module.css +225 -41
  52. package/src/components/TimePicker/TimePicker.stories.tsx +105 -4
  53. package/src/components/TimePicker/TimePicker.tsx +287 -7
  54. package/src/components/TimePicker/USAGE.md +23 -2
  55. package/src/components/Timeline/USAGE.md +2 -10
  56. package/src/components/Toast/USAGE.md +3 -33
  57. package/src/components/Tooltip/USAGE.md +7 -51
  58. package/src/components/Tree/IMPLEMENTATION_NOTES.md +31 -3
  59. package/src/components/Tree/Tree.module.css +84 -40
  60. package/src/components/Tree/Tree.stories.tsx +13 -0
  61. package/src/components/Tree/Tree.tsx +116 -27
  62. package/src/components/Tree/USAGE.md +18 -1
  63. package/src/index.ts +1 -0
@@ -1,12 +1,61 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { TimePicker } from "./TimePicker";
3
- import { ComingSoon } from "@recursica/storybook-template";
3
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
4
4
 
5
5
  const meta: Meta<typeof TimePicker> = {
6
- title: "UI-Kit/🚧 TimePicker",
6
+ title: "UI-Kit/TimePicker",
7
7
  component: TimePicker,
8
8
  tags: ["autodocs"],
9
- argTypes: {},
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component: `
13
+ The \`TimePicker\` primitive provides a segmented hour/minute (optionally seconds) time entry input, paired with a dedicated AM/PM \`Dropdown\`-style selector, integrated directly into the \`FormControlWrapper\` architecture. This 12-hour + AM/PM composite is the only way this component operates — a Recursica-specific design, not a user-configurable option.
14
+
15
+ ### Examples
16
+ Always structure horizontal architectures via the generic \`formLayout\` parameter.
17
+ \`\`\`tsx
18
+ <TimePicker
19
+ label="Start Time"
20
+ assistiveText="Select the deployment kick-off time."
21
+ formLayout="stacked"
22
+ />
23
+ \`\`\`
24
+ `,
25
+ },
26
+ },
27
+ },
28
+ argTypes: {
29
+ ...formControlArgTypes,
30
+ disabled: {
31
+ control: "boolean",
32
+ description:
33
+ "Maps the formal disabled variable states structurally to the input core.",
34
+ },
35
+ error: {
36
+ control: "text",
37
+ description:
38
+ "Applies the strict error string boundary rendering invalid structures seamlessly.",
39
+ },
40
+ required: {
41
+ control: "boolean",
42
+ },
43
+ label: {
44
+ control: "text",
45
+ },
46
+ assistiveText: {
47
+ control: "text",
48
+ },
49
+ readOnly: {
50
+ control: "boolean",
51
+ description:
52
+ "Toggles structural read-only data presentation explicitly blocking standard component bindings.",
53
+ },
54
+ withSeconds: {
55
+ control: "boolean",
56
+ description: "Shows and allows editing the seconds segment.",
57
+ },
58
+ },
10
59
  };
11
60
 
12
61
  export default meta;
@@ -14,5 +63,57 @@ export default meta;
14
63
  type Story = StoryObj<typeof TimePicker>;
15
64
 
16
65
  export const Default: Story = {
17
- render: () => <ComingSoon componentName="TimePicker" />,
66
+ args: {
67
+ disabled: false,
68
+ label: "Meeting Time",
69
+ assistiveText: "Choose the start time in your local timezone.",
70
+ },
71
+ };
72
+
73
+ export const FormsSideBySide: Story = {
74
+ args: {
75
+ label: "Incident Start Time",
76
+ assistiveText: "When did the incident originally occur?",
77
+ formLayout: "side-by-side",
78
+ },
79
+ };
80
+
81
+ export const WithSeconds: Story = {
82
+ args: {
83
+ label: "Precise Execution Time",
84
+ assistiveText: "Includes a seconds segment for exact scheduling.",
85
+ withSeconds: true,
86
+ },
87
+ };
88
+
89
+ export const Disabled: Story = {
90
+ args: {
91
+ label: "Disabled Time Slot",
92
+ disabled: true,
93
+ },
94
+ };
95
+
96
+ export const ErrorState: Story = {
97
+ args: {
98
+ label: "Deployment Window",
99
+ error: "The chosen time falls outside the allowed deployment window.",
100
+ required: true,
101
+ },
102
+ };
103
+
104
+ export const StaticReadOnly: Story = {
105
+ args: {
106
+ label: "Static ReadOnly Review",
107
+ value: "14:30",
108
+ readOnly: true,
109
+ },
110
+ };
111
+
112
+ export const EditableReadOnly: Story = {
113
+ args: {
114
+ label: "Editable ReadOnly Review",
115
+ defaultValue: "09:00",
116
+ readOnly: true,
117
+ labelWithEditIcon: true,
118
+ },
18
119
  };
@@ -1,9 +1,289 @@
1
- import React from "react";
2
- import { type RecursicaTimePickerProps } from "@recursica/adapter-common";
1
+ import React, { forwardRef, useEffect, useRef, useState } from "react";
2
+ import {
3
+ TimePicker as MantineTimePicker,
4
+ type TimePickerProps as MantineTimePickerProps,
5
+ } from "@mantine/dates";
6
+ import { type InputWrapperProps } from "@mantine/core";
7
+ import { type ReadOnlyControlProps } from "@recursica/adapter-common";
8
+ import {
9
+ filterStylingProps,
10
+ type RecursicaOverStyled,
11
+ } from "../../utils/filterStylingProps";
12
+ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
13
+ import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
14
+ import { BareDropdown } from "../Dropdown/BareDropdown";
15
+ import styles from "./TimePicker.module.css";
3
16
 
4
- export type TimePickerProps = React.HTMLAttributes<HTMLDivElement> &
5
- RecursicaTimePickerProps;
17
+ import { type RecursicaTimePickerProps as BaseRecursicaTimePickerProps } from "@recursica/adapter-common";
6
18
 
7
- export const TimePicker: React.FC<TimePickerProps> = (props) => {
8
- return <div {...props}>TimePicker</div>;
9
- };
19
+ const AM_PM_DATA = [
20
+ { value: "AM", label: "AM" },
21
+ { value: "PM", label: "PM" },
22
+ ];
23
+
24
+ /** Parses an "HH:mm"/"HH:mm:ss" string's hour, or undefined if not set/parseable. */
25
+ function getHour(value: string | undefined): number | undefined {
26
+ if (!value) return undefined;
27
+ const hour = parseInt(value.slice(0, 2), 10);
28
+ return Number.isNaN(hour) ? undefined : hour;
29
+ }
30
+
31
+ /** Replaces the hour segment of an "HH:mm"/"HH:mm:ss" string, preserving minutes/seconds. */
32
+ function withHour(value: string, hour: number): string {
33
+ return `${String(hour).padStart(2, "0")}${value.slice(2)}`;
34
+ }
35
+
36
+ /**
37
+ * Formats an "HH:mm"/"HH:mm:ss" 24-hour value as a 12-hour + AM/PM string for read-only display
38
+ * (e.g. "14:30" -> "2:30 PM") — the raw 24-hour string was being shown as-is in read-only mode,
39
+ * with no AM/PM, unlike the interactive composite. Returns undefined if not parseable.
40
+ */
41
+ function formatReadOnlyTime(value: string | undefined): string | undefined {
42
+ if (!value) return undefined;
43
+ const [hourStr, minute, second] = value.split(":");
44
+ const hour24 = parseInt(hourStr, 10);
45
+ if (Number.isNaN(hour24) || minute === undefined) return value;
46
+ const isPM = hour24 >= 12;
47
+ const hour12 = hour24 % 12 === 0 ? 12 : hour24 % 12;
48
+ const rest = second !== undefined ? `${minute}:${second}` : minute;
49
+ return `${hour12}:${rest} ${isPM ? "PM" : "AM"}`;
50
+ }
51
+
52
+ /**
53
+ * Simulates a real user interaction on Mantine's own (CSS-hidden) native AM/PM <select>, since it's
54
+ * a React-controlled element — setting `.value` directly and dispatching a plain DOM event doesn't
55
+ * trigger React's change handling; using the native property setter first does. See "Why AM/PM is
56
+ * seeded on mount" in TIMEPICKER_IMPLEMENTATION_NOTES.md.
57
+ */
58
+ function setNativeSelectValue(el: HTMLSelectElement, value: string): void {
59
+ const nativeSetter = Object.getOwnPropertyDescriptor(
60
+ window.HTMLSelectElement.prototype,
61
+ "value",
62
+ )?.set;
63
+ nativeSetter?.call(el, value);
64
+ el.dispatchEvent(new Event("change", { bubbles: true }));
65
+ }
66
+
67
+ export interface RecursicaTimePickerProps
68
+ extends Omit<
69
+ MantineTimePickerProps,
70
+ | "size"
71
+ | "variant"
72
+ | "radius"
73
+ | "wrapperProps"
74
+ | "format"
75
+ | "min"
76
+ | "max"
77
+ // AM/PM is always shown via our own BareDropdown, driving a fixed 12h format — these all
78
+ // control Mantine's own native (now CSS-hidden) AM/PM select and would be misleading to
79
+ // expose, since they'd have no visible effect. See TIMEPICKER_IMPLEMENTATION_NOTES.md.
80
+ | "amPmInputLabel"
81
+ | "amPmLabels"
82
+ | "amPmSelectProps"
83
+ | "amPmRef"
84
+ // The optional time-presets dropdown isn't wired up; keep the public API to what's supported.
85
+ | "withDropdown"
86
+ | "presets"
87
+ | "maxDropdownContentHeight"
88
+ | "scrollAreaProps"
89
+ | "reverseTimeControlsList"
90
+ | "popoverProps"
91
+ >,
92
+ Pick<
93
+ InputWrapperProps,
94
+ "label" | "error" | "required" | "withAsterisk" | "id"
95
+ >,
96
+ Omit<
97
+ RecursicaFormControlWrapperProps,
98
+ "controlMaxWidth" | "controlMinWidth"
99
+ >,
100
+ ReadOnlyControlProps,
101
+ BaseRecursicaTimePickerProps {}
102
+
103
+ export type TimePickerProps = RecursicaOverStyled<RecursicaTimePickerProps>;
104
+
105
+ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
106
+ function TimePicker(props, ref) {
107
+ const {
108
+ overStyled = false,
109
+ formLayout = "stacked",
110
+
111
+ // Label & Wrapper Maps
112
+ labelSize,
113
+ labelAlignment,
114
+ labelOptionalText,
115
+ labelWithEditIcon,
116
+ onLabelEditClick,
117
+
118
+ label,
119
+ assistiveText,
120
+ assistiveWithIcon,
121
+ error,
122
+ required,
123
+ withAsterisk,
124
+ id,
125
+ className,
126
+ style,
127
+ disabled,
128
+ readOnly,
129
+ readOnlyComponent,
130
+ emptyValueComponent,
131
+ value,
132
+ defaultValue,
133
+ onChange,
134
+ withSeconds,
135
+ minTime,
136
+ maxTime,
137
+ ...rest
138
+ } = props;
139
+
140
+ const sanitizedProps = filterStylingProps(rest, overStyled);
141
+ const restRecord = sanitizedProps as Record<string, unknown>;
142
+
143
+ delete restRecord["size"];
144
+ delete restRecord["variant"];
145
+ delete restRecord["radius"];
146
+
147
+ // Internal full 24-hour value. Needed because Mantine's TimePicker (hour/minute/second entry)
148
+ // and our own BareDropdown (AM/PM) both mutate the same conceptual value — see
149
+ // TIMEPICKER_IMPLEMENTATION_NOTES.md.
150
+ const [internalValue, setInternalValue] = useState<string | undefined>(
151
+ () => value ?? defaultValue,
152
+ );
153
+
154
+ useEffect(() => {
155
+ if (value !== undefined) {
156
+ setInternalValue(value);
157
+ }
158
+ }, [value]);
159
+
160
+ // Mantine's own internal amPm state starts `null` whenever there's no initial hour to derive it
161
+ // from (see convertTimeTo12HourFormat in @mantine/dates), and it stays null — meaning Mantine
162
+ // never reports a valid onChange, no matter what's typed — until something interacts with the
163
+ // (CSS-hidden) native AM/PM <select>. Simulating that interaction once on mount, defaulting to
164
+ // AM, breaks the deadlock: a freshly-typed time now resolves and reports immediately, and our
165
+ // own BareDropdown (which drives the same native select the same way, see handleMeridiemChange)
166
+ // correctly displays and changes it from there. Skipped whenever a real initial value/defaultValue
167
+ // is already present — Mantine already derives the correct AM/PM from that on its own. See
168
+ // TIMEPICKER_IMPLEMENTATION_NOTES.md.
169
+ const amPmRef = useRef<HTMLSelectElement>(null);
170
+ useEffect(() => {
171
+ if (getHour(value ?? defaultValue) === undefined && amPmRef.current) {
172
+ setNativeSelectValue(amPmRef.current, "AM");
173
+ }
174
+ // Intentionally mount-only — this seeds Mantine's internal state once; after that it's driven
175
+ // by real interaction (typing, or our own BareDropdown).
176
+ // eslint-disable-next-line react-hooks/exhaustive-deps
177
+ }, []);
178
+
179
+ const emitChange = (next: string) => {
180
+ setInternalValue(next);
181
+ onChange?.(next);
182
+ };
183
+
184
+ const hour = getHour(internalValue);
185
+ const isPM = hour !== undefined && hour >= 12;
186
+
187
+ const handleFieldChange = (next: string) => {
188
+ emitChange(next);
189
+ };
190
+
191
+ const handleMeridiemChange = (next: string | null) => {
192
+ if (hour === undefined || !internalValue || !next) return;
193
+ const wantsPM = next === "PM";
194
+ if (wantsPM === isPM) return;
195
+ const nextHour = wantsPM ? hour + 12 : hour - 12;
196
+ emitChange(withHour(internalValue, nextHour));
197
+ };
198
+
199
+ const wrapperClass = className
200
+ ? `${styles.layoutOverride} ${className}`
201
+ : styles.layoutOverride;
202
+
203
+ return (
204
+ <WithReadOnlyWrapper
205
+ className={wrapperClass}
206
+ style={style as React.CSSProperties}
207
+ controlMaxWidth={undefined}
208
+ controlMinWidth={undefined}
209
+ overStyled={overStyled as true}
210
+ formLayout={formLayout}
211
+ labelSize={labelSize}
212
+ labelAlignment={labelAlignment}
213
+ labelOptionalText={labelOptionalText}
214
+ labelWithEditIcon={labelWithEditIcon}
215
+ onLabelEditClick={onLabelEditClick}
216
+ label={label}
217
+ assistiveText={assistiveText}
218
+ assistiveWithIcon={assistiveWithIcon}
219
+ error={error}
220
+ required={required}
221
+ withAsterisk={withAsterisk}
222
+ id={id}
223
+ readOnly={readOnly}
224
+ readOnlyComponent={readOnlyComponent}
225
+ emptyValueComponent={emptyValueComponent}
226
+ readOnlyType="text"
227
+ readOnlyValue={formatReadOnlyTime(
228
+ value !== undefined ? value : defaultValue,
229
+ )}
230
+ readOnlyNativeProps={props}
231
+ activeComponent={
232
+ /* Naked field execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks.
233
+ format="12h" is always on — this is the only way this component operates, not a user
234
+ choice (see TIMEPICKER_IMPLEMENTATION_NOTES.md). Mantine's own native AM/PM <select>
235
+ (bundled unconditionally with format="12h") is CSS-hidden; our own BareDropdown next to
236
+ it is the only AM/PM control the user interacts with. */
237
+ <div
238
+ className={styles.root}
239
+ data-disabled={disabled ? "true" : undefined}
240
+ data-error={error ? "true" : undefined}
241
+ >
242
+ <MantineTimePicker
243
+ ref={ref}
244
+ classNames={{
245
+ wrapper: styles.timeWrapper,
246
+ input: styles.timeInput,
247
+ fieldsGroup: styles.fieldsGroup,
248
+ field: styles.timeField,
249
+ }}
250
+ disabled={disabled}
251
+ value={internalValue}
252
+ onChange={handleFieldChange}
253
+ format="12h"
254
+ withSeconds={withSeconds}
255
+ min={minTime}
256
+ max={maxTime}
257
+ withDropdown={false}
258
+ // Internal-only — not part of the public API (see the Omit list above) — used solely
259
+ // to seed the mount-time AM default onto Mantine's own hidden native select. See
260
+ // TIMEPICKER_IMPLEMENTATION_NOTES.md.
261
+ amPmRef={amPmRef}
262
+ {...(sanitizedProps as unknown as MantineTimePickerProps)}
263
+ />
264
+ <BareDropdown
265
+ overStyled
266
+ className={styles.amPmSelect}
267
+ // Dropdown.module.css's own .root sets width: 100% (correct for a standalone
268
+ // Dropdown filling its form-control column) — overStyled lets us override just the
269
+ // width, keeping every other Recursica style (border, colors, padding) intact.
270
+ // A plain `style` prop won't do this: Mantine's Select/InputBase internals
271
+ // (useInputProps) route a top-level `style` prop to the *label* InputWrapper, not
272
+ // the bordered input box itself — `styles={{ wrapper: ... }}` is the styles-api hook
273
+ // that actually targets that box. See TIMEPICKER_IMPLEMENTATION_NOTES.md.
274
+ styles={{ wrapper: { width: "fit-content" } }}
275
+ data={AM_PM_DATA}
276
+ value={hour === undefined ? null : isPM ? "PM" : "AM"}
277
+ onChange={handleMeridiemChange}
278
+ disabled={disabled}
279
+ error={!!error}
280
+ aria-label="AM or PM"
281
+ />
282
+ </div>
283
+ }
284
+ />
285
+ );
286
+ },
287
+ );
288
+
289
+ TimePicker.displayName = "TimePicker";
@@ -19,10 +19,25 @@ import React from "react";
19
19
  import { TimePicker } from "@recursica/mantine-adapter";
20
20
 
21
21
  export default function Demo() {
22
- return <TimePicker label="Select Time" placeholder="Pick a time" />;
22
+ return <TimePicker label="Select Time" />;
23
23
  }
24
24
  ```
25
25
 
26
+ > [!IMPORTANT] > **Recursica-specific behavior:** `TimePicker` always renders in **12-hour format with a dedicated AM/PM `Dropdown`-style selector** next to the hour/minute input — this deviates from the underlying Mantine library's own default (24-hour, no AM/PM control) and is **not configurable**. There is no prop to switch to a plain 24-hour input; this is the only way the component operates.
27
+
28
+ Pass `withSeconds` to add a seconds segment, and `minTime`/`maxTime` (`"HH:mm"` or `"HH:mm:ss"` with `withSeconds`) to bound the allowed range — these always describe 24-hour boundaries.
29
+
30
+ ```tsx
31
+ <TimePicker
32
+ label="Precise Time"
33
+ withSeconds
34
+ minTime="09:00:00"
35
+ maxTime="17:00:00"
36
+ />
37
+ ```
38
+
39
+ The AM/PM control visually matches Recursica's `Dropdown` component exactly, rather than a native `<select>`.
40
+
26
41
  ---
27
42
 
28
43
  ## 3. Design System Integration
@@ -31,6 +46,12 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
31
46
 
32
47
  > [!IMPORTANT]
33
48
  >
34
- > - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
49
+ > - **Anti-override protection**: Rogue style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
35
50
  > - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
36
51
  > - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
52
+
53
+ ---
54
+
55
+ ## 4. Read-Only Mode
56
+
57
+ Pass `readOnly` to render the current value as static text, matching every other Recursica form control. The value is formatted as 12-hour + AM/PM (e.g. `"14:30"` displays as `"2:30 PM"`).
@@ -44,14 +44,6 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
44
44
 
45
45
  ## 4. Key Integration Features & Constraints
46
46
 
47
- ## Architecture
47
+ `Timeline.Item` accepts a `timestamp` prop that renders below the item's content, and a `bulletVariant` prop (`"default" | "avatar" | "icon" | "icon-alternative"`) to control the bullet's appearance. The `lineWidth` and `bulletSize` props are not configurable, since geometry is controlled by the design system tokens.
48
48
 
49
- The `Timeline` component is a strict structural wrapper around Mantine's `<Timeline>` and `<Timeline.Item>` components.
50
-
51
- - `Timeline.tsx` intercepts overarching properties like `lineWidth` and `bulletSize` to strip them out via `overStyled`, strictly adhering to the CSS token mapping in `.item` rules instead.
52
- - `TimelineItem.tsx` implements a custom `timestamp` React node rendering slot to match the design system, positioning the text directly below the item's `children`.
53
- - `TimelineItem.tsx` supports a custom `bulletVariant` prop (`"default" | "avatar" | "icon" | "icon-alternative"`) mapped onto `data-variant` to handle CSS variations dynamically.
54
-
55
- ## Limitations & Missing Tokens
56
-
57
- - **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 Mantine's connector line `calc()` equations, 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.
49
+ A known limitation: when using `bulletVariant="avatar"`, the avatar bullet always renders at the default bullet size rather than a custom size.
@@ -23,7 +23,7 @@ export default function Demo() {
23
23
  <Toast
24
24
  title="Success"
25
25
  message="Your action completed successfully"
26
- state="success"
26
+ variant="success"
27
27
  />
28
28
  );
29
29
  }
@@ -45,36 +45,6 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
45
45
 
46
46
  ## 4. Key Integration Features & Constraints
47
47
 
48
- ## 1. Standalone Visual Wrapper
48
+ `Toast` can be used directly for a static or inline message, or wired up to `@mantine/notifications` for dynamic popups. The `variant` prop (`"default" | "error" | "success"`) controls the toast's color treatment.
49
49
 
50
- **Decision:** We wrap `@mantine/core`'s standalone `Notification` component instead of wrapping the `@mantine/notifications` provider.
51
-
52
- **Implementation:** The UI Kit provides variables for the `Toast` component itself (e.g., `--recursica_ui-kit_components_toast_*`). We use these variables to style the standard Mantine `Notification` element. This allows developers to use `<Toast>` manually if they want a static or inline message.
53
-
54
- If dynamic popups are required, developers can configure `@mantine/notifications` to utilize this component or use its classes.
55
-
56
- ---
57
-
58
- ## 2. Variant Mapping via `data-variant`
59
-
60
- **Decision:** `variant` props (`"default" | "error" | "success"`) are mapped directly to `data-variant` on the Mantine root `Box`.
61
-
62
- **Implementation:** Mantine's `Notification` doesn't inherently support our custom variants out of the box in the way we want them styled. By passing `data-variant` directly to the `Box`, we can explicitly target the root element in our `Toast.module.css` (e.g., `.root[data-variant="success"]`) and pipe in the corresponding UI Kit layer colors.
63
-
64
- ---
65
-
66
- ## 3. Minimal CSS Override Philosophy
67
-
68
- **Decision:** The CSS module only overrides visual design tokens (colors, typography, padding, borders, shadows).
69
-
70
- **Implementation:** We defer layout structure, icon rendering, loader transitions, and close button mechanics to Mantine. The `border-style: none;` is hardcoded to reset any underlying styles from Mantine's defaults, ensuring a clean mapping of elevation and shadows.
71
-
72
- ---
73
-
74
- ## 4. Unsupported `loading` State
75
-
76
- **Decision:** The native `loading` state is explicitly stripped and bypassed from the `<Toast />` component wrapper.
77
-
78
- **Implementation:** Mantine's `Notification` inherently supports a `loading={true}` state that natively spins up a loader instead of an icon. However, Recursica's UI Kit strictly does not define structural tokens for loader states inside toasts.
79
- Instead of attempting to tightly couple the internal `Loader` abstraction or mapping variables incorrectly, the `loading` property is explicitly omitted and `false`-enforced from the public API.
80
- If consumers explicitly require a loading toast, they must manually inject a `<Loader />` component into the `icon` slot.
50
+ The `loading` state is not supported. If a loading toast is needed, pass a `<Loader />` component into the `icon` slot instead.
@@ -53,63 +53,21 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
53
53
 
54
54
  ---
55
55
 
56
- ## 2. Token Namespace: `tooltip`
56
+ ## 2. Behavior Notes
57
57
 
58
- **Decision:** The CSS module exclusively uses variables from the `--recursica_ui-kit_components_tooltip_*` namespace.
59
-
60
- **Implementation:** The Recursica token system defines the `tooltip` namespace covering:
61
-
62
- - Geometry: border-radius, border-size, min-width, min-height, max-width, padding
63
- - Typography: text_font-\* (family, size, style, weight, letter-spacing, line-height, text-decoration, text-transform)
64
- - Colors (layer-aware): background, border-color, text
65
- - Elevation: box-shadow
66
- - Beak: beak-size (16px), beak-inset (8px)
67
-
68
- No tokens from other component namespaces are referenced.
69
-
70
- ---
71
-
72
- ## 3. Hardcoded Values
73
-
74
- ### `border-style: solid` (CSS module)
75
-
76
- Mantine renders the tooltip using its `Box` component, which does not set `border-style` natively. Without this hardcoded value, the border-width and border-color tokens would have no visible effect. Same pattern as Menu and HoverCard dropdowns.
77
-
78
- ### `arrowSize` defaulted to `16` (Tooltip.tsx)
79
-
80
- Mantine's `arrowSize` prop is a JavaScript number used for inline style calculations: it sets `width`, `height`, and a positioning offset (`-arrowSize/2`) directly on the arrow `<div>` element. These inline styles cannot be overridden via CSS without `!important`, and the positioning offset has no CSS equivalent. The beak size cannot be fully CSS-driven.
81
-
82
- The default value `16` matches the Recursica `beak-size` token (`--recursica_ui-kit_components_tooltip_properties_beak-size: 16px`). Developers can override `arrowSize` if needed. This is documented as an open issue in `docs/COMPONENT_ISSUES.md`.
83
-
84
- **Note:** Mantine calls this the "arrow"; Recursica calls it the "beak". The Recursica prop `withBeak` (defaulting to `true`) maps to Mantine's `withArrow`. Both are accepted; `withBeak` takes precedence.
85
-
86
- ### `multiline={true}` (Tooltip.tsx)
87
-
88
- Mantine's `multiline` prop controls whether tooltip text wraps (`white-space: nowrap` when false). Recursica always enables multiline because the design system defines a `max-width` token (300px) — text should wrap naturally within that constraint rather than overflowing. The `multiline` prop is not exposed to developers.
89
-
90
- ### Flexbox centering (CSS module)
91
-
92
- `display: flex; align-items: center; justify-content: center;` is applied to the `.tooltip` class. This ensures text is vertically and horizontally centered within the `min-height: 48px` container defined by the design token. Without this, text sits at the top of the tooltip.
58
+ Tooltip text always wraps to fit within the token-defined max width, rather than staying on a single line or overflowing. The `multiline` prop is not exposed, since this behavior is always on.
93
59
 
94
60
  ---
95
61
 
96
- ## 4. Recursica `withBeak` Prop
62
+ ## 3. Recursica `withBeak` Prop
97
63
 
98
64
  **Decision:** `withBeak` is the official Recursica prop for controlling beak visibility, defaulting to `true`.
99
65
 
100
- **Implementation:** Both `withBeak` and Mantine's `withArrow` are accepted. Resolution order: `withBeak ?? withArrow`. When both are provided, `withBeak` takes precedence. The default of `true` means tooltips show the beak by default, matching the Recursica design intent.
66
+ **Implementation:** Both `withBeak` and Mantine's `withArrow` are accepted. When both are provided, `withBeak` takes precedence. The beak's size can be adjusted via the `arrowSize` prop (default `16`).
101
67
 
102
68
  ---
103
69
 
104
- ## 5. ClassNames Binding
105
-
106
- **Decision:** CSS module classes are bound via the `classNames` prop on Mantine's Tooltip root.
107
-
108
- **Implementation:** The stylesNames for Tooltip are `tooltip` (the container) and `arrow` (the beak). Both are mapped to their respective CSS module classes: `{ tooltip: styles.tooltip, arrow: styles.arrow }`. Consumer-provided `classNames` are merged additively when `overStyled` is true.
109
-
110
- ---
111
-
112
- ## 6. Tooltip.Floating and Tooltip.Group
70
+ ## 4. Tooltip.Floating and Tooltip.Group
113
71
 
114
72
  **Decision:** These static sub-components are direct pass-throughs to Mantine with no Recursica styling.
115
73
 
@@ -117,8 +75,6 @@ Mantine's `multiline` prop controls whether tooltip text wraps (`white-space: no
117
75
 
118
76
  ---
119
77
 
120
- ## 7. Default Position Override
121
-
122
- **Decision:** Recursica defaults `position` to `"top"`. Mantine defaults to `"bottom"`.
78
+ ## 5. Default Position
123
79
 
124
- **Implementation:** The `position="top"` default is set on the Mantine root element before the prop spread, so developer-provided `position` values still take precedence. This aligns with Recursica's design intent for overlay components to appear above their trigger by default.
80
+ Recursica defaults `position` to `"top"` instead of Mantine's default of `"bottom"`. Pass your own `position` value to override it.