@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.
- package/CHANGELOG.md +28 -0
- package/README.md +3 -3
- package/dist/mantine-adapter.cjs +2 -2
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +2352 -2099
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/Dropdown/BareDropdown.d.ts +19 -0
- package/dist/src/components/TimePicker/TimePicker.d.ts +9 -3
- package/dist/src/components/Tree/Tree.d.ts +5 -0
- package/dist/src/index.d.ts +1 -1
- package/docs/PHILOSOPHY.md +39 -0
- package/package.json +3 -2
- package/src/components/Accordion/USAGE.md +2 -41
- package/src/components/AutoComplete/USAGE.md +2 -18
- package/src/components/Avatar/USAGE.md +0 -27
- package/src/components/Badge/USAGE.md +3 -6
- package/src/components/Breadcrumb/USAGE.md +1 -5
- package/src/components/Button/Button.tsx +5 -0
- package/src/components/Button/IMPLEMENTATION_NOTES.md +8 -0
- package/src/components/Button/USAGE.md +5 -25
- package/src/components/Card/USAGE.md +4 -12
- package/src/components/Checkbox/USAGE.md +4 -20
- package/src/components/Chip/USAGE.md +3 -32
- package/src/components/DatePicker/USAGE.md +2 -12
- package/src/components/Dropdown/BareDropdown.tsx +85 -0
- package/src/components/Dropdown/Dropdown.tsx +12 -3
- package/src/components/Dropdown/USAGE.md +2 -6
- package/src/components/Flex/USAGE.md +1 -1
- package/src/components/FormControlWrapper/USAGE.md +3 -31
- package/src/components/Grid/USAGE.md +1 -1
- package/src/components/Group/USAGE.md +1 -1
- package/src/components/HoverCard/USAGE.md +3 -72
- package/src/components/Label/USAGE.md +8 -48
- package/src/components/Link/USAGE.md +4 -10
- package/src/components/Loader/USAGE.md +4 -23
- package/src/components/Menu/USAGE.md +3 -73
- package/src/components/Modal/USAGE.md +3 -3
- package/src/components/NumberInput/USAGE.md +5 -8
- package/src/components/Pagination/USAGE.md +0 -19
- package/src/components/Panel/USAGE.md +6 -95
- package/src/components/Popover/USAGE.md +6 -66
- package/src/components/ReadOnlyField/USAGE.md +2 -10
- package/src/components/SegmentedControl/USAGE.md +1 -15
- package/src/components/Slider/USAGE.md +1 -45
- package/src/components/Stack/USAGE.md +1 -1
- package/src/components/Switch/USAGE.md +1 -24
- package/src/components/TextArea/USAGE.md +2 -2
- package/src/components/TextField/USAGE.md +1 -17
- package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +72 -0
- package/src/components/TimePicker/TimePicker.module.css +225 -41
- package/src/components/TimePicker/TimePicker.stories.tsx +105 -4
- package/src/components/TimePicker/TimePicker.tsx +287 -7
- package/src/components/TimePicker/USAGE.md +23 -2
- package/src/components/Timeline/USAGE.md +2 -10
- package/src/components/Toast/USAGE.md +3 -33
- package/src/components/Tooltip/USAGE.md +7 -51
- package/src/components/Tree/IMPLEMENTATION_NOTES.md +31 -3
- package/src/components/Tree/Tree.module.css +84 -40
- package/src/components/Tree/Tree.stories.tsx +13 -0
- package/src/components/Tree/Tree.tsx +116 -27
- package/src/components/Tree/USAGE.md +18 -1
- 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 {
|
|
3
|
+
import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof TimePicker> = {
|
|
6
|
-
title: "UI-Kit
|
|
6
|
+
title: "UI-Kit/TimePicker",
|
|
7
7
|
component: TimePicker,
|
|
8
8
|
tags: ["autodocs"],
|
|
9
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
5
|
-
RecursicaTimePickerProps;
|
|
17
|
+
import { type RecursicaTimePickerProps as BaseRecursicaTimePickerProps } from "@recursica/adapter-common";
|
|
6
18
|
|
|
7
|
-
|
|
8
|
-
|
|
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"
|
|
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**:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
56
|
+
## 2. Behavior Notes
|
|
57
57
|
|
|
58
|
-
|
|
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
|
-
##
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
121
|
-
|
|
122
|
-
**Decision:** Recursica defaults `position` to `"top"`. Mantine defaults to `"bottom"`.
|
|
78
|
+
## 5. Default Position
|
|
123
79
|
|
|
124
|
-
|
|
80
|
+
Recursica defaults `position` to `"top"` instead of Mantine's default of `"bottom"`. Pass your own `position` value to override it.
|