@digdir/designsystemet-react 1.11.1 → 1.12.1

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 (190) hide show
  1. package/dist/cjs/components/Combobox/Combobox.js +1 -0
  2. package/dist/cjs/components/Combobox/Option/useComboboxOption.js +1 -0
  3. package/dist/cjs/components/Combobox/useComboboxKeyboard.js +1 -0
  4. package/dist/cjs/components/avatar/avatar.js +2 -2
  5. package/dist/cjs/components/breadcrumbs/breadcrumbs-link.js +2 -1
  6. package/dist/cjs/components/breadcrumbs/breadcrumbs-list.js +1 -11
  7. package/dist/cjs/components/breadcrumbs/breadcrumbs.js +2 -1
  8. package/dist/cjs/components/button/button.js +8 -3
  9. package/dist/cjs/components/card/card.js +17 -9
  10. package/dist/cjs/components/details/details-summary.js +3 -3
  11. package/dist/cjs/components/details/details.js +2 -2
  12. package/dist/cjs/components/dialog/dialog-trigger-context.js +5 -6
  13. package/dist/cjs/components/dialog/dialog-trigger.js +3 -8
  14. package/dist/cjs/components/dialog/dialog.js +25 -55
  15. package/dist/cjs/components/error-summary/error-summary-heading.js +2 -8
  16. package/dist/cjs/components/error-summary/error-summary.js +4 -9
  17. package/dist/cjs/components/field/field-counter.js +6 -41
  18. package/dist/cjs/components/field/field-description.js +2 -1
  19. package/dist/cjs/components/field/field.js +6 -4
  20. package/dist/cjs/components/label/label.js +2 -1
  21. package/dist/cjs/components/pagination/pagination-button.js +5 -3
  22. package/dist/cjs/components/pagination/pagination.js +7 -3
  23. package/dist/cjs/components/popover/popover-trigger.js +6 -10
  24. package/dist/cjs/components/popover/popover.js +15 -62
  25. package/dist/cjs/components/select/select.js +2 -12
  26. package/dist/cjs/components/skeleton/skeleton.js +1 -0
  27. package/dist/cjs/components/spinner/spinner.js +1 -0
  28. package/dist/cjs/components/suggestion/suggestion-clear.js +4 -2
  29. package/dist/cjs/components/suggestion/suggestion-empty.js +3 -1
  30. package/dist/cjs/components/suggestion/suggestion-input.js +4 -3
  31. package/dist/cjs/components/suggestion/suggestion-list.js +5 -41
  32. package/dist/cjs/components/suggestion/suggestion-option.js +3 -1
  33. package/dist/cjs/components/suggestion/suggestion.js +9 -9
  34. package/dist/cjs/components/tabs/tabs-list.js +4 -7
  35. package/dist/cjs/components/tabs/tabs-panel.js +5 -28
  36. package/dist/cjs/components/tabs/tabs-tab.js +11 -9
  37. package/dist/cjs/components/tabs/tabs.js +16 -6
  38. package/dist/cjs/components/toggle-group/index.js +1 -1
  39. package/dist/cjs/components/toggle-group/toggle-group-item.js +8 -6
  40. package/dist/cjs/components/toggle-group/toggle-group.js +6 -6
  41. package/dist/cjs/components/tooltip/tooltip.js +6 -147
  42. package/dist/cjs/components/validation-message/validation-message.js +2 -1
  43. package/dist/cjs/index.js +1 -0
  44. package/dist/cjs/utilities/hooks/use-pagination/use-pagination.js +13 -25
  45. package/dist/cjs/utilities/index.js +17 -0
  46. package/dist/cjs/utilities/roving-focus/roving-focus-item.js +2 -0
  47. package/dist/cjs/utilities/roving-focus/roving-focus-root.js +4 -0
  48. package/dist/cjs/utilities/roving-focus/use-roving-focus.js +3 -1
  49. package/dist/esm/components/Combobox/Combobox.js +1 -0
  50. package/dist/esm/components/Combobox/Option/useComboboxOption.js +1 -0
  51. package/dist/esm/components/Combobox/useComboboxKeyboard.js +1 -0
  52. package/dist/esm/components/avatar/avatar.js +2 -2
  53. package/dist/esm/components/breadcrumbs/breadcrumbs-link.js +2 -1
  54. package/dist/esm/components/breadcrumbs/breadcrumbs-list.js +2 -12
  55. package/dist/esm/components/breadcrumbs/breadcrumbs.js +2 -1
  56. package/dist/esm/components/button/button.js +9 -4
  57. package/dist/esm/components/card/card.js +18 -10
  58. package/dist/esm/components/details/details-summary.js +3 -3
  59. package/dist/esm/components/details/details.js +2 -2
  60. package/dist/esm/components/dialog/dialog-trigger-context.js +6 -7
  61. package/dist/esm/components/dialog/dialog-trigger.js +3 -8
  62. package/dist/esm/components/dialog/dialog.js +26 -56
  63. package/dist/esm/components/error-summary/error-summary-heading.js +3 -9
  64. package/dist/esm/components/error-summary/error-summary.js +6 -10
  65. package/dist/esm/components/field/field-counter.js +8 -43
  66. package/dist/esm/components/field/field-description.js +2 -1
  67. package/dist/esm/components/field/field.js +7 -5
  68. package/dist/esm/components/label/label.js +2 -1
  69. package/dist/esm/components/pagination/pagination-button.js +5 -3
  70. package/dist/esm/components/pagination/pagination.js +7 -3
  71. package/dist/esm/components/popover/popover-trigger.js +6 -10
  72. package/dist/esm/components/popover/popover.js +15 -62
  73. package/dist/esm/components/select/select.js +2 -12
  74. package/dist/esm/components/skeleton/skeleton.js +1 -0
  75. package/dist/esm/components/spinner/spinner.js +1 -0
  76. package/dist/esm/components/suggestion/suggestion-clear.js +4 -2
  77. package/dist/esm/components/suggestion/suggestion-empty.js +3 -1
  78. package/dist/esm/components/suggestion/suggestion-input.js +4 -3
  79. package/dist/esm/components/suggestion/suggestion-list.js +5 -41
  80. package/dist/esm/components/suggestion/suggestion-option.js +3 -1
  81. package/dist/esm/components/suggestion/suggestion.js +9 -9
  82. package/dist/esm/components/tabs/tabs-list.js +5 -8
  83. package/dist/esm/components/tabs/tabs-panel.js +6 -29
  84. package/dist/esm/components/tabs/tabs-tab.js +12 -10
  85. package/dist/esm/components/tabs/tabs.js +17 -7
  86. package/dist/esm/components/toggle-group/index.js +1 -1
  87. package/dist/esm/components/toggle-group/toggle-group-item.js +10 -8
  88. package/dist/esm/components/toggle-group/toggle-group.js +7 -7
  89. package/dist/esm/components/tooltip/tooltip.js +8 -149
  90. package/dist/esm/components/validation-message/validation-message.js +2 -1
  91. package/dist/esm/index.js +1 -0
  92. package/dist/esm/utilities/hooks/use-pagination/use-pagination.js +13 -25
  93. package/dist/esm/utilities/index.js +11 -0
  94. package/dist/esm/utilities/roving-focus/roving-focus-item.js +2 -0
  95. package/dist/esm/utilities/roving-focus/roving-focus-root.js +4 -0
  96. package/dist/esm/utilities/roving-focus/use-roving-focus.js +3 -1
  97. package/dist/react-types.d.ts +8 -0
  98. package/dist/types/components/avatar/avatar.d.ts +12 -7
  99. package/dist/types/components/avatar/avatar.d.ts.map +1 -1
  100. package/dist/types/components/breadcrumbs/breadcrumbs-link.d.ts.map +1 -1
  101. package/dist/types/components/breadcrumbs/breadcrumbs-list.d.ts.map +1 -1
  102. package/dist/types/components/breadcrumbs/breadcrumbs.d.ts +5 -3
  103. package/dist/types/components/breadcrumbs/breadcrumbs.d.ts.map +1 -1
  104. package/dist/types/components/button/button.d.ts +1 -1
  105. package/dist/types/components/button/button.d.ts.map +1 -1
  106. package/dist/types/components/card/card.d.ts.map +1 -1
  107. package/dist/types/components/details/details-summary.d.ts.map +1 -1
  108. package/dist/types/components/details/details.d.ts +1 -1
  109. package/dist/types/components/details/details.d.ts.map +1 -1
  110. package/dist/types/components/dialog/dialog-trigger-context.d.ts +10 -3
  111. package/dist/types/components/dialog/dialog-trigger-context.d.ts.map +1 -1
  112. package/dist/types/components/dialog/dialog-trigger.d.ts +1 -1
  113. package/dist/types/components/dialog/dialog-trigger.d.ts.map +1 -1
  114. package/dist/types/components/dialog/dialog.d.ts +3 -3
  115. package/dist/types/components/dialog/dialog.d.ts.map +1 -1
  116. package/dist/types/components/dropdown/dropdown.d.ts +1 -2
  117. package/dist/types/components/dropdown/dropdown.d.ts.map +1 -1
  118. package/dist/types/components/error-summary/error-summary-heading.d.ts.map +1 -1
  119. package/dist/types/components/error-summary/error-summary.d.ts +6 -6
  120. package/dist/types/components/error-summary/error-summary.d.ts.map +1 -1
  121. package/dist/types/components/field/field-counter.d.ts +2 -8
  122. package/dist/types/components/field/field-counter.d.ts.map +1 -1
  123. package/dist/types/components/field/field-description.d.ts.map +1 -1
  124. package/dist/types/components/field/field.d.ts +6 -2
  125. package/dist/types/components/field/field.d.ts.map +1 -1
  126. package/dist/types/components/index.d.ts +1 -0
  127. package/dist/types/components/index.d.ts.map +1 -1
  128. package/dist/types/components/input/input.d.ts +13 -1
  129. package/dist/types/components/input/input.d.ts.map +1 -1
  130. package/dist/types/components/label/label.d.ts.map +1 -1
  131. package/dist/types/components/pagination/pagination-button.d.ts +13 -4
  132. package/dist/types/components/pagination/pagination-button.d.ts.map +1 -1
  133. package/dist/types/components/pagination/pagination.d.ts +27 -5
  134. package/dist/types/components/pagination/pagination.d.ts.map +1 -1
  135. package/dist/types/components/popover/popover-trigger.d.ts.map +1 -1
  136. package/dist/types/components/popover/popover.d.ts +2 -14
  137. package/dist/types/components/popover/popover.d.ts.map +1 -1
  138. package/dist/types/components/search/search-button.d.ts +1 -1
  139. package/dist/types/components/select/select.d.ts +2 -0
  140. package/dist/types/components/select/select.d.ts.map +1 -1
  141. package/dist/types/components/suggestion/suggestion-clear.d.ts +7 -5
  142. package/dist/types/components/suggestion/suggestion-clear.d.ts.map +1 -1
  143. package/dist/types/components/suggestion/suggestion-empty.d.ts +1 -0
  144. package/dist/types/components/suggestion/suggestion-empty.d.ts.map +1 -1
  145. package/dist/types/components/suggestion/suggestion-input.d.ts +2 -1
  146. package/dist/types/components/suggestion/suggestion-input.d.ts.map +1 -1
  147. package/dist/types/components/suggestion/suggestion-list.d.ts +1 -1
  148. package/dist/types/components/suggestion/suggestion-list.d.ts.map +1 -1
  149. package/dist/types/components/suggestion/suggestion-option.d.ts +1 -0
  150. package/dist/types/components/suggestion/suggestion-option.d.ts.map +1 -1
  151. package/dist/types/components/suggestion/suggestion.d.ts +5 -6
  152. package/dist/types/components/suggestion/suggestion.d.ts.map +1 -1
  153. package/dist/types/components/tabs/tabs-list.d.ts +4 -2
  154. package/dist/types/components/tabs/tabs-list.d.ts.map +1 -1
  155. package/dist/types/components/tabs/tabs-panel.d.ts +4 -2
  156. package/dist/types/components/tabs/tabs-panel.d.ts.map +1 -1
  157. package/dist/types/components/tabs/tabs-tab.d.ts +4 -2
  158. package/dist/types/components/tabs/tabs-tab.d.ts.map +1 -1
  159. package/dist/types/components/tabs/tabs.d.ts +6 -6
  160. package/dist/types/components/tabs/tabs.d.ts.map +1 -1
  161. package/dist/types/components/textfield/textfield.d.ts.map +1 -1
  162. package/dist/types/components/toggle-group/index.d.ts +1 -1
  163. package/dist/types/components/toggle-group/toggle-group-item.d.ts +12 -3
  164. package/dist/types/components/toggle-group/toggle-group-item.d.ts.map +1 -1
  165. package/dist/types/components/toggle-group/toggle-group.d.ts +12 -4
  166. package/dist/types/components/toggle-group/toggle-group.d.ts.map +1 -1
  167. package/dist/types/components/tooltip/tooltip.d.ts +10 -3
  168. package/dist/types/components/tooltip/tooltip.d.ts.map +1 -1
  169. package/dist/types/components/validation-message/validation-message.d.ts.map +1 -1
  170. package/dist/types/types.d.ts +2 -0
  171. package/dist/types/types.d.ts.map +1 -1
  172. package/dist/types/utilities/hooks/use-pagination/use-pagination.d.ts +1 -1
  173. package/dist/types/utilities/hooks/use-pagination/use-pagination.d.ts.map +1 -1
  174. package/dist/types/utilities/index.d.ts +6 -0
  175. package/dist/types/utilities/index.d.ts.map +1 -1
  176. package/dist/types/utilities/roving-focus/roving-focus-item.d.ts +1 -0
  177. package/dist/types/utilities/roving-focus/roving-focus-item.d.ts.map +1 -1
  178. package/dist/types/utilities/roving-focus/roving-focus-root.d.ts +1 -0
  179. package/dist/types/utilities/roving-focus/roving-focus-root.d.ts.map +1 -1
  180. package/dist/types/utilities/roving-focus/use-roving-focus.d.ts +3 -1
  181. package/dist/types/utilities/roving-focus/use-roving-focus.d.ts.map +1 -1
  182. package/package.json +11 -14
  183. package/dist/cjs/components/field/field-observer.js +0 -112
  184. package/dist/cjs/components/toggle-group/use-toggle-groupitem.js +0 -34
  185. package/dist/esm/components/field/field-observer.js +0 -107
  186. package/dist/esm/components/toggle-group/use-toggle-groupitem.js +0 -32
  187. package/dist/types/components/field/field-observer.d.ts +0 -5
  188. package/dist/types/components/field/field-observer.d.ts.map +0 -1
  189. package/dist/types/components/toggle-group/use-toggle-groupitem.d.ts +0 -12
  190. package/dist/types/components/toggle-group/use-toggle-groupitem.d.ts.map +0 -1
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { forwardRef } from 'react';
4
+ import '@digdir/designsystemet-web';
4
5
 
5
6
  /**
6
7
  * A component for rendering individual options in the Suggestion list.
@@ -15,7 +16,8 @@ import { forwardRef } from 'react';
15
16
  * </Suggestion>
16
17
  */
17
18
  const SuggestionOption = forwardRef(function SuggestionOption({ className, ...rest }, ref) {
18
- return (jsx("u-option", { class: className, ref: ref, ...rest }));
19
+ return (jsx("u-option", { class: className, ref: ref, suppressHydrationWarning // Since <u-option> adds attributes
20
+ : true, ...rest }));
19
21
  });
20
22
 
21
23
  export { SuggestionOption };
@@ -1,10 +1,9 @@
1
1
  'use client';
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { forwardRef, useRef, useId, useState, useEffect, useCallback, createContext } from 'react';
4
- import '@u-elements/u-combobox';
5
3
  import cl from 'clsx/lite';
4
+ import { forwardRef, useRef, useId, useState, useEffect, useCallback, createContext } from 'react';
6
5
  import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-refs.js';
7
- import { Chip as ChipComponent } from '../chip/index.js';
6
+ import '@digdir/designsystemet-web';
8
7
 
9
8
  const text = (el) => el.textContent?.trim() || '';
10
9
  const sanitizeItems = (values = []) => typeof values === 'string'
@@ -22,11 +21,11 @@ const nextItems = (data, prev, multiple) => {
22
21
  };
23
22
  const defaultFilter = ({ label, input }) => label.toLowerCase().includes(input.value.trim().toLowerCase());
24
23
  const Suggestion = forwardRef(function Suggestion({ children, className, creatable = false, defaultSelected, filter = true, multiple = false, name, onBeforeMatch, onSelectedChange, renderSelected = ({ label }) => label, selected, ...rest }, ref) {
25
- const uComboboxRef = useRef(null);
24
+ const dsSuggestionRef = useRef(null);
26
25
  const genId = useId();
27
26
  const selectId = rest.id ? `${rest.id}-select` : genId;
28
27
  const isControlled = selected !== undefined;
29
- const mergedRefs = useMergeRefs([ref, uComboboxRef]);
28
+ const mergedRefs = useMergeRefs([ref, dsSuggestionRef]);
30
29
  const [isEmpty, setIsEmpty] = useState(false);
31
30
  const [defaultItems, setDefaultItems] = useState(sanitizeItems(defaultSelected));
32
31
  const selectedItems = selected ? sanitizeItems(selected) : defaultItems;
@@ -43,7 +42,7 @@ const Suggestion = forwardRef(function Suggestion({ children, className, creatab
43
42
  * Listerners and handling of adding/removing
44
43
  */
45
44
  useEffect(() => {
46
- const combobox = uComboboxRef.current;
45
+ const combobox = dsSuggestionRef.current;
47
46
  const beforeChange = (event) => {
48
47
  event.preventDefault();
49
48
  const multiple = combobox?.multiple;
@@ -58,13 +57,13 @@ const Suggestion = forwardRef(function Suggestion({ children, className, creatab
58
57
  }, [isControlled]);
59
58
  // Before match event listener
60
59
  useEffect(() => {
61
- const combobox = uComboboxRef.current;
60
+ const combobox = dsSuggestionRef.current;
62
61
  const beforeMatch = (e) => onBeforeMatch?.(e);
63
62
  combobox?.addEventListener('comboboxbeforematch', beforeMatch);
64
63
  return () => combobox?.removeEventListener('comboboxbeforematch', beforeMatch);
65
64
  }, [onBeforeMatch]);
66
65
  const handleFilter = useCallback(() => {
67
- const { control: input, options = [] } = uComboboxRef?.current || {};
66
+ const { control: input, options = [] } = dsSuggestionRef?.current || {};
68
67
  const filterFn = filter === true ? defaultFilter : filter;
69
68
  let disabled = 0;
70
69
  let index = 0;
@@ -84,7 +83,8 @@ const Suggestion = forwardRef(function Suggestion({ children, className, creatab
84
83
  }
85
84
  setIsEmpty(index === disabled);
86
85
  }, [filter]);
87
- return (jsx(SuggestionContext.Provider, { value: { isEmpty, handleFilter, uComboboxRef }, children: jsxs("u-combobox", { "data-multiple": multiple || undefined, "data-creatable": creatable || undefined, class: cl('ds-suggestion', className), ref: mergedRefs, ...rest, children: [selectedItems.map((item) => (jsx(ChipComponent.Removable, { value: item.value, asChild: true, children: jsx("data", { children: renderSelected(item) }) }, item.value))), children, !!name && (jsx("select", { name: name, multiple: true, hidden: true, id: selectId }))] }) }));
86
+ return (jsx(SuggestionContext.Provider, { value: { isEmpty, handleFilter, dsSuggestionRef }, children: jsxs("ds-suggestion", { "data-multiple": multiple || undefined, "data-creatable": creatable || undefined, class: cl('ds-suggestion', className), ref: mergedRefs, suppressHydrationWarning // Since <ds-suggestion> adds attributes
87
+ : true, ...rest, children: [selectedItems.map((item) => (jsx("data", { value: item.value, children: renderSelected(item) }, item.value))), children, !!name && (jsx("select", { name: name, multiple: true, hidden: true, id: selectId }))] }) }));
88
88
  });
89
89
  const SuggestionContext = createContext({
90
90
  handleFilter: () => undefined,
@@ -1,9 +1,7 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useContext } from 'react';
4
- import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-refs.js';
5
- import { RovingFocusRoot } from '../../utilities/roving-focus/roving-focus-root.js';
6
- import { Context } from './tabs.js';
3
+ import '@digdir/designsystemet-web';
4
+ import { forwardRef } from 'react';
7
5
 
8
6
  /**
9
7
  * The container for all `Tab` components.
@@ -14,10 +12,9 @@ import { Context } from './tabs.js';
14
12
  * <TabsTab value='2'>Tab 2</TabsTab>
15
13
  * </TabsList>
16
14
  */
17
- const TabsList = forwardRef(function TabsList({ children, ...rest }, ref) {
18
- const { value, tablistRef } = useContext(Context);
19
- const mergedRefs = useMergeRefs([ref, tablistRef]);
20
- return (jsx(RovingFocusRoot, { role: 'tablist', activeValue: value, orientation: 'ambiguous', ref: mergedRefs, ...rest, children: children }));
15
+ const TabsList = forwardRef(function TabsList({ className, children, ...rest }, ref) {
16
+ return (jsx("ds-tablist", { suppressHydrationWarning // Since <ds-tablist> adds attributes
17
+ : true, ref: ref, class: className, ...rest, children: children }));
21
18
  });
22
19
 
23
20
  export { TabsList };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useContext, useId, useState, useRef, useEffect } from 'react';
4
- import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-refs.js';
3
+ import '@digdir/designsystemet-web';
4
+ import { forwardRef, useContext } from 'react';
5
5
  import { Context } from './tabs.js';
6
6
 
7
7
  /**
@@ -10,33 +10,10 @@ import { Context } from './tabs.js';
10
10
  * @example
11
11
  * <TabsPanel value='1'>content 1</TabsPanel>
12
12
  */
13
- const TabsPanel = forwardRef(function TabsPanel({ children, value, id, ...rest }, ref) {
14
- const { value: tabsValue, tablistRef, setPanelButtonMap, } = useContext(Context);
15
- const active = value === tabsValue;
16
- const generatedId = useId();
17
- const panelId = id ?? `tabpanel-${generatedId}`;
18
- const [hasTabbableElement, setHasTabbableElement] = useState(false);
19
- const [labelledBy, setLabelledBy] = useState(undefined);
20
- const internalRef = useRef(null);
21
- const mergedRef = useMergeRefs([ref, internalRef]);
22
- /* Check if the panel has any tabbable elements */
23
- useEffect(() => {
24
- if (!internalRef.current)
25
- return;
26
- const tabbableElements = internalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
27
- setHasTabbableElement(tabbableElements.length > 0);
28
- }, [children]);
29
- /* get associated button */
30
- useEffect(() => {
31
- if (!tablistRef)
32
- return;
33
- const button = tablistRef.current?.querySelector(`[role="tab"][data-value="${value}"]`);
34
- setLabelledBy(button ? button.id : undefined);
35
- if (button) {
36
- setPanelButtonMap?.((prev) => new Map(prev).set(button.id, panelId));
37
- }
38
- }, [tablistRef]);
39
- return (jsx("div", { ref: mergedRef, id: panelId, role: 'tabpanel', tabIndex: hasTabbableElement ? undefined : 0, "aria-labelledby": labelledBy, hidden: !active, ...rest, children: children }));
13
+ const TabsPanel = forwardRef(function TabsPanel({ children, value, id, className, ...rest }, ref) {
14
+ const { getPrefixedValue } = useContext(Context);
15
+ return (jsx("ds-tabpanel", { suppressHydrationWarning // Since <ds-tablist> adds attributes
16
+ : true, ref: ref, id: id ?? getPrefixedValue?.(value), class: className, ...rest, children: children }));
40
17
  });
41
18
 
42
19
  export { TabsPanel };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useContext, useId } from 'react';
4
- import { RovingFocusItem } from '../../utilities/roving-focus/roving-focus-item.js';
3
+ import '@digdir/designsystemet-web';
4
+ import { forwardRef, useContext } from 'react';
5
5
  import { Context } from './tabs.js';
6
6
 
7
7
  /**
@@ -10,14 +10,16 @@ import { Context } from './tabs.js';
10
10
  * @example
11
11
  * <TabsTab value='1'>Tab 1</TabsTab>
12
12
  */
13
- const TabsTab = forwardRef(function TabsTab({ value, id, onClick, ...rest }, ref) {
14
- const tabs = useContext(Context);
15
- const generatedId = useId();
16
- const buttonId = id ?? `tab-${generatedId}`;
17
- return (jsx(RovingFocusItem, { value: value, ...rest, asChild: true, children: jsx("button", { ref: ref, id: buttonId, "aria-selected": tabs.value === value, "data-value": value, role: 'tab', type: 'button', onClick: (e) => {
18
- tabs.onChange?.(value);
19
- onClick?.(e);
20
- }, "aria-controls": tabs.panelButtonMap?.get(buttonId), ...rest }) }));
13
+ const TabsTab = forwardRef(function TabsTab({ value, className, onClick, ...rest }, ref) {
14
+ const { onChange, getPrefixedValue } = useContext(Context);
15
+ return (
16
+ // biome-ignore lint/a11y/noStaticElementInteractions: ds-tabs IS interactive
17
+ jsx("ds-tab", { "aria-controls": rest['aria-controls'] ?? getPrefixedValue?.(value), "data-value": value, ref: ref, suppressHydrationWarning // Since <ds-tablist> adds attributes
18
+ : true, onClick: (e) => {
19
+ if (e.isTrusted)
20
+ onChange?.(value); // Only call onChange is user actually clicked, not when programmatically clicked/controlled
21
+ onClick?.(e);
22
+ }, class: className, ...rest, children: rest.children }));
21
23
  });
22
24
 
23
25
  export { TabsTab };
@@ -1,7 +1,9 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
+ import '@digdir/designsystemet-web';
3
4
  import cl from 'clsx/lite';
4
- import { createContext, forwardRef, useRef, useState } from 'react';
5
+ import { createContext, forwardRef, useState, useRef, useId, useEffect } from 'react';
6
+ import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-refs.js';
5
7
 
6
8
  const Context = createContext({});
7
9
  /**
@@ -20,10 +22,11 @@ const Context = createContext({});
20
22
  * </Tabs>
21
23
  */
22
24
  const Tabs = forwardRef(function Tabs({ value, defaultValue, className, onChange, ...rest }, ref) {
23
- const tablistRef = useRef(null);
24
25
  const isControlled = value !== undefined;
25
26
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
26
- const [panelButtonMap, setPanelButtonMap] = useState(new Map());
27
+ const tabsRef = useRef(null);
28
+ const valuePrefix = useId(); // Used to generate unique value-based ids for tabs and panels
29
+ const mergedRefs = useMergeRefs([ref, tabsRef]);
27
30
  let onValueChange = onChange;
28
31
  if (!isControlled) {
29
32
  onValueChange = (newValue) => {
@@ -32,14 +35,21 @@ const Tabs = forwardRef(function Tabs({ value, defaultValue, className, onChange
32
35
  };
33
36
  value = uncontrolledValue;
34
37
  }
38
+ useEffect(() => {
39
+ if (!isControlled || !tabsRef.current || value === undefined)
40
+ return;
41
+ tabsRef.current?.tabList?.tabs?.forEach((tab) => {
42
+ if (tab.getAttribute('data-value') === value)
43
+ tab.click();
44
+ });
45
+ }, [value, isControlled]);
35
46
  return (jsx(Context.Provider, { value: {
36
47
  value,
37
48
  defaultValue,
38
49
  onChange: onValueChange,
39
- tablistRef,
40
- panelButtonMap,
41
- setPanelButtonMap,
42
- }, children: jsx("div", { className: cl('ds-tabs', className), ref: ref, ...rest }) }));
50
+ getPrefixedValue: (value) => value && `${valuePrefix}-${value}`,
51
+ }, children: jsx("ds-tabs", { suppressHydrationWarning // Since <ds-tablist> adds attributes
52
+ : true, ref: mergedRefs, class: cl('ds-tabs', className), ...rest }) }));
43
53
  });
44
54
 
45
55
  export { Context, Tabs };
@@ -6,7 +6,7 @@ import { ToggleGroupItem } from './toggle-group-item.js';
6
6
  * Display a group of buttons that can be toggled between.
7
7
  *
8
8
  * @example
9
- * <ToggleGroup onChange={(value) => console.log(value)}>
9
+ * <ToggleGroup data-toggle-group="Label" onChange={(value) => console.log(value)}>
10
10
  * <ToggleGroup.Item value='1'>Toggle 1</ToggleGroup.Item>
11
11
  * <ToggleGroup.Item value='2'>Toggle 2</ToggleGroup.Item>
12
12
  * <ToggleGroup.Item value='3'>Toggle 3</ToggleGroup.Item>
@@ -1,18 +1,20 @@
1
1
  'use client';
2
- import { jsx } from 'react/jsx-runtime';
3
- import { forwardRef } from 'react';
4
- import { RovingFocusItem } from '../../utilities/roving-focus/roving-focus-item.js';
5
- import { Button } from '../button/button.js';
6
- import { useToggleGroupItem } from './use-toggle-groupitem.js';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import cl from 'clsx/lite';
4
+ import { forwardRef, useId, useContext } from 'react';
5
+ import { ToggleGroupContext } from './toggle-group.js';
7
6
 
8
7
  /**
9
8
  * A single item in a ToggleGroup.
10
9
  * @example
11
10
  * <ToggleGroupItem value='1'>Toggle 1</ToggleGroupItem>
12
11
  */
13
- const ToggleGroupItem = forwardRef(function ToggleGroupItem(rest, ref) {
14
- const { active, buttonProps, value, variant } = useToggleGroupItem(rest);
15
- return (jsx(RovingFocusItem, { asChild: true, value: value, children: jsx(Button, { variant: active ? variant : 'tertiary', ref: ref, ...rest, ...buttonProps }) }));
12
+ const ToggleGroupItem = forwardRef(function ToggleGroupItem({ className, children, icon, value: rawValue, ...rest }, ref) {
13
+ const genValue = useId();
14
+ const toggleGroup = useContext(ToggleGroupContext);
15
+ const value = rawValue ?? genValue;
16
+ const active = toggleGroup.value === value;
17
+ return (jsxs("label", { ref: ref, ...rest, className: cl('ds-button', className), "data-variant": 'tertiary', children: [jsx("input", { checked: active, name: toggleGroup.name, onChange: () => toggleGroup.onChange?.(value), type: 'radio', value: value }), children] }));
16
18
  });
17
19
 
18
20
  export { ToggleGroupItem };
@@ -1,21 +1,20 @@
1
1
  'use client';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { jsx } from 'react/jsx-runtime';
3
3
  import cl from 'clsx/lite';
4
4
  import { createContext, forwardRef, useId, useState } from 'react';
5
- import { RovingFocusRoot } from '../../utilities/roving-focus/roving-focus-root.js';
6
5
 
7
6
  const ToggleGroupContext = createContext({});
8
7
  /**
9
8
  * Display a group of buttons that can be toggled between.
10
9
  *
11
10
  * @example
12
- * <ToggleGroup onChange={(value) => console.log(value)}>
11
+ * <ToggleGroup data-toggle-group="Label" onChange={(value) => console.log(value)}>
13
12
  * <ToggleGroup.Item value='1'>Toggle 1</ToggleGroup.Item>
14
13
  * <ToggleGroup.Item value='2'>Toggle 2</ToggleGroup.Item>
15
14
  * <ToggleGroup.Item value='3'>Toggle 3</ToggleGroup.Item>
16
15
  * </ToggleGroup>
17
16
  */
18
- const ToggleGroup = forwardRef(function ToggleGroup({ children, variant = 'primary', value, defaultValue, onChange, name, className, ...rest }, ref) {
17
+ const ToggleGroup = forwardRef(function ToggleGroup({ children, className, defaultValue, name, onChange, value, variant = 'primary', ...rest }, ref) {
19
18
  const nameId = useId();
20
19
  const isControlled = value !== undefined;
21
20
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
@@ -28,12 +27,13 @@ const ToggleGroup = forwardRef(function ToggleGroup({ children, variant = 'prima
28
27
  value = uncontrolledValue;
29
28
  }
30
29
  return (jsx(ToggleGroupContext.Provider, { value: {
31
- variant,
32
- value,
33
30
  defaultValue,
34
31
  name: name ?? `togglegroup-name-${nameId}`,
35
32
  onChange: onValueChange,
36
- }, children: jsx(RovingFocusRoot, { asChild: true, activeValue: value, orientation: 'ambiguous', children: jsxs("div", { className: cl('ds-toggle-group', className), role: 'radiogroup', "data-variant": variant, ref: ref, ...rest, children: [name && jsx("input", { type: 'hidden', name: name, value: value }), children] }) }) }));
33
+ value,
34
+ variant,
35
+ }, children: jsx("fieldset", { className: cl('ds-toggle-group', className), "data-toggle-group": '' // Default to empty string to ensure attribute is present
36
+ , "data-variant": variant, ref: ref, ...rest, children: children }) }));
37
37
  });
38
38
 
39
39
  export { ToggleGroup, ToggleGroupContext };
@@ -1,10 +1,8 @@
1
1
  'use client';
2
- import { jsxs, Fragment as Fragment$1, jsx } from 'react/jsx-runtime';
3
- import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
2
+ import { jsx } from 'react/jsx-runtime';
4
3
  import { Slot } from '@radix-ui/react-slot';
5
- import cl from 'clsx/lite';
6
- import { forwardRef, useId, useState, useRef, useEffect, Fragment, version } from 'react';
7
- import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-refs.js';
4
+ import '@digdir/designsystemet-web';
5
+ import { forwardRef } from 'react';
8
6
 
9
7
  /**
10
8
  * Tooltip component that displays a small piece of information when hovering or focusing on an element.
@@ -19,150 +17,11 @@ import { useMergeRefs } from '../../utilities/hooks/use-merge-refs/use-merge-ref
19
17
  * Hover me
20
18
  * </Tooltip>
21
19
  */
22
- const Tooltip = forwardRef(function Tooltip({ id, children, content, placement = 'top', autoPlacement = true, open, className, type, ...rest }, ref) {
23
- const randomTooltipId = useId();
24
- const [internalOpen, setInternalOpen] = useState(false);
25
- const triggerRef = useRef(null);
26
- const tooltipRef = useRef(null);
27
- const mergedRefs = useMergeRefs([tooltipRef, ref]);
28
- const controlledOpen = open ?? internalOpen;
29
- const tooltipId = id ?? randomTooltipId;
30
- const setOpen = () => {
31
- setInternalOpen(true);
32
- };
33
- const setClose = () => {
34
- setInternalOpen(false);
35
- };
36
- // Position with floating-ui
37
- useEffect(() => {
38
- const tooltip = tooltipRef.current;
39
- const trigger = triggerRef.current;
40
- tooltip?.togglePopover?.(controlledOpen);
41
- if (tooltip)
42
- tooltip.style.opacity = controlledOpen ? '1' : '0';
43
- if (tooltip && trigger && controlledOpen) {
44
- return autoUpdate(trigger, tooltip, () => {
45
- computePosition(trigger, tooltip, {
46
- placement,
47
- strategy: 'fixed',
48
- middleware: [
49
- offset((data) => {
50
- // get pseudo element arrow size
51
- const styles = getComputedStyle(data.elements.floating, '::before');
52
- return parseFloat(styles.height);
53
- }),
54
- ...(autoPlacement
55
- ? [flip({ fallbackAxisSideDirection: 'start' }), shift()]
56
- : []),
57
- shift(),
58
- arrowPseudoElement,
59
- safeAreaElement,
60
- ],
61
- }).then(({ x, y }) => {
62
- tooltip.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;
63
- });
64
- });
65
- }
66
- }, [controlledOpen, placement]);
67
- /* Add listeners for ESC to dismiss and click outside on mobile */
68
- useEffect(() => {
69
- const tooltip = tooltipRef.current;
70
- const trigger = triggerRef.current;
71
- const handleKeyDown = (event) => {
72
- if (event.key === 'Escape') {
73
- setInternalOpen(false);
74
- }
75
- };
76
- const handleClick = (event) => {
77
- const el = event.target;
78
- const isTooltip = tooltip?.contains(el);
79
- const isTrigger = trigger?.contains(el);
80
- const isOutside = !isTrigger && !isTooltip;
81
- if (isOutside && controlledOpen) {
82
- setInternalOpen(false);
83
- }
84
- };
85
- if (controlledOpen) {
86
- window.addEventListener('keydown', handleKeyDown);
87
- /* Add click listener to handle mobile tap-to-close */
88
- document.addEventListener('click', handleClick);
89
- }
90
- return () => {
91
- window.removeEventListener('keydown', handleKeyDown);
92
- document.removeEventListener('click', handleClick);
93
- };
94
- }, [controlledOpen]);
95
- /* If children is only a string, make a span */
96
- const ChildContainer = typeof children === 'string' ? 'span' : Slot;
97
- /* Make sure it is valid */
98
- if (typeof children !== 'string' && children.type === Fragment) {
99
- console.error('<Tooltip> children needs to be a single ReactElement that can receive a ref and not: <Fragment/> | <></>');
100
- return null;
101
- }
102
- const popoverProps = {
103
- [version.startsWith('19') ? 'popoverTarget' : 'popovertarget']: tooltipId,
104
- [version.startsWith('19')
105
- ? 'popoverTargetAction'
106
- : 'popovertargetaction']: 'show',
107
- };
108
- const autoType = `aria-${triggerRef.current?.innerText?.trim() ? 'describedby' : 'labelledby'}`;
109
- return (jsxs(Fragment$1, { children: [jsx(ChildContainer, { ref: triggerRef, ...popoverProps, onMouseEnter: setOpen, onMouseLeave: setClose, onFocus: setOpen, onBlur: setClose, [type ? 'aria-' + type : autoType]: tooltipId, children: children }), jsx("span", { onMouseEnter: setOpen, onMouseLeave: setClose, ref: mergedRefs, role: 'tooltip', className: cl('ds-tooltip', className), id: tooltipId, popover: 'manual', ...rest, children: content })] }));
20
+ const Tooltip = forwardRef(function Tooltip({ content, placement = 'top', autoPlacement = true, ...rest }, _ref) {
21
+ /* check if children is a string */
22
+ const isString = typeof rest.children === 'string';
23
+ return (jsx(Slot, { "aria-label": content, "data-tooltip": content, "data-placement": placement, "data-autoplacement": autoPlacement, suppressHydrationWarning // Since data-tooltip adds aria-label/aria-description
24
+ : true, ...rest, children: isString ? jsx("span", { tabIndex: 0, children: rest.children }) : rest.children }));
110
25
  });
111
- const arrowPseudoElement = {
112
- name: 'ArrowPseudoElement',
113
- fn(data) {
114
- const { elements, rects, placement } = data;
115
- let arrowX = `${Math.round(rects.reference.width / 2 + rects.reference.x - data.x)}px`;
116
- let arrowY = `${Math.round(rects.reference.height / 2 + rects.reference.y - data.y)}px`;
117
- switch (placement) {
118
- case 'top':
119
- arrowY = '100%';
120
- break;
121
- case 'right':
122
- arrowX = '0';
123
- break;
124
- case 'bottom':
125
- arrowY = '0';
126
- break;
127
- case 'left':
128
- arrowX = '100%';
129
- break;
130
- }
131
- elements.floating.style.setProperty('--dsc-tooltip-arrow-x', arrowX);
132
- elements.floating.style.setProperty('--dsc-tooltip-arrow-y', arrowY);
133
- return data;
134
- },
135
- };
136
- const safeAreaElement = {
137
- name: 'SafeAreaElement',
138
- fn(data) {
139
- const { elements, placement } = data;
140
- let width = '100%';
141
- let height = 'var(--dsc-tooltip-arrow-size)';
142
- let translate = '0px';
143
- switch (placement) {
144
- case 'top':
145
- translate = `-50% 0%`;
146
- break;
147
- case 'right':
148
- height = '100%';
149
- width = 'var(--dsc-tooltip-arrow-size)';
150
- translate = '-100% -50%';
151
- break;
152
- case 'bottom':
153
- translate = '-50% -100%';
154
- break;
155
- case 'left':
156
- height = '100%';
157
- width = 'var(--dsc-tooltip-arrow-size)';
158
- translate = '0 -50%';
159
- break;
160
- }
161
- elements.floating.style.setProperty('--_dsc-tooltip-safearea-height', height);
162
- elements.floating.style.setProperty('--_dsc-tooltip-safearea-width', width);
163
- elements.floating.style.setProperty('--_dsc-tooltip-safearea-translate', translate);
164
- return data;
165
- },
166
- };
167
26
 
168
27
  export { Tooltip };
@@ -12,7 +12,8 @@ import { forwardRef } from 'react';
12
12
  */
13
13
  const ValidationMessage = forwardRef(function ValidationMessage({ className, asChild, ...rest }, ref) {
14
14
  const Component = asChild ? Slot : 'p';
15
- return (jsx(Component, { className: cl('ds-validation-message', className), "data-field": 'validation', ref: ref, ...rest }));
15
+ return (jsx(Component, { className: cl('ds-validation-message', className), "data-field": 'validation', ref: ref, suppressHydrationWarning // Since <ds-field> adds attributes
16
+ : true, ...rest }));
16
17
  });
17
18
 
18
19
  export { ValidationMessage };
package/dist/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import '@digdir/designsystemet-web';
2
3
  export { Alert } from './components/alert/alert.js';
3
4
  export { Avatar } from './components/avatar/avatar.js';
4
5
  export { EXPERIMENTAL_AvatarStack } from './components/avatar-stack/avatar-stack.js';
@@ -1,17 +1,7 @@
1
1
  'use client';
2
+ import { pagination } from '@digdir/designsystemet-web';
2
3
  import { useMemo } from 'react';
3
4
 
4
- const getSteps = (now, max, show) => {
5
- const offset = (show - 1) / 2;
6
- const start = Math.max(1, Math.min(Math.max(now - Math.floor(offset), 1), max - show + 1));
7
- const end = Math.min(Math.max(now + Math.ceil(offset), show), max);
8
- const pages = Array.from({ length: end + 1 - start }, (_, i) => i + start);
9
- if (show > 4 && start > 1)
10
- pages.splice(0, 2, 1, 0);
11
- if (show > 3 && end < max)
12
- pages.splice(-2, 2, 0, max);
13
- return pages;
14
- };
15
5
  /**
16
6
  * Hook to help manage pagination state
17
7
  *
@@ -41,11 +31,10 @@ const getSteps = (now, max, show) => {
41
31
  * </Pagination.Item>
42
32
  * </Pagination>
43
33
  **/
44
- const usePagination = ({ currentPage = 1, setCurrentPage, onChange, totalPages = 1, showPages = 7, }) => useMemo(() => {
45
- const hasNext = currentPage < totalPages;
46
- const hasPrev = currentPage !== 1;
34
+ const usePagination = ({ currentPage: current = 1, setCurrentPage, onChange, totalPages: total = 1, showPages: show = 7, }) => useMemo(() => {
35
+ const { next, prev, pages } = pagination({ current, total, show });
47
36
  const handleClick = (page) => (event) => {
48
- if (page < 1 || page > totalPages)
37
+ if (page < 1 || page > total)
49
38
  return event.preventDefault(); // Prevent out of bounds navigation
50
39
  onChange?.(event, page);
51
40
  if (!event.defaultPrevented)
@@ -53,7 +42,7 @@ const usePagination = ({ currentPage = 1, setCurrentPage, onChange, totalPages =
53
42
  };
54
43
  return {
55
44
  /** Number of steps */
56
- pages: getSteps(currentPage, totalPages, showPages).map((page, index) => ({
45
+ pages: pages.map(({ page, current }, index) => ({
57
46
  /**
58
47
  * Page number or "ellipsis" for the ellipsis item
59
48
  */
@@ -67,29 +56,28 @@ const usePagination = ({ currentPage = 1, setCurrentPage, onChange, totalPages =
67
56
  */
68
57
  buttonProps: (page
69
58
  ? {
70
- 'aria-current': page === currentPage ? 'page' : undefined,
59
+ 'aria-current': current ? 'true' : undefined,
71
60
  onClick: handleClick(page),
72
- variant: page === currentPage ? 'primary' : 'tertiary',
73
61
  }
74
62
  : null),
75
63
  })),
76
64
  /** Properties to spread on Pagination.Button used for previous naviagation */
77
65
  prevButtonProps: {
78
- 'aria-hidden': !hasPrev, // Using aria-hidden to support all HTML elements because of potential asChild
79
- onClick: handleClick(currentPage - 1),
66
+ 'aria-hidden': !prev, // Using aria-hidden to support all HTML elements because of potential asChild
67
+ onClick: handleClick(prev),
80
68
  variant: 'tertiary',
81
69
  },
82
70
  /** Properties to spread on Pagination.Button used for next naviagation */
83
71
  nextButtonProps: {
84
- 'aria-hidden': !hasNext, // Using aria-hidden to support all HTML elements because of potential asChild
85
- onClick: handleClick(currentPage + 1),
72
+ 'aria-hidden': !next, // Using aria-hidden to support all HTML elements because of potential asChild
73
+ onClick: handleClick(next),
86
74
  variant: 'tertiary',
87
75
  },
88
76
  /** Indication if previous page action should be shown or not */
89
- hasPrev,
77
+ hasPrev: !!prev,
90
78
  /** Indication if next page action should be shown or not */
91
- hasNext,
79
+ hasNext: !!next,
92
80
  };
93
- }, [currentPage, totalPages, showPages]);
81
+ }, [current, total, show]);
94
82
 
95
83
  export { usePagination };
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import 'react';
3
+ import '@digdir/designsystemet-web';
4
+ export { RovingFocusItem, getNextFocusableValue, getPrevFocusableValue } from './roving-focus/roving-focus-item.js';
5
+ export { RovingFocusRoot } from './roving-focus/roving-focus-root.js';
6
+
7
+ const warn = (message, ...args) => typeof window === 'undefined' ||
8
+ window.dsWarnings === false ||
9
+ console.warn(`Designsystemet: ${message}`, ...args);
10
+
11
+ export { warn };
@@ -3,6 +3,7 @@ import { jsx } from 'react/jsx-runtime';
3
3
  import { Slot } from '@radix-ui/react-slot';
4
4
  import { forwardRef } from 'react';
5
5
  import { useMergeRefs } from '../hooks/use-merge-refs/use-merge-refs.js';
6
+ import '@digdir/designsystemet-web';
6
7
  import { useRovingFocus } from './use-roving-focus.js';
7
8
 
8
9
  /** Get the next focusable RovingFocusItem */
@@ -15,6 +16,7 @@ function getPrevFocusableValue(items, value) {
15
16
  const currIndex = items.findIndex((item) => item.value === value);
16
17
  return items.at(currIndex === 0 ? -1 : currIndex - 1);
17
18
  }
19
+ /** @deprecated RovingFocusItem is deprecated.*/
18
20
  const RovingFocusItem = forwardRef(({ value, asChild, ...rest }, ref) => {
19
21
  const Component = asChild ? Slot : 'div';
20
22
  const focusValue = value ?? (typeof rest.children === 'string' ? rest.children : '');