@navikt/ds-react 0.17.12 → 0.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/cjs/index.js +2 -0
  2. package/cjs/stepper/Step.js +63 -0
  3. package/cjs/stepper/Stepper.js +60 -0
  4. package/cjs/stepper/index.js +23 -0
  5. package/cjs/stepper/package.json +6 -0
  6. package/cjs/tabs/Tab.js +61 -0
  7. package/cjs/tabs/TabList.js +109 -0
  8. package/cjs/tabs/TabPanel.js +47 -0
  9. package/cjs/tabs/Tabs.js +58 -0
  10. package/cjs/tabs/index.js +8 -0
  11. package/cjs/tabs/package.json +6 -0
  12. package/esm/index.d.ts +2 -0
  13. package/esm/index.js +2 -0
  14. package/esm/index.js.map +1 -1
  15. package/esm/stepper/Step.d.ts +16 -0
  16. package/esm/stepper/Step.js +36 -0
  17. package/esm/stepper/Step.js.map +1 -0
  18. package/esm/stepper/Stepper.d.ts +31 -0
  19. package/esm/stepper/Stepper.js +32 -0
  20. package/esm/stepper/Stepper.js.map +1 -0
  21. package/esm/stepper/index.d.ts +2 -0
  22. package/esm/stepper/index.js +3 -0
  23. package/esm/stepper/index.js.map +1 -0
  24. package/esm/tabs/Tab.d.ts +24 -0
  25. package/esm/tabs/Tab.js +34 -0
  26. package/esm/tabs/Tab.js.map +1 -0
  27. package/esm/tabs/TabList.d.ts +14 -0
  28. package/esm/tabs/TabList.js +82 -0
  29. package/esm/tabs/TabList.js.map +1 -0
  30. package/esm/tabs/TabPanel.d.ts +14 -0
  31. package/esm/tabs/TabPanel.js +20 -0
  32. package/esm/tabs/TabPanel.js.map +1 -0
  33. package/esm/tabs/Tabs.d.ts +43 -0
  34. package/esm/tabs/Tabs.js +30 -0
  35. package/esm/tabs/Tabs.js.map +1 -0
  36. package/esm/tabs/index.d.ts +2 -0
  37. package/esm/tabs/index.js +2 -0
  38. package/esm/tabs/index.js.map +1 -0
  39. package/package.json +3 -2
  40. package/src/index.ts +2 -0
  41. package/src/step-indicator/stories/step-indicator.stories.mdx +1 -1
  42. package/src/stepper/Step.tsx +59 -0
  43. package/src/stepper/Stepper.tsx +73 -0
  44. package/src/stepper/index.ts +2 -0
  45. package/src/stepper/stories/Example.tsx +28 -0
  46. package/src/stepper/stories/stepper.stories.mdx +61 -0
  47. package/src/stepper/stories/stepper.stories.tsx +54 -0
  48. package/src/tabs/Tab.tsx +79 -0
  49. package/src/tabs/TabList.tsx +127 -0
  50. package/src/tabs/TabPanel.tsx +30 -0
  51. package/src/tabs/Tabs.stories.tsx +188 -0
  52. package/src/tabs/Tabs.tsx +89 -0
  53. package/src/tabs/index.ts +2 -0
