@obosbbl/grunnmuren-react 2.0.0-canary.3 → 2.0.0-canary.31

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/dist/index.mjs CHANGED
@@ -1,10 +1,156 @@
1
- import { Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ComboBox, Group, Input, Button as Button$1, Popover, ListBox, ListBoxItem, RadioGroup as RadioGroup$1, Radio as Radio$1, Select as Select$1, SelectValue, TextField as TextField$1, TextArea as TextArea$1 } from 'react-aria-components';
2
- export { Form, I18nProvider } from 'react-aria-components';
1
+ 'use client';
2
+ import { I18nProvider, RouterProvider, useContextProps, Provider, Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, FieldError, CheckboxGroup as CheckboxGroup$1, ListBoxItem as ListBoxItem$1, ListBox as ListBox$1, ComboBox, Group, Input, Button as Button$1, Popover, RadioGroup as RadioGroup$1, Radio as Radio$1, Select as Select$1, SelectValue, TextField as TextField$1, TextArea as TextArea$1, NumberField as NumberField$1, useLocale, Breadcrumbs as Breadcrumbs$1, Breadcrumb as Breadcrumb$1, Link } from 'react-aria-components';
3
+ export { Form } from 'react-aria-components';
3
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { useRef, useState, useId } from 'react';
5
- import { cva, cx, compose } from 'cva';
6
- import { LoadingSpinner, Check, ChevronDown } from '@obosbbl/grunnmuren-icons-react';
7
- import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5nawgR.js';
5
+ import { useLayoutEffect, createContext, forwardRef, Children, useId, useState, useRef } from 'react';
6
+ import { cx, cva, compose } from 'cva';
7
+ import { ChevronDown, LoadingSpinner, Check, Close, InfoCircle, CheckCircle, Warning, CloseCircle, ChevronRight, ChevronLeft } from '@obosbbl/grunnmuren-icons-react';
8
+ import { mergeRefs } from '@react-aria/utils';
9
+
10
+ function GrunnmurenProvider({ children, locale = 'nb', navigate }) {
11
+ return /*#__PURE__*/ jsx(I18nProvider, {
12
+ locale: locale,
13
+ children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
14
+ navigate: navigate,
15
+ children: children
16
+ }) : children
17
+ });
18
+ }
19
+
20
+ const canUseDOM = ()=>{
21
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
22
+ };
23
+ const useClientLayoutEffect = canUseDOM() ? useLayoutEffect : ()=>{};
24
+
25
+ const HeadingContext = /*#__PURE__*/ createContext({});
26
+ const Heading = (props, ref)=>{
27
+ [props, ref] = useContextProps(props, ref, HeadingContext);
28
+ const { children, level, className, _innerWrapper: innerWrapper, ...restProps } = props;
29
+ const Element = `h${level}`;
30
+ return /*#__PURE__*/ jsx(Element, {
31
+ ...restProps,
32
+ className: className,
33
+ "data-slot": "heading",
34
+ children: innerWrapper ? innerWrapper(children) : children
35
+ });
36
+ };
37
+ const ContentContext = /*#__PURE__*/ createContext({});
38
+ const Content = (props, ref)=>{
39
+ [props, ref] = useContextProps(props, ref, ContentContext);
40
+ const { _outerWrapper: outerWrapper, ...restProps } = props;
41
+ const content = /*#__PURE__*/ jsx("div", {
42
+ ...restProps,
43
+ "data-slot": "content"
44
+ });
45
+ return outerWrapper ? outerWrapper(content) : content;
46
+ };
47
+ const Footer = (props)=>/*#__PURE__*/ jsx("div", {
48
+ ...props,
49
+ "data-slot": "footer"
50
+ });
51
+
52
+ function Accordion(props, ref) {
53
+ const { children, className, ...restProps } = props;
54
+ const childCount = Children.count(children);
55
+ return /*#__PURE__*/ jsx("div", {
56
+ ...restProps,
57
+ ref: ref,
58
+ className: cx('rounded-lg bg-white', className),
59
+ children: Children.map(children, (child, index)=>/*#__PURE__*/ jsxs(Fragment, {
60
+ children: [
61
+ child,
62
+ index < childCount - 1 && // Margin is added to enable support for containers with a background color
63
+ /*#__PURE__*/ jsx("hr", {
64
+ className: "mx-2 border-gray-light",
65
+ "aria-hidden": true
66
+ })
67
+ ]
68
+ }))
69
+ });
70
+ }
71
+ function AccordionItem(props, ref) {
72
+ const { className, children, defaultOpen = false, isOpen: controlledIsOpen, onOpenChange, ...restProps } = props;
73
+ const contentId = useId();
74
+ const buttonId = useId();
75
+ const isControlled = controlledIsOpen != null;
76
+ // This component has internal state that controls whether it is open or not,
77
+ // regardless if we are controlled or uncontrolled.
78
+ // If we are controlled, we use a layout effect to sync the controlled state
79
+ // with the internal state.
80
+ //
81
+ const [isOpen, setIsOpen] = useState(// If we are controlled, use that open state, otherwise use the uncontrolled
82
+ isControlled ? controlledIsOpen : defaultOpen);
83
+ useClientLayoutEffect(()=>{
84
+ if (isControlled) {
85
+ setIsOpen(controlledIsOpen);
86
+ }
87
+ }, [
88
+ controlledIsOpen,
89
+ isControlled
90
+ ]);
91
+ const handleOpenChange = ()=>{
92
+ const newOpenState = !isOpen;
93
+ if (!isControlled) {
94
+ setIsOpen(newOpenState);
95
+ }
96
+ // Always call the change handler, even if we're uncontrolled.
97
+ // Easier to add stuff such as tracking etc.
98
+ if (onOpenChange) {
99
+ onOpenChange(newOpenState);
100
+ }
101
+ };
102
+ return /*#__PURE__*/ jsx("div", {
103
+ ...restProps,
104
+ className: cx('group relative px-2', className),
105
+ ref: ref,
106
+ "data-open": isOpen,
107
+ children: /*#__PURE__*/ jsx(Provider, {
108
+ values: [
109
+ [
110
+ HeadingContext,
111
+ {
112
+ // Negative margin to strech the button to the entire with of the accordion (to support containers with a background color)
113
+ className: 'font-semibold leading-7 -mx-2 text-base',
114
+ // Supply a default level here to make this typecheck ok. Will be overwritten with the consumers set heading level anyways
115
+ level: 3,
116
+ _innerWrapper: (children)=>/*#__PURE__*/ jsxs("button", {
117
+ "aria-controls": contentId,
118
+ "aria-expanded": isOpen,
119
+ // Use outline with offset as focus indicator, this does not cover the left mint border on the expanded content and works with or without a background color on the accordion container
120
+ className: "flex min-h-[44px] w-full items-center justify-between gap-1.5 rounded-lg px-2 py-3.5 text-left focus-visible:outline focus-visible:outline-4 focus-visible:outline-offset-[-6px] focus-visible:outline-black",
121
+ id: buttonId,
122
+ onClick: handleOpenChange,
123
+ children: [
124
+ children,
125
+ /*#__PURE__*/ jsx(ChevronDown, {
126
+ className: cx('flex-none transition-transform duration-300 motion-reduce:transition-none', isOpen && 'rotate-180')
127
+ })
128
+ ]
129
+ })
130
+ }
131
+ ],
132
+ [
133
+ ContentContext,
134
+ {
135
+ className: // Uses pseudo element for vertical padding, since that doesn't affect the height when the accordion is closed
136
+ 'text-sm font-light leading-6 px-3.5 relative overflow-hidden border-mint border-l-[3px] before:relative before:block before:h-1.5 after:relative after:block after:h-1.5',
137
+ role: 'region',
138
+ // @ts-expect-error TODO: remove this expect-error when we're on React 19 https://github.com/facebook/react/issues/17157#issuecomment-2003750544
139
+ inert: isOpen ? undefined : 'true',
140
+ 'aria-labelledby': buttonId,
141
+ _outerWrapper: (children)=>/*#__PURE__*/ jsx("div", {
142
+ className: cx('grid transition-all duration-300 after:relative after:block after:h-0 after:transition-all after:duration-300 motion-reduce:transition-none', isOpen ? 'grid-rows-[1fr] after:h-3.5' : 'grid-rows-[0fr] '),
143
+ children: children
144
+ })
145
+ }
146
+ ]
147
+ ],
148
+ children: children
149
+ })
150
+ });
151
+ }
152
+ const _Accordion = /*#__PURE__*/ forwardRef(Accordion);
153
+ const _AccordionItem = /*#__PURE__*/ forwardRef(AccordionItem);
8
154
 
9
155
  /**
10
156
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
@@ -35,8 +181,7 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
35
181
  * @default false
36
182
  */ isIconOnly: {
37
183
  true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
38
- false: // The of-type classes takes care to add spacing when the button is used with icons
39
- 'px-4 py-2 [&>svg]:first-of-type:mr-2.5 [&>svg]:last-of-type:ml-2.5'
184
+ false: 'gap-2.5 px-4 py-2'
40
185
  }
41
186
  },
42
187
  compoundVariants: [
@@ -89,15 +234,15 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
89
234
  isIconOnly: false
90
235
  }
91
236
  });
92
- function Button(props) {
237
+ function Button(props, forwardedRef) {
93
238
  const { children, className, color, isIconOnly, isLoading, variant, style, ...restProps } = props;
94
- // TODO: Merge refs when we use RAC
95
- const buttonRef = useRef(null);
96
239
  const [widthOverride, setWidthOverride] = useState();
240
+ const ownRef = useRef(null);
241
+ const ref = mergeRefs(ownRef, forwardedRef);
97
242
  useClientLayoutEffect(()=>{
98
243
  if (isLoading) {
99
244
  const requestID = window.requestAnimationFrame(()=>{
100
- setWidthOverride(buttonRef?.current?.getBoundingClientRect()?.width);
245
+ setWidthOverride(ownRef.current?.getBoundingClientRect()?.width);
101
246
  });
102
247
  return ()=>{
103
248
  setWidthOverride(undefined);
@@ -123,7 +268,7 @@ function Button(props) {
123
268
  isIconOnly,
124
269
  variant
125
270
  }),
126
- ref: buttonRef,
271
+ ref: ref,
127
272
  style: {
128
273
  ...style,
129
274
  width: widthOverride
@@ -135,14 +280,17 @@ function Button(props) {
135
280
  }) : children
136
281
  }));
137
282
  }
283
+ const _Button = /*#__PURE__*/ forwardRef(Button);
138
284
 
139
285
  const formField = cx('group flex flex-col gap-2');
140
286
  const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
141
287
  const input = cva({
142
288
  base: [
143
- 'rounded-md px-3 py-2.5 text-sm font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
289
+ 'rounded-md py-2.5 text-base font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
144
290
  // invalid styles
145
- 'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red'
291
+ 'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red',
292
+ // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
293
+ 'appearance-none'
146
294
  ],
147
295
  variants: {
148
296
  // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
@@ -151,9 +299,8 @@ const input = cva({
151
299
  visible: 'data-[focus-visible]:ring-2 group-data-[invalid]:data-[focus-visible]:ring'
152
300
  },
153
301
  isGrouped: {
154
- false: '',
155
- //
156
- true: 'flex-1 !ring-0 first:pl-0 last:pr-0'
302
+ false: 'bg-white px-3',
303
+ true: 'flex-1 !ring-0'
157
304
  }
158
305
  },
159
306
  defaultVariants: {
@@ -161,10 +308,14 @@ const input = cva({
161
308
  isGrouped: false
162
309
  }
163
310
  });
164
- const inputGroup = cx('inline-flex items-center overflow-hidden rounded-md px-3 ring-1 ring-black focus-within:ring-2 group-data-[invalid]:ring-2 group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring');
311
+ const inputGroup = cx([
312
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-2',
313
+ 'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring'
314
+ ]);
165
315
  const dropdown = {
166
- popover: cx('min-w-[--trigger-width] overflow-auto rounded-md border border-black bg-white shadow data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out'),
167
- listbox: cx('text-sm outline-none'),
316
+ popover: cx(// Use outline + clip-path hack instead of border to prevent scrollbars from overflowing border-radius
317
+ 'min-w-[--trigger-width] overflow-y-auto rounded-md bg-white shadow outline outline-1 outline-offset-[-1px] outline-black [clip-path:content-box] data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out'),
318
+ listbox: cx('max-h-[25rem] text-sm outline-none'),
168
319
  chevronIcon: cx('text-base transition-transform duration-150 group-data-[open]:rotate-180 motion-reduce:transition-none')
169
320
  };
170
321
 
@@ -178,22 +329,13 @@ function ErrorMessage(props) {
178
329
  });
179
330
  }
180
331
 
181
- function Description(props) {
182
- const { className, ...restProps } = props;
183
- return /*#__PURE__*/ jsx(Text, {
184
- ...restProps,
185
- className: cx(className, 'text-sm font-light leading-6'),
186
- slot: "description"
187
- });
188
- }
189
-
190
332
  const defaultClasses$1 = cx([
191
333
  'group relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7'
192
334
  ]);
193
335
  // Pulling this out into it's own component. Will probably export it in the future
194
336
  // so it can be used in other views, outside of an input of type checkbox, like in table rows.
195
337
  function CheckmarkBox() {
196
- return /*#__PURE__*/ jsx("div", {
338
+ return /*#__PURE__*/ jsx("span", {
197
339
  className: cx([
198
340
  'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
199
341
  // to vertically align the radio we need to calculate the label's height, which is equal to it's font size multiplied by the line height.
@@ -216,12 +358,12 @@ function CheckmarkBox() {
216
358
  })
217
359
  });
218
360
  }
219
- function Checkbox(props) {
361
+ function Checkbox(props, ref) {
220
362
  const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
221
363
  const id = useId();
222
364
  const descriptionId = 'desc' + id;
223
365
  const errorMessageId = 'error' + id;
224
- const isInvalid = _isInvalid || errorMessage != null;
366
+ const isInvalid = errorMessage != null || _isInvalid;
225
367
  return /*#__PURE__*/ jsx("div", {
226
368
  children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
227
369
  value: {
@@ -233,17 +375,20 @@ function Checkbox(props) {
233
375
  ...restProps,
234
376
  className: cx(className, defaultClasses$1),
235
377
  isInvalid: isInvalid,
378
+ ref: ref,
236
379
  children: [
237
- /*#__PURE__*/ jsx("div", {
380
+ /*#__PURE__*/ jsx("span", {
238
381
  className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
239
382
  }),
240
383
  /*#__PURE__*/ jsx(CheckmarkBox, {}),
241
384
  children
242
385
  ]
243
386
  }),
244
- description && /*#__PURE__*/ jsx(Description, {
245
- className: "block",
387
+ description && // {/* Use a div instead of the Description component to avoid infinite re-render loops in React until this bug in RAC is fixed: https://github.com/adobe/react-spectrum/issues/6229 */}
388
+ /*#__PURE__*/ jsx("div", {
246
389
  id: descriptionId,
390
+ slot: "description",
391
+ className: "description block",
247
392
  children: description
248
393
  }),
249
394
  errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
@@ -255,6 +400,7 @@ function Checkbox(props) {
255
400
  })
256
401
  });
257
402
  }
403
+ const _Checkbox = /*#__PURE__*/ forwardRef(Checkbox);
258
404
 
259
405
  function Label(props) {
260
406
  const { children, className, ...restProps } = props;
@@ -265,14 +411,37 @@ function Label(props) {
265
411
  });
266
412
  }
267
413
 
268
- function CheckboxGroup(props) {
414
+ function Description(props) {
415
+ const { className, ...restProps } = props;
416
+ return /*#__PURE__*/ jsx(Text, {
417
+ ...restProps,
418
+ className: cx(className, 'description'),
419
+ slot: "description"
420
+ });
421
+ }
422
+
423
+ /**
424
+ * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
425
+ * In other words, this handles controlled and uncontrolled form errors.
426
+ */ function ErrorMessageOrFieldError({ errorMessage }) {
427
+ return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
428
+ children: errorMessage
429
+ }) : /*#__PURE__*/ jsx(FieldError, {
430
+ className: formFieldError
431
+ });
432
+ }
433
+
434
+ function CheckboxGroup(props, ref) {
269
435
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
270
- const isInvalid = _isInvalid || errorMessage != null;
436
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
437
+ // which will override any built in validation
438
+ const isInvalid = errorMessage != null || _isInvalid;
271
439
  return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
272
440
  ...restProps,
273
441
  className: cx(className, 'flex flex-col gap-2'),
274
442
  isInvalid: isInvalid,
275
443
  isRequired: isRequired,
444
+ ref: ref,
276
445
  children: [
277
446
  label && /*#__PURE__*/ jsx(Label, {
278
447
  children: label
@@ -281,27 +450,52 @@ function CheckboxGroup(props) {
281
450
  children: description
282
451
  }),
283
452
  children,
284
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
285
- children: errorMessage
453
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
454
+ errorMessage: errorMessage
286
455
  })
287
456
  ]
288
457
  });
289
458
  }
459
+ const _CheckboxGroup = /*#__PURE__*/ forwardRef(CheckboxGroup);
290
460
 
291
- /**
292
- * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
293
- * In other words, this handles controlled and uncontrolled form errors.
294
- */ function ErrorMessageOrFieldError({ errorMessage }) {
295
- return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
296
- children: errorMessage
297
- }) : /*#__PURE__*/ jsx(FieldError, {
298
- className: formFieldError
461
+ const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
462
+ ...restProps,
463
+ className: cx(dropdown.listbox, className)
464
+ });
465
+ const ListBoxItem = (props)=>{
466
+ let textValue = props.textValue;
467
+ // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
468
+ // Since we use a render function (to handle the selected state) the child is never a string.
469
+ // This condition adds back that behaviour
470
+ if (textValue == null && typeof props.children === 'string') {
471
+ textValue = props.children;
472
+ }
473
+ return /*#__PURE__*/ jsx(ListBoxItem$1, {
474
+ ...props,
475
+ className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-[focused]:bg-sky-lightest'),
476
+ textValue: textValue,
477
+ children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
478
+ children: [
479
+ isSelected && /*#__PURE__*/ jsx(Check, {
480
+ className: "-ml-6 text-base"
481
+ }),
482
+ props.children
483
+ ]
484
+ })
485
+ });
486
+ };
487
+
488
+ function InputAddonDivider() {
489
+ return /*#__PURE__*/ jsx("span", {
490
+ className: "block h-6 w-px flex-none bg-black"
299
491
  });
300
492
  }
301
493
 
302
- function Combobox(props) {
494
+ function Combobox(props, ref) {
303
495
  const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
304
- const isInvalid = _isInvalid || errorMessage != null;
496
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
497
+ // which will override any built in validation
498
+ const isInvalid = errorMessage != null || _isInvalid;
305
499
  return /*#__PURE__*/ jsxs(ComboBox, {
306
500
  ...restProps,
307
501
  className: cx(className, formField),
@@ -319,7 +513,8 @@ function Combobox(props) {
319
513
  /*#__PURE__*/ jsx(Input, {
320
514
  className: input({
321
515
  isGrouped: true
322
- })
516
+ }),
517
+ ref: ref
323
518
  }),
324
519
  /*#__PURE__*/ jsx(Button$1, {
325
520
  children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
@@ -348,37 +543,19 @@ function Combobox(props) {
348
543
  ]
349
544
  });
350
545
  }
351
- const ComboboxItem = (props)=>{
352
- let textValue = props.textValue;
353
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
354
- // Since we use a render function (to handle the selected state) the child is never a string.
355
- // This condition adds back that behaviour
356
- if (textValue == null && typeof props.children === 'string') {
357
- textValue = props.children;
358
- }
359
- return /*#__PURE__*/ jsx(ListBoxItem, {
360
- ...props,
361
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
362
- textValue: textValue,
363
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
364
- children: [
365
- isSelected && /*#__PURE__*/ jsx(Check, {
366
- className: "-ml-6 text-base"
367
- }),
368
- props.children
369
- ]
370
- })
371
- });
372
- };
546
+ const _Combobox = /*#__PURE__*/ forwardRef(Combobox);
373
547
 
374
- function RadioGroup(props) {
548
+ function RadioGroup(props, ref) {
375
549
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
376
- const isInvalid = _isInvalid || errorMessage != null;
550
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
551
+ // which will override any built in validation
552
+ const isInvalid = errorMessage != null || _isInvalid;
377
553
  return /*#__PURE__*/ jsxs(RadioGroup$1, {
378
554
  ...restProps,
379
555
  className: cx(className, 'flex flex-col gap-2'),
380
556
  isInvalid: isInvalid,
381
557
  isRequired: isRequired,
558
+ ref: ref,
382
559
  children: [
383
560
  label && /*#__PURE__*/ jsx(Label, {
384
561
  children: label
@@ -387,12 +564,13 @@ function RadioGroup(props) {
387
564
  children: description
388
565
  }),
389
566
  children,
390
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
391
- children: errorMessage
567
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
568
+ errorMessage: errorMessage
392
569
  })
393
570
  ]
394
571
  });
395
572
  }
573
+ const _RadioGroup = /*#__PURE__*/ forwardRef(RadioGroup);
396
574
 
397
575
  const defaultClasses = cx([
398
576
  'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
@@ -413,13 +591,14 @@ const defaultClasses = cx([
413
591
  // so we use an inner outline to artifically pad the border
414
592
  'data-[invalid]:before:outline-solid data-[invalid]:before:border-red data-[invalid]:data-[selected]:before:!bg-red data-[invalid]:before:outline data-[invalid]:before:outline-[3px] data-[invalid]:before:outline-offset-[-3px] data-[invalid]:before:outline-red'
415
593
  ]);
416
- function Radio(props) {
594
+ function Radio(props, ref) {
417
595
  const { children, className, description, ...restProps } = props;
418
596
  return /*#__PURE__*/ jsxs(Radio$1, {
419
597
  ...restProps,
420
598
  className: cx(className, defaultClasses),
599
+ ref: ref,
421
600
  children: [
422
- /*#__PURE__*/ jsx("div", {
601
+ /*#__PURE__*/ jsx("span", {
423
602
  className: "absolute -left-2.5 top-0 z-10 h-11 w-11 "
424
603
  }),
425
604
  /*#__PURE__*/ jsxs("div", {
@@ -434,10 +613,13 @@ function Radio(props) {
434
613
  ]
435
614
  });
436
615
  }
616
+ const _Radio = /*#__PURE__*/ forwardRef(Radio);
437
617
 
438
- function Select(props) {
618
+ function Select(props, ref) {
439
619
  const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
440
- const isInvalid = _isInvalid || errorMessage != null;
620
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
621
+ // which will override any built in validation
622
+ const isInvalid = errorMessage != null || _isInvalid;
441
623
  return /*#__PURE__*/ jsxs(Select$1, {
442
624
  ...restProps,
443
625
  className: cx(className, formField),
@@ -454,6 +636,8 @@ function Select(props) {
454
636
  focusModifier: 'visible'
455
637
  }), // How to reuse placeholder text?
456
638
  'inline-flex cursor-default items-center gap-2'),
639
+ // See https://github.com/adobe/react-spectrum/discussions/4792#discussioncomment-6492305
640
+ ref: ref,
457
641
  children: [
458
642
  /*#__PURE__*/ jsx(SelectValue, {
459
643
  className: "flex-1 truncate text-left data-[placeholder]:text-[#727070]"
@@ -476,32 +660,11 @@ function Select(props) {
476
660
  ]
477
661
  });
478
662
  }
479
- const SelectItem = (props)=>{
480
- let textValue = props.textValue;
481
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
482
- // Since we use a render function (to handle the selected state) the child is never a string.
483
- // This condition adds back that behaviour
484
- if (textValue == null && typeof props.children === 'string') {
485
- textValue = props.children;
486
- }
487
- return /*#__PURE__*/ jsx(ListBoxItem, {
488
- ...props,
489
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
490
- textValue: textValue,
491
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
492
- children: [
493
- isSelected && /*#__PURE__*/ jsx(Check, {
494
- className: "-ml-6 text-base"
495
- }),
496
- props.children
497
- ]
498
- })
499
- });
500
- };
663
+ const _Select = /*#__PURE__*/ forwardRef(Select);
501
664
 
502
- function TextArea(props) {
665
+ function TextArea(props, ref) {
503
666
  const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
504
- const isInvalid = _isInvalid || errorMessage != null;
667
+ const isInvalid = errorMessage != null || _isInvalid;
505
668
  return /*#__PURE__*/ jsxs(TextField$1, {
506
669
  ...restProps,
507
670
  className: cx(className, formField),
@@ -515,7 +678,8 @@ function TextArea(props) {
515
678
  }),
516
679
  /*#__PURE__*/ jsx(TextArea$1, {
517
680
  className: input(),
518
- rows: rows
681
+ rows: rows,
682
+ ref: ref
519
683
  }),
520
684
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
521
685
  errorMessage: errorMessage
@@ -523,19 +687,26 @@ function TextArea(props) {
523
687
  ]
524
688
  });
525
689
  }
690
+ const _TextArea = /*#__PURE__*/ forwardRef(TextArea);
526
691
 
527
- const inputWithAlignment = compose(input, cva({
692
+ const inputVariants$1 = compose(input, cva({
528
693
  base: '',
529
694
  variants: {
530
695
  textAlign: {
531
696
  right: 'text-right',
532
697
  left: ''
698
+ },
699
+ autoWidth: {
700
+ true: 'box-content max-w-fit',
701
+ false: ''
533
702
  }
534
703
  }
535
704
  }));
536
- function TextField(props) {
537
- const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
538
- const isInvalid = _isInvalid || errorMessage != null;
705
+ function TextField(props, ref) {
706
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
707
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
708
+ // which will override any built in validation
709
+ const isInvalid = errorMessage != null || _isInvalid;
539
710
  return /*#__PURE__*/ jsxs(TextField$1, {
540
711
  ...restProps,
541
712
  className: cx(className, formField),
@@ -548,27 +719,96 @@ function TextField(props) {
548
719
  children: description
549
720
  }),
550
721
  leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
551
- className: inputGroup,
722
+ className: cx(inputGroup, {
723
+ 'w-fit': !!size
724
+ }),
552
725
  children: [
553
726
  leftAddon,
554
- withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider, {
555
- className: "ml-3"
556
- }),
727
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
557
728
  /*#__PURE__*/ jsx(Input, {
558
- className: inputWithAlignment({
729
+ className: inputVariants$1({
559
730
  textAlign,
560
- isGrouped: true
561
- })
731
+ isGrouped: true,
732
+ autoWidth: !!size
733
+ }),
734
+ ref: ref,
735
+ size: size
562
736
  }),
563
- withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider, {
564
- className: "mr-3"
737
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
738
+ rightAddon
739
+ ]
740
+ }) : /*#__PURE__*/ jsx(Input, {
741
+ className: inputVariants$1({
742
+ textAlign,
743
+ autoWidth: !!size
744
+ }),
745
+ ref: ref,
746
+ size: size
747
+ }),
748
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
749
+ errorMessage: errorMessage
750
+ })
751
+ ]
752
+ });
753
+ }
754
+ const _TextField = /*#__PURE__*/ forwardRef(TextField);
755
+
756
+ // This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
757
+ const inputVariants = compose(input, cva({
758
+ base: '',
759
+ variants: {
760
+ textAlign: {
761
+ right: 'text-right',
762
+ left: ''
763
+ },
764
+ autoWidth: {
765
+ true: 'box-content max-w-fit',
766
+ false: ''
767
+ }
768
+ }
769
+ }));
770
+ function NumberField(props, ref) {
771
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
772
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
773
+ // which will override any built in validation
774
+ const isInvalid = errorMessage != null || _isInvalid;
775
+ return /*#__PURE__*/ jsxs(NumberField$1, {
776
+ ...restProps,
777
+ className: cx(className, formField),
778
+ isInvalid: isInvalid,
779
+ children: [
780
+ label && /*#__PURE__*/ jsx(Label, {
781
+ children: label
782
+ }),
783
+ description && /*#__PURE__*/ jsx(Description, {
784
+ children: description
785
+ }),
786
+ leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
787
+ className: cx(inputGroup, {
788
+ 'w-fit': !!size
789
+ }),
790
+ children: [
791
+ leftAddon,
792
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
793
+ /*#__PURE__*/ jsx(Input, {
794
+ className: inputVariants({
795
+ textAlign,
796
+ isGrouped: true,
797
+ autoWidth: !!size
798
+ }),
799
+ ref: ref,
800
+ size: size
565
801
  }),
802
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
566
803
  rightAddon
567
804
  ]
568
805
  }) : /*#__PURE__*/ jsx(Input, {
569
- className: inputWithAlignment({
570
- textAlign
571
- })
806
+ className: inputVariants({
807
+ textAlign,
808
+ autoWidth: !!size
809
+ }),
810
+ ref: ref,
811
+ size: size
572
812
  }),
573
813
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
574
814
  errorMessage: errorMessage
@@ -576,10 +816,172 @@ function TextField(props) {
576
816
  ]
577
817
  });
578
818
  }
579
- function Divider({ className }) {
580
- return /*#__PURE__*/ jsx("span", {
581
- className: cx(className, 'block h-6 w-px flex-none bg-black')
819
+ const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
820
+
821
+ // TODO: add new icons
822
+ const iconMap = {
823
+ info: InfoCircle,
824
+ success: CheckCircle,
825
+ warning: Warning,
826
+ danger: CloseCircle
827
+ };
828
+ const alertVariants = cva({
829
+ base: [
830
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
831
+ // Heading styles:
832
+ '[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
833
+ // Content styles:
834
+ '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
835
+ // Footer styles:
836
+ '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
837
+ ],
838
+ variants: {
839
+ /**
840
+ * The variant of the alert
841
+ * @default info
842
+ */ variant: {
843
+ info: 'border-[#1A7FA7] bg-sky-light',
844
+ success: 'border-[#0F9B6E] bg-mint-light',
845
+ warning: 'border-[#C57C13] bg-[#FFF2DE]',
846
+ danger: 'border-[#C0385D] bg-red-light'
847
+ }
848
+ },
849
+ defaultVariants: {
850
+ variant: 'info'
851
+ }
852
+ });
853
+ const translations = {
854
+ close: {
855
+ nb: 'Lukk',
856
+ sv: 'Stäng',
857
+ en: 'Close'
858
+ },
859
+ showMore: {
860
+ nb: 'Les mer',
861
+ sv: 'Läs mer',
862
+ en: 'Read more'
863
+ },
864
+ showLess: {
865
+ nb: 'Vis mindre',
866
+ sv: 'Dölj',
867
+ en: 'Show less'
868
+ }
869
+ };
870
+ const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
871
+ const Icon = iconMap[variant];
872
+ const { locale } = useLocale();
873
+ const id = useId();
874
+ const [isExpanded, setIsExpanded] = useState(false);
875
+ const isCollapsed = isExpandable && !isExpanded;
876
+ const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
877
+ const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
878
+ if (!isVisible) return;
879
+ const close = ()=>{
880
+ setIsUncontrolledVisible(false);
881
+ if (onDismiss) onDismiss();
882
+ };
883
+ const isInDevMode = process.env.NODE_ENV !== 'production';
884
+ if (isInDevMode && onDismiss && !isDismissable) {
885
+ console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
886
+ }
887
+ if (isInDevMode && !children) {
888
+ console.error('`No children was passed to the <AlertBox/>` component.');
889
+ return;
890
+ }
891
+ const [firstChild, ...restChildren] = Children.toArray(children);
892
+ const lastChild = restChildren.pop();
893
+ return /*#__PURE__*/ jsxs("div", {
894
+ className: alertVariants({
895
+ className,
896
+ variant
897
+ }),
898
+ // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
899
+ // role="none" will not have any effect on a div, so it can be omitted.
900
+ role: role === 'none' ? undefined : role,
901
+ children: [
902
+ /*#__PURE__*/ jsx(Icon, {}),
903
+ firstChild,
904
+ isDismissable && /*#__PURE__*/ jsx("button", {
905
+ className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus:outline-none focus:-outline-offset-8 focus:outline-black'),
906
+ onClick: close,
907
+ "aria-label": translations.close[locale],
908
+ children: /*#__PURE__*/ jsx(Close, {})
909
+ }),
910
+ isExpandable && /*#__PURE__*/ jsxs("button", {
911
+ className: cx('relative col-span-full row-start-2 -my-3 inline-flex max-w-fit cursor-pointer items-center gap-1 py-3 text-sm leading-6', // Focus styles:
912
+ 'outline-none after:absolute after:bottom-3 after:left-0 after:right-0 after:h-0 after:bg-transparent after:transition-all after:duration-200', 'focus:after:h-[1px] focus:after:bg-black'),
913
+ onClick: ()=>setIsExpanded((prevState)=>!prevState),
914
+ "aria-expanded": isExpanded,
915
+ "aria-controls": id,
916
+ children: [
917
+ isExpanded ? translations.showLess[locale] : translations.showMore[locale],
918
+ /*#__PURE__*/ jsx(ChevronDown, {
919
+ className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
920
+ })
921
+ ]
922
+ }),
923
+ !isCollapsed && restChildren.length > 0 && /*#__PURE__*/ jsx("div", {
924
+ className: "col-span-full grid gap-y-4",
925
+ id: id,
926
+ children: restChildren
927
+ }),
928
+ lastChild
929
+ ]
930
+ });
931
+ };
932
+
933
+ function Breadcrumbs(props, ref) {
934
+ const { className, children, ...restProps } = props;
935
+ return /*#__PURE__*/ jsx(Breadcrumbs$1, {
936
+ ...restProps,
937
+ className: cx(className, 'flex flex-wrap text-sm leading-6'),
938
+ ref: ref,
939
+ children: children
940
+ });
941
+ }
942
+ const _Breadcrumbs = /*#__PURE__*/ forwardRef(Breadcrumbs);
943
+
944
+ function Breadcrumb(props, ref) {
945
+ const { className, children, href, ...restProps } = props;
946
+ return /*#__PURE__*/ jsxs(Breadcrumb$1, {
947
+ className: cx(className, 'group flex items-center'),
948
+ ...restProps,
949
+ ref: ref,
950
+ children: [
951
+ href ? /*#__PURE__*/ jsx(Link, {
952
+ href: href,
953
+ // use outline instead of ring for focus marker that can be offset without creating a white background between the focus marker and the element content
954
+ className: "rounded-sm focus:outline-none group-last:no-underline data-[focus-visible]:outline data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-black",
955
+ children: children
956
+ }) : children,
957
+ /*#__PURE__*/ jsx(ChevronRight, {
958
+ className: "px-1 group-last:hidden"
959
+ })
960
+ ]
961
+ });
962
+ }
963
+ const _Breadcrumb = /*#__PURE__*/ forwardRef(Breadcrumb);
964
+
965
+ function Backlink(props, ref) {
966
+ const { className, children, href, withUnderline, ...restProps } = props;
967
+ return /*#__PURE__*/ jsxs(Link, {
968
+ className: cx(className, 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 no-underline focus:outline-none data-[focus-visible]:ring data-[focus-visible]:ring-black'),
969
+ ...restProps,
970
+ ref: ref,
971
+ href: href,
972
+ children: [
973
+ /*#__PURE__*/ jsx(ChevronLeft, {
974
+ className: cx('-ml-[0.5em] flex-shrink-0 transition-transform duration-300 group-hover:-translate-x-1')
975
+ }),
976
+ /*#__PURE__*/ jsx("span", {
977
+ children: /*#__PURE__*/ jsx("span", {
978
+ className: cx('border-b-[1px] border-t-[1px] border-transparent transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
979
+ children: children
980
+ })
981
+ })
982
+ ]
582
983
  });
583
984
  }
985
+ const _Backlink = /*#__PURE__*/ forwardRef(Backlink);
584
986
 
585
- export { Button, Checkbox, CheckboxGroup, Combobox, ComboboxItem, Radio, RadioGroup, Select, SelectItem, TextArea, TextField };
987
+ export { _Accordion as Accordion, _AccordionItem as AccordionItem, Alertbox, _Backlink as Backlink, _Breadcrumb as Breadcrumb, _Breadcrumbs as Breadcrumbs, _Button as Button, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, Content, ContentContext, Footer, GrunnmurenProvider, Heading, HeadingContext, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField };