@recursica/mantine-adapter 0.15.0 → 0.17.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 +12 -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 +1688 -1330
- package/dist/mantine-adapter.js.map +1 -1
- package/dist/src/components/Button/Button.d.ts +7 -1
- package/dist/src/components/Container/Container.d.ts +8 -5
- package/dist/src/components/Flex/Flex.d.ts +10 -7
- package/dist/src/components/Group/Group.d.ts +8 -5
- package/dist/src/components/Link/Link.d.ts +32 -2
- package/dist/src/components/Link/index.d.ts +1 -0
- package/dist/src/components/Pagination/Pagination.d.ts +50 -2
- package/dist/src/components/Pagination/Pagination.icons.d.ts +8 -0
- package/dist/src/components/Pagination/index.d.ts +1 -0
- package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +16 -3
- package/dist/src/components/Stack/Stack.d.ts +8 -5
- package/dist/src/components/Stepper/Stepper.d.ts +12 -2
- package/dist/src/components/Tabs/Tabs.d.ts +19 -3
- package/dist/src/components/index.d.ts +0 -2
- package/package.json +1 -1
- package/src/OverStyling.stories.tsx +22 -0
- package/src/components/Button/Button.stories.tsx +11 -0
- package/src/components/Button/Button.tsx +7 -1
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/Container/Container.tsx +13 -17
- package/src/components/Flex/Flex.tsx +13 -17
- package/src/components/Group/Group.tsx +13 -17
- package/src/components/Link/IMPLEMENTATION_NOTES.md +26 -0
- package/src/components/Link/Link.module.css +133 -0
- package/src/components/Link/Link.stories.tsx +46 -4
- package/src/components/Link/Link.tsx +82 -5
- package/src/components/Link/index.ts +1 -0
- package/src/components/Pagination/IMPLEMENTATION_NOTES.md +16 -0
- package/src/components/Pagination/Pagination.icons.tsx +66 -0
- package/src/components/Pagination/Pagination.module.css +225 -0
- package/src/components/Pagination/Pagination.stories.tsx +43 -4
- package/src/components/Pagination/Pagination.tsx +214 -4
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Panel/Panel.stories.tsx +1 -1
- package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +17 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +138 -0
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +108 -6
- package/src/components/SegmentedControl/SegmentedControl.tsx +96 -5
- package/src/components/Stack/Stack.tsx +13 -17
- package/src/components/Stepper/Stepper.module.css +353 -0
- package/src/components/Stepper/Stepper.stories.tsx +135 -4
- package/src/components/Stepper/Stepper.tsx +68 -5
- package/src/components/Tabs/Tabs.module.css +401 -0
- package/src/components/Tabs/Tabs.stories.tsx +141 -4
- package/src/components/Tabs/Tabs.tsx +107 -5
- package/src/components/index.ts +0 -2
- package/dist/src/components/Popover/Popover.d.ts +0 -3
- package/dist/src/components/Search/Search.d.ts +0 -3
- package/src/components/Popover/Popover.stories.tsx +0 -17
- package/src/components/Popover/Popover.tsx +0 -7
- package/src/components/Search/Search.stories.tsx +0 -17
- package/src/components/Search/Search.tsx +0 -7
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { Pagination } from "./Pagination";
|
|
3
|
-
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
3
|
|
|
5
4
|
const meta: Meta<typeof Pagination> = {
|
|
6
|
-
title: "UI-Kit
|
|
5
|
+
title: "UI-Kit/Pagination",
|
|
7
6
|
component: Pagination,
|
|
8
7
|
tags: ["autodocs"],
|
|
8
|
+
argTypes: {
|
|
9
|
+
total: { control: "number" },
|
|
10
|
+
withEdges: { control: "boolean" },
|
|
11
|
+
withControls: { control: "boolean" },
|
|
12
|
+
disabled: { control: "boolean" },
|
|
13
|
+
withLabels: {
|
|
14
|
+
control: "boolean",
|
|
15
|
+
description: "Toggle text labels alongside icons",
|
|
16
|
+
},
|
|
17
|
+
defaultChecked: { table: { disable: true } },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
total: 10,
|
|
21
|
+
withEdges: false,
|
|
22
|
+
withControls: true,
|
|
23
|
+
withLabels: false,
|
|
24
|
+
},
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
26
|
+
render: ({ withLayer, layer, ...args }: any) => {
|
|
27
|
+
return <Pagination {...args} />;
|
|
28
|
+
},
|
|
9
29
|
};
|
|
10
30
|
|
|
11
31
|
export default meta;
|
|
12
32
|
|
|
13
33
|
type Story = StoryObj<typeof Pagination>;
|
|
14
34
|
|
|
15
|
-
export const Default: Story = {
|
|
16
|
-
|
|
35
|
+
export const Default: Story = {};
|
|
36
|
+
|
|
37
|
+
export const WithEdges: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
withEdges: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Compositional: Story = {
|
|
44
|
+
render: () => (
|
|
45
|
+
<Pagination.Root total={10}>
|
|
46
|
+
<Pagination.Items />
|
|
47
|
+
</Pagination.Root>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const WithTextLabels: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
withEdges: true,
|
|
54
|
+
withLabels: true,
|
|
55
|
+
},
|
|
17
56
|
};
|
|
@@ -1,7 +1,217 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React, { forwardRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Pagination as MantinePagination,
|
|
5
|
+
type PaginationProps as MantinePaginationProps,
|
|
6
|
+
type PaginationRootProps as MantinePaginationRootProps,
|
|
7
|
+
} from "@mantine/core";
|
|
8
|
+
import {
|
|
9
|
+
filterStylingProps,
|
|
10
|
+
type RecursicaOverStyled,
|
|
11
|
+
} from "../../utils/filterStylingProps";
|
|
12
|
+
import styles from "./Pagination.module.css";
|
|
13
|
+
import {
|
|
14
|
+
NextWithLabel,
|
|
15
|
+
PrevWithLabel,
|
|
16
|
+
FirstWithLabel,
|
|
17
|
+
LastWithLabel,
|
|
18
|
+
PaginationIcon,
|
|
19
|
+
} from "./Pagination.icons";
|
|
2
20
|
|
|
3
|
-
export type PaginationProps =
|
|
21
|
+
export type PaginationProps = RecursicaOverStyled<MantinePaginationProps> & {
|
|
22
|
+
/** If set to true, displays text labels alongside the icons for next/previous/first/last edges. */
|
|
23
|
+
withLabels?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type PaginationRootProps =
|
|
27
|
+
RecursicaOverStyled<MantinePaginationRootProps>;
|
|
28
|
+
|
|
29
|
+
export type PaginationEdgeProps<T extends React.ElementType> =
|
|
30
|
+
React.ComponentProps<T> & {
|
|
31
|
+
/** If set to true, displays text labels alongside the icon. */
|
|
32
|
+
withLabel?: boolean;
|
|
33
|
+
icon?: any;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function usePaginationClassNames(restRecord: Record<string, unknown>): {
|
|
37
|
+
className: string;
|
|
38
|
+
classNames: Partial<Record<string, string>>;
|
|
39
|
+
} {
|
|
40
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
41
|
+
root: styles.root,
|
|
42
|
+
control: styles.control,
|
|
43
|
+
dots: styles.dots,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const classNamesProp = restRecord.classNames;
|
|
47
|
+
if (
|
|
48
|
+
classNamesProp &&
|
|
49
|
+
typeof classNamesProp === "object" &&
|
|
50
|
+
!Array.isArray(classNamesProp)
|
|
51
|
+
) {
|
|
52
|
+
const o = classNamesProp as Partial<Record<string, string>>;
|
|
53
|
+
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
54
|
+
mergedClassNames.control = o.control
|
|
55
|
+
? `${styles.control} ${o.control}`
|
|
56
|
+
: styles.control;
|
|
57
|
+
mergedClassNames.dots = o.dots ? `${styles.dots} ${o.dots}` : styles.dots;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const classNameProp = restRecord.className as string | undefined;
|
|
61
|
+
const finalClass = classNameProp
|
|
62
|
+
? `${styles.root} ${classNameProp}`
|
|
63
|
+
: styles.root;
|
|
64
|
+
|
|
65
|
+
return { className: finalClass, classNames: mergedClassNames };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const _PaginationRoot = forwardRef<HTMLDivElement, PaginationRootProps>(
|
|
69
|
+
function PaginationRoot({ overStyled = false, ...rest }, ref) {
|
|
70
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
71
|
+
const stylingParams = usePaginationClassNames(
|
|
72
|
+
sanitizedProps as Record<string, unknown>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<MantinePagination.Root
|
|
77
|
+
ref={ref}
|
|
78
|
+
className={stylingParams.className}
|
|
79
|
+
classNames={stylingParams.classNames}
|
|
80
|
+
{...(sanitizedProps as MantinePaginationRootProps)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
_PaginationRoot.displayName = "Pagination.Root";
|
|
4
86
|
|
|
5
|
-
|
|
6
|
-
|
|
87
|
+
const _PaginationNext = forwardRef<
|
|
88
|
+
HTMLButtonElement,
|
|
89
|
+
PaginationEdgeProps<typeof MantinePagination.Next>
|
|
90
|
+
>(function PaginationNext({ withLabel, icon, ...props }, ref) {
|
|
91
|
+
const renderIcon = icon || (withLabel ? NextWithLabel : undefined);
|
|
92
|
+
return (
|
|
93
|
+
<MantinePagination.Next
|
|
94
|
+
ref={ref}
|
|
95
|
+
data-variant="text"
|
|
96
|
+
icon={renderIcon as any}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
_PaginationNext.displayName = "Pagination.Next";
|
|
102
|
+
|
|
103
|
+
const _PaginationPrevious = forwardRef<
|
|
104
|
+
HTMLButtonElement,
|
|
105
|
+
PaginationEdgeProps<typeof MantinePagination.Previous>
|
|
106
|
+
>(function PaginationPrevious({ withLabel, icon, ...props }, ref) {
|
|
107
|
+
const renderIcon = icon || (withLabel ? PrevWithLabel : undefined);
|
|
108
|
+
return (
|
|
109
|
+
<MantinePagination.Previous
|
|
110
|
+
ref={ref}
|
|
111
|
+
data-variant="text"
|
|
112
|
+
icon={renderIcon as any}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
_PaginationPrevious.displayName = "Pagination.Previous";
|
|
118
|
+
|
|
119
|
+
const _PaginationFirst = forwardRef<
|
|
120
|
+
HTMLButtonElement,
|
|
121
|
+
PaginationEdgeProps<typeof MantinePagination.First>
|
|
122
|
+
>(function PaginationFirst({ withLabel, icon, ...props }, ref) {
|
|
123
|
+
const renderIcon = icon || (withLabel ? FirstWithLabel : undefined);
|
|
124
|
+
return (
|
|
125
|
+
<MantinePagination.First
|
|
126
|
+
ref={ref}
|
|
127
|
+
data-variant="text"
|
|
128
|
+
icon={renderIcon as any}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
_PaginationFirst.displayName = "Pagination.First";
|
|
134
|
+
|
|
135
|
+
const _PaginationLast = forwardRef<
|
|
136
|
+
HTMLButtonElement,
|
|
137
|
+
PaginationEdgeProps<typeof MantinePagination.Last>
|
|
138
|
+
>(function PaginationLast({ withLabel, icon, ...props }, ref) {
|
|
139
|
+
const renderIcon = icon || (withLabel ? LastWithLabel : undefined);
|
|
140
|
+
return (
|
|
141
|
+
<MantinePagination.Last
|
|
142
|
+
ref={ref}
|
|
143
|
+
data-variant="text"
|
|
144
|
+
icon={renderIcon as any}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
_PaginationLast.displayName = "Pagination.Last";
|
|
150
|
+
|
|
151
|
+
const _Pagination = forwardRef<HTMLDivElement, PaginationProps>(
|
|
152
|
+
function Pagination(
|
|
153
|
+
{ overStyled = false, getControlProps, withLabels, ...rest },
|
|
154
|
+
ref,
|
|
155
|
+
) {
|
|
156
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
157
|
+
const stylingParams = usePaginationClassNames(
|
|
158
|
+
sanitizedProps as Record<string, unknown>,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const mergedGetControlProps = (
|
|
162
|
+
control: "first" | "previous" | "last" | "next",
|
|
163
|
+
) => {
|
|
164
|
+
const baseProps = { "data-variant": "text" };
|
|
165
|
+
if (getControlProps) {
|
|
166
|
+
return { ...baseProps, ...getControlProps(control) };
|
|
167
|
+
}
|
|
168
|
+
return baseProps;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const labelsIcons = withLabels
|
|
172
|
+
? {
|
|
173
|
+
nextIcon: NextWithLabel,
|
|
174
|
+
previousIcon: PrevWithLabel,
|
|
175
|
+
firstIcon: FirstWithLabel,
|
|
176
|
+
lastIcon: LastWithLabel,
|
|
177
|
+
}
|
|
178
|
+
: {};
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<MantinePagination
|
|
182
|
+
ref={ref}
|
|
183
|
+
className={stylingParams.className}
|
|
184
|
+
classNames={stylingParams.classNames}
|
|
185
|
+
getControlProps={mergedGetControlProps}
|
|
186
|
+
{...labelsIcons}
|
|
187
|
+
{...(sanitizedProps as MantinePaginationProps)}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
_Pagination.displayName = "Pagination";
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Recursica Pagination component wrapping Mantine's Pagination.
|
|
196
|
+
*/
|
|
197
|
+
export const Pagination = _Pagination as typeof _Pagination & {
|
|
198
|
+
Root: typeof _PaginationRoot;
|
|
199
|
+
Items: typeof MantinePagination.Items;
|
|
200
|
+
Control: typeof MantinePagination.Control;
|
|
201
|
+
Dots: typeof MantinePagination.Dots;
|
|
202
|
+
Next: typeof _PaginationNext;
|
|
203
|
+
Previous: typeof _PaginationPrevious;
|
|
204
|
+
First: typeof _PaginationFirst;
|
|
205
|
+
Last: typeof _PaginationLast;
|
|
206
|
+
Icon: typeof PaginationIcon;
|
|
7
207
|
};
|
|
208
|
+
|
|
209
|
+
Pagination.Root = _PaginationRoot;
|
|
210
|
+
Pagination.Items = MantinePagination.Items;
|
|
211
|
+
Pagination.Control = MantinePagination.Control;
|
|
212
|
+
Pagination.Dots = MantinePagination.Dots;
|
|
213
|
+
Pagination.Next = _PaginationNext;
|
|
214
|
+
Pagination.Previous = _PaginationPrevious;
|
|
215
|
+
Pagination.First = _PaginationFirst;
|
|
216
|
+
Pagination.Last = _PaginationLast;
|
|
217
|
+
Pagination.Icon = PaginationIcon;
|
|
@@ -94,12 +94,12 @@ export const Default: Story = {
|
|
|
94
94
|
Open Panel
|
|
95
95
|
</Button>
|
|
96
96
|
<Panel
|
|
97
|
+
{...(args as PanelProps)}
|
|
97
98
|
opened={opened}
|
|
98
99
|
onClose={() => setOpened(false)}
|
|
99
100
|
title="Panel Title"
|
|
100
101
|
position="right"
|
|
101
102
|
wrapHeaderText={wrapHeaderText}
|
|
102
|
-
{...(args as PanelProps)}
|
|
103
103
|
>
|
|
104
104
|
<Text>
|
|
105
105
|
This is the panel body content area. Panels slide in from the edge
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SegmentedControl Implementation Notes
|
|
2
|
+
|
|
3
|
+
## 1. Stripping Mantine's Native Variants and Sizes
|
|
4
|
+
|
|
5
|
+
The Figma design tokens for the SegmentedControl component do not define nested layers of variants (such as `solid`, `outline`) or specific sizing steps (`xs`, `sm`, etc.). They are defined globally. Therefore, we explicitly `Omit` the standard `variant`, `size`, `radius`, and `color` props from the generic Mantine `SegmentedControlProps` interface.
|
|
6
|
+
|
|
7
|
+
## 2. Hardcoded Overrides for Figma Strictness
|
|
8
|
+
|
|
9
|
+
Mantine injects inline hover styles on `.label` (specifically, adding a subtle gray background when hovering). Since Recursica defines explicit transparent or specifically-driven hover states, we use `!important` tags within `SegmentedControl.module.css` for background and typography overriding.
|
|
10
|
+
|
|
11
|
+
## 3. Divider Separators
|
|
12
|
+
|
|
13
|
+
Mantine uses an `::before` pseudo-element on the `.control` block to draw standard visual separators between adjacent elements. Instead of stripping this functionality out, we hook directly into the pseudo-element and override its `background-color` with `--recursica_ui-kit_components_segmented-control_properties_colors_divider-color`.
|
|
14
|
+
|
|
15
|
+
## 4. Indicator Mapping
|
|
16
|
+
|
|
17
|
+
The moving active background element (`.indicator`) is decoupled from the actual text label. It is styled natively with its own background color, border size, and elevation shadow variables to match the exact visual parity of a "floating active chip" as defined in the Recursica properties map.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* HARDCODED VALUES:
|
|
2
|
+
- border-style: solid; on container and indicator
|
|
3
|
+
- background-color: transparent; on label hover (overriding Mantine)
|
|
4
|
+
- !important usage to strictly enforce Figma tokens over Mantine's inline calculation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.root {
|
|
8
|
+
background-color: var(
|
|
9
|
+
--recursica_ui-kit_components_segmented-control_properties_container_colors_background
|
|
10
|
+
) !important;
|
|
11
|
+
border: var(
|
|
12
|
+
--recursica_ui-kit_components_segmented-control_properties_container_border-size
|
|
13
|
+
)
|
|
14
|
+
solid
|
|
15
|
+
var(
|
|
16
|
+
--recursica_ui-kit_components_segmented-control_properties_container_colors_border-color
|
|
17
|
+
);
|
|
18
|
+
border-radius: var(
|
|
19
|
+
--recursica_ui-kit_components_segmented-control_properties_container_border-radius
|
|
20
|
+
) !important;
|
|
21
|
+
padding: var(
|
|
22
|
+
--recursica_ui-kit_components_segmented-control_properties_container_padding-vertical
|
|
23
|
+
)
|
|
24
|
+
var(
|
|
25
|
+
--recursica_ui-kit_components_segmented-control_properties_container_padding-horizontal
|
|
26
|
+
) !important;
|
|
27
|
+
box-shadow: var(
|
|
28
|
+
--recursica_ui-kit_components_segmented-control_properties_container_elevation
|
|
29
|
+
);
|
|
30
|
+
gap: var(--recursica_ui-kit_components_segmented-control_properties_item-gap);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.label {
|
|
34
|
+
padding-left: var(
|
|
35
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_padding-horizontal
|
|
36
|
+
) !important;
|
|
37
|
+
padding-right: var(
|
|
38
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_padding-horizontal
|
|
39
|
+
) !important;
|
|
40
|
+
height: var(
|
|
41
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_height
|
|
42
|
+
) !important;
|
|
43
|
+
border-radius: var(
|
|
44
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_border-radius
|
|
45
|
+
) !important;
|
|
46
|
+
|
|
47
|
+
font-family: var(
|
|
48
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_font-family
|
|
49
|
+
);
|
|
50
|
+
font-size: var(
|
|
51
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_font-size
|
|
52
|
+
);
|
|
53
|
+
font-style: var(
|
|
54
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_font-style
|
|
55
|
+
);
|
|
56
|
+
font-weight: var(
|
|
57
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_font-weight
|
|
58
|
+
);
|
|
59
|
+
letter-spacing: var(
|
|
60
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_letter-spacing
|
|
61
|
+
);
|
|
62
|
+
line-height: var(
|
|
63
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_line-height
|
|
64
|
+
);
|
|
65
|
+
text-decoration: var(
|
|
66
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_text-decoration
|
|
67
|
+
);
|
|
68
|
+
text-transform: var(
|
|
69
|
+
--recursica_ui-kit_components_segmented-control-item_properties_text_text-transform
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
color: var(
|
|
73
|
+
--recursica_ui-kit_components_segmented-control_properties_colors_text
|
|
74
|
+
) !important;
|
|
75
|
+
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Mantine natively wraps label contents in an innerLabel span */
|
|
82
|
+
.label :global(.mantine-SegmentedControl-innerLabel) {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
gap: var(
|
|
87
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_icon-text-gap
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.label svg {
|
|
92
|
+
width: var(
|
|
93
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_icon-size
|
|
94
|
+
);
|
|
95
|
+
height: var(
|
|
96
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_icon-size
|
|
97
|
+
);
|
|
98
|
+
flex-shrink: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Mantine adds a background on hover, we must strictly strip it */
|
|
102
|
+
.label:hover {
|
|
103
|
+
background-color: transparent !important;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Mantine adds separators via ::before on the .control */
|
|
107
|
+
.control::before {
|
|
108
|
+
background-color: var(
|
|
109
|
+
--recursica_ui-kit_components_segmented-control_properties_colors_divider-color
|
|
110
|
+
) !important;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Indicator (the moving selected block) */
|
|
114
|
+
.indicator {
|
|
115
|
+
background-color: var(
|
|
116
|
+
--recursica_ui-kit_components_segmented-control-item_properties_selected_colors_background
|
|
117
|
+
) !important;
|
|
118
|
+
border: var(
|
|
119
|
+
--recursica_ui-kit_components_segmented-control-item_properties_selected_border-size
|
|
120
|
+
)
|
|
121
|
+
solid
|
|
122
|
+
var(
|
|
123
|
+
--recursica_ui-kit_components_segmented-control-item_properties_selected_colors_border-color
|
|
124
|
+
) !important;
|
|
125
|
+
box-shadow: var(
|
|
126
|
+
--recursica_ui-kit_components_segmented-control-item_properties_selected_elevation
|
|
127
|
+
) !important;
|
|
128
|
+
border-radius: var(
|
|
129
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_border-radius
|
|
130
|
+
) !important;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Selected label text color override */
|
|
134
|
+
.control[data-active] .label {
|
|
135
|
+
color: var(
|
|
136
|
+
--recursica_ui-kit_components_segmented-control-item_properties_selected_colors_text-color
|
|
137
|
+
) !important;
|
|
138
|
+
}
|
|
@@ -1,18 +1,120 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { SegmentedControl } from "./SegmentedControl";
|
|
3
|
-
import { ComingSoon } from "@recursica/storybook-template";
|
|
4
3
|
|
|
5
4
|
const meta: Meta<typeof SegmentedControl> = {
|
|
6
|
-
title: "UI-Kit
|
|
5
|
+
title: "UI-Kit/SegmentedControl",
|
|
7
6
|
component: SegmentedControl,
|
|
8
7
|
tags: ["autodocs"],
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
"SegmentedControl provides a linear set of two or more segments, each of which functions as a mutually exclusive button.",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
orientation: {
|
|
18
|
+
control: "radio",
|
|
19
|
+
options: ["horizontal", "vertical"],
|
|
20
|
+
},
|
|
21
|
+
fullWidth: {
|
|
22
|
+
control: "boolean",
|
|
23
|
+
},
|
|
24
|
+
disabled: {
|
|
25
|
+
control: "boolean",
|
|
26
|
+
},
|
|
27
|
+
data: { table: { disable: true } },
|
|
28
|
+
defaultChecked: { table: { disable: true } },
|
|
29
|
+
},
|
|
30
|
+
} satisfies Meta<typeof SegmentedControl>;
|
|
11
31
|
|
|
12
32
|
export default meta;
|
|
13
|
-
|
|
14
33
|
type Story = StoryObj<typeof SegmentedControl>;
|
|
15
34
|
|
|
16
35
|
export const Default: Story = {
|
|
17
|
-
|
|
36
|
+
args: {
|
|
37
|
+
data: ["React", "Angular", "Vue", "Svelte"],
|
|
38
|
+
orientation: "horizontal",
|
|
39
|
+
fullWidth: false,
|
|
40
|
+
disabled: false,
|
|
41
|
+
},
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
43
|
+
render: ({ withLayer, layer, ...args }: any) => {
|
|
44
|
+
return <SegmentedControl {...args} />;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const FullWidth: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
data: ["Daily", "Weekly", "Monthly"],
|
|
51
|
+
fullWidth: true,
|
|
52
|
+
},
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
54
|
+
render: ({ withLayer, layer, ...args }: any) => {
|
|
55
|
+
return <SegmentedControl {...args} />;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Vertical: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
data: ["Option 1", "Option 2", "Option 3"],
|
|
62
|
+
orientation: "vertical",
|
|
63
|
+
},
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
65
|
+
render: ({ withLayer, layer, ...args }: any) => {
|
|
66
|
+
return <SegmentedControl {...args} />;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const CheckIcon = () => (
|
|
71
|
+
<svg
|
|
72
|
+
viewBox="0 0 24 24"
|
|
73
|
+
fill="none"
|
|
74
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
strokeWidth="2"
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
>
|
|
80
|
+
<polyline points="20 6 9 17 4 12" />
|
|
81
|
+
</svg>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export const WithIcons: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
data: [
|
|
87
|
+
{
|
|
88
|
+
value: "daily",
|
|
89
|
+
label: (
|
|
90
|
+
<>
|
|
91
|
+
<CheckIcon />
|
|
92
|
+
<span>Daily</span>
|
|
93
|
+
</>
|
|
94
|
+
),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "weekly",
|
|
98
|
+
label: (
|
|
99
|
+
<>
|
|
100
|
+
<CheckIcon />
|
|
101
|
+
<span>Weekly</span>
|
|
102
|
+
</>
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
value: "monthly",
|
|
107
|
+
label: (
|
|
108
|
+
<>
|
|
109
|
+
<CheckIcon />
|
|
110
|
+
<span>Monthly</span>
|
|
111
|
+
</>
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
117
|
+
render: ({ withLayer, layer, ...args }: any) => {
|
|
118
|
+
return <SegmentedControl {...args} />;
|
|
119
|
+
},
|
|
18
120
|
};
|
|
@@ -1,7 +1,98 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
SegmentedControl as MantineSegmentedControl,
|
|
4
|
+
type SegmentedControlProps as MantineSegmentedControlProps,
|
|
5
|
+
} from "@mantine/core";
|
|
6
|
+
import {
|
|
7
|
+
filterStylingProps,
|
|
8
|
+
type RecursicaOverStyled,
|
|
9
|
+
} from "../../utils/filterStylingProps";
|
|
10
|
+
import styles from "./SegmentedControl.module.css";
|
|
2
11
|
|
|
3
|
-
export
|
|
12
|
+
export interface RecursicaSegmentedControlProps {
|
|
13
|
+
/** The orientation of the control */
|
|
14
|
+
orientation?: "horizontal" | "vertical";
|
|
15
|
+
/** If true, the control will take up the full width of its container */
|
|
16
|
+
fullWidth?: boolean;
|
|
17
|
+
}
|
|
4
18
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
export type SegmentedControlProps = RecursicaOverStyled<
|
|
20
|
+
Omit<
|
|
21
|
+
MantineSegmentedControlProps,
|
|
22
|
+
"variant" | "size" | "radius" | "color" | "classNames" | "className"
|
|
23
|
+
> & {
|
|
24
|
+
className?: string;
|
|
25
|
+
classNames?: Partial<Record<string, string>>;
|
|
26
|
+
} & RecursicaSegmentedControlProps
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
function useSegmentedControlClassNames(restRecord: Record<string, unknown>): {
|
|
30
|
+
className: string;
|
|
31
|
+
classNames: Partial<Record<string, string>>;
|
|
32
|
+
} {
|
|
33
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
34
|
+
root: styles.root,
|
|
35
|
+
control: styles.control,
|
|
36
|
+
label: styles.label,
|
|
37
|
+
indicator: styles.indicator,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const classNamesProp = restRecord.classNames;
|
|
41
|
+
if (
|
|
42
|
+
classNamesProp &&
|
|
43
|
+
typeof classNamesProp === "object" &&
|
|
44
|
+
!Array.isArray(classNamesProp)
|
|
45
|
+
) {
|
|
46
|
+
const o = classNamesProp as Partial<Record<string, string>>;
|
|
47
|
+
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
48
|
+
mergedClassNames.control = o.control
|
|
49
|
+
? `${styles.control} ${o.control}`
|
|
50
|
+
: styles.control;
|
|
51
|
+
mergedClassNames.label = o.label
|
|
52
|
+
? `${styles.label} ${o.label}`
|
|
53
|
+
: styles.label;
|
|
54
|
+
mergedClassNames.indicator = o.indicator
|
|
55
|
+
? `${styles.indicator} ${o.indicator}`
|
|
56
|
+
: styles.indicator;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const classNameProp = restRecord.className as string | undefined;
|
|
60
|
+
const finalClass = classNameProp
|
|
61
|
+
? `${styles.root} ${classNameProp}`
|
|
62
|
+
: styles.root;
|
|
63
|
+
|
|
64
|
+
return { className: finalClass, classNames: mergedClassNames };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
68
|
+
function SegmentedControl(
|
|
69
|
+
{ overStyled = false, orientation = "horizontal", fullWidth, ...rest },
|
|
70
|
+
ref,
|
|
71
|
+
) {
|
|
72
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
73
|
+
const stylingParams = useSegmentedControlClassNames(
|
|
74
|
+
sanitizedProps as Record<string, unknown>,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<MantineSegmentedControl
|
|
79
|
+
ref={ref}
|
|
80
|
+
className={stylingParams.className}
|
|
81
|
+
classNames={stylingParams.classNames}
|
|
82
|
+
orientation={orientation}
|
|
83
|
+
fullWidth={fullWidth}
|
|
84
|
+
data-orientation={orientation}
|
|
85
|
+
{...(sanitizedProps as Omit<
|
|
86
|
+
MantineSegmentedControlProps,
|
|
87
|
+
"variant" | "size"
|
|
88
|
+
>)}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
_SegmentedControl.displayName = "SegmentedControl";
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Recursica SegmentedControl component wrapping Mantine's SegmentedControl.
|
|
97
|
+
*/
|
|
98
|
+
export const SegmentedControl = _SegmentedControl;
|