@@ -0,0 +1,79 @@
1
+ import * as RadixTabs from "@radix-ui/react-tabs";
2
+ import cl from "classnames";
3
+ import React, { forwardRef, useContext } from "react";
4
+ import { Label, OverridableComponent } from "..";
5
+ import { TabsContext } from "./Tabs";
6
+
7
+ export interface TabProps
8
+ extends Omit<React.HTMLAttributes<HTMLButtonElement>, "children"> {
9
+ /**
10
+ * Content
11
+ */
12
+ label?: React.ReactNode;
13
+ /**
14
+ * Icon
15
+ */
16
+ icon?: React.ReactNode;
17
+ /**
18
+ * Value for state-handling
19
+ */
20
+ value: string;
21
+ /**
22
+ * Icon position
23
+ * @default "left"
24
+ */
25
+ iconPosition?: "left" | "top";
26
+ }
27
+
28
+ export type TabType = OverridableComponent<TabProps, HTMLButtonElement>;
29
+
30
+ const Tab: TabType = forwardRef(
31
+ (
32
+ {
33
+ className,
34
+ as: Component = "button",
35
+ label,
36
+ icon,
37
+ iconPosition,
38
+ value,
39
+ ...rest
40
+ },
41
+ ref
42
+ ) => {
43
+ const context = useContext(TabsContext);
44
+
45
+ if (!label && !icon) {
46
+ console.error("<Tabs.Tab/> needs label/icon");
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ <RadixTabs.Trigger value={value} asChild>
52
+ <Component
53
+ ref={ref}
54
+ className={cl(
55
+ "navds-tabs__tab",
56
+ `navds-tabs__tab--${context?.size ?? "medium"}`,
57
+ `navds-tabs__tab-icon--${iconPosition}`,
58
+ className,
59
+ {
60
+ "navds-tabs__tab--icon-only": icon && !label,
61
+ }
62
+ )}
63
+ {...rest}
64
+ >
65
+ <Label
66
+ as="span"
67
+ className="navds-tabs__tab-inner"
68
+ size={context?.size}
69
+ >
70
+ {icon}
71
+ {label}
72
+ </Label>
73
+ </Component>
74
+ </RadixTabs.Trigger>
75
+ );
76
+ }
77
+ );
78
+
79
+ export default Tab;
@@ -0,0 +1,127 @@
1
+ import { debounce } from "@material-ui/core";
2
+ import { Back, Next } from "@navikt/ds-icons";
3
+ import { TabsList } from "@radix-ui/react-tabs";
4
+ import cl from "classnames";
5
+ import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
6
+ import mergeRefs from "react-merge-refs";
7
+
8
+ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ /**
10
+ * Tab elements
11
+ */
12
+ children: React.ReactNode;
13
+ /**
14
+ * Loops back to start when navigating past last item
15
+ */
16
+ loop?: boolean;
17
+ }
18
+
19
+ export type ListType = React.ForwardRefExoticComponent<
20
+ ListProps & React.RefAttributes<HTMLDivElement>
21
+ >;
22
+
23
+ const List = forwardRef<HTMLDivElement, ListProps>(
24
+ ({ className, ...rest }, ref) => {
25
+ const listRef = useRef<HTMLDivElement | null>(null);
26
+ const mergedRef = mergeRefs([listRef, ref]);
27
+ const [displayScroll, setDisplayScroll] = useState({
28
+ start: false,
29
+ end: false,
30
+ });
31
+
32
+ const updateScrollButtonState = useMemo(
33
+ () =>
34
+ debounce(() => {
35
+ if (!listRef?.current) return;
36
+ const { scrollWidth, clientWidth } = listRef?.current;
37
+ let showStartScroll;
38
+ let showEndScroll;
39
+
40
+ const scrollLeft = listRef?.current?.scrollLeft;
41
+ // use 1 for the potential rounding error with browser zooms.
42
+ showStartScroll = scrollLeft > 1;
43
+ showEndScroll = scrollLeft < scrollWidth - clientWidth - 1;
44
+
45
+ setDisplayScroll((displayScroll) =>
46
+ showStartScroll === displayScroll.start &&
47
+ showEndScroll === displayScroll.end
48
+ ? displayScroll
49
+ : { start: showStartScroll, end: showEndScroll }
50
+ );
51
+ }),
52
+ []
53
+ );
54
+
55
+ useEffect(() => {
56
+ const handleResize = () => updateScrollButtonState();
57
+ const win = listRef.current?.ownerDocument ?? document ?? window;
58
+ win.addEventListener("resize", handleResize);
59
+
60
+ let resizeObserver;
61
+
62
+ if (typeof ResizeObserver !== "undefined") {
63
+ resizeObserver = new ResizeObserver(handleResize);
64
+ resizeObserver.observe(listRef.current);
65
+ }
66
+
67
+ return () => {
68
+ win.removeEventListener("resize", handleResize);
69
+ if (resizeObserver) {
70
+ resizeObserver.disconnect();
71
+ }
72
+ };
73
+ }, [updateScrollButtonState]);
74
+
75
+ useEffect(() => {
76
+ updateScrollButtonState();
77
+ });
78
+
79
+ useEffect(() => {
80
+ return () => {
81
+ updateScrollButtonState.clear();
82
+ };
83
+ }, [updateScrollButtonState]);
84
+
85
+ const ScrollButton = ({
86
+ dir,
87
+ hidden,
88
+ }: {
89
+ dir: 1 | -1;
90
+ hidden: boolean;
91
+ }) => (
92
+ <div
93
+ className={cl("navds-tabs__scroll-button", {
94
+ "navds-tabs__scroll-button--hidden": hidden,
95
+ })}
96
+ onClick={() => {
97
+ if (!listRef.current) return;
98
+ listRef.current.scrollLeft &&= listRef.current.scrollLeft + dir * 100;
99
+ }}
100
+ >
101
+ {dir === -1 ? (
102
+ <Back title="scroll tilbake" />
103
+ ) : (
104
+ <Next title="scroll neste" />
105
+ )}
106
+ </div>
107
+ );
108
+
109
+ const showSteppers = displayScroll.end || displayScroll.start;
110
+ return (
111
+ <div className="navds-tabs__tablist-wrapper">
112
+ {showSteppers && (
113
+ <ScrollButton dir={-1} hidden={!displayScroll.start} />
114
+ )}
115
+ <TabsList
116
+ {...rest}
117
+ ref={mergedRef}
118
+ onScroll={updateScrollButtonState}
119
+ className={cl("navds-tabs__tablist", className)}
120
+ />
121
+ {showSteppers && <ScrollButton dir={1} hidden={!displayScroll.end} />}
122
+ </div>
123
+ );
124
+ }
125
+ ) as ListType;
126
+
127
+ export default List;
@@ -0,0 +1,30 @@
1
+ import { TabsContent } from "@radix-ui/react-tabs";
2
+ import cl from "classnames";
3
+ import React, { forwardRef } from "react";
4
+
5
+ export interface PanelProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ /**
7
+ * Tab panel
8
+ */
9
+ children: React.ReactNode;
10
+ /**
11
+ * Value for state-handling
12
+ */
13
+ value: string;
14
+ }
15
+
16
+ export type PanelType = React.ForwardRefExoticComponent<
17
+ PanelProps & React.RefAttributes<HTMLDivElement>
18
+ >;
19
+
20
+ const Panel = forwardRef<HTMLDivElement, PanelProps>(
21
+ ({ className, ...rest }, ref) => (
22
+ <TabsContent
23
+ {...rest}
24
+ ref={ref}
25
+ className={cl("navds-tabs__tabpanel", className)}
26
+ />
27
+ )
28
+ ) as PanelType;
29
+
30
+ export default Panel;
@@ -0,0 +1,188 @@
1
+ import { Cup, Dishwasher, Freezer } from "@navikt/ds-icons";
2
+ import { Meta } from "@storybook/react/types-6-0";
3
+ import React, { useState } from "react";
4
+ import { Tabs } from ".";
5
+ import { Link } from "../link";
6
+
7
+ export default {
8
+ title: "ds-react/tabs",
9
+ component: Tabs,
10
+ } as Meta;
11
+
12
+ export const UUDemo = () => (
13
+ <Tabs defaultValue="skap" lang="no">
14
+ <Tabs.List>
15
+ <Tabs.Tab value="skap" label="Skap" icon={<Cup aria-hidden />} />
16
+ <Tabs.Tab
17
+ value="oppvaskmaskin"
18
+ label="Oppvaskmaskin"
19
+ icon={<Dishwasher aria-hidden />}
20
+ />
21
+ <Tabs.Tab value="fryser" icon={<Freezer aria-hidden />} label="Fryser" />
22
+ </Tabs.List>
23
+ <Tabs.Panel
24
+ value="skap"
25
+ style={{ background: "var(--navds-global-color-gray-50)", height: 300 }}
26
+ >
27
+ Innholdspanel for skap med lenke <Link href="#">Dette er en lenke</Link>
28
+ </Tabs.Panel>
29
+ <Tabs.Panel
30
+ value="oppvaskmaskin"
31
+ style={{ background: "var(--navds-global-color-green-50)", height: 300 }}
32
+ >
33
+ Innholdspanel for oppvaskmaskin med lenke{" "}
34
+ <Link href="#">Dette er en lenke</Link>
35
+ </Tabs.Panel>
36
+ <Tabs.Panel
37
+ value="fryser"
38
+ style={{ background: "var(--navds-global-color-red-50)", height: 300 }}
39
+ >
40
+ Innholdspanel for fryser med lenke <Link href="#">Dette er en lenke</Link>
41
+ </Tabs.Panel>
42
+ </Tabs>
43
+ );
44
+
45
+ const Panel = () => {
46
+ return (
47
+ <>
48
+ <Tabs.Panel
49
+ value="test1"
50
+ style={{ background: "var(--navds-global-color-gray-50)", height: 100 }}
51
+ >
52
+ Innholdspanel for Skap-tab
53
+ </Tabs.Panel>
54
+ <Tabs.Panel
55
+ value="test2"
56
+ style={{
57
+ background: "var(--navds-global-color-green-50)",
58
+ height: 100,
59
+ }}
60
+ >
61
+ Innholdspanel for Oppvaskmaskin-tab
62
+ </Tabs.Panel>
63
+ <Tabs.Panel
64
+ value="test3"
65
+ style={{ background: "var(--navds-global-color-red-50)", height: 100 }}
66
+ >
67
+ Innholdspanel for Fryser-tab
68
+ </Tabs.Panel>
69
+ </>
70
+ );
71
+ };
72
+
73
+ export const All = () => {
74
+ const [activeValue, setActiveValue] = useState("test1");
75
+
76
+ return (
77
+ <div>
78
+ <h2>Tabs</h2>
79
+ <Tabs defaultValue="test2">
80
+ <Tabs.List>
81
+ <Tabs.Tab value="test1" icon={<Cup />} label="Skap" />
82
+ <Tabs.Tab value="test2" label="Oppvaskmaskin" icon={<Dishwasher />} />
83
+ <Tabs.Tab value="test3" icon={<Freezer />} label="Fryser" />
84
+ </Tabs.List>
85
+ <Panel />
86
+ </Tabs>
87
+
88
+ <h2>Controlled</h2>
89
+ <Tabs value={activeValue} onChange={setActiveValue}>
90
+ <Tabs.List>
91
+ <Tabs.Tab value="test1" icon={<Cup />} label="Skap" />
92
+ <Tabs.Tab value="test2" label="Oppvaskmaskin" icon={<Dishwasher />} />
93
+ <Tabs.Tab value="test3" icon={<Freezer />} label="Fryser" />
94
+ </Tabs.List>
95
+ <Panel />
96
+ </Tabs>
97
+
98
+ <h2>selectionFollowsFocus</h2>
99
+ <Tabs defaultValue="test2" selectionFollowsFocus>
100
+ <Tabs.List>
101
+ <Tabs.Tab value="test1" icon={<Cup />} label="Skap" />
102
+ <Tabs.Tab value="test2" label="Oppvaskmaskin" icon={<Dishwasher />} />
103
+ <Tabs.Tab value="test3" icon={<Freezer />} label="Fryser" />
104
+ </Tabs.List>
105
+ <Panel />
106
+ </Tabs>
107
+
108
+ <h2>Tabs iconPosition="top"</h2>
109
+ <Tabs defaultValue="test2">
110
+ <Tabs.List>
111
+ <Tabs.Tab
112
+ value="test1"
113
+ icon={<Cup />}
114
+ label="Skap"
115
+ iconPosition="top"
116
+ />
117
+ <Tabs.Tab
118
+ value="test2"
119
+ label="Oppvaskmaskin"
120
+ icon={<Dishwasher />}
121
+ iconPosition="top"
122
+ />
123
+ <Tabs.Tab
124
+ value="test3"
125
+ icon={<Freezer />}
126
+ label="Fryser"
127
+ iconPosition="top"
128
+ />
129
+ </Tabs.List>
130
+ <Panel />
131
+ </Tabs>
132
+
133
+ <h2>Tabs small</h2>
134
+ <Tabs defaultValue="test2" size="small">
135
+ <Tabs.List>
136
+ <Tabs.Tab value="test1" icon={<Cup />} label="Skap" />
137
+ <Tabs.Tab value="test2" label="Oppvaskmaskin" icon={<Dishwasher />} />
138
+ <Tabs.Tab value="test3" icon={<Freezer />} label="Fryser" />
139
+ </Tabs.List>
140
+ <Panel />
141
+ </Tabs>
142
+ <br />
143
+ <Tabs defaultValue="test2" size="small">
144
+ <Tabs.List>
145
+ <Tabs.Tab
146
+ value="test1"
147
+ icon={<Cup />}
148
+ label="Skap"
149
+ iconPosition="top"
150
+ />
151
+
152
+ <Tabs.Tab
153
+ value="test2"
154
+ label="Oppvaskmaskin"
155
+ icon={<Dishwasher />}
156
+ iconPosition="top"
157
+ />
158
+ <Tabs.Tab
159
+ value="test3"
160
+ icon={<Freezer />}
161
+ label="Fryser"
162
+ iconPosition="top"
163
+ />
164
+ </Tabs.List>
165
+ <Panel />
166
+ </Tabs>
167
+
168
+ <h2>Tabs Icon-only</h2>
169
+ <Tabs defaultValue="test2">
170
+ <Tabs.List>
171
+ <Tabs.Tab value="test1" icon={<Cup />} />
172
+ <Tabs.Tab value="test2" icon={<Dishwasher />} />
173
+ <Tabs.Tab value="test3" icon={<Freezer />} />
174
+ </Tabs.List>
175
+ <Panel />
176
+ </Tabs>
177
+ <br />
178
+ <Tabs defaultValue="test2" size="small">
179
+ <Tabs.List>
180
+ <Tabs.Tab value="test1" icon={<Cup />} />
181
+ <Tabs.Tab value="test2" icon={<Dishwasher />} />
182
+ <Tabs.Tab value="test3" icon={<Freezer />} />
183
+ </Tabs.List>
184
+ <Panel />
185
+ </Tabs>
186
+ </div>
187
+ );
188
+ };
@@ -0,0 +1,89 @@
1
+ import cl from "classnames";
2
+ import React, { createContext, forwardRef, HTMLAttributes } from "react";
3
+ import * as RadixTabs from "@radix-ui/react-tabs";
4
+ import Tab, { TabType } from "./Tab";
5
+ import List, { ListType } from "./TabList";
6
+ import Panel, { PanelType } from "./TabPanel";
7
+
8
+ export interface TabsProps
9
+ extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "dir"> {
10
+ /**
11
+ * Tabs elements
12
+ */
13
+ children: React.ReactNode;
14
+ /**
15
+ * Changes padding and font-size
16
+ * @default "medium"
17
+ */
18
+ size?: "medium" | "small";
19
+ /**
20
+ * onChange
21
+ */
22
+ onChange?: (value: string) => void;
23
+ /**
24
+ * Controlled selected value
25
+ */
26
+ value?: string;
27
+ /**
28
+ * If not controlled, a default-value needs to be set
29
+ */
30
+ defaultValue?: string;
31
+ /**
32
+ * Automatically activates tab on focus/navigation
33
+ * @default false
34
+ */
35
+ selectionFollowsFocus?: boolean;
36
+ }
37
+
38
+ interface TabsComponent
39
+ extends React.ForwardRefExoticComponent<
40
+ TabsProps & React.RefAttributes<HTMLDivElement>
41
+ > {
42
+ Tab: TabType;
43
+ List: ListType;
44
+ Panel: PanelType;
45
+ }
46
+
47
+ interface TabsContextProps {
48
+ size: "medium" | "small";
49
+ }
50
+
51
+ export const TabsContext = createContext<TabsContextProps | null>(null);
52
+
53
+ const Tabs = forwardRef<HTMLDivElement, TabsProps>(
54
+ (
55
+ {
56
+ className,
57
+ children,
58
+ onChange,
59
+ size = "medium",
60
+ selectionFollowsFocus = false,
61
+ ...rest
62
+ },
63
+ ref
64
+ ) => {
65
+ return (
66
+ <RadixTabs.Root
67
+ {...rest}
68
+ ref={ref}
69
+ className={cl("navds-tabs", className, `navds-tabs--${size}`)}
70
+ activationMode={selectionFollowsFocus ? "automatic" : "manual"}
71
+ onValueChange={onChange}
72
+ >
73
+ <TabsContext.Provider
74
+ value={{
75
+ size,
76
+ }}
77
+ >
78
+ {children}
79
+ </TabsContext.Provider>
80
+ </RadixTabs.Root>
81
+ );
82
+ }
83
+ ) as TabsComponent;
84
+
85
+ Tabs.Tab = Tab;
86
+ Tabs.List = List;
87
+ Tabs.Panel = Panel;
88
+
89
+ export default Tabs;
@@ -0,0 +1,2 @@
1
+ export { default as Tabs, TabsProps } from "./Tabs";
2
+ export { TabProps } from "./Tab";