@navikt/ds-react 6.4.0 → 6.5.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/cjs/date/datepicker/parts/DayButton.js +1 -1
- package/cjs/date/datepicker/parts/DayButton.js.map +1 -1
- package/cjs/date/hooks/useDatepicker.js +4 -1
- package/cjs/date/hooks/useDatepicker.js.map +1 -1
- package/cjs/date/hooks/useMonthPicker.js +4 -1
- package/cjs/date/hooks/useMonthPicker.js.map +1 -1
- package/cjs/date/monthpicker/MonthButton.js +1 -1
- package/cjs/date/monthpicker/MonthButton.js.map +1 -1
- package/cjs/tabs/Tabs.context.d.ts +30 -0
- package/cjs/tabs/Tabs.context.js +14 -0
- package/cjs/tabs/Tabs.context.js.map +1 -0
- package/cjs/tabs/Tabs.d.ts +8 -43
- package/cjs/tabs/Tabs.js +19 -12
- package/cjs/tabs/Tabs.js.map +1 -1
- package/cjs/tabs/Tabs.types.d.ts +41 -0
- package/cjs/tabs/Tabs.types.js +3 -0
- package/cjs/tabs/Tabs.types.js.map +1 -0
- package/cjs/tabs/index.d.ts +5 -4
- package/cjs/tabs/index.js +6 -6
- package/cjs/tabs/index.js.map +1 -1
- package/cjs/tabs/parts/tab/Tab.d.ts +25 -0
- package/cjs/tabs/{Tab.js → parts/tab/Tab.js} +15 -14
- package/cjs/tabs/parts/tab/Tab.js.map +1 -0
- package/cjs/tabs/parts/tab/useTab.d.ts +20 -0
- package/cjs/tabs/parts/tab/useTab.js +29 -0
- package/cjs/tabs/parts/tab/useTab.js.map +1 -0
- package/cjs/tabs/parts/tablist/ScrollButtons.d.ts +8 -0
- package/cjs/tabs/parts/tablist/ScrollButtons.js +15 -0
- package/cjs/tabs/parts/tablist/ScrollButtons.js.map +1 -0
- package/{esm/tabs → cjs/tabs/parts/tablist}/TabList.d.ts +1 -1
- package/cjs/tabs/parts/tablist/TabList.js +61 -0
- package/cjs/tabs/parts/tablist/TabList.js.map +1 -0
- package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +12 -0
- package/cjs/tabs/parts/tablist/useScrollButtons.js +61 -0
- package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -0
- package/cjs/tabs/parts/tablist/useTabList.d.ts +7 -0
- package/cjs/tabs/parts/tablist/useTabList.js +66 -0
- package/cjs/tabs/parts/tablist/useTabList.js.map +1 -0
- package/cjs/tabs/parts/tabpanel/TabPanel.d.ts +25 -0
- package/cjs/tabs/{TabPanel.js → parts/tabpanel/TabPanel.js} +5 -3
- package/cjs/tabs/parts/tabpanel/TabPanel.js.map +1 -0
- package/cjs/tabs/parts/tabpanel/useTabPanel.d.ts +12 -0
- package/cjs/tabs/parts/tabpanel/useTabPanel.js +17 -0
- package/cjs/tabs/parts/tabpanel/useTabPanel.js.map +1 -0
- package/cjs/tabs/useTabs.d.ts +14 -0
- package/cjs/tabs/useTabs.js +43 -0
- package/cjs/tabs/useTabs.js.map +1 -0
- package/cjs/toggle-group/ToggleGroup.context.d.ts +31 -0
- package/cjs/toggle-group/ToggleGroup.context.js +16 -0
- package/cjs/toggle-group/ToggleGroup.context.js.map +1 -0
- package/cjs/toggle-group/ToggleGroup.d.ts +5 -36
- package/cjs/toggle-group/ToggleGroup.js +24 -24
- package/cjs/toggle-group/ToggleGroup.js.map +1 -1
- package/cjs/toggle-group/ToggleGroup.types.d.ts +38 -0
- package/cjs/toggle-group/ToggleGroup.types.js +3 -0
- package/cjs/toggle-group/ToggleGroup.types.js.map +1 -0
- package/cjs/toggle-group/index.d.ts +3 -2
- package/cjs/toggle-group/index.js +1 -1
- package/cjs/toggle-group/index.js.map +1 -1
- package/cjs/toggle-group/{ToggleItem.d.ts → parts/ToggleItem.d.ts} +2 -2
- package/cjs/toggle-group/{ToggleItem.js → parts/ToggleItem.js} +9 -8
- package/cjs/toggle-group/parts/ToggleItem.js.map +1 -0
- package/cjs/toggle-group/parts/useToggleItem.d.ts +20 -0
- package/cjs/toggle-group/parts/useToggleItem.js +76 -0
- package/cjs/toggle-group/parts/useToggleItem.js.map +1 -0
- package/cjs/toggle-group/useToggleGroup.d.ts +8 -0
- package/cjs/toggle-group/useToggleGroup.js +29 -0
- package/cjs/toggle-group/useToggleGroup.js.map +1 -0
- package/esm/date/datepicker/parts/DayButton.js +1 -1
- package/esm/date/datepicker/parts/DayButton.js.map +1 -1
- package/esm/date/hooks/useDatepicker.js +4 -1
- package/esm/date/hooks/useDatepicker.js.map +1 -1
- package/esm/date/hooks/useMonthPicker.js +4 -1
- package/esm/date/hooks/useMonthPicker.js.map +1 -1
- package/esm/date/monthpicker/MonthButton.js +1 -1
- package/esm/date/monthpicker/MonthButton.js.map +1 -1
- package/esm/tabs/Tabs.context.d.ts +30 -0
- package/esm/tabs/Tabs.context.js +10 -0
- package/esm/tabs/Tabs.context.js.map +1 -0
- package/esm/tabs/Tabs.d.ts +8 -43
- package/esm/tabs/Tabs.js +19 -12
- package/esm/tabs/Tabs.js.map +1 -1
- package/esm/tabs/Tabs.types.d.ts +41 -0
- package/esm/tabs/Tabs.types.js +2 -0
- package/esm/tabs/Tabs.types.js.map +1 -0
- package/esm/tabs/index.d.ts +5 -4
- package/esm/tabs/index.js +3 -3
- package/esm/tabs/index.js.map +1 -1
- package/esm/tabs/parts/tab/Tab.d.ts +25 -0
- package/esm/tabs/parts/tab/Tab.js +35 -0
- package/esm/tabs/parts/tab/Tab.js.map +1 -0
- package/esm/tabs/parts/tab/useTab.d.ts +20 -0
- package/esm/tabs/parts/tab/useTab.js +25 -0
- package/esm/tabs/parts/tab/useTab.js.map +1 -0
- package/esm/tabs/parts/tablist/ScrollButtons.d.ts +8 -0
- package/esm/tabs/parts/tablist/ScrollButtons.js +10 -0
- package/esm/tabs/parts/tablist/ScrollButtons.js.map +1 -0
- package/{cjs/tabs → esm/tabs/parts/tablist}/TabList.d.ts +1 -1
- package/esm/tabs/parts/tablist/TabList.js +32 -0
- package/esm/tabs/parts/tablist/TabList.js.map +1 -0
- package/esm/tabs/parts/tablist/useScrollButtons.d.ts +12 -0
- package/esm/tabs/parts/tablist/useScrollButtons.js +57 -0
- package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -0
- package/esm/tabs/parts/tablist/useTabList.d.ts +7 -0
- package/esm/tabs/parts/tablist/useTabList.js +62 -0
- package/esm/tabs/parts/tablist/useTabList.js.map +1 -0
- package/esm/tabs/parts/tabpanel/TabPanel.d.ts +25 -0
- package/esm/tabs/parts/tabpanel/TabPanel.js +22 -0
- package/esm/tabs/parts/tabpanel/TabPanel.js.map +1 -0
- package/esm/tabs/parts/tabpanel/useTabPanel.d.ts +12 -0
- package/esm/tabs/parts/tabpanel/useTabPanel.js +13 -0
- package/esm/tabs/parts/tabpanel/useTabPanel.js.map +1 -0
- package/esm/tabs/useTabs.d.ts +14 -0
- package/esm/tabs/useTabs.js +39 -0
- package/esm/tabs/useTabs.js.map +1 -0
- package/esm/toggle-group/ToggleGroup.context.d.ts +31 -0
- package/esm/toggle-group/ToggleGroup.context.js +12 -0
- package/esm/toggle-group/ToggleGroup.context.js.map +1 -0
- package/esm/toggle-group/ToggleGroup.d.ts +5 -36
- package/esm/toggle-group/ToggleGroup.js +24 -24
- package/esm/toggle-group/ToggleGroup.js.map +1 -1
- package/esm/toggle-group/ToggleGroup.types.d.ts +38 -0
- package/esm/toggle-group/ToggleGroup.types.js +2 -0
- package/esm/toggle-group/ToggleGroup.types.js.map +1 -0
- package/esm/toggle-group/index.d.ts +3 -2
- package/esm/toggle-group/index.js +1 -1
- package/esm/toggle-group/index.js.map +1 -1
- package/esm/toggle-group/{ToggleItem.d.ts → parts/ToggleItem.d.ts} +2 -2
- package/esm/toggle-group/parts/ToggleItem.js +25 -0
- package/esm/toggle-group/parts/ToggleItem.js.map +1 -0
- package/esm/toggle-group/parts/useToggleItem.d.ts +20 -0
- package/esm/toggle-group/parts/useToggleItem.js +72 -0
- package/esm/toggle-group/parts/useToggleItem.js.map +1 -0
- package/esm/toggle-group/useToggleGroup.d.ts +8 -0
- package/esm/toggle-group/useToggleGroup.js +25 -0
- package/esm/toggle-group/useToggleGroup.js.map +1 -0
- package/package.json +3 -5
- package/src/date/datepicker/datepicker.stories.tsx +39 -0
- package/src/date/datepicker/parts/DayButton.tsx +2 -0
- package/src/date/hooks/useDatepicker.tsx +5 -1
- package/src/date/hooks/useMonthPicker.tsx +5 -1
- package/src/date/monthpicker/MonthButton.tsx +1 -0
- package/src/date/monthpicker/monthpicker.stories.tsx +36 -19
- package/src/modal/modal.stories.tsx +2 -6
- package/src/tabs/Tabs.context.ts +24 -0
- package/src/tabs/Tabs.stories.tsx +233 -113
- package/src/tabs/Tabs.test.tsx +99 -37
- package/src/tabs/Tabs.tsx +48 -70
- package/src/tabs/Tabs.types.ts +43 -0
- package/src/tabs/index.ts +11 -4
- package/src/tabs/parts/tab/Tab.tsx +93 -0
- package/src/tabs/parts/tab/useTab.ts +52 -0
- package/src/tabs/parts/tablist/ScrollButtons.tsx +29 -0
- package/src/tabs/parts/tablist/TabList.tsx +56 -0
- package/src/tabs/parts/tablist/useScrollButtons.ts +69 -0
- package/src/tabs/parts/tablist/useTabList.ts +68 -0
- package/src/tabs/parts/tabpanel/TabPanel.tsx +50 -0
- package/src/tabs/parts/tabpanel/useTabPanel.ts +18 -0
- package/src/tabs/useTabs.ts +51 -0
- package/src/toggle-group/ToggleGroup.context.ts +31 -0
- package/src/toggle-group/ToggleGroup.stories.tsx +67 -6
- package/src/toggle-group/ToggleGroup.test.tsx +57 -16
- package/src/toggle-group/ToggleGroup.tsx +63 -90
- package/src/toggle-group/ToggleGroup.types.ts +40 -0
- package/src/toggle-group/index.ts +3 -2
- package/src/toggle-group/parts/ToggleItem.tsx +55 -0
- package/src/toggle-group/parts/useToggleItem.ts +104 -0
- package/src/toggle-group/useToggleGroup.ts +33 -0
- package/cjs/tabs/Tab.d.ts +0 -18
- package/cjs/tabs/Tab.js.map +0 -1
- package/cjs/tabs/TabList.js +0 -111
- package/cjs/tabs/TabList.js.map +0 -1
- package/cjs/tabs/TabPanel.d.ts +0 -13
- package/cjs/tabs/TabPanel.js.map +0 -1
- package/cjs/tabs/context.d.ts +0 -8
- package/cjs/tabs/context.js +0 -6
- package/cjs/tabs/context.js.map +0 -1
- package/cjs/toggle-group/ToggleItem.js.map +0 -1
- package/cjs/toggle-group/context.d.ts +0 -6
- package/cjs/toggle-group/context.js +0 -6
- package/cjs/toggle-group/context.js.map +0 -1
- package/esm/tabs/Tab.d.ts +0 -18
- package/esm/tabs/Tab.js +0 -34
- package/esm/tabs/Tab.js.map +0 -1
- package/esm/tabs/TabList.js +0 -82
- package/esm/tabs/TabList.js.map +0 -1
- package/esm/tabs/TabPanel.d.ts +0 -13
- package/esm/tabs/TabPanel.js +0 -20
- package/esm/tabs/TabPanel.js.map +0 -1
- package/esm/tabs/context.d.ts +0 -8
- package/esm/tabs/context.js +0 -3
- package/esm/tabs/context.js.map +0 -1
- package/esm/toggle-group/ToggleItem.js +0 -24
- package/esm/toggle-group/ToggleItem.js.map +0 -1
- package/esm/toggle-group/context.d.ts +0 -6
- package/esm/toggle-group/context.js +0 -3
- package/esm/toggle-group/context.js.map +0 -1
- package/src/tabs/Tab.tsx +0 -66
- package/src/tabs/TabList.tsx +0 -128
- package/src/tabs/TabPanel.tsx +0 -26
- package/src/tabs/context.ts +0 -9
- package/src/toggle-group/ToggleItem.tsx +0 -41
- package/src/toggle-group/context.ts +0 -9
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useId } from "../util";
|
|
3
|
+
import { useControllableState } from "../util/hooks/useControllableState";
|
|
4
|
+
import { TabsProps } from "./Tabs.types";
|
|
5
|
+
|
|
6
|
+
export function useTabs({
|
|
7
|
+
onChange,
|
|
8
|
+
value,
|
|
9
|
+
defaultValue = "",
|
|
10
|
+
id,
|
|
11
|
+
}: Pick<TabsProps, "onChange" | "value" | "defaultValue" | "id">) {
|
|
12
|
+
const [focusedValue, setFocusedValue] = useState(defaultValue);
|
|
13
|
+
|
|
14
|
+
const [selectedValue, setSelectedValue] = useControllableState({
|
|
15
|
+
defaultValue,
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sync focused `value` with controlled `selectedValue`
|
|
22
|
+
*/
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
setFocusedValue(value);
|
|
26
|
+
}
|
|
27
|
+
}, [value]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Scope ids for better tracking
|
|
31
|
+
*/
|
|
32
|
+
const uuid = useId();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
id: `tabs-${id ?? uuid}`,
|
|
36
|
+
selectedValue,
|
|
37
|
+
setSelectedValue,
|
|
38
|
+
focusedValue,
|
|
39
|
+
setFocusedValue,
|
|
40
|
+
makeTabId,
|
|
41
|
+
makeTabPanelId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeTabId(id: string, value: string) {
|
|
46
|
+
return `${id}--tab-${value}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeTabPanelId(id: string, value: string) {
|
|
50
|
+
return `${id}--tabpanel-${value}`;
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createContext as ReactCreateContext } from "react";
|
|
2
|
+
import { createContext } from "../util/create-context";
|
|
3
|
+
import { createDescendantContext } from "../util/hooks/descendants/useDescendant";
|
|
4
|
+
import { ToggleGroupProps } from "./ToggleGroup.types";
|
|
5
|
+
import { useToggleGroup } from "./useToggleGroup";
|
|
6
|
+
|
|
7
|
+
interface ToggleContextProps {
|
|
8
|
+
size: "medium" | "small";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ToggleGroupContext = ReactCreateContext<ToggleContextProps | null>(
|
|
12
|
+
null,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const [
|
|
16
|
+
ToggleGroupDescendantsProvider,
|
|
17
|
+
useToggleGroupDescendantsContext,
|
|
18
|
+
useToggleGroupDescendants,
|
|
19
|
+
useToggleGroupDescendant,
|
|
20
|
+
] = createDescendantContext<HTMLButtonElement, { value: string }>();
|
|
21
|
+
|
|
22
|
+
type ToggleGroupProviderProps = ReturnType<typeof useToggleGroup> &
|
|
23
|
+
Pick<ToggleGroupProps, "size">;
|
|
24
|
+
|
|
25
|
+
/* State context */
|
|
26
|
+
export const [ToggleGroupProvider, useToggleGroupContext] =
|
|
27
|
+
createContext<ToggleGroupProviderProps>({
|
|
28
|
+
name: "ToggleGroupContext",
|
|
29
|
+
hookName: "useToggleGroupContext",
|
|
30
|
+
providerName: "ToggleGroupProvider",
|
|
31
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
EnvelopeOpenIcon,
|
|
6
6
|
InboxUpIcon,
|
|
7
7
|
} from "@navikt/aksel-icons";
|
|
8
|
+
import { VStack } from "../layout/stack";
|
|
8
9
|
import ToggleGroup from "./ToggleGroup";
|
|
9
10
|
|
|
10
11
|
const meta: Meta<typeof ToggleGroup> = {
|
|
@@ -22,7 +23,11 @@ const meta: Meta<typeof ToggleGroup> = {
|
|
|
22
23
|
control: { type: "radio" },
|
|
23
24
|
},
|
|
24
25
|
},
|
|
26
|
+
parameters: {
|
|
27
|
+
chromatic: { disable: true },
|
|
28
|
+
},
|
|
25
29
|
};
|
|
30
|
+
|
|
26
31
|
export default meta;
|
|
27
32
|
|
|
28
33
|
const Items = (icon?: boolean, both?: boolean) => (
|
|
@@ -71,6 +76,7 @@ export const Default = (props) => {
|
|
|
71
76
|
</ToggleGroup>
|
|
72
77
|
);
|
|
73
78
|
};
|
|
79
|
+
|
|
74
80
|
Default.args = {
|
|
75
81
|
icon: true,
|
|
76
82
|
text: true,
|
|
@@ -81,7 +87,7 @@ export const Compositions = () => {
|
|
|
81
87
|
const [activeValue, setActiveValue] = useState("ulest");
|
|
82
88
|
|
|
83
89
|
return (
|
|
84
|
-
<
|
|
90
|
+
<VStack gap="6">
|
|
85
91
|
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
|
86
92
|
{Items()}
|
|
87
93
|
</ToggleGroup>
|
|
@@ -91,7 +97,10 @@ export const Compositions = () => {
|
|
|
91
97
|
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
|
92
98
|
{Items(true)}
|
|
93
99
|
</ToggleGroup>
|
|
94
|
-
|
|
100
|
+
<ToggleGroup fill value={activeValue} onChange={setActiveValue}>
|
|
101
|
+
{Items(true)}
|
|
102
|
+
</ToggleGroup>
|
|
103
|
+
</VStack>
|
|
95
104
|
);
|
|
96
105
|
};
|
|
97
106
|
|
|
@@ -99,7 +108,7 @@ export const Variants = () => {
|
|
|
99
108
|
const [activeValue, setActiveValue] = useState("ulest");
|
|
100
109
|
|
|
101
110
|
return (
|
|
102
|
-
<
|
|
111
|
+
<VStack gap="6">
|
|
103
112
|
<ToggleGroup
|
|
104
113
|
variant="action"
|
|
105
114
|
value={activeValue}
|
|
@@ -114,7 +123,7 @@ export const Variants = () => {
|
|
|
114
123
|
>
|
|
115
124
|
{Items(true, true)}
|
|
116
125
|
</ToggleGroup>
|
|
117
|
-
</
|
|
126
|
+
</VStack>
|
|
118
127
|
);
|
|
119
128
|
};
|
|
120
129
|
|
|
@@ -122,7 +131,7 @@ export const Small = () => {
|
|
|
122
131
|
const [activeValue, setActiveValue] = useState("ulest");
|
|
123
132
|
|
|
124
133
|
return (
|
|
125
|
-
<
|
|
134
|
+
<VStack gap="6">
|
|
126
135
|
<ToggleGroup size="small" value={activeValue} onChange={setActiveValue}>
|
|
127
136
|
{Items()}
|
|
128
137
|
</ToggleGroup>
|
|
@@ -132,6 +141,58 @@ export const Small = () => {
|
|
|
132
141
|
<ToggleGroup size="small" value={activeValue} onChange={setActiveValue}>
|
|
133
142
|
{Items(true)}
|
|
134
143
|
</ToggleGroup>
|
|
135
|
-
</
|
|
144
|
+
</VStack>
|
|
136
145
|
);
|
|
137
146
|
};
|
|
147
|
+
|
|
148
|
+
export const Chromatic = {
|
|
149
|
+
render: () => (
|
|
150
|
+
<VStack gap="6">
|
|
151
|
+
<div>
|
|
152
|
+
<h2>Text</h2>
|
|
153
|
+
<ToggleGroup value="ulest" onChange={console.log}>
|
|
154
|
+
{Items()}
|
|
155
|
+
</ToggleGroup>
|
|
156
|
+
</div>
|
|
157
|
+
<div>
|
|
158
|
+
<h2>Icon</h2>
|
|
159
|
+
<ToggleGroup value="ulest" onChange={console.log}>
|
|
160
|
+
{Items(true)}
|
|
161
|
+
</ToggleGroup>
|
|
162
|
+
</div>
|
|
163
|
+
<div>
|
|
164
|
+
<h2>Text + icon</h2>
|
|
165
|
+
<ToggleGroup value="ulest" onChange={console.log}>
|
|
166
|
+
{Items(true, true)}
|
|
167
|
+
</ToggleGroup>
|
|
168
|
+
</div>
|
|
169
|
+
<div style={{ minWidth: 600 }}>
|
|
170
|
+
<h2>Fill</h2>
|
|
171
|
+
<ToggleGroup value="ulest" onChange={console.log} fill>
|
|
172
|
+
{Items(true, true)}
|
|
173
|
+
</ToggleGroup>
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<h2>Small</h2>
|
|
177
|
+
<ToggleGroup value="ulest" onChange={console.log} size="small">
|
|
178
|
+
{Items(true, true)}
|
|
179
|
+
</ToggleGroup>
|
|
180
|
+
</div>
|
|
181
|
+
<div>
|
|
182
|
+
<h2>Small + fill</h2>
|
|
183
|
+
<ToggleGroup value="ulest" onChange={console.log} size="small" fill>
|
|
184
|
+
{Items(true, true)}
|
|
185
|
+
</ToggleGroup>
|
|
186
|
+
</div>
|
|
187
|
+
<div>
|
|
188
|
+
<h2>Neutral</h2>
|
|
189
|
+
<ToggleGroup value="ulest" onChange={console.log} variant="neutral">
|
|
190
|
+
{Items(true, true)}
|
|
191
|
+
</ToggleGroup>
|
|
192
|
+
</div>
|
|
193
|
+
</VStack>
|
|
194
|
+
),
|
|
195
|
+
parameters: {
|
|
196
|
+
chromatic: { disable: false },
|
|
197
|
+
},
|
|
198
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from "@testing-library/react";
|
|
1
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import { describe, expect, test } from "vitest";
|
|
4
5
|
import { ToggleGroup } from "./ToggleGroup";
|
|
@@ -20,36 +21,76 @@ const TestToggleGroup = ({ value, onChange, defaultValue }: any) => (
|
|
|
20
21
|
describe("ToggleGroup", () => {
|
|
21
22
|
test("sets default value correctly", () => {
|
|
22
23
|
render(<TestToggleGroup defaultValue="toggle2" />);
|
|
23
|
-
const
|
|
24
|
+
const toggle2 = screen.getByTestId("toggle2");
|
|
24
25
|
|
|
25
|
-
expect(
|
|
26
|
+
expect(toggle2).toHaveAttribute("aria-checked", "true");
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
test("sets correct attributes on active toggle", () => {
|
|
29
30
|
render(<TestToggleGroup defaultValue="toggle2" />);
|
|
30
|
-
const
|
|
31
|
+
const toggle2 = screen.getByTestId("toggle2");
|
|
31
32
|
|
|
32
|
-
expect(
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
expect(
|
|
33
|
+
expect(toggle2).toHaveAttribute("aria-checked", "true");
|
|
34
|
+
expect(toggle2).toHaveAttribute("role", "radio");
|
|
35
|
+
expect(toggle2).toHaveAttribute("type", "button");
|
|
36
|
+
expect(toggle2).toHaveAttribute("tabindex", "0");
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
test("sets correct attributes on idle toggle", () => {
|
|
39
40
|
render(<TestToggleGroup defaultValue="toggle1" />);
|
|
40
|
-
const
|
|
41
|
+
const toggle2 = screen.getByTestId("toggle2");
|
|
41
42
|
|
|
42
|
-
expect(
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
43
|
+
expect(toggle2).toHaveAttribute("aria-checked", "false");
|
|
44
|
+
expect(toggle2).toHaveAttribute("role", "radio");
|
|
45
|
+
expect(toggle2).toHaveAttribute("type", "button");
|
|
46
|
+
expect(toggle2).toHaveAttribute("tabindex", "-1");
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
test("sets tabindex to 0 when focused", () => {
|
|
49
50
|
render(<TestToggleGroup defaultValue="toggle2" />);
|
|
50
|
-
const
|
|
51
|
+
const toggle2 = screen.getByTestId("toggle2");
|
|
51
52
|
|
|
52
|
-
fireEvent.focus(
|
|
53
|
-
expect(
|
|
53
|
+
fireEvent.focus(toggle2);
|
|
54
|
+
expect(toggle2).toHaveAttribute("tabindex", "0");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("roving tabindex keydown moves focus", () => {
|
|
58
|
+
render(<TestToggleGroup defaultValue="toggle1" />);
|
|
59
|
+
const toggle1 = screen.getByTestId("toggle1");
|
|
60
|
+
|
|
61
|
+
expect(toggle1).toHaveAttribute("tabindex", "0");
|
|
62
|
+
fireEvent.keyDown(toggle1, { key: "ArrowRight" });
|
|
63
|
+
|
|
64
|
+
expect(toggle1).toHaveAttribute("tabindex", "-1");
|
|
65
|
+
expect(screen.getByTestId("toggle2")).toHaveAttribute("tabindex", "0");
|
|
66
|
+
expect(screen.getByTestId("toggle2")).toHaveAttribute(
|
|
67
|
+
"aria-checked",
|
|
68
|
+
"false",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("Space selects focused toggle-item", async () => {
|
|
73
|
+
render(<TestToggleGroup defaultValue="toggle1" />);
|
|
74
|
+
const toggle1 = screen.getByTestId("toggle1");
|
|
75
|
+
|
|
76
|
+
expect(toggle1).toHaveAttribute("tabindex", "0");
|
|
77
|
+
fireEvent.keyDown(toggle1, { key: "ArrowRight" });
|
|
78
|
+
|
|
79
|
+
expect(toggle1).toHaveAttribute("tabindex", "-1");
|
|
80
|
+
expect(screen.getByTestId("toggle2")).toHaveAttribute("tabindex", "0");
|
|
81
|
+
expect(screen.getByTestId("toggle2")).toHaveAttribute(
|
|
82
|
+
"aria-checked",
|
|
83
|
+
"false",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line testing-library/no-unnecessary-act
|
|
87
|
+
await act(async () => {
|
|
88
|
+
await userEvent.keyboard(" ");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(screen.getByTestId("toggle2")).toHaveAttribute(
|
|
92
|
+
"aria-checked",
|
|
93
|
+
"true",
|
|
94
|
+
);
|
|
54
95
|
});
|
|
55
96
|
});
|
|
@@ -1,55 +1,24 @@
|
|
|
1
|
-
import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
|
|
2
1
|
import cl from "clsx";
|
|
3
|
-
import React, {
|
|
2
|
+
import React, { forwardRef } from "react";
|
|
4
3
|
import { Label } from "../typography";
|
|
5
|
-
import { useId } from "../util
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
children: React.ReactNode;
|
|
15
|
-
/**
|
|
16
|
-
* Changes padding and font-size
|
|
17
|
-
* @default "medium"
|
|
18
|
-
*/
|
|
19
|
-
size?: "medium" | "small";
|
|
20
|
-
/**
|
|
21
|
-
* Controlled selected value
|
|
22
|
-
*/
|
|
23
|
-
value?: string;
|
|
24
|
-
/**
|
|
25
|
-
* If not controlled, a default-value needs to be set
|
|
26
|
-
*/
|
|
27
|
-
defaultValue?: string;
|
|
28
|
-
/**
|
|
29
|
-
* Callback for selected toggle
|
|
30
|
-
*/
|
|
31
|
-
onChange: (value: string) => void;
|
|
32
|
-
/**
|
|
33
|
-
* Label describing ToggleGroup
|
|
34
|
-
*/
|
|
35
|
-
label?: React.ReactNode;
|
|
36
|
-
/**
|
|
37
|
-
* Changes design and interaction-visuals
|
|
38
|
-
* @default "action"
|
|
39
|
-
*/
|
|
40
|
-
variant?: "action" | "neutral";
|
|
41
|
-
}
|
|
4
|
+
import { useId } from "../util";
|
|
5
|
+
import {
|
|
6
|
+
ToggleGroupDescendantsProvider,
|
|
7
|
+
ToggleGroupProvider,
|
|
8
|
+
useToggleGroupDescendants,
|
|
9
|
+
} from "./ToggleGroup.context";
|
|
10
|
+
import { ToggleGroupProps } from "./ToggleGroup.types";
|
|
11
|
+
import ToggleItem from "./parts/ToggleItem";
|
|
12
|
+
import { useToggleGroup } from "./useToggleGroup";
|
|
42
13
|
|
|
43
14
|
interface ToggleGroupComponent
|
|
44
15
|
extends React.ForwardRefExoticComponent<
|
|
45
16
|
ToggleGroupProps & React.RefAttributes<HTMLDivElement>
|
|
46
17
|
> {
|
|
47
18
|
/**
|
|
48
|
-
* @see 🏷️ {@link
|
|
19
|
+
* @see 🏷️ {@link ToggleItem}
|
|
49
20
|
*/
|
|
50
|
-
Item:
|
|
51
|
-
ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>
|
|
52
|
-
>;
|
|
21
|
+
Item: typeof ToggleItem;
|
|
53
22
|
}
|
|
54
23
|
|
|
55
24
|
/**
|
|
@@ -77,70 +46,74 @@ export const ToggleGroup = forwardRef<HTMLDivElement, ToggleGroupProps>(
|
|
|
77
46
|
label,
|
|
78
47
|
value,
|
|
79
48
|
defaultValue,
|
|
80
|
-
"aria-describedby":
|
|
49
|
+
"aria-describedby": userDescribedby,
|
|
81
50
|
variant = "action",
|
|
51
|
+
fill = false,
|
|
82
52
|
...rest
|
|
83
53
|
},
|
|
84
54
|
ref,
|
|
85
55
|
) => {
|
|
86
|
-
const
|
|
87
|
-
|
|
56
|
+
const descendants = useToggleGroupDescendants();
|
|
57
|
+
|
|
58
|
+
const toggleGroupContext = useToggleGroup({
|
|
59
|
+
defaultValue,
|
|
60
|
+
value,
|
|
61
|
+
onChange,
|
|
62
|
+
});
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
/**
|
|
65
|
+
* ToggleGroupProvider handles memoization.
|
|
66
|
+
*/
|
|
67
|
+
const context = {
|
|
68
|
+
...toggleGroupContext,
|
|
69
|
+
size,
|
|
94
70
|
};
|
|
95
71
|
|
|
72
|
+
const labelId = useId();
|
|
73
|
+
|
|
96
74
|
if (!value && !defaultValue) {
|
|
97
|
-
console.error("ToggleGroup without value
|
|
75
|
+
console.error("ToggleGroup without value or defaultvalue is not allowed");
|
|
98
76
|
}
|
|
99
77
|
|
|
100
|
-
const describeBy = cl({
|
|
101
|
-
[desc ?? ""]: !!desc,
|
|
102
|
-
[labelId ?? ""]: !!label,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
78
|
if (!value && !defaultValue) {
|
|
106
79
|
console.error("ToggleGroup needs either a value or defaultValue");
|
|
107
80
|
}
|
|
108
81
|
|
|
109
82
|
return (
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
{label && (
|
|
117
|
-
<Label
|
|
118
|
-
size={size}
|
|
119
|
-
className="navds-toggle-group__label"
|
|
120
|
-
id={labelId}
|
|
121
|
-
>
|
|
122
|
-
{label}
|
|
123
|
-
</Label>
|
|
124
|
-
)}
|
|
125
|
-
<RadixToggleGroup.Root
|
|
126
|
-
{...rest}
|
|
127
|
-
onValueChange={handleValueChange}
|
|
128
|
-
value={value ?? groupValue}
|
|
129
|
-
defaultValue={defaultValue}
|
|
130
|
-
ref={ref}
|
|
131
|
-
className={cl(
|
|
132
|
-
"navds-toggle-group",
|
|
133
|
-
`navds-toggle-group--${size}`,
|
|
134
|
-
`navds-toggle-group--${variant}`,
|
|
135
|
-
)}
|
|
136
|
-
{...(describeBy && { "aria-describedby": describeBy })}
|
|
137
|
-
role="radiogroup"
|
|
138
|
-
type="single"
|
|
83
|
+
<ToggleGroupDescendantsProvider value={descendants}>
|
|
84
|
+
<ToggleGroupProvider {...context}>
|
|
85
|
+
<div
|
|
86
|
+
className={cl("navds-toggle-group__wrapper", className, {
|
|
87
|
+
"navds-toggle-group__wrapper--fill": fill,
|
|
88
|
+
})}
|
|
139
89
|
>
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
90
|
+
{label && (
|
|
91
|
+
<Label
|
|
92
|
+
size={size}
|
|
93
|
+
className="navds-toggle-group__label"
|
|
94
|
+
id={labelId}
|
|
95
|
+
>
|
|
96
|
+
{label}
|
|
97
|
+
</Label>
|
|
98
|
+
)}
|
|
99
|
+
<div
|
|
100
|
+
{...rest}
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={cl(
|
|
103
|
+
"navds-toggle-group",
|
|
104
|
+
`navds-toggle-group--${size}`,
|
|
105
|
+
`navds-toggle-group--${variant}`,
|
|
106
|
+
)}
|
|
107
|
+
aria-describedby={
|
|
108
|
+
cl(userDescribedby, !!label && labelId) || undefined
|
|
109
|
+
}
|
|
110
|
+
role="radiogroup"
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</ToggleGroupProvider>
|
|
116
|
+
</ToggleGroupDescendantsProvider>
|
|
144
117
|
);
|
|
145
118
|
},
|
|
146
119
|
) as ToggleGroupComponent;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ToggleGroupProps
|
|
4
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "dir"> {
|
|
5
|
+
/**
|
|
6
|
+
* Toggles.Item elements.
|
|
7
|
+
*/
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Changes padding and font-size.
|
|
11
|
+
* @default "medium"
|
|
12
|
+
*/
|
|
13
|
+
size?: "medium" | "small";
|
|
14
|
+
/**
|
|
15
|
+
* Controlled selected value.
|
|
16
|
+
*/
|
|
17
|
+
value?: string;
|
|
18
|
+
/**
|
|
19
|
+
* If not controlled, a default-value needs to be set.
|
|
20
|
+
*/
|
|
21
|
+
defaultValue?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Callback for selected toggle.
|
|
24
|
+
*/
|
|
25
|
+
onChange: (value: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Label describing ToggleGroup.
|
|
28
|
+
*/
|
|
29
|
+
label?: React.ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Changes design and interaction-visuals.
|
|
32
|
+
* @default "action"
|
|
33
|
+
*/
|
|
34
|
+
variant?: "action" | "neutral";
|
|
35
|
+
/**
|
|
36
|
+
* Stretch each button to fill avaliable space in container.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
fill?: boolean;
|
|
40
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { default as ToggleGroup
|
|
2
|
+
export { default as ToggleGroup } from "./ToggleGroup";
|
|
3
|
+
export { type ToggleGroupProps } from "./ToggleGroup.types";
|
|
3
4
|
export {
|
|
4
5
|
default as ToggleGroupItem,
|
|
5
6
|
type ToggleGroupItemProps,
|
|
6
|
-
} from "./ToggleItem";
|
|
7
|
+
} from "./parts/ToggleItem";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import cl from "clsx";
|
|
2
|
+
import React, { forwardRef } from "react";
|
|
3
|
+
import { BodyShort } from "../../typography/BodyShort";
|
|
4
|
+
import { useToggleGroupContext } from "../ToggleGroup.context";
|
|
5
|
+
import { useToggleItem } from "./useToggleItem";
|
|
6
|
+
|
|
7
|
+
export interface ToggleGroupItemProps
|
|
8
|
+
extends React.HTMLAttributes<HTMLButtonElement> {
|
|
9
|
+
/**
|
|
10
|
+
* Content.
|
|
11
|
+
*/
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Value for state-handling.
|
|
15
|
+
*/
|
|
16
|
+
value: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ToggleItem = forwardRef<HTMLButtonElement, ToggleGroupItemProps>(
|
|
20
|
+
(
|
|
21
|
+
{ className, children, value, onClick, onFocus, onKeyDown, ...rest },
|
|
22
|
+
forwardedRef,
|
|
23
|
+
) => {
|
|
24
|
+
const itemCtx = useToggleItem(
|
|
25
|
+
{ value, onClick, onFocus, disabled: false, onKeyDown },
|
|
26
|
+
forwardedRef,
|
|
27
|
+
);
|
|
28
|
+
const ctx = useToggleGroupContext();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<button
|
|
32
|
+
{...rest}
|
|
33
|
+
ref={itemCtx.ref}
|
|
34
|
+
className={cl("navds-toggle-group__button", className)}
|
|
35
|
+
type="button"
|
|
36
|
+
role="radio"
|
|
37
|
+
aria-checked={itemCtx.isSelected}
|
|
38
|
+
tabIndex={itemCtx.isFocused ? 0 : -1}
|
|
39
|
+
onClick={itemCtx.onClick}
|
|
40
|
+
onFocus={itemCtx.onFocus}
|
|
41
|
+
onKeyDown={itemCtx.onKeyDown}
|
|
42
|
+
>
|
|
43
|
+
<BodyShort
|
|
44
|
+
as="span"
|
|
45
|
+
className="navds-toggle-group__button-inner"
|
|
46
|
+
size={ctx?.size}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</BodyShort>
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export default ToggleItem;
|