@recursica/mantine-adapter 0.6.0 → 0.8.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/.storybook/commonArgTypes.ts +148 -0
- package/CHANGELOG.md +75 -0
- package/dist/mantine-adapter.cjs +1 -1
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +1085 -687
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/Checkbox/CheckboxGroup.d.ts +1 -1
- package/dist/src/components/Chip/Chip.d.ts +14 -2
- package/dist/src/components/Container/Container.d.ts +14 -0
- package/dist/src/components/Dropdown/Dropdown.d.ts +10 -2
- package/dist/src/components/Flex/Flex.d.ts +17 -0
- package/dist/src/components/Group/Group.d.ts +17 -0
- package/dist/src/components/Stack/Stack.d.ts +15 -0
- package/dist/src/components/TextArea/TextArea.d.ts +14 -2
- package/dist/src/components/index.d.ts +6 -0
- package/package.json +5 -2
- package/src/OverStyling.stories.tsx +174 -0
- package/src/Version.stories.tsx +56 -0
- package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +48 -0
- package/src/components/Accordion/Accordion.module.css +261 -0
- package/src/components/Accordion/Accordion.stories.tsx +145 -0
- package/src/components/Accordion/Accordion.tsx +200 -0
- package/src/components/Accordion/index.ts +1 -0
- package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
- package/src/components/AssistiveElement/AssistiveElement.stories.tsx +73 -0
- package/src/components/AssistiveElement/AssistiveElement.tsx +91 -0
- package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +24 -0
- package/src/components/Avatar/Avatar.module.css +340 -0
- package/src/components/Avatar/Avatar.stories.tsx +102 -0
- package/src/components/Avatar/Avatar.tsx +100 -0
- package/src/components/Avatar/index.ts +1 -0
- package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
- package/src/components/Badge/Badge.module.css +124 -0
- package/src/components/Badge/Badge.stories.tsx +77 -0
- package/src/components/Badge/Badge.tsx +66 -0
- package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +14 -0
- package/src/components/Breadcrumb/Breadcrumb.module.css +26 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +77 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +64 -0
- package/src/components/Button/Button.module.css +275 -0
- package/src/components/Button/Button.stories.tsx +105 -0
- package/src/components/Button/Button.tsx +122 -0
- package/src/components/Button/IMPLEMENTATION_NOTES.md +45 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +10 -0
- package/src/components/Card/Card.module.css +60 -0
- package/src/components/Card/Card.stories.tsx +141 -0
- package/src/components/Card/Card.tsx +176 -0
- package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
- package/src/components/Checkbox/Checkbox.module.css +204 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +73 -0
- package/src/components/Checkbox/Checkbox.tsx +111 -0
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +138 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +140 -0
- package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +35 -0
- package/src/components/Chip/Chip.module.css +218 -0
- package/src/components/Chip/Chip.stories.tsx +115 -0
- package/src/components/Chip/Chip.tsx +143 -0
- package/src/components/Container/CONTAINER_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Container/Container.module.css +7 -0
- package/src/components/Container/Container.stories.tsx +102 -0
- package/src/components/Container/Container.tsx +63 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +22 -0
- package/src/components/DatePicker/DatePicker.tsx +7 -0
- package/src/components/Dropdown/DROPDOWN_IMPLEMENTATION_NOTES.md +7 -0
- package/src/components/Dropdown/Dropdown.module.css +272 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +94 -0
- package/src/components/Dropdown/Dropdown.tsx +186 -0
- package/src/components/FileInput/FileInput.stories.tsx +22 -0
- package/src/components/FileInput/FileInput.tsx +7 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +22 -0
- package/src/components/FileUpload/FileUpload.tsx +7 -0
- package/src/components/Flex/FLEX_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Flex/Flex.module.css +7 -0
- package/src/components/Flex/Flex.stories.tsx +125 -0
- package/src/components/Flex/Flex.tsx +68 -0
- package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
- package/src/components/FormControlWrapper/FormControlWrapper.module.css +46 -0
- package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +116 -0
- package/src/components/FormControlWrapper/FormControlWrapper.tsx +170 -0
- package/src/components/Group/GROUP_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Group/Group.module.css +7 -0
- package/src/components/Group/Group.stories.tsx +117 -0
- package/src/components/Group/Group.tsx +68 -0
- package/src/components/HoverCard/HoverCard.stories.tsx +17 -0
- package/src/components/HoverCard/HoverCard.tsx +7 -0
- package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
- package/src/components/Label/Label.module.css +177 -0
- package/src/components/Label/Label.stories.tsx +123 -0
- package/src/components/Label/Label.tsx +132 -0
- package/src/components/Label/index.ts +1 -0
- package/src/components/Link/Link.stories.tsx +17 -0
- package/src/components/Link/Link.tsx +7 -0
- package/src/components/Loader/Loader.stories.tsx +17 -0
- package/src/components/Loader/Loader.tsx +7 -0
- package/src/components/Menu/Menu.stories.tsx +17 -0
- package/src/components/Menu/Menu.tsx +7 -0
- package/src/components/Modal/Modal.stories.tsx +17 -0
- package/src/components/Modal/Modal.tsx +7 -0
- package/src/components/NumberInput/NumberInput.stories.tsx +22 -0
- package/src/components/NumberInput/NumberInput.tsx +7 -0
- package/src/components/Pagination/Pagination.stories.tsx +17 -0
- package/src/components/Pagination/Pagination.tsx +7 -0
- package/src/components/Panel/Panel.stories.tsx +17 -0
- package/src/components/Panel/Panel.tsx +7 -0
- package/src/components/Popover/Popover.stories.tsx +17 -0
- package/src/components/Popover/Popover.tsx +7 -0
- package/src/components/Radio/Radio.stories.tsx +22 -0
- package/src/components/Radio/Radio.tsx +7 -0
- package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
- package/src/components/ReadOnlyField/ReadOnlyField.module.css +49 -0
- package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +120 -0
- package/src/components/ReadOnlyField/ReadOnlyField.tsx +75 -0
- package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +46 -0
- package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +85 -0
- package/src/components/ReadOnlyField/index.ts +3 -0
- package/src/components/Search/Search.stories.tsx +17 -0
- package/src/components/Search/Search.tsx +7 -0
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +22 -0
- package/src/components/SegmentedControl/SegmentedControl.tsx +7 -0
- package/src/components/Slider/Slider.stories.tsx +22 -0
- package/src/components/Slider/Slider.tsx +7 -0
- package/src/components/Stack/STACK_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Stack/Stack.module.css +7 -0
- package/src/components/Stack/Stack.stories.tsx +105 -0
- package/src/components/Stack/Stack.tsx +66 -0
- package/src/components/Stepper/Stepper.stories.tsx +17 -0
- package/src/components/Stepper/Stepper.tsx +7 -0
- package/src/components/Switch/Switch.stories.tsx +22 -0
- package/src/components/Switch/Switch.tsx +7 -0
- package/src/components/Table/Table.stories.tsx +17 -0
- package/src/components/Table/Table.tsx +7 -0
- package/src/components/Tabs/Tabs.stories.tsx +17 -0
- package/src/components/Tabs/Tabs.tsx +7 -0
- package/src/components/Text/Text.stories.tsx +88 -0
- package/src/components/Text/Text.tsx +54 -0
- package/src/components/TextArea/TEXTAREA_IMPLEMENTATION_NOTES.md +7 -0
- package/src/components/TextArea/TextArea.module.css +225 -0
- package/src/components/TextArea/TextArea.stories.tsx +94 -0
- package/src/components/TextArea/TextArea.tsx +164 -0
- package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
- package/src/components/TextField/TextField.module.css +225 -0
- package/src/components/TextField/TextField.stories.tsx +162 -0
- package/src/components/TextField/TextField.tsx +138 -0
- package/src/components/TimePicker/TimePicker.stories.tsx +22 -0
- package/src/components/TimePicker/TimePicker.tsx +7 -0
- package/src/components/Timeline/Timeline.stories.tsx +17 -0
- package/src/components/Timeline/Timeline.tsx +7 -0
- package/src/components/Title/Title.stories.tsx +63 -0
- package/src/components/Title/Title.tsx +45 -0
- package/src/components/Toast/Toast.stories.tsx +17 -0
- package/src/components/Toast/Toast.tsx +7 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +17 -0
- package/src/components/Tooltip/Tooltip.tsx +7 -0
- package/src/components/TransferList/TransferList.stories.tsx +17 -0
- package/src/components/TransferList/TransferList.tsx +7 -0
- package/src/components/index.ts +45 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +3 -0
- package/src/types/index.ts +9 -0
- package/src/types/mantine.d.ts +7 -0
- package/src/types/scss-modules.d.ts +9 -0
- package/src/utils/ColorSchemeWrapper.tsx +27 -0
- package/src/utils/copyToClipboard.ts +36 -0
- package/src/utils/filterStylingProps.ts +139 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Group as MantineGroup,
|
|
4
|
+
type GroupProps as MantineGroupProps,
|
|
5
|
+
} from "@mantine/core";
|
|
6
|
+
import {
|
|
7
|
+
filterStylingProps,
|
|
8
|
+
type RecursicaOverStyled,
|
|
9
|
+
type RecursicaSpacing,
|
|
10
|
+
} from "../../utils/filterStylingProps";
|
|
11
|
+
import styles from "./Group.module.css";
|
|
12
|
+
|
|
13
|
+
export interface RecursicaGroupProps {
|
|
14
|
+
/**
|
|
15
|
+
* Children components inside the group
|
|
16
|
+
*/
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
gap?: MantineGroupProps["gap"] | RecursicaSpacing;
|
|
19
|
+
rowGap?: MantineGroupProps["gap"] | RecursicaSpacing;
|
|
20
|
+
columnGap?: MantineGroupProps["gap"] | RecursicaSpacing;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Group flex layout wrapper
|
|
25
|
+
*/
|
|
26
|
+
export type GroupProps = RecursicaOverStyled<
|
|
27
|
+
MantineGroupProps & RecursicaGroupProps
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export const Group = forwardRef<HTMLDivElement, GroupProps>(function Group(
|
|
31
|
+
{ children, overStyled = false, gap = "rec-default", ...rest },
|
|
32
|
+
ref,
|
|
33
|
+
) {
|
|
34
|
+
const sanitizedProps = filterStylingProps({ ...rest, gap }, overStyled);
|
|
35
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
38
|
+
root: styles.root,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const classNamesProp = restRecord.classNames;
|
|
42
|
+
if (
|
|
43
|
+
classNamesProp &&
|
|
44
|
+
typeof classNamesProp === "object" &&
|
|
45
|
+
!Array.isArray(classNamesProp)
|
|
46
|
+
) {
|
|
47
|
+
const o = classNamesProp as Partial<Record<string, string>>;
|
|
48
|
+
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const classNameProp = restRecord.className as string | undefined;
|
|
52
|
+
const finalClass = classNameProp
|
|
53
|
+
? `${styles.root} ${classNameProp}`
|
|
54
|
+
: styles.root;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<MantineGroup
|
|
58
|
+
ref={ref}
|
|
59
|
+
className={finalClass}
|
|
60
|
+
classNames={mergedClassNames}
|
|
61
|
+
{...sanitizedProps}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</MantineGroup>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
Group.displayName = "Group";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { HoverCard } from "./HoverCard";
|
|
3
|
+
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof HoverCard> = {
|
|
6
|
+
title: "UI-Kit/🚧 HoverCard",
|
|
7
|
+
component: HoverCard,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof HoverCard>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => <ComingSoon componentName="HoverCard" />,
|
|
17
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Label Implementation Notes
|
|
2
|
+
|
|
3
|
+
## Architecture Overview
|
|
4
|
+
|
|
5
|
+
The `Label` component is fundamentally built as a strict, localized wrapper around Mantine's native `Input.Label`. The core goal is to preserve context, ref-forwarding, and native accessibility links, while completely overriding visual behaviors via Recursica design variables in scoped CSS.
|
|
6
|
+
|
|
7
|
+
## Key Design Decisions
|
|
8
|
+
|
|
9
|
+
### **Layout Architecture**
|
|
10
|
+
|
|
11
|
+
- Because Recursica dictates complex alignments between primary label text, asterisks, edit icons, and optional strings, Mantine's rigid form label flow could not be used out-of-the-box.
|
|
12
|
+
- Implemented `display: flex; flex-wrap: wrap;` directly on `.root`.
|
|
13
|
+
- Injected strict integer-based flex `order` properties (e.g. `order: 1` for text, `order: 2` for `required`, `order: 5` for `optionalText`) to physically decouple DOM rendering from markup flow.
|
|
14
|
+
- Because `optionalText` often acts as secondary contextual detail, it forces `flex-basis: 100%`, securely wrapping to a secondary line underneath the primary label properties and transforming the horizontal gap spacing seamlessly into a `margin-top` vector.
|
|
15
|
+
|
|
16
|
+
### **Optional Text & Required Mutually Exclusive Parsing**
|
|
17
|
+
|
|
18
|
+
- By design standards, a component cannot logically be both "Required" and mapped as "Optional".
|
|
19
|
+
- To bulletproof implementations natively, the adapter forcibly suppresses rendering of the `resolvedOptionalText` strings if the parent wrapper invokes `required={true}`.
|
|
20
|
+
- `optionalText` can operate as dynamic data or specifically as a boolean `true`, which forces the adapter to natively render the formal `(Optional)` string map.
|
|
21
|
+
|
|
22
|
+
### **The "Edit Icon" Replacer Logic**
|
|
23
|
+
|
|
24
|
+
- Passing `withEditIcon={true}` evaluates it as fundamentally mutually exclusive to the standard required indicator asterisk.
|
|
25
|
+
- `required={required && !withEditIcon}` is passed to the underlying Mantine structure so the native `*` is entirely suppressed.
|
|
26
|
+
- Added localized styling via `data-replaces-asterisk={required ? "true" : undefined}` onto the `.editIconWrapper` element. If an editable instance is simultaneously designated as required, the edit icon functionally assumes the indicator role natively, overriding its default icon metrics explicitly to match `--recursica_ui-kit_components_label_properties_colors_asterisk`.
|
|
27
|
+
|
|
28
|
+
### **Bypassing `Input.Wrapper` Integrations**
|
|
29
|
+
|
|
30
|
+
- By decoupling from Mantine's standard `Input.Wrapper`, the `Label` component is exclusively mapped manually through `FormControlWrapper`. This grants us exact visual sync regarding where the label renders based on `formLayout="stacked"` or `formLayout="side-by-side"`, without fighting internal Mantine positional hooks that assume vertical stacking by default.
|
|
31
|
+
|
|
32
|
+
### **Label Size Container Constraints**
|
|
33
|
+
|
|
34
|
+
- The `labelSize` parameter (mapping values like `"small"`) internally **does not scale typographic font metrics**. Instead, it dynamically defines the explicit horizontal bounding width limit of the block container itself.
|
|
35
|
+
- **Architectural Rule:** `labelSize` modifications are strictly designed to execute exclusively when `formLayout="side-by-side"` is active. It acts as an optical grid threshold ensuring left-aligned string wrappers constrain correctly uniformly down a column without bleeding into the physical input arrays alongside them.
|
|
36
|
+
|
|
37
|
+
## Outstanding Technical Debt / Issues
|
|
38
|
+
|
|
39
|
+
### **Description Property Omission**
|
|
40
|
+
|
|
41
|
+
- **Issue:** Currently, the adapter `Label` does not natively parse or support a structured `description` node directly attached beneath it.
|
|
42
|
+
- **Context:** Standard forms often attach subtext beneath inputs, but mapping a description purely within the `<Label>` (distinct from an overall form-control description) is omitted structurally until subsequent UI Kit requirements mandate dedicated `description` styles locally on the label component itself.
|
|
43
|
+
|
|
44
|
+
### **Global Interactive Hover Mappings**
|
|
45
|
+
|
|
46
|
+
- **Issue:** Currently, the `editIconWrapper` lacks a dedicated hover background layer.
|
|
47
|
+
- **Context:** While the global theme abstracts interactive hover environments deeply using layered variable syntax (e.g., `--recursica_brand_layer_1_elements_interactive_hover-color`), Recursica's token generation framework natively fails to expose a universally generic placeholder token (e.g., `--recursica_elements_interactive_hover-color`) which downstream wrappers can map their `<Layer>` injections directly into safely.
|
|
48
|
+
- **Resolution Path:** We are explicitly ignoring the `.editIconWrapper:hover` background color assignment until the raw Figma token exports directly scaffold a generic token placeholder that we can interface cleanly with across arbitrary `<Layer>` contexts, avoiding manually mapping statically hardcoded depths (`layer-0`, `layer-1`).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/* HARDCODED VALUES:
|
|
2
|
+
- display: flex helps us leverage flex 'order' to perfectly position Mantine's injected asterisk.
|
|
3
|
+
- align-items: baseline ensures text logic lines up nicely if font sizes differ slightly.
|
|
4
|
+
- margin/padding resets.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.root {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
align-items: baseline;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
margin: 0;
|
|
13
|
+
color: var(--recursica_ui-kit_components_label_properties_colors_text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.root[data-alignment="left"] {
|
|
17
|
+
justify-content: flex-start;
|
|
18
|
+
text-align: left;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.root[data-alignment="right"] {
|
|
22
|
+
justify-content: flex-end;
|
|
23
|
+
text-align: right;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.root[data-layout="stacked"] {
|
|
27
|
+
padding-bottom: var(
|
|
28
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_properties_bottom-padding
|
|
29
|
+
);
|
|
30
|
+
min-height: var(
|
|
31
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_properties_min-height
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.root[data-layout="side-by-side"] {
|
|
36
|
+
padding-top: var(
|
|
37
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
|
|
38
|
+
);
|
|
39
|
+
padding-bottom: var(
|
|
40
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
|
|
41
|
+
);
|
|
42
|
+
min-height: var(
|
|
43
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_min-height
|
|
44
|
+
);
|
|
45
|
+
/* In side-by-side, the label exists to the left of its input, so we use the gutter gap here. */
|
|
46
|
+
margin-right: var(
|
|
47
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_gutter
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.root[data-layout="side-by-side"][data-size="default"] {
|
|
52
|
+
width: var(
|
|
53
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_default_properties_width
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.root[data-layout="side-by-side"][data-size="small"] {
|
|
58
|
+
width: var(
|
|
59
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_small_properties_width
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.labelText {
|
|
64
|
+
order: 1;
|
|
65
|
+
font-family: var(
|
|
66
|
+
--recursica_ui-kit_components_label_properties_label-text_font-family
|
|
67
|
+
);
|
|
68
|
+
font-size: var(
|
|
69
|
+
--recursica_ui-kit_components_label_properties_label-text_font-size
|
|
70
|
+
);
|
|
71
|
+
font-weight: var(
|
|
72
|
+
--recursica_ui-kit_components_label_properties_label-text_font-weight
|
|
73
|
+
);
|
|
74
|
+
letter-spacing: var(
|
|
75
|
+
--recursica_ui-kit_components_label_properties_label-text_letter-spacing
|
|
76
|
+
);
|
|
77
|
+
line-height: var(
|
|
78
|
+
--recursica_ui-kit_components_label_properties_label-text_line-height
|
|
79
|
+
);
|
|
80
|
+
font-style: var(
|
|
81
|
+
--recursica_ui-kit_components_label_properties_label-text_font-style,
|
|
82
|
+
normal
|
|
83
|
+
);
|
|
84
|
+
text-decoration: var(
|
|
85
|
+
--recursica_ui-kit_components_label_properties_label-text_text-decoration,
|
|
86
|
+
none
|
|
87
|
+
);
|
|
88
|
+
text-transform: var(
|
|
89
|
+
--recursica_ui-kit_components_label_properties_label-text_text-transform,
|
|
90
|
+
none
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.required {
|
|
95
|
+
order: 2;
|
|
96
|
+
margin-left: var(
|
|
97
|
+
--recursica_ui-kit_components_label_properties_required-indicator-gap
|
|
98
|
+
);
|
|
99
|
+
color: var(--recursica_ui-kit_components_label_properties_colors_asterisk);
|
|
100
|
+
|
|
101
|
+
/* Inherit identical typography bounds to prevent flex row line-height stretching */
|
|
102
|
+
font-family: var(
|
|
103
|
+
--recursica_ui-kit_components_label_properties_label-text_font-family
|
|
104
|
+
);
|
|
105
|
+
font-size: var(
|
|
106
|
+
--recursica_ui-kit_components_label_properties_label-text_font-size
|
|
107
|
+
);
|
|
108
|
+
line-height: var(
|
|
109
|
+
--recursica_ui-kit_components_label_properties_label-text_line-height
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.optionalText {
|
|
114
|
+
order: 5;
|
|
115
|
+
flex-basis: 100%;
|
|
116
|
+
margin-top: var(
|
|
117
|
+
--recursica_ui-kit_components_label_properties_label-optional-text-gap
|
|
118
|
+
);
|
|
119
|
+
margin-left: 0;
|
|
120
|
+
font-family: var(
|
|
121
|
+
--recursica_ui-kit_components_label_properties_optional-text_font-family
|
|
122
|
+
);
|
|
123
|
+
font-size: var(
|
|
124
|
+
--recursica_ui-kit_components_label_properties_optional-text_font-size
|
|
125
|
+
);
|
|
126
|
+
font-weight: var(
|
|
127
|
+
--recursica_ui-kit_components_label_properties_optional-text_font-weight
|
|
128
|
+
);
|
|
129
|
+
letter-spacing: var(
|
|
130
|
+
--recursica_ui-kit_components_label_properties_optional-text_letter-spacing
|
|
131
|
+
);
|
|
132
|
+
line-height: var(
|
|
133
|
+
--recursica_ui-kit_components_label_properties_optional-text_line-height
|
|
134
|
+
);
|
|
135
|
+
font-style: normal;
|
|
136
|
+
text-decoration: var(
|
|
137
|
+
--recursica_ui-kit_components_label_properties_optional-text_text-decoration,
|
|
138
|
+
none
|
|
139
|
+
);
|
|
140
|
+
text-transform: var(
|
|
141
|
+
--recursica_ui-kit_components_label_properties_optional-text_text-transform,
|
|
142
|
+
none
|
|
143
|
+
);
|
|
144
|
+
color: inherit;
|
|
145
|
+
opacity: var(
|
|
146
|
+
--recursica_ui-kit_components_label_properties_colors_optional-text-opacity
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.actionAreaWrapper {
|
|
151
|
+
order: 4;
|
|
152
|
+
margin-left: var(
|
|
153
|
+
--recursica_ui-kit_components_label_properties_edit-icon-gap
|
|
154
|
+
);
|
|
155
|
+
display: inline-flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: center;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.editIconWrapper {
|
|
161
|
+
order: 4;
|
|
162
|
+
margin-left: var(
|
|
163
|
+
--recursica_ui-kit_components_label_properties_edit-icon-gap
|
|
164
|
+
);
|
|
165
|
+
background: none;
|
|
166
|
+
border: none;
|
|
167
|
+
padding: 0;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
display: inline-flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
justify-content: center;
|
|
172
|
+
color: inherit;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.editIconWrapper[data-replaces-asterisk="true"] {
|
|
176
|
+
color: var(--recursica_ui-kit_components_label_properties_colors_asterisk);
|
|
177
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
4
|
+
import { Label } from "./Label";
|
|
5
|
+
import { TextField } from "../TextField/TextField";
|
|
6
|
+
import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
|
|
7
|
+
|
|
8
|
+
type LabelStoryProps = React.ComponentProps<typeof Label>;
|
|
9
|
+
|
|
10
|
+
const meta: Meta<LabelStoryProps> = {
|
|
11
|
+
title: "UI-Kit/Label",
|
|
12
|
+
component: Label,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
parameters: {
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component:
|
|
18
|
+
"The `Label` component is a strict Recursica-styled wrapper around Mantine's native `Input.Label`. It serves as the primary compositional primitive for all form fields, preserving Mantine's accessibility associations and context while strictly enforcing the Recursica atomic design system.\n\n### Usage with Form Inputs\nWhen working with form structures, render this `Label` component directly above your inputs or supply it to a component's overriding properties. The component automatically maps structural layout dimensions, dynamic alignment (`left` vs `right`), custom indicator gaps, and integrates a customized `optionalText` and `withEditIcon` flow that safely bypasses Mantine's native required asterisk mechanisms.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
...formControlArgTypes,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
|
|
29
|
+
type Story = StoryObj<LabelStoryProps>;
|
|
30
|
+
|
|
31
|
+
// Utility mapping to pipe raw Label args structurally into TextField accurately
|
|
32
|
+
const renderWithTextField = ({ children, ...args }: LabelStoryProps) => (
|
|
33
|
+
<TextField
|
|
34
|
+
label={children as React.ReactNode}
|
|
35
|
+
placeholder="Form Control primitive mapped..."
|
|
36
|
+
{...(args as any)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
children: "Dynamic Label (Controls)",
|
|
43
|
+
formLayout: "stacked",
|
|
44
|
+
labelSize: "default",
|
|
45
|
+
labelAlignment: "left",
|
|
46
|
+
required: false,
|
|
47
|
+
labelOptionalText: "",
|
|
48
|
+
labelWithEditIcon: false,
|
|
49
|
+
},
|
|
50
|
+
render: renderWithTextField,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const StackedDefault: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
children: "Email Address",
|
|
56
|
+
formLayout: "stacked",
|
|
57
|
+
},
|
|
58
|
+
render: renderWithTextField,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const StackedRequired: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
children: "Primary Network Node",
|
|
64
|
+
formLayout: "stacked",
|
|
65
|
+
required: true,
|
|
66
|
+
},
|
|
67
|
+
render: renderWithTextField,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const StackedWithEditIcon: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
children: "Environment Variables",
|
|
73
|
+
formLayout: "stacked",
|
|
74
|
+
labelWithEditIcon: true,
|
|
75
|
+
},
|
|
76
|
+
render: renderWithTextField,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const SideBySideDefault: Story = {
|
|
80
|
+
args: {
|
|
81
|
+
children: "Status",
|
|
82
|
+
formLayout: "side-by-side",
|
|
83
|
+
labelSize: "default",
|
|
84
|
+
},
|
|
85
|
+
render: renderWithTextField,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const RequiredSuppressesOptionalText: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
children: "Full Name",
|
|
91
|
+
formLayout: "stacked",
|
|
92
|
+
required: true,
|
|
93
|
+
labelOptionalText: "This should not render",
|
|
94
|
+
},
|
|
95
|
+
render: renderWithTextField,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const BooleanOptionalText: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
children: "Middle Initial",
|
|
101
|
+
formLayout: "side-by-side",
|
|
102
|
+
labelOptionalText: true,
|
|
103
|
+
},
|
|
104
|
+
render: renderWithTextField,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const WithEditIcon: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
children: "Shipping Address",
|
|
110
|
+
formLayout: "side-by-side",
|
|
111
|
+
labelWithEditIcon: true,
|
|
112
|
+
},
|
|
113
|
+
render: renderWithTextField,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const LayerOneSideBySide: Story = {
|
|
117
|
+
args: {
|
|
118
|
+
children: "Configuration",
|
|
119
|
+
formLayout: "side-by-side",
|
|
120
|
+
labelWithEditIcon: true,
|
|
121
|
+
},
|
|
122
|
+
render: renderWithTextField,
|
|
123
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import { Input, type InputLabelProps } from "@mantine/core";
|
|
3
|
+
import {
|
|
4
|
+
filterStylingProps,
|
|
5
|
+
type RecursicaOverStyled,
|
|
6
|
+
} from "../../utils/filterStylingProps";
|
|
7
|
+
import styles from "./Label.module.css";
|
|
8
|
+
|
|
9
|
+
export interface RecursicaLabelProps {
|
|
10
|
+
/** Overall structural flow mapping the Form Control natively cascading down to Label and Input logic. */
|
|
11
|
+
formLayout?: "stacked" | "side-by-side";
|
|
12
|
+
/** Specifies the sizing metrics natively mapping the Label boundaries. */
|
|
13
|
+
labelSize?: "default" | "small";
|
|
14
|
+
/** Overall alignment directive for the label strings natively forcing Left/Right justification. */
|
|
15
|
+
labelAlignment?: "left" | "right";
|
|
16
|
+
/** Injects an indicator text block alongside the label. Can be boolean (`true` maps to '(Optional)') or custom React nodes. */
|
|
17
|
+
labelOptionalText?: boolean | React.ReactNode;
|
|
18
|
+
/** When true, forces the native Edit Icon to replace the standard asterisk visually. */
|
|
19
|
+
labelWithEditIcon?: boolean;
|
|
20
|
+
/** Custom action area to render alongside the label instead of the default edit icon. */
|
|
21
|
+
labelActionArea?: React.ReactNode;
|
|
22
|
+
/** Interaction hook invoked whenever a generated edit icon block natively triggers a click event. */
|
|
23
|
+
onLabelEditClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LabelProps = RecursicaOverStyled<
|
|
27
|
+
Omit<InputLabelProps, "size"> & RecursicaLabelProps
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
|
|
31
|
+
{
|
|
32
|
+
formLayout = "stacked",
|
|
33
|
+
labelSize = "default",
|
|
34
|
+
labelAlignment,
|
|
35
|
+
required = false,
|
|
36
|
+
labelOptionalText,
|
|
37
|
+
labelWithEditIcon,
|
|
38
|
+
labelActionArea,
|
|
39
|
+
onLabelEditClick,
|
|
40
|
+
children,
|
|
41
|
+
overStyled = false,
|
|
42
|
+
...rest
|
|
43
|
+
},
|
|
44
|
+
ref,
|
|
45
|
+
) {
|
|
46
|
+
const resolvedAlignment =
|
|
47
|
+
labelAlignment || (formLayout === "side-by-side" ? "right" : "left");
|
|
48
|
+
|
|
49
|
+
let resolvedOptionalText: React.ReactNode | undefined;
|
|
50
|
+
if (!required) {
|
|
51
|
+
if (labelOptionalText === true) resolvedOptionalText = "optional";
|
|
52
|
+
else if (labelOptionalText) resolvedOptionalText = labelOptionalText;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
56
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
57
|
+
|
|
58
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
59
|
+
label: styles.root,
|
|
60
|
+
required: styles.required,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const classNamesProp = restRecord.classNames;
|
|
64
|
+
if (
|
|
65
|
+
classNamesProp &&
|
|
66
|
+
typeof classNamesProp === "object" &&
|
|
67
|
+
!Array.isArray(classNamesProp)
|
|
68
|
+
) {
|
|
69
|
+
const o = classNamesProp as Partial<Record<string, string>>;
|
|
70
|
+
mergedClassNames.label = o.label
|
|
71
|
+
? `${styles.root} ${o.label}`
|
|
72
|
+
: styles.root;
|
|
73
|
+
mergedClassNames.required = o.required
|
|
74
|
+
? `${styles.required} ${o.required}`
|
|
75
|
+
: styles.required;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const classNameProp = restRecord.className as string | undefined;
|
|
79
|
+
const finalClass = classNameProp
|
|
80
|
+
? `${styles.root} ${classNameProp}`
|
|
81
|
+
: styles.root;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Input.Label
|
|
85
|
+
ref={ref}
|
|
86
|
+
className={finalClass}
|
|
87
|
+
classNames={mergedClassNames}
|
|
88
|
+
data-layout={formLayout}
|
|
89
|
+
data-size={labelSize}
|
|
90
|
+
data-alignment={resolvedAlignment}
|
|
91
|
+
required={required && !labelWithEditIcon}
|
|
92
|
+
{...sanitizedProps}
|
|
93
|
+
>
|
|
94
|
+
<span className={styles.labelText}>{children}</span>
|
|
95
|
+
{resolvedOptionalText && (
|
|
96
|
+
<span className={styles.optionalText}>
|
|
97
|
+
{typeof resolvedOptionalText === "string"
|
|
98
|
+
? `(${resolvedOptionalText})`
|
|
99
|
+
: resolvedOptionalText}
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
102
|
+
{labelActionArea ? (
|
|
103
|
+
<span className={styles.actionAreaWrapper}>{labelActionArea}</span>
|
|
104
|
+
) : labelWithEditIcon ? (
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
className={styles.editIconWrapper}
|
|
108
|
+
data-replaces-asterisk={required ? "true" : undefined}
|
|
109
|
+
onClick={onLabelEditClick}
|
|
110
|
+
aria-label="Edit"
|
|
111
|
+
>
|
|
112
|
+
<svg
|
|
113
|
+
width="16"
|
|
114
|
+
height="16"
|
|
115
|
+
viewBox="0 0 16 16"
|
|
116
|
+
fill="none"
|
|
117
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
118
|
+
>
|
|
119
|
+
<path
|
|
120
|
+
d="M11.5303 2.46967C11.8232 2.17678 12.2981 2.17678 12.591 2.46967L13.5303 3.40898C13.8232 3.70188 13.8232 4.17675 13.5303 4.46964L5.61288 12.3871C5.45268 12.5473 5.24434 12.6483 5.01809 12.6766L2.39534 13.0044C2.10091 13.0412 1.83856 12.7789 1.87538 12.4845L2.2032 9.86175C2.23147 9.63551 2.3325 9.42716 2.4927 9.26696L11.5303 2.46967Z"
|
|
121
|
+
stroke="currentColor"
|
|
122
|
+
strokeLinecap="round"
|
|
123
|
+
strokeLinejoin="round"
|
|
124
|
+
/>
|
|
125
|
+
</svg>
|
|
126
|
+
</button>
|
|
127
|
+
) : null}
|
|
128
|
+
</Input.Label>
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
Label.displayName = "Label";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Label";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Link } from "./Link";
|
|
3
|
+
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Link> = {
|
|
6
|
+
title: "UI-Kit/🚧 Link",
|
|
7
|
+
component: Link,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Link>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => <ComingSoon componentName="Link" />,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Loader } from "./Loader";
|
|
3
|
+
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Loader> = {
|
|
6
|
+
title: "UI-Kit/🚧 Loader",
|
|
7
|
+
component: Loader,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Loader>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => <ComingSoon componentName="Loader" />,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Menu } from "./Menu";
|
|
3
|
+
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Menu> = {
|
|
6
|
+
title: "UI-Kit/🚧 Menu",
|
|
7
|
+
component: Menu,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Menu>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => <ComingSoon componentName="Menu" />,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Modal } from "./Modal";
|
|
3
|
+
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Modal> = {
|
|
6
|
+
title: "UI-Kit/🚧 Modal",
|
|
7
|
+
component: Modal,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Modal>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => <ComingSoon componentName="Modal" />,
|
|
17
|
+
};
|