@obosbbl/grunnmuren-react 2.0.0-canary.2 → 2.0.0-canary.20

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';
1
+ 'use client';
2
+ import { I18nProvider, RouterProvider, useContextProps, Provider, Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, 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';
2
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',
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('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) {
93
- const { children, className, color, isIconOnly, loading, variant, style, ...restProps } = props;
94
- // TODO: Merge refs when we use RAC
95
- const buttonRef = useRef(null);
237
+ function Button(props, forwardedRef) {
238
+ const { children, className, color, isIconOnly, isLoading, variant, style, ...restProps } = props;
96
239
  const [widthOverride, setWidthOverride] = useState();
240
+ const ownRef = useRef(null);
241
+ const ref = mergeRefs(ownRef, forwardedRef);
97
242
  useClientLayoutEffect(()=>{
98
- if (loading) {
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);
@@ -105,7 +250,7 @@ function Button(props) {
105
250
  };
106
251
  }
107
252
  }, [
108
- loading,
253
+ isLoading,
109
254
  children
110
255
  ]);
111
256
  let Component = 'a';
@@ -116,14 +261,14 @@ function Button(props) {
116
261
  }
117
262
  return(// @ts-expect-error TS doesn't agree here taht restProps is safe to spread, because restProps for anchors aren't type compatible with restProps for buttons, but that should be okay here
118
263
  /*#__PURE__*/ jsx(Component, {
119
- "aria-busy": loading ? true : undefined,
264
+ "aria-busy": isLoading ? true : undefined,
120
265
  className: buttonVariants({
121
266
  className,
122
267
  color,
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-sm 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: 'px-3',
303
+ true: 'flex-1 !ring-0'
157
304
  }
158
305
  },
159
306
  defaultVariants: {
@@ -161,7 +308,12 @@ 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-sm 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
+ // Make sure icons are the correct size
315
+ '[&_svg]:text-base'
316
+ ]);
165
317
  const dropdown = {
166
318
  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
319
  listbox: cx('text-sm outline-none'),
@@ -193,7 +345,7 @@ const defaultClasses$1 = cx([
193
345
  // Pulling this out into it's own component. Will probably export it in the future
194
346
  // so it can be used in other views, outside of an input of type checkbox, like in table rows.
195
347
  function CheckmarkBox() {
196
- return /*#__PURE__*/ jsx("div", {
348
+ return /*#__PURE__*/ jsx("span", {
197
349
  className: cx([
198
350
  'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
199
351
  // 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,7 +368,7 @@ function CheckmarkBox() {
216
368
  })
217
369
  });
218
370
  }
219
- function Checkbox(props) {
371
+ function Checkbox(props, ref) {
220
372
  const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
221
373
  const id = useId();
222
374
  const descriptionId = 'desc' + id;
@@ -233,8 +385,9 @@ function Checkbox(props) {
233
385
  ...restProps,
234
386
  className: cx(className, defaultClasses$1),
235
387
  isInvalid: isInvalid,
388
+ ref: ref,
236
389
  children: [
237
- /*#__PURE__*/ jsx("div", {
390
+ /*#__PURE__*/ jsx("span", {
238
391
  className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
239
392
  }),
240
393
  /*#__PURE__*/ jsx(CheckmarkBox, {}),
@@ -255,6 +408,7 @@ function Checkbox(props) {
255
408
  })
256
409
  });
257
410
  }
411
+ const _Checkbox = /*#__PURE__*/ forwardRef(Checkbox);
258
412
 
259
413
  function Label(props) {
260
414
  const { children, className, ...restProps } = props;
@@ -265,7 +419,7 @@ function Label(props) {
265
419
  });
266
420
  }
267
421
 
268
- function CheckboxGroup(props) {
422
+ function CheckboxGroup(props, ref) {
269
423
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
270
424
  const isInvalid = _isInvalid || errorMessage != null;
271
425
  return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
@@ -273,6 +427,7 @@ function CheckboxGroup(props) {
273
427
  className: cx(className, 'flex flex-col gap-2'),
274
428
  isInvalid: isInvalid,
275
429
  isRequired: isRequired,
430
+ ref: ref,
276
431
  children: [
277
432
  label && /*#__PURE__*/ jsx(Label, {
278
433
  children: label
@@ -287,6 +442,7 @@ function CheckboxGroup(props) {
287
442
  ]
288
443
  });
289
444
  }
445
+ const _CheckboxGroup = /*#__PURE__*/ forwardRef(CheckboxGroup);
290
446
 
291
447
  /**
292
448
  * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
@@ -299,7 +455,40 @@ function CheckboxGroup(props) {
299
455
  });
300
456
  }
301
457
 
302
- function Combobox(props) {
458
+ const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
459
+ ...restProps,
460
+ className: cx(dropdown.listbox, className)
461
+ });
462
+ const ListBoxItem = (props)=>{
463
+ let textValue = props.textValue;
464
+ // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
465
+ // Since we use a render function (to handle the selected state) the child is never a string.
466
+ // This condition adds back that behaviour
467
+ if (textValue == null && typeof props.children === 'string') {
468
+ textValue = props.children;
469
+ }
470
+ return /*#__PURE__*/ jsx(ListBoxItem$1, {
471
+ ...props,
472
+ className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-[focused]:bg-sky-lightest'),
473
+ textValue: textValue,
474
+ children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
475
+ children: [
476
+ isSelected && /*#__PURE__*/ jsx(Check, {
477
+ className: "-ml-6 text-base"
478
+ }),
479
+ props.children
480
+ ]
481
+ })
482
+ });
483
+ };
484
+
485
+ function InputAddonDivider() {
486
+ return /*#__PURE__*/ jsx("span", {
487
+ className: "block h-6 w-px flex-none bg-black"
488
+ });
489
+ }
490
+
491
+ function Combobox(props, ref) {
303
492
  const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
304
493
  const isInvalid = _isInvalid || errorMessage != null;
305
494
  return /*#__PURE__*/ jsxs(ComboBox, {
@@ -319,7 +508,8 @@ function Combobox(props) {
319
508
  /*#__PURE__*/ jsx(Input, {
320
509
  className: input({
321
510
  isGrouped: true
322
- })
511
+ }),
512
+ ref: ref
323
513
  }),
324
514
  /*#__PURE__*/ jsx(Button$1, {
325
515
  children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
@@ -341,37 +531,15 @@ function Combobox(props) {
341
531
  className: cx(dropdown.popover, 'min-w-[calc(var(--trigger-width)+26px)]'),
342
532
  crossOffset: -13,
343
533
  children: /*#__PURE__*/ jsx(ListBox, {
344
- className: dropdown.listbox,
345
534
  children: children
346
535
  })
347
536
  })
348
537
  ]
349
538
  });
350
539
  }
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
- };
540
+ const _Combobox = /*#__PURE__*/ forwardRef(Combobox);
373
541
 
374
- function RadioGroup(props) {
542
+ function RadioGroup(props, ref) {
375
543
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
376
544
  const isInvalid = _isInvalid || errorMessage != null;
377
545
  return /*#__PURE__*/ jsxs(RadioGroup$1, {
@@ -379,6 +547,7 @@ function RadioGroup(props) {
379
547
  className: cx(className, 'flex flex-col gap-2'),
380
548
  isInvalid: isInvalid,
381
549
  isRequired: isRequired,
550
+ ref: ref,
382
551
  children: [
383
552
  label && /*#__PURE__*/ jsx(Label, {
384
553
  children: label
@@ -393,6 +562,7 @@ function RadioGroup(props) {
393
562
  ]
394
563
  });
395
564
  }
565
+ const _RadioGroup = /*#__PURE__*/ forwardRef(RadioGroup);
396
566
 
397
567
  const defaultClasses = cx([
398
568
  'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
@@ -413,13 +583,14 @@ const defaultClasses = cx([
413
583
  // so we use an inner outline to artifically pad the border
414
584
  '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
585
  ]);
416
- function Radio(props) {
586
+ function Radio(props, ref) {
417
587
  const { children, className, description, ...restProps } = props;
418
588
  return /*#__PURE__*/ jsxs(Radio$1, {
419
589
  ...restProps,
420
590
  className: cx(className, defaultClasses),
591
+ ref: ref,
421
592
  children: [
422
- /*#__PURE__*/ jsx("div", {
593
+ /*#__PURE__*/ jsx("span", {
423
594
  className: "absolute -left-2.5 top-0 z-10 h-11 w-11 "
424
595
  }),
425
596
  /*#__PURE__*/ jsxs("div", {
@@ -434,8 +605,9 @@ function Radio(props) {
434
605
  ]
435
606
  });
436
607
  }
608
+ const _Radio = /*#__PURE__*/ forwardRef(Radio);
437
609
 
438
- function Select(props) {
610
+ function Select(props, ref) {
439
611
  const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
440
612
  const isInvalid = _isInvalid || errorMessage != null;
441
613
  return /*#__PURE__*/ jsxs(Select$1, {
@@ -454,6 +626,8 @@ function Select(props) {
454
626
  focusModifier: 'visible'
455
627
  }), // How to reuse placeholder text?
456
628
  'inline-flex cursor-default items-center gap-2'),
629
+ // See https://github.com/adobe/react-spectrum/discussions/4792#discussioncomment-6492305
630
+ ref: ref,
457
631
  children: [
458
632
  /*#__PURE__*/ jsx(SelectValue, {
459
633
  className: "flex-1 truncate text-left data-[placeholder]:text-[#727070]"
@@ -469,37 +643,15 @@ function Select(props) {
469
643
  /*#__PURE__*/ jsx(Popover, {
470
644
  className: dropdown.popover,
471
645
  children: /*#__PURE__*/ jsx(ListBox, {
472
- className: dropdown.listbox,
473
646
  children: children
474
647
  })
475
648
  })
476
649
  ]
477
650
  });
478
651
  }
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
- };
652
+ const _Select = /*#__PURE__*/ forwardRef(Select);
501
653
 
502
- function TextArea(props) {
654
+ function TextArea(props, ref) {
503
655
  const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
504
656
  const isInvalid = _isInvalid || errorMessage != null;
505
657
  return /*#__PURE__*/ jsxs(TextField$1, {
@@ -515,7 +667,8 @@ function TextArea(props) {
515
667
  }),
516
668
  /*#__PURE__*/ jsx(TextArea$1, {
517
669
  className: input(),
518
- rows: rows
670
+ rows: rows,
671
+ ref: ref
519
672
  }),
520
673
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
521
674
  errorMessage: errorMessage
@@ -523,8 +676,9 @@ function TextArea(props) {
523
676
  ]
524
677
  });
525
678
  }
679
+ const _TextArea = /*#__PURE__*/ forwardRef(TextArea);
526
680
 
527
- const inputWithAlignment = compose(input, cva({
681
+ const inputWithAlignment$1 = compose(input, cva({
528
682
  base: '',
529
683
  variants: {
530
684
  textAlign: {
@@ -533,7 +687,7 @@ const inputWithAlignment = compose(input, cva({
533
687
  }
534
688
  }
535
689
  }));
536
- function TextField(props) {
690
+ function TextField(props, ref) {
537
691
  const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
538
692
  const isInvalid = _isInvalid || errorMessage != null;
539
693
  return /*#__PURE__*/ jsxs(TextField$1, {
@@ -551,24 +705,75 @@ function TextField(props) {
551
705
  className: inputGroup,
552
706
  children: [
553
707
  leftAddon,
554
- withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider, {
555
- className: "ml-3"
708
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
709
+ /*#__PURE__*/ jsx(Input, {
710
+ className: inputWithAlignment$1({
711
+ textAlign,
712
+ isGrouped: true
713
+ }),
714
+ ref: ref
556
715
  }),
716
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
717
+ rightAddon
718
+ ]
719
+ }) : /*#__PURE__*/ jsx(Input, {
720
+ className: inputWithAlignment$1({
721
+ textAlign
722
+ }),
723
+ ref: ref
724
+ }),
725
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
726
+ errorMessage: errorMessage
727
+ })
728
+ ]
729
+ });
730
+ }
731
+ const _TextField = /*#__PURE__*/ forwardRef(TextField);
732
+
733
+ // This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
734
+ const inputWithAlignment = compose(input, cva({
735
+ base: '',
736
+ variants: {
737
+ textAlign: {
738
+ right: 'text-right',
739
+ left: ''
740
+ }
741
+ }
742
+ }));
743
+ function NumberField(props, ref) {
744
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
745
+ const isInvalid = _isInvalid || errorMessage != null;
746
+ return /*#__PURE__*/ jsxs(NumberField$1, {
747
+ ...restProps,
748
+ className: cx(className, formField),
749
+ isInvalid: isInvalid,
750
+ children: [
751
+ label && /*#__PURE__*/ jsx(Label, {
752
+ children: label
753
+ }),
754
+ description && /*#__PURE__*/ jsx(Description, {
755
+ children: description
756
+ }),
757
+ leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
758
+ className: inputGroup,
759
+ children: [
760
+ leftAddon,
761
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
557
762
  /*#__PURE__*/ jsx(Input, {
558
763
  className: inputWithAlignment({
559
764
  textAlign,
560
765
  isGrouped: true
561
- })
562
- }),
563
- withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider, {
564
- className: "mr-3"
766
+ }),
767
+ ref: ref
565
768
  }),
769
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
566
770
  rightAddon
567
771
  ]
568
772
  }) : /*#__PURE__*/ jsx(Input, {
569
773
  className: inputWithAlignment({
570
774
  textAlign
571
- })
775
+ }),
776
+ ref: ref
572
777
  }),
573
778
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
574
779
  errorMessage: errorMessage
@@ -576,10 +781,171 @@ function TextField(props) {
576
781
  ]
577
782
  });
578
783
  }
579
- function Divider({ className }) {
580
- return /*#__PURE__*/ jsx("span", {
581
- className: cx(className, 'block h-6 w-px flex-none bg-black')
784
+ const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
785
+
786
+ // TODO: add new icons
787
+ const iconMap = {
788
+ info: InfoCircle,
789
+ success: CheckCircle,
790
+ warning: Warning,
791
+ danger: CloseCircle
792
+ };
793
+ const alertVariants = cva({
794
+ base: [
795
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
796
+ // Heading styles:
797
+ '[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
798
+ // Content styles:
799
+ '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
800
+ // Footer styles:
801
+ '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
802
+ ],
803
+ variants: {
804
+ /**
805
+ * The variant of the alert
806
+ * @default info
807
+ */ variant: {
808
+ info: 'border-[#1A7FA7] bg-sky-light',
809
+ success: 'border-[#0F9B6E] bg-mint-light',
810
+ warning: 'border-[#C57C13] bg-[#FFF2DE]',
811
+ danger: 'border-[#C0385D] bg-red-light'
812
+ }
813
+ },
814
+ defaultVariants: {
815
+ variant: 'info'
816
+ }
817
+ });
818
+ const translations = {
819
+ close: {
820
+ nb: 'Lukk',
821
+ sv: 'Stäng',
822
+ en: 'Close'
823
+ },
824
+ showMore: {
825
+ nb: 'Les mer',
826
+ sv: 'Läs mer',
827
+ en: 'Read more'
828
+ },
829
+ showLess: {
830
+ nb: 'Vis mindre',
831
+ sv: 'Dölj',
832
+ en: 'Show less'
833
+ }
834
+ };
835
+ const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
836
+ const Icon = iconMap[variant];
837
+ const { locale } = useLocale();
838
+ const id = useId();
839
+ const [isExpanded, setIsExpanded] = useState(false);
840
+ const isCollapsed = isExpandable && !isExpanded;
841
+ const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
842
+ const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
843
+ if (!isVisible) return;
844
+ const close = ()=>{
845
+ setIsUncontrolledVisible(false);
846
+ if (onDismiss) onDismiss();
847
+ };
848
+ const isInDevMode = process.env.NODE_ENV !== 'production';
849
+ if (isInDevMode && onDismiss && !isDismissable) {
850
+ console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
851
+ }
852
+ if (isInDevMode && !children) {
853
+ console.error('`No children was passed to the <AlertBox/>` component.');
854
+ return;
855
+ }
856
+ const [firstChild, ...restChildren] = Children.toArray(children);
857
+ const lastChild = restChildren.pop();
858
+ return /*#__PURE__*/ jsxs("div", {
859
+ className: alertVariants({
860
+ className,
861
+ variant
862
+ }),
863
+ // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
864
+ // role="none" will not have any effect on a div, so it can be omitted.
865
+ role: role === 'none' ? undefined : role,
866
+ children: [
867
+ /*#__PURE__*/ jsx(Icon, {}),
868
+ firstChild,
869
+ isDismissable && /*#__PURE__*/ jsx("button", {
870
+ className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus:outline-none focus:-outline-offset-8 focus:outline-black'),
871
+ onClick: close,
872
+ "aria-label": translations.close[locale],
873
+ children: /*#__PURE__*/ jsx(Close, {})
874
+ }),
875
+ isExpandable && /*#__PURE__*/ jsxs("button", {
876
+ 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:
877
+ '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'),
878
+ onClick: ()=>setIsExpanded((prevState)=>!prevState),
879
+ "aria-expanded": isExpanded,
880
+ "aria-controls": id,
881
+ children: [
882
+ isExpanded ? translations.showLess[locale] : translations.showMore[locale],
883
+ /*#__PURE__*/ jsx(ChevronDown, {
884
+ className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
885
+ })
886
+ ]
887
+ }),
888
+ !isCollapsed && restChildren.length > 0 && /*#__PURE__*/ jsx("div", {
889
+ className: "col-span-full grid gap-y-4",
890
+ id: id,
891
+ children: restChildren
892
+ }),
893
+ lastChild
894
+ ]
895
+ });
896
+ };
897
+
898
+ function Breadcrumbs(props, ref) {
899
+ const { className, children, ...restProps } = props;
900
+ return /*#__PURE__*/ jsx(Breadcrumbs$1, {
901
+ ...restProps,
902
+ className: cx(className, 'flex flex-wrap text-sm leading-6'),
903
+ ref: ref,
904
+ children: children
905
+ });
906
+ }
907
+ const _Breadcrumbs = /*#__PURE__*/ forwardRef(Breadcrumbs);
908
+
909
+ function Breadcrumb(props, ref) {
910
+ const { className, children, href, ...restProps } = props;
911
+ return /*#__PURE__*/ jsxs(Breadcrumb$1, {
912
+ className: cx(className, 'group flex items-center'),
913
+ ...restProps,
914
+ ref: ref,
915
+ children: [
916
+ href ? /*#__PURE__*/ jsx(Link, {
917
+ href: href,
918
+ className: "group-last:no-underline",
919
+ children: children
920
+ }) : children,
921
+ /*#__PURE__*/ jsx(ChevronRight, {
922
+ className: "px-1 group-last:hidden"
923
+ })
924
+ ]
925
+ });
926
+ }
927
+ const _Breadcrumb = /*#__PURE__*/ forwardRef(Breadcrumb);
928
+
929
+ function Backlink(props, ref) {
930
+ const { className, children, href, withUnderline, ...restProps } = props;
931
+ return /*#__PURE__*/ jsxs("a", {
932
+ className: cx(className, 'group flex max-w-fit items-center gap-3 rounded-md p-2.5 no-underline focus:outline-none focus-visible:ring focus-visible:ring-black'),
933
+ ...restProps,
934
+ ref: ref,
935
+ href: href,
936
+ children: [
937
+ /*#__PURE__*/ jsx(ChevronLeft, {
938
+ className: cx('-ml-[0.5em] flex-shrink-0 transition-transform duration-300 group-hover:-translate-x-1')
939
+ }),
940
+ /*#__PURE__*/ jsx("span", {
941
+ children: /*#__PURE__*/ jsx("span", {
942
+ className: cx('border-b-[1px] border-t-[1px] border-transparent transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
943
+ children: children
944
+ })
945
+ })
946
+ ]
582
947
  });
583
948
  }
949
+ const _Backlink = /*#__PURE__*/ forwardRef(Backlink);
584
950
 
585
- export { Button, Checkbox, CheckboxGroup, Combobox, ComboboxItem, Radio, RadioGroup, Select, SelectItem, TextArea, TextField };
951
+ 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 };