@obosbbl/grunnmuren-react 2.0.0-canary.9 → 2.0.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.
package/dist/index.mjs CHANGED
@@ -1,31 +1,392 @@
1
- import { Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ListBoxItem, ComboBox, Group, Input, Button, Popover, ListBox, 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 } from 'react-aria-components';
2
- export { Form, I18nProvider } from 'react-aria-components';
3
- export { _ as Button } from './Button-client-wuoyidfi.js';
1
+ 'use client';
2
+ import { I18nProvider, RouterProvider, useLocale, useContextProps, Provider, Link, Button as Button$1, Text, CheckboxContext, Checkbox as Checkbox$1, FieldError, Label as Label$1, CheckboxGroup as CheckboxGroup$1, ListBoxItem as ListBoxItem$1, ListBoxSection as ListBoxSection$1, Header, ListBox as ListBox$1, ComboBox, Group, Input, 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, Breadcrumbs as Breadcrumbs$1, Breadcrumb as Breadcrumb$1, ButtonContext, DisclosureContext, DisclosureGroupStateContext, DEFAULT_SLOT, useSlottedContext, FormContext, FieldErrorContext, LabelContext, InputContext, DialogTrigger as DialogTrigger$1, Modal as Modal$1, Dialog as Dialog$1, ModalOverlay as ModalOverlay$1, TagGroup as TagGroup$1, TagList as TagList$1, Tag as Tag$1 } from 'react-aria-components';
3
+ export { Form, DisclosureGroup as UNSAFE_DisclosureGroup } from 'react-aria-components';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { forwardRef, useId } from 'react';
5
+ import { ChevronDown, LoadingSpinner, Check, Close, InfoCircle, CheckCircle, Warning, Error, ChevronRight, ChevronLeft, PlayerPause, PlayerPlay, Trash, User } from '@obosbbl/grunnmuren-icons-react';
6
+ import { useLayoutEffect, filterDOMProps, mergeRefs, mergeProps, useObjectRef, useFormReset, useUpdateEffect } from '@react-aria/utils';
6
7
  import { cx, cva, compose } from 'cva';
7
- import { Check, LoadingSpinner, ChevronDown } from '@obosbbl/grunnmuren-icons-react';
8
+ import { createContext, Children, useId, useState, useRef, useEffect, useContext, useCallback } from 'react';
9
+ import { useProgressBar, useDateFormatter, useFocusRing, useDisclosure, useField } from 'react-aria';
10
+ import { useDisclosureState } from 'react-stately';
11
+ import { useFormValidation } from '@react-aria/form';
12
+ import { useFormValidationState } from '@react-stately/form';
13
+ import { useControlledState } from '@react-stately/utils';
14
+ import { PressResponder } from '@react-aria/interactions';
15
+
16
+ function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
17
+ return /*#__PURE__*/ jsx(I18nProvider, {
18
+ locale: locale,
19
+ children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
20
+ navigate: navigate,
21
+ useHref: useHref,
22
+ children: children
23
+ }) : children
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Returns the locale set in `<GrunnmurenProvider />`
29
+ */ function _useLocale() {
30
+ // a small wrapper around react-arias useLocale with a simpler return type with only the locales that we actually support
31
+ const locale = useLocale();
32
+ return locale.locale;
33
+ }
34
+
35
+ const HeadingContext = /*#__PURE__*/ createContext({});
36
+ const Heading = ({ ref = null, ...props })=>{
37
+ [props, ref] = useContextProps(props, ref, HeadingContext);
38
+ const { children, level, className, _innerWrapper: innerWrapper, _outerWrapper: outerWrapper, ...restProps } = props;
39
+ const Element = `h${level}`;
40
+ const content = /*#__PURE__*/ jsx(Element, {
41
+ ...restProps,
42
+ className: className,
43
+ "data-slot": "heading",
44
+ children: innerWrapper ? innerWrapper(children) : children
45
+ });
46
+ return outerWrapper ? outerWrapper(content) : content;
47
+ };
48
+ const ContentContext = /*#__PURE__*/ createContext({});
49
+ const Content = ({ ref = null, ...props })=>{
50
+ [props, ref] = useContextProps(props, ref, ContentContext);
51
+ const { _outerWrapper: outerWrapper, ...restProps } = props;
52
+ const content = /*#__PURE__*/ jsx("div", {
53
+ ...restProps,
54
+ "data-slot": "content"
55
+ });
56
+ return outerWrapper ? outerWrapper(content) : content;
57
+ };
58
+ const Media = (props)=>/*#__PURE__*/ jsx("div", {
59
+ ...props,
60
+ "data-slot": "media"
61
+ });
62
+ const Caption = ({ className, ...restProps })=>/*#__PURE__*/ jsx("div", {
63
+ ...restProps,
64
+ className: cx('description', className),
65
+ "data-slot": "caption"
66
+ });
67
+ const Footer = (props)=>/*#__PURE__*/ jsx("div", {
68
+ ...props,
69
+ "data-slot": "footer"
70
+ });
71
+
72
+ function Accordion(props) {
73
+ const { children, className, ...restProps } = props;
74
+ const childCount = Children.count(children);
75
+ return /*#__PURE__*/ jsx("div", {
76
+ ...restProps,
77
+ className: cx('rounded-lg bg-white', className),
78
+ children: Children.map(children, (child, index)=>/*#__PURE__*/ jsxs(Fragment, {
79
+ children: [
80
+ child,
81
+ index < childCount - 1 && // Margin is added to enable support for containers with a background color
82
+ /*#__PURE__*/ jsx("hr", {
83
+ className: "mx-2 border-gray-light",
84
+ "aria-hidden": true
85
+ })
86
+ ]
87
+ }))
88
+ });
89
+ }
90
+ function AccordionItem(props) {
91
+ const { className, children, defaultOpen = false, isOpen: controlledIsOpen, onOpenChange, ...restProps } = props;
92
+ const contentId = useId();
93
+ const buttonId = useId();
94
+ const isControlled = controlledIsOpen != null;
95
+ // This component has internal state that controls whether it is open or not,
96
+ // regardless if we are controlled or uncontrolled.
97
+ // If we are controlled, we use a layout effect to sync the controlled state
98
+ // with the internal state.
99
+ //
100
+ const [isOpen, setIsOpen] = useState(// If we are controlled, use that open state, otherwise use the uncontrolled
101
+ isControlled ? controlledIsOpen : defaultOpen);
102
+ useLayoutEffect(()=>{
103
+ if (isControlled) {
104
+ setIsOpen(controlledIsOpen);
105
+ }
106
+ }, [
107
+ controlledIsOpen,
108
+ isControlled
109
+ ]);
110
+ const handleOpenChange = ()=>{
111
+ const newOpenState = !isOpen;
112
+ if (!isControlled) {
113
+ setIsOpen(newOpenState);
114
+ }
115
+ // Always call the change handler, even if we're uncontrolled.
116
+ // Easier to add stuff such as tracking etc.
117
+ if (onOpenChange) {
118
+ onOpenChange(newOpenState);
119
+ }
120
+ };
121
+ return /*#__PURE__*/ jsx("div", {
122
+ ...restProps,
123
+ className: cx('relative px-2', className),
124
+ "data-open": isOpen,
125
+ children: /*#__PURE__*/ jsx(Provider, {
126
+ values: [
127
+ [
128
+ HeadingContext,
129
+ {
130
+ // Negative margin to strech the button to the entire with of the accordion (to support containers with a background color)
131
+ className: 'font-semibold leading-7 -mx-2 text-base',
132
+ // Supply a default level here to make this typecheck ok. Will be overwritten with the consumers set heading level anyways
133
+ level: 3,
134
+ _innerWrapper: (children)=>/*#__PURE__*/ jsxs("button", {
135
+ "aria-controls": contentId,
136
+ "aria-expanded": isOpen,
137
+ // 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
138
+ className: "flex min-h-[44px] w-full cursor-pointer items-center justify-between gap-1.5 rounded-lg px-2 py-3.5 text-left focus-visible:outline-focus focus-visible:outline-focus-inset",
139
+ id: buttonId,
140
+ onClick: handleOpenChange,
141
+ type: "button",
142
+ children: [
143
+ children,
144
+ /*#__PURE__*/ jsx(ChevronDown, {
145
+ className: cx('flex-none transition-transform duration-300 motion-reduce:transition-none', isOpen && 'rotate-180')
146
+ })
147
+ ]
148
+ })
149
+ }
150
+ ],
151
+ [
152
+ ContentContext,
153
+ {
154
+ className: // Uses pseudo element for vertical padding, since that doesn't affect the height when the accordion is closed
155
+ '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',
156
+ role: 'region',
157
+ inert: isOpen,
158
+ 'aria-labelledby': buttonId,
159
+ _outerWrapper: (children)=>/*#__PURE__*/ jsx("div", {
160
+ 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]'),
161
+ children: children
162
+ })
163
+ }
164
+ ]
165
+ ],
166
+ children: children
167
+ })
168
+ });
169
+ }
170
+
171
+ const badgeVariants = cva({
172
+ base: [
173
+ 'inline-flex w-fit items-center justify-center gap-1.5 rounded-lg [&_svg]:shrink-0'
174
+ ],
175
+ variants: {
176
+ color: {
177
+ 'gray-dark': 'bg-gray-dark text-white',
178
+ mint: 'bg-mint',
179
+ sky: 'bg-sky',
180
+ white: 'bg-white',
181
+ 'blue-dark': 'bg-blue-dark text-white',
182
+ 'green-dark': 'bg-green-dark text-white'
183
+ },
184
+ size: {
185
+ small: 'description px-2 py-0.5 [&_svg]:h-4 [&_svg]:w-4',
186
+ medium: 'description px-2.5 py-1.5 [&_svg]:h-4 [&_svg]:w-4',
187
+ large: 'paragraph px-3 py-2 [&_svg]:h-5 [&_svg]:w-5'
188
+ }
189
+ },
190
+ defaultVariants: {
191
+ size: 'medium'
192
+ }
193
+ });
194
+ function Badge(props) {
195
+ const { className: _className, color, size, ...restProps } = props;
196
+ const className = badgeVariants({
197
+ className: _className,
198
+ color,
199
+ size
200
+ });
201
+ return /*#__PURE__*/ jsx("span", {
202
+ className: className,
203
+ ...restProps,
204
+ "data-slot": "badge"
205
+ });
206
+ }
207
+
208
+ const translations$1 = {
209
+ close: {
210
+ nb: 'Lukk',
211
+ sv: 'Stäng',
212
+ en: 'Close'
213
+ },
214
+ pending: {
215
+ nb: 'venter',
216
+ sv: 'väntar',
217
+ en: 'pending'
218
+ },
219
+ showMore: {
220
+ nb: 'Les mer',
221
+ sv: 'Läs mer',
222
+ en: 'Read more'
223
+ },
224
+ showLess: {
225
+ nb: 'Vis mindre',
226
+ sv: 'Dölj',
227
+ en: 'Show less'
228
+ }
229
+ };
230
+
231
+ /**
232
+ * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
233
+ */ const buttonVariants = cva({
234
+ base: [
235
+ 'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-colors duration-200 focus-visible:outline-focus-offset'
236
+ ],
237
+ variants: {
238
+ /**
239
+ * The variant of the button
240
+ * @default primary
241
+ */ variant: {
242
+ primary: 'no-underline',
243
+ // by using an inset box-shadow to emulate a border instead of an actual border, the button size will be equal regardless of the variant
244
+ secondary: 'no-underline shadow-[inset_0_0_0_2px]',
245
+ tertiary: 'underline hover:no-underline'
246
+ },
247
+ /**
248
+ * Adjusts the color of the button for usage on different backgrounds.
249
+ * @default green
250
+ */ color: {
251
+ green: 'focus-visible:outline-focus',
252
+ mint: 'focus-visible:outline-focus focus-visible:outline-mint',
253
+ white: 'focus-visible:outline-focus focus-visible:outline-white'
254
+ },
255
+ /**
256
+ * When the button is without text, but with a single icon.
257
+ * @default false
258
+ */ isIconOnly: {
259
+ true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
260
+ false: 'gap-2.5 px-4 py-2'
261
+ },
262
+ // Make the content of the button transparent to hide it's content, but keep the button width
263
+ isPending: {
264
+ true: '!text-transparent relative',
265
+ false: null
266
+ }
267
+ },
268
+ compoundVariants: [
269
+ {
270
+ color: 'green',
271
+ variant: 'primary',
272
+ // Darken bg by 20% on hover. The color is manually crafted
273
+ className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352] [&_[role="progressbar"]]:text-white'
274
+ },
275
+ {
276
+ color: 'green',
277
+ variant: 'secondary',
278
+ className: 'text-black shadow-green hover:bg-green hover:text-white active:bg-green [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-black'
279
+ },
280
+ {
281
+ color: 'green',
282
+ variant: 'tertiary',
283
+ className: '[&_[role="progressbar"]]:text-black'
284
+ },
285
+ {
286
+ color: 'mint',
287
+ variant: 'primary',
288
+ // Darken bg by 20% on hover. The color is manually crafted
289
+ className: 'bg-mint text-black hover:bg-[#8dd4bd] active:[#9ddac6] [&_[role="progressbar"]]:text-black'
290
+ },
291
+ {
292
+ color: 'mint',
293
+ variant: 'secondary',
294
+ className: 'text-mint shadow-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint'
295
+ },
296
+ {
297
+ color: 'mint',
298
+ variant: 'tertiary',
299
+ className: 'text-mint [&_[role="progressbar"]]:text-mint'
300
+ },
301
+ {
302
+ color: 'white',
303
+ variant: 'primary',
304
+ className: 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black'
305
+ },
306
+ {
307
+ color: 'white',
308
+ variant: 'secondary',
309
+ className: 'text-white shadow-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white'
310
+ },
311
+ {
312
+ color: 'white',
313
+ variant: 'tertiary',
314
+ className: 'text-white [&_[role="progressbar"]]:text-white'
315
+ }
316
+ ],
317
+ defaultVariants: {
318
+ variant: 'primary',
319
+ color: 'green',
320
+ isIconOnly: false,
321
+ isPending: false
322
+ }
323
+ });
324
+ function isLinkProps$1(props) {
325
+ return !!props.href;
326
+ }
327
+ function Button(props) {
328
+ const { children: _children, color, isIconOnly, isLoading, variant, isPending: _isPending, ref, ...restProps } = props;
329
+ const isPending = _isPending || isLoading;
330
+ const className = buttonVariants({
331
+ className: props.className,
332
+ color,
333
+ isIconOnly,
334
+ variant,
335
+ isPending
336
+ });
337
+ const locale = _useLocale();
338
+ const { progressBarProps } = useProgressBar({
339
+ isIndeterminate: true,
340
+ 'aria-label': translations$1.pending[locale]
341
+ });
342
+ const children = isPending ? /*#__PURE__*/ jsxs(Fragment, {
343
+ children: [
344
+ _children,
345
+ /*#__PURE__*/ jsx(LoadingSpinner, {
346
+ className: "absolute m-auto motion-safe:animate-spin",
347
+ ...progressBarProps
348
+ })
349
+ ]
350
+ }) : _children;
351
+ return isLinkProps$1(restProps) ? /*#__PURE__*/ jsx(Link, {
352
+ ...restProps,
353
+ className: className,
354
+ ref: ref,
355
+ children: children
356
+ }) : /*#__PURE__*/ jsx(Button$1, {
357
+ ...restProps,
358
+ className: className,
359
+ isPending: isPending,
360
+ ref: ref,
361
+ children: children
362
+ });
363
+ }
8
364
 
9
365
  const formField = cx('group flex flex-col gap-2');
10
- const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
366
+ const formFieldError = cx('w-fit bg-red-light px-2 py-1 text-red text-sm leading-6', 'group-data-[slot=file-upload]:rounded-lg');
11
367
  const input = cva({
12
368
  base: [
13
- 'rounded-md px-3 py-2.5 text-sm font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
369
+ // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
370
+ 'bg-white',
371
+ // Use box-content to enable auto width based on number of characters (size)
372
+ // Setting min-height to prevent the input from collapsing in Safari
373
+ // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
374
+ 'box-content min-h-6 py-2.5',
375
+ 'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
14
376
  // invalid styles
15
- 'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red',
377
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red',
16
378
  // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
17
379
  'appearance-none'
18
380
  ],
19
381
  variants: {
20
382
  // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
21
383
  focusModifier: {
22
- focus: 'focus:ring-2 group-data-[invalid]:focus:ring',
23
- visible: 'data-[focus-visible]:ring-2 group-data-[invalid]:data-[focus-visible]:ring'
384
+ focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
385
+ visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
24
386
  },
25
387
  isGrouped: {
26
- false: '',
27
- //
28
- true: 'flex-1 !ring-0 first:pl-0 last:pr-0'
388
+ false: 'px-3',
389
+ true: '!ring-0 flex-1'
29
390
  }
30
391
  },
31
392
  defaultVariants: {
@@ -33,11 +394,15 @@ const input = cva({
33
394
  isGrouped: false
34
395
  }
35
396
  });
36
- 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');
397
+ const inputGroup = cx([
398
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
399
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
400
+ ]);
37
401
  const dropdown = {
38
- 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'),
39
- listbox: cx('text-sm outline-none'),
40
- chevronIcon: cx('text-base transition-transform duration-150 group-data-[open]:rotate-180 motion-reduce:transition-none')
402
+ popover: cx('data-entering:fade-in data-exiting:fade-out min-w-(--trigger-width) overflow-y-auto rounded-md border border-black bg-white shadow-sm data-entering:animate-in data-exiting:animate-out'),
403
+ // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
404
+ listbox: cx('max-h-[25rem] overflow-x-hidden text-sm outline-hidden'),
405
+ chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
41
406
  };
42
407
 
43
408
  function ErrorMessage(props) {
@@ -50,17 +415,8 @@ function ErrorMessage(props) {
50
415
  });
51
416
  }
52
417
 
53
- function Description(props) {
54
- const { className, ...restProps } = props;
55
- return /*#__PURE__*/ jsx(Text, {
56
- ...restProps,
57
- className: cx(className, 'text-sm font-light leading-6'),
58
- slot: "description"
59
- });
60
- }
61
-
62
418
  const defaultClasses$1 = cx([
63
- 'group relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7'
419
+ 'group -mx-2.5 relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 p-2.5 leading-7'
64
420
  ]);
65
421
  // Pulling this out into it's own component. Will probably export it in the future
66
422
  // so it can be used in other views, outside of an input of type checkbox, like in table rows.
@@ -74,26 +430,26 @@ function CheckmarkBox() {
74
430
  // TODO: 1.75 here is the unit less lineheight, altough we use 1.75rem as the line height, so there is a mismatch here. Revisit this when we've worked on typography in v2. Should this be a CSS custom property instead?
75
431
  'mt-[calc((1em_*_1.75_-_24px)_/_2)] h-[24px] w-[24px]',
76
432
  // selected
77
- 'group-data-[selected]:!border-green group-data-[selected]:!bg-green',
433
+ 'group-data-selected:!border-green group-data-selected:!bg-green',
78
434
  // focus
79
- 'group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-black group-data-[focus-visible]:ring-offset-[9px]',
435
+ 'group-data-focus-visible:outline-focus-offset',
80
436
  // hovered
81
- 'group-data-[hovered]:border-green group-data-[hovered]:group-data-[invalid]:border-red group-data-[hovered]:bg-green-lightest group-data-[hovered]:group-data-[invalid]:bg-red-light',
437
+ 'group-data-hovered:group-data-invalid:border-red group-data-hovered:group-data-invalid:bg-red-light group-data-hovered:border-green group-data-hovered:bg-green-lightest',
82
438
  // invalid - The border is 1 px thicker when invalid. We don't actually want to change the border width, as that causes the element's size to change
83
439
  // so we use an inner shadow of 1 px instead to pad the actual border
84
- 'group-data-[invalid]:border-red group-data-[invalid]:group-data-[selected]:shadow-none group-data-[invalid]:shadow-[inset_0_0_0_1px] group-data-[invalid]:shadow-red'
440
+ 'group-data-invalid:group-data-selected:shadow-none group-data-invalid:border-red group-data-invalid:shadow-[inset_0_0_0_1px] group-data-invalid:shadow-red'
85
441
  ]),
86
442
  children: /*#__PURE__*/ jsx(Check, {
87
- className: "h-full w-full opacity-0 group-data-[selected]:opacity-100"
443
+ className: "h-full w-full opacity-0 group-data-selected:opacity-100"
88
444
  })
89
445
  });
90
446
  }
91
- function Checkbox(props, ref) {
447
+ function Checkbox(props) {
92
448
  const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
93
449
  const id = useId();
94
- const descriptionId = 'desc' + id;
95
- const errorMessageId = 'error' + id;
96
- const isInvalid = _isInvalid || errorMessage != null;
450
+ const descriptionId = `desc${id}`;
451
+ const errorMessageId = `error${id}`;
452
+ const isInvalid = errorMessage != null || _isInvalid;
97
453
  return /*#__PURE__*/ jsx("div", {
98
454
  children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
99
455
  value: {
@@ -105,18 +461,16 @@ function Checkbox(props, ref) {
105
461
  ...restProps,
106
462
  className: cx(className, defaultClasses$1),
107
463
  isInvalid: isInvalid,
108
- ref: ref,
109
464
  children: [
110
- /*#__PURE__*/ jsx("span", {
111
- className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
112
- }),
113
465
  /*#__PURE__*/ jsx(CheckmarkBox, {}),
114
466
  children
115
467
  ]
116
468
  }),
117
- description && /*#__PURE__*/ jsx(Description, {
118
- className: "block",
469
+ 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 */}
470
+ /*#__PURE__*/ jsx("div", {
119
471
  id: descriptionId,
472
+ slot: "description",
473
+ className: "description block",
120
474
  children: description
121
475
  }),
122
476
  errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
@@ -128,7 +482,26 @@ function Checkbox(props, ref) {
128
482
  })
129
483
  });
130
484
  }
131
- const _Checkbox = /*#__PURE__*/ forwardRef(Checkbox);
485
+
486
+ function Description(props) {
487
+ const { className, ...restProps } = props;
488
+ return /*#__PURE__*/ jsx(Text, {
489
+ ...restProps,
490
+ className: cx(className, 'description'),
491
+ slot: "description"
492
+ });
493
+ }
494
+
495
+ /**
496
+ * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
497
+ * In other words, this handles controlled and uncontrolled form errors.
498
+ */ function ErrorMessageOrFieldError({ errorMessage }) {
499
+ return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
500
+ children: errorMessage
501
+ }) : /*#__PURE__*/ jsx(FieldError, {
502
+ className: formFieldError
503
+ });
504
+ }
132
505
 
133
506
  function Label(props) {
134
507
  const { children, className, ...restProps } = props;
@@ -139,15 +512,16 @@ function Label(props) {
139
512
  });
140
513
  }
141
514
 
142
- function CheckboxGroup(props, ref) {
515
+ function CheckboxGroup(props) {
143
516
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
144
- const isInvalid = _isInvalid || errorMessage != null;
517
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
518
+ // which will override any built in validation
519
+ const isInvalid = errorMessage != null || _isInvalid;
145
520
  return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
146
521
  ...restProps,
147
522
  className: cx(className, 'flex flex-col gap-2'),
148
523
  isInvalid: isInvalid,
149
524
  isRequired: isRequired,
150
- ref: ref,
151
525
  children: [
152
526
  label && /*#__PURE__*/ jsx(Label, {
153
527
  children: label
@@ -156,28 +530,66 @@ function CheckboxGroup(props, ref) {
156
530
  children: description
157
531
  }),
158
532
  children,
159
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
160
- children: errorMessage
533
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
534
+ errorMessage: errorMessage
161
535
  })
162
536
  ]
163
537
  });
164
538
  }
165
- const _CheckboxGroup = /*#__PURE__*/ forwardRef(CheckboxGroup);
166
539
 
540
+ const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
541
+ ...restProps,
542
+ className: cx(dropdown.listbox, className)
543
+ });
544
+ const ListBoxItem = (props)=>{
545
+ let textValue = props.textValue;
546
+ // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
547
+ // Since we use a render function (to handle the selected state) the child is never a string.
548
+ // This condition adds back that behaviour
549
+ if (textValue == null && typeof props.children === 'string') {
550
+ textValue = props.children;
551
+ }
552
+ return /*#__PURE__*/ jsx(ListBoxItem$1, {
553
+ ...props,
554
+ className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-focused:bg-sky-lightest'),
555
+ textValue: textValue,
556
+ children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
557
+ children: [
558
+ isSelected && /*#__PURE__*/ jsx(Check, {
559
+ className: "-ml-6 text-base"
560
+ }),
561
+ props.children
562
+ ]
563
+ })
564
+ });
565
+ };
167
566
  /**
168
- * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
169
- * In other words, this handles controlled and uncontrolled form errors.
170
- */ function ErrorMessageOrFieldError({ errorMessage }) {
171
- return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
172
- children: errorMessage
173
- }) : /*#__PURE__*/ jsx(FieldError, {
174
- className: formFieldError
567
+ * This component can be used to group items in a listbox
568
+ */ const ListBoxSection = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBoxSection$1, {
569
+ ...restProps,
570
+ // The :not(:first-child) selector adds extra spacing to all the options, but not the section (group) headings
571
+ // This way we get the desired extra indent on all options within a group
572
+ className: cx(className, 'pb-1 [&>:not(:first-child)]:pl-10')
573
+ });
574
+ /**
575
+ * This component can be used to label grouped items in a `ListBoxSection` with a heading
576
+ */ const ListBoxHeader = (props)=>/*#__PURE__*/ jsx(Header, {
577
+ ...props,
578
+ className: cx(props.className, 'mx-6 cursor-default py-2 font-medium text-blue-dark leading-6')
579
+ });
580
+
581
+ function InputAddonDivider() {
582
+ return /*#__PURE__*/ jsx("span", {
583
+ className: "block h-6 w-px flex-none bg-black"
175
584
  });
176
585
  }
177
586
 
178
- function Combobox(props, ref) {
179
- const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
180
- const isInvalid = _isInvalid || errorMessage != null;
587
+ function Combobox(props) {
588
+ const { className, children, description, errorMessage, isLoading, isPending: _isPending, label, isInvalid: _isInvalid, ref, ...restProps } = props;
589
+ const isPending = _isPending || isLoading;
590
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
591
+ // which will override any built in validation
592
+ const isInvalid = errorMessage != null || _isInvalid;
181
593
  return /*#__PURE__*/ jsxs(ComboBox, {
182
594
  ...restProps,
183
595
  className: cx(className, formField),
@@ -198,8 +610,8 @@ function Combobox(props, ref) {
198
610
  }),
199
611
  ref: ref
200
612
  }),
201
- /*#__PURE__*/ jsx(Button, {
202
- children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
613
+ /*#__PURE__*/ jsx(Button$1, {
614
+ children: isPending ? /*#__PURE__*/ jsx(LoadingSpinner, {
203
615
  className: "animate-spin"
204
616
  }) : /*#__PURE__*/ jsx(ChevronDown, {
205
617
  className: dropdown.chevronIcon
@@ -225,39 +637,17 @@ function Combobox(props, ref) {
225
637
  ]
226
638
  });
227
639
  }
228
- const ComboboxItem = (props)=>{
229
- let textValue = props.textValue;
230
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
231
- // Since we use a render function (to handle the selected state) the child is never a string.
232
- // This condition adds back that behaviour
233
- if (textValue == null && typeof props.children === 'string') {
234
- textValue = props.children;
235
- }
236
- return /*#__PURE__*/ jsx(ListBoxItem, {
237
- ...props,
238
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
239
- textValue: textValue,
240
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
241
- children: [
242
- isSelected && /*#__PURE__*/ jsx(Check, {
243
- className: "-ml-6 text-base"
244
- }),
245
- props.children
246
- ]
247
- })
248
- });
249
- };
250
- const _Combobox = /*#__PURE__*/ forwardRef(Combobox);
251
640
 
252
- function RadioGroup(props, ref) {
641
+ function RadioGroup(props) {
253
642
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
254
- const isInvalid = _isInvalid || errorMessage != null;
643
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
644
+ // which will override any built in validation
645
+ const isInvalid = errorMessage != null || _isInvalid;
255
646
  return /*#__PURE__*/ jsxs(RadioGroup$1, {
256
647
  ...restProps,
257
648
  className: cx(className, 'flex flex-col gap-2'),
258
649
  isInvalid: isInvalid,
259
650
  isRequired: isRequired,
260
- ref: ref,
261
651
  children: [
262
652
  label && /*#__PURE__*/ jsx(Label, {
263
653
  children: label
@@ -266,16 +656,15 @@ function RadioGroup(props, ref) {
266
656
  children: description
267
657
  }),
268
658
  children,
269
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
270
- children: errorMessage
659
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
660
+ errorMessage: errorMessage
271
661
  })
272
662
  ]
273
663
  });
274
664
  }
275
- const _RadioGroup = /*#__PURE__*/ forwardRef(RadioGroup);
276
665
 
277
666
  const defaultClasses = cx([
278
- 'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
667
+ '-ml-2.5 relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
279
668
  // the radio button itself
280
669
  'before:flex-none before:rounded-full before:border-2 before:border-black',
281
670
  // 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.
@@ -284,42 +673,37 @@ const defaultClasses = cx([
284
673
  // TODO: 1.75 here is the unit less lineheight, altough we use 1.75rem as the line height, so there is a mismatch here. Revisit this when we've worked on typography in v2. Should this be a CSS custom property instead?
285
674
  'before:mt-[calc((1em_*_1.75_-_24px)_/_2)] before:h-[24px] before:w-[24px]',
286
675
  // selected
287
- 'data-[selected]:before:border-black data-[selected]:before:bg-green data-[selected]:before:shadow-[inset_0_0_0_4px_rgb(255,255,255)]',
676
+ 'data-selected:before:border-black data-selected:before:bg-green data-selected:before:shadow-[inset_0_0_0_4px_rgb(255,255,255)]',
288
677
  // hover
289
- 'data-[hovered]:before:border-green data-[hovered]:before:bg-green-lightest data-[hovered]:data-[invalid]:before:bg-red-light',
678
+ 'data-hovered:data-invalid:before:bg-red-light data-hovered:before:border-green data-hovered:before:bg-green-lightest',
290
679
  // focus
291
- 'data-[focus-visible]:before:ring data-[focus-visible]:before:ring-black data-[focus-visible]:before:ring-offset-[9px]',
680
+ 'data-focus-visible:before:ring-focus-offset',
292
681
  // invalid - The border is 1 px thicker when invalid. We don't actually want to change the border width, as that causes the element's size to change
293
682
  // so we use an inner outline to artifically pad the border
294
- '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'
683
+ 'data-invalid:data-selected:before:!bg-red data-invalid:before:border-red data-invalid:before:outline data-invalid:before:outline-[3px] data-invalid:before:outline-red data-invalid:before:outline-solid data-invalid:before:outline-offset-[-3px]'
295
684
  ]);
296
- function Radio(props, ref) {
685
+ function Radio(props) {
297
686
  const { children, className, description, ...restProps } = props;
298
- return /*#__PURE__*/ jsxs(Radio$1, {
687
+ return /*#__PURE__*/ jsx(Radio$1, {
299
688
  ...restProps,
300
689
  className: cx(className, defaultClasses),
301
- ref: ref,
302
- children: [
303
- /*#__PURE__*/ jsx("span", {
304
- className: "absolute -left-2.5 top-0 z-10 h-11 w-11 "
305
- }),
306
- /*#__PURE__*/ jsxs("div", {
307
- children: [
308
- children,
309
- description && /*#__PURE__*/ jsx(Description, {
310
- className: "mt-2 block",
311
- children: description
312
- })
313
- ]
314
- })
315
- ]
690
+ children: /*#__PURE__*/ jsxs("div", {
691
+ children: [
692
+ children,
693
+ description && /*#__PURE__*/ jsx(Description, {
694
+ className: "mt-2 block",
695
+ children: description
696
+ })
697
+ ]
698
+ })
316
699
  });
317
700
  }
318
- const _Radio = /*#__PURE__*/ forwardRef(Radio);
319
701
 
320
- function Select(props, ref) {
321
- const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
322
- const isInvalid = _isInvalid || errorMessage != null;
702
+ function Select(props) {
703
+ const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ref, ...restProps } = props;
704
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
705
+ // which will override any built in validation
706
+ const isInvalid = errorMessage != null || _isInvalid;
323
707
  return /*#__PURE__*/ jsxs(Select$1, {
324
708
  ...restProps,
325
709
  className: cx(className, formField),
@@ -331,7 +715,7 @@ function Select(props, ref) {
331
715
  description && /*#__PURE__*/ jsx(Description, {
332
716
  children: description
333
717
  }),
334
- /*#__PURE__*/ jsxs(Button, {
718
+ /*#__PURE__*/ jsxs(Button$1, {
335
719
  className: cx(input({
336
720
  focusModifier: 'visible'
337
721
  }), // How to reuse placeholder text?
@@ -360,33 +744,10 @@ function Select(props, ref) {
360
744
  ]
361
745
  });
362
746
  }
363
- const SelectItem = (props)=>{
364
- let textValue = props.textValue;
365
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
366
- // Since we use a render function (to handle the selected state) the child is never a string.
367
- // This condition adds back that behaviour
368
- if (textValue == null && typeof props.children === 'string') {
369
- textValue = props.children;
370
- }
371
- return /*#__PURE__*/ jsx(ListBoxItem, {
372
- ...props,
373
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
374
- textValue: textValue,
375
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
376
- children: [
377
- isSelected && /*#__PURE__*/ jsx(Check, {
378
- className: "-ml-6 text-base"
379
- }),
380
- props.children
381
- ]
382
- })
383
- });
384
- };
385
- const _Select = /*#__PURE__*/ forwardRef(Select);
386
747
 
387
- function TextArea(props, ref) {
388
- const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
389
- const isInvalid = _isInvalid || errorMessage != null;
748
+ function TextArea(props) {
749
+ const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ref, ...restProps } = props;
750
+ const isInvalid = errorMessage != null || _isInvalid;
390
751
  return /*#__PURE__*/ jsxs(TextField$1, {
391
752
  ...restProps,
392
753
  className: cx(className, formField),
@@ -409,20 +770,25 @@ function TextArea(props, ref) {
409
770
  ]
410
771
  });
411
772
  }
412
- const _TextArea = /*#__PURE__*/ forwardRef(TextArea);
413
773
 
414
- const inputWithAlignment$1 = compose(input, cva({
774
+ const inputVariants$1 = compose(input, cva({
415
775
  base: '',
416
776
  variants: {
417
777
  textAlign: {
418
778
  right: 'text-right',
419
779
  left: ''
780
+ },
781
+ autoWidth: {
782
+ true: 'max-w-fit',
783
+ false: ''
420
784
  }
421
785
  }
422
786
  }));
423
- function TextField(props, ref) {
424
- const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
425
- const isInvalid = _isInvalid || errorMessage != null;
787
+ function TextField(props) {
788
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
789
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
790
+ // which will override any built in validation
791
+ const isInvalid = errorMessage != null || _isInvalid;
426
792
  return /*#__PURE__*/ jsxs(TextField$1, {
427
793
  ...restProps,
428
794
  className: cx(className, formField),
@@ -435,29 +801,31 @@ function TextField(props, ref) {
435
801
  children: description
436
802
  }),
437
803
  leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
438
- className: inputGroup,
804
+ className: cx(inputGroup, {
805
+ 'w-fit': !!size
806
+ }),
439
807
  children: [
440
808
  leftAddon,
441
- withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider$1, {
442
- className: "ml-3"
443
- }),
809
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
444
810
  /*#__PURE__*/ jsx(Input, {
445
- className: inputWithAlignment$1({
811
+ className: inputVariants$1({
446
812
  textAlign,
447
- isGrouped: true
813
+ isGrouped: true,
814
+ autoWidth: !!size
448
815
  }),
449
- ref: ref
450
- }),
451
- withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider$1, {
452
- className: "mr-3"
816
+ ref: ref,
817
+ size: size
453
818
  }),
819
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
454
820
  rightAddon
455
821
  ]
456
822
  }) : /*#__PURE__*/ jsx(Input, {
457
- className: inputWithAlignment$1({
458
- textAlign
823
+ className: inputVariants$1({
824
+ textAlign,
825
+ autoWidth: !!size
459
826
  }),
460
- ref: ref
827
+ ref: ref,
828
+ size: size
461
829
  }),
462
830
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
463
831
  errorMessage: errorMessage
@@ -465,26 +833,26 @@ function TextField(props, ref) {
465
833
  ]
466
834
  });
467
835
  }
468
- function Divider$1({ className }) {
469
- return /*#__PURE__*/ jsx("span", {
470
- className: cx(className, 'block h-6 w-px flex-none bg-black')
471
- });
472
- }
473
- const _TextField = /*#__PURE__*/ forwardRef(TextField);
474
836
 
475
837
  // This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
476
- const inputWithAlignment = compose(input, cva({
838
+ const inputVariants = compose(input, cva({
477
839
  base: '',
478
840
  variants: {
479
841
  textAlign: {
480
842
  right: 'text-right',
481
843
  left: ''
844
+ },
845
+ autoWidth: {
846
+ true: 'max-w-fit',
847
+ false: ''
482
848
  }
483
849
  }
484
850
  }));
485
- function NumberField(props, ref) {
486
- const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
487
- const isInvalid = _isInvalid || errorMessage != null;
851
+ function NumberField(props) {
852
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
853
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
854
+ // which will override any built in validation
855
+ const isInvalid = errorMessage != null || _isInvalid;
488
856
  return /*#__PURE__*/ jsxs(NumberField$1, {
489
857
  ...restProps,
490
858
  className: cx(className, formField),
@@ -497,29 +865,31 @@ function NumberField(props, ref) {
497
865
  children: description
498
866
  }),
499
867
  leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
500
- className: inputGroup,
868
+ className: cx(inputGroup, {
869
+ 'w-fit': !!size
870
+ }),
501
871
  children: [
502
872
  leftAddon,
503
- withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider, {
504
- className: "ml-3"
505
- }),
873
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
506
874
  /*#__PURE__*/ jsx(Input, {
507
- className: inputWithAlignment({
875
+ className: inputVariants({
508
876
  textAlign,
509
- isGrouped: true
877
+ isGrouped: true,
878
+ autoWidth: !!size
510
879
  }),
511
- ref: ref
512
- }),
513
- withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider, {
514
- className: "mr-3"
880
+ ref: ref,
881
+ size: size
515
882
  }),
883
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
516
884
  rightAddon
517
885
  ]
518
886
  }) : /*#__PURE__*/ jsx(Input, {
519
- className: inputWithAlignment({
520
- textAlign
887
+ className: inputVariants({
888
+ textAlign,
889
+ autoWidth: !!size
521
890
  }),
522
- ref: ref
891
+ ref: ref,
892
+ size: size
523
893
  }),
524
894
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
525
895
  errorMessage: errorMessage
@@ -527,11 +897,1049 @@ function NumberField(props, ref) {
527
897
  ]
528
898
  });
529
899
  }
530
- function Divider({ className }) {
531
- return /*#__PURE__*/ jsx("span", {
532
- className: cx(className, 'block h-6 w-px flex-none bg-black')
900
+
901
+ const iconMap = {
902
+ info: InfoCircle,
903
+ success: CheckCircle,
904
+ warning: Warning,
905
+ danger: Error
906
+ };
907
+ const alertVariants = cva({
908
+ base: [
909
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
910
+ // Heading styles:
911
+ '[&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:leading-7',
912
+ // Content styles:
913
+ '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
914
+ // Footer styles:
915
+ '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:leading-6'
916
+ ],
917
+ variants: {
918
+ /**
919
+ * The variant of the alert
920
+ * @default info
921
+ */ variant: {
922
+ info: 'border-[#1A7FA7] bg-sky-light',
923
+ success: 'border-[#0F9B6E] bg-mint-light',
924
+ warning: 'border-[#C57C13] bg-[#FFF2DE]',
925
+ danger: 'border-[#C0385D] bg-red-light'
926
+ }
927
+ },
928
+ defaultVariants: {
929
+ variant: 'info'
930
+ }
931
+ });
932
+ const Alertbox = ({ children, role, className, icon, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
933
+ const Icon = icon ?? iconMap[variant];
934
+ const locale = _useLocale();
935
+ const id = useId();
936
+ const [isExpanded, setIsExpanded] = useState(false);
937
+ const isCollapsed = isExpandable && !isExpanded;
938
+ const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
939
+ const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
940
+ if (!isVisible) return;
941
+ const close = ()=>{
942
+ setIsUncontrolledVisible(false);
943
+ if (onDismiss) onDismiss();
944
+ };
945
+ const isInDevMode = process.env.NODE_ENV !== 'production';
946
+ if (isInDevMode && onDismiss && !isDismissable) {
947
+ console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
948
+ }
949
+ if (isInDevMode && !children) {
950
+ console.error('`No children was passed to the <AlertBox/>` component.');
951
+ return;
952
+ }
953
+ const [firstChild, ...restChildren] = Children.toArray(children);
954
+ return /*#__PURE__*/ jsxs("div", {
955
+ className: alertVariants({
956
+ className,
957
+ variant
958
+ }),
959
+ // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
960
+ // role="none" will not have any effect on a div, so it can be omitted.
961
+ role: role === 'none' ? undefined : role,
962
+ children: [
963
+ /*#__PURE__*/ jsx(Icon, {}),
964
+ firstChild,
965
+ isDismissable && /*#__PURE__*/ jsx("button", {
966
+ className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus-visible:-outline-offset-8 cursor-pointer focus-visible:outline-focus'),
967
+ onClick: close,
968
+ "aria-label": translations$1.close[locale],
969
+ type: "button",
970
+ children: /*#__PURE__*/ jsx(Close, {})
971
+ }),
972
+ isExpandable && /*#__PURE__*/ jsxs("button", {
973
+ className: cx('-my-3 relative col-span-full row-start-2 inline-flex max-w-fit cursor-pointer items-center gap-1 py-3 text-sm leading-6', // Focus styles:
974
+ 'outline-none after:absolute after:right-0 after:bottom-3 after:left-0 after:h-0', 'focus-visible:after:h-[2px] focus-visible:after:bg-black'),
975
+ onClick: ()=>setIsExpanded((prevState)=>!prevState),
976
+ "aria-expanded": isExpanded,
977
+ "aria-controls": id,
978
+ type: "button",
979
+ children: [
980
+ isExpanded ? translations$1.showLess[locale] : translations$1.showMore[locale],
981
+ /*#__PURE__*/ jsx(ChevronDown, {
982
+ className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
983
+ })
984
+ ]
985
+ }),
986
+ restChildren?.length > 0 && /*#__PURE__*/ jsx("div", {
987
+ className: cx('col-span-full grid gap-y-4', isCollapsed && '[&>*:not([data-slot="footer"])]:hidden'),
988
+ id: id,
989
+ children: restChildren
990
+ })
991
+ ]
992
+ });
993
+ };
994
+
995
+ function Breadcrumbs(props) {
996
+ const { className, children, ...restProps } = props;
997
+ return /*#__PURE__*/ jsx(Breadcrumbs$1, {
998
+ ...restProps,
999
+ className: cx(className, 'flex flex-wrap text-sm leading-6'),
1000
+ children: children
1001
+ });
1002
+ }
1003
+
1004
+ function Breadcrumb(props) {
1005
+ const { className, children, href, ...restProps } = props;
1006
+ return /*#__PURE__*/ jsxs(Breadcrumb$1, {
1007
+ className: cx(className, 'group flex items-center'),
1008
+ ...restProps,
1009
+ children: [
1010
+ href ? /*#__PURE__*/ jsx(Link, {
1011
+ href: href,
1012
+ // use outline instead of ring-3 for focus marker that can be offset without creating a white background between the focus marker and the element content
1013
+ className: "rounded-xs focus-visible:outline-focus group-last:no-underline",
1014
+ children: children
1015
+ }) : children,
1016
+ /*#__PURE__*/ jsx(ChevronRight, {
1017
+ className: "px-1 group-last:hidden"
1018
+ })
1019
+ ]
1020
+ });
1021
+ }
1022
+
1023
+ function isLinkProps(props) {
1024
+ return !!props.href;
1025
+ }
1026
+ function Backlink(props) {
1027
+ const { className, style, children, withUnderline, ref, ...restProps } = props;
1028
+ const _className = cx(className, 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 no-underline focus-visible:outline-focus');
1029
+ const content = /*#__PURE__*/ jsxs(Fragment, {
1030
+ children: [
1031
+ /*#__PURE__*/ jsx(ChevronLeft, {
1032
+ className: cx('-ml-[0.5em] group-hover:-translate-x-1 shrink-0 transition-transform duration-300')
1033
+ }),
1034
+ /*#__PURE__*/ jsx("span", {
1035
+ children: /*#__PURE__*/ jsx("span", {
1036
+ className: cx('border-transparent border-t-[1px] border-b-[1px] transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
1037
+ children: children
1038
+ })
1039
+ })
1040
+ ]
1041
+ });
1042
+ if (isLinkProps(props)) {
1043
+ return /*#__PURE__*/ jsx(Link, {
1044
+ ...restProps,
1045
+ className: _className,
1046
+ style: style,
1047
+ ref: ref,
1048
+ children: content
1049
+ });
1050
+ }
1051
+ return /*#__PURE__*/ jsx(Button$1, {
1052
+ ...restProps,
1053
+ className: _className,
1054
+ style: style,
1055
+ ref: ref,
1056
+ children: content
1057
+ });
1058
+ }
1059
+
1060
+ const cardVariants = cva({
1061
+ base: [
1062
+ 'group/card',
1063
+ 'rounded-2xl border p-3',
1064
+ 'flex gap-y-4',
1065
+ 'relative',
1066
+ // **** Heading ****
1067
+ '[&_[data-slot="heading"]]:inline',
1068
+ '[&_[data-slot="heading"]]:heading-s',
1069
+ '[&_[data-slot="heading"]]:leading-6',
1070
+ '[&_[data-slot="heading"]]:w-fit',
1071
+ '[&_[data-slot="heading"]]:text-pretty',
1072
+ '[&_[data-slot="heading"]]:hyphens-auto',
1073
+ '[&_[data-slot="heading"]]:[word-break:break-word]',
1074
+ // **** Content ****
1075
+ '[&_[data-slot="content"]]:flex [&_[data-slot="content"]]:flex-col [&_[data-slot="content"]]:gap-y-4',
1076
+ // **** Media ****
1077
+ '[&_[data-slot="media"]_*]:pointer-events-none',
1078
+ '[&_[data-slot="media"]]:overflow-hidden',
1079
+ '[&_[data-slot="media"]]:relative',
1080
+ // Position media at the edges of the card (because of these negative margins the media-element must be a wrapper around the actual image or other media content)
1081
+ '[&_[data-slot="media"]]:mx-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))] [&_[data-slot="media"]]:mt-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
1082
+ // Sets the aspect ratio of the media content (width: 100% is necessary to make aspect ratio work on images in FF)
1083
+ '[&_[data-slot="media"]>*:not([data-slot="badge"])]:aspect-[3/2] [&_[data-slot="media"]>img]:w-full [&_[data-slot="media"]>img]:object-cover',
1084
+ // Prepare zoom animation for hover effects. The hover effect can also be enabled by classes on the parent component, so it is always prepared here.
1085
+ '[&_[data-slot="media"]>*]:duration-300 [&_[data-slot="media"]>*]:ease-in-out [&_[data-slot="media"]>*]:motion-safe:transition-transform',
1086
+ // **** Card link ****
1087
+ // **** Hover ****
1088
+ // Enables the zoom hover effect on media (note that we can't use group-hover/card here, because there might be other clickable elements in the card aside from the heading)
1089
+ '[&:has([data-slot="card-link"]_a:hover)_[data-slot="media"]>img]:scale-110',
1090
+ // **** Card link in Heading ****
1091
+ '[&:has([data-slot="heading"]_[data-slot="card-link"]:hover)_[data-slot="media"]>img]:scale-110',
1092
+ // Border (bottom/top) is set to transparent to make sure the bottom underline is not visible when the card is hovered
1093
+ // Border top is set to even out the border bottom used for the underline
1094
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:no-underline',
1095
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-2',
1096
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-transparent',
1097
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:transition-colors',
1098
+ '[&_[data-slot="heading"]_[data-slot="card-link"]:hover]:border-b-current',
1099
+ // Mimic heading styles for the card link if placed in the heading slot. This is necessary to make the custom underline align with the link text
1100
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:heading-s',
1101
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:leading-6',
1102
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:text-pretty',
1103
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:hyphens-auto',
1104
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:[word-break:break-word]',
1105
+ // **** Fail-safe for interactive elements ****
1106
+ // Make interactive elements clickable by themselves, while the rest of the card is clickable as a whole
1107
+ // The card is made clickable by a pseudo-element on the heading that covers the entire card
1108
+ '[&:not(:has([data-slot="card-link"]_a))_a:not([data-slot="card-link"])]:relative [&_button]:relative [&_input]:relative',
1109
+ // Our Button component has position: relative by default, so we need to override that if it is used in a CardLink (to make the entire card clickable)
1110
+ '[&_[data-slot="card-link"]_a]:static',
1111
+ // Place other interactive on top of the pseudo-element that makes the entire card clickable
1112
+ // by setting a higher z-index than the pseudo-element (which implicitly z-index 0)
1113
+ '[&_a:not([data-slot="card-link"])]:z-[1] [&_button]:z-[1] [&_input]:z-[1]',
1114
+ // **** Badge ****
1115
+ '[&_[data-slot="media"]_[data-slot="badge"]]:absolute [&_[data-slot="media"]_[data-slot="badge"]]:top-0',
1116
+ // Increasing z-index Preserves badge position when media content is hovered (the transform scale effect might otherwise move the badge behind the other media content)
1117
+ '[&_[data-slot="media"]_[data-slot="badge"]]:z-[1]',
1118
+ // Left aligned - override default corner radius of the badge
1119
+ '[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tl-2xl',
1120
+ '[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-br-2xl',
1121
+ '[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tr-none',
1122
+ '[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-bl-none',
1123
+ // Right aligned - override default corner radius of the badge
1124
+ '[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tl-none',
1125
+ '[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-br-none',
1126
+ '[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tr-2xl',
1127
+ '[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-bl-2xl',
1128
+ // ... and position the badge at the right edge of the media content
1129
+ '[&_[data-slot="media"]_[data-slot="badge"]:last-child]:right-0'
1130
+ ],
1131
+ variants: {
1132
+ /**
1133
+ * The variant of the card
1134
+ * @default subtle
1135
+ */ variant: {
1136
+ subtle: [
1137
+ 'border-transparent',
1138
+ // **** Media styles ****
1139
+ '[&_[data-slot="media"]]:rounded-2xl'
1140
+ ],
1141
+ outlined: 'border border-black'
1142
+ },
1143
+ /**
1144
+ * The layout of the card
1145
+ * @default vertical
1146
+ */ layout: {
1147
+ vertical: [
1148
+ 'flex-col',
1149
+ // **** Media ****
1150
+ '[&_[data-slot="media"]]:rounded-t-2xl'
1151
+ ],
1152
+ horizontal: [
1153
+ 'gap-x-4',
1154
+ // **** With Media ****
1155
+ '[&:has(>[data-slot="media"]:first-child)]:flex-col',
1156
+ '[&:has(>[data-slot="media"]:last-child)]:flex-col-reverse',
1157
+ '[&:has(>[data-slot="media"])]:md:!flex-row',
1158
+ '[&_[data-slot="media"]]:md:h-fit',
1159
+ '[&:has(>[data-slot="media"])>*]:md:basis-1/2',
1160
+ // Position media at the edges of the card
1161
+ '[&_[data-slot="media"]]:md:mb-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
1162
+ '[&_[data-slot="media"]:first-child]:md:mr-0',
1163
+ '[&_[data-slot="media"]:last-child]:md:ml-0',
1164
+ // Make sure the card link is clickable when the media is on the right side
1165
+ // This i necessary because the media content is positioned after the card link in the DOM
1166
+ '[&:has(>[data-slot="media"]:last-child)_[data-slot="card-link"]]:z-[1]',
1167
+ // **** Without Media ****
1168
+ '[&:not(:has(>[data-slot="media"]))]:flex-row',
1169
+ // Make the layout responsive: when the Content reaches a minimum width of 12rem, the layout switches to vertical. Also makes sure Content takes up the remaining space available.
1170
+ '[&:not(:has(>[data-slot="media"]))]:flex-wrap [&:not(:has(>[data-slot="media"]))_[data-slot="content"]]:grow [&:not(:has(>[data-slot="media"]))_[data-slot="content"]]:basis-[12rem]',
1171
+ // Make sure svg's etc. are not shrinkable
1172
+ '[&>:not([data-slot="content"],[data-slot="media"])]:shrink-0'
1173
+ ]
1174
+ }
1175
+ },
1176
+ defaultVariants: {
1177
+ variant: 'subtle',
1178
+ layout: 'vertical'
1179
+ },
1180
+ compoundVariants: [
1181
+ {
1182
+ variant: 'outlined',
1183
+ layout: 'horizontal',
1184
+ className: [
1185
+ // **** Media ****
1186
+ // Some rounded corners are removed when the card is outlined
1187
+ '[&_[data-slot="media"]]:rounded-t-2xl',
1188
+ '[&_[data-slot="media"]:first-child]:md:rounded-tr-none [&_[data-slot="media"]:first-child]:md:rounded-bl-2xl',
1189
+ '[&_[data-slot="media"]:last-child]:md:rounded-tl-none [&_[data-slot="media"]:last-child]:md:rounded-br-2xl',
1190
+ // **** Badge ****
1191
+ // Override default corner radius of the badge to match the media border radius
1192
+ '[&_[data-slot="media"]:first-child_[data-slot="badge"]:last-child]:md:rounded-tr-none',
1193
+ '[&_[data-slot="media"]:last-child_[data-slot="badge"]:first-child]:md:rounded-tl-none'
1194
+ ]
1195
+ }
1196
+ ]
1197
+ });
1198
+ const Card = ({ children, className: _className, variant, layout, ...restProps })=>{
1199
+ const className = cardVariants({
1200
+ className: _className,
1201
+ variant,
1202
+ layout
1203
+ });
1204
+ return /*#__PURE__*/ jsx("div", {
1205
+ className: className,
1206
+ ...restProps,
1207
+ children: children
1208
+ });
1209
+ };
1210
+ const cardLinkVariants = cva({
1211
+ base: 'w-fit max-w-full',
1212
+ variants: {
1213
+ withHref: {
1214
+ true: [
1215
+ // **** Clickarea ****
1216
+ 'cursor-pointer',
1217
+ 'after:absolute',
1218
+ 'after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
1219
+ 'after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
1220
+ // **** Focus ****
1221
+ 'focus-visible:outline-none',
1222
+ 'data-focus-visible:after:outline-focus',
1223
+ 'data-focus-visible:after:outline-offset-2',
1224
+ // **** Hover ****
1225
+ // Links are underlined by default, and the underline is removed on hover.
1226
+ // So we make sure that also happens when the user hovers the clickable area.
1227
+ 'hover:no-underline'
1228
+ ],
1229
+ false: [
1230
+ // **** Clickarea ****
1231
+ '[&_a]:after:cursor-pointer',
1232
+ '[&_a]:after:absolute',
1233
+ '[&_a]:after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
1234
+ '[&_a]:after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
1235
+ // **** Focus ****
1236
+ '[&_a[data-focus-visible]]:outline-none',
1237
+ '[&_a[data-focus-visible]]:after:outline-focus',
1238
+ '[&_a[data-focus-visible]]:after:outline-offset-2',
1239
+ // **** Hover ****
1240
+ // Links are underlined by default, and the underline is removed on hover.
1241
+ // So we make sure that also happens when the user hovers the card.
1242
+ // The group-hover ensures that the hover effect also applies when this component is used as a wrapper around a link.
1243
+ '[&_a]:group-hover/card:no-underline'
1244
+ ]
1245
+ }
1246
+ }
1247
+ });
1248
+ /**
1249
+ * A component that creates a clickable area on a card.
1250
+ * It can be used either as a wrapper around a link or as a standalone link.
1251
+ */ const CardLink = ({ className: _className, href, ...restProps })=>{
1252
+ const className = cardLinkVariants({
1253
+ className: _className,
1254
+ withHref: !!href
1255
+ });
1256
+ return href ? /*#__PURE__*/ jsx(Link, {
1257
+ "data-slot": "card-link",
1258
+ ...restProps,
1259
+ href: href,
1260
+ className: className
1261
+ }) : // We can't utilize that the `Link` component from react-aria-components renders as a span if it doesn't have an href,
1262
+ // because it still renders with role="link" and tabindex="0" which makes it focusable.
1263
+ // So we need to render a div instead.
1264
+ /*#__PURE__*/ jsx("div", {
1265
+ ...restProps,
1266
+ "data-slot": "card-link",
1267
+ className: className
1268
+ });
1269
+ };
1270
+
1271
+ /**
1272
+ * A React component that wraps https://react-spectrum.adobe.com/react-aria/useDateFormatter.html
1273
+ * By default it sets the timeZone to `Europe/Berlin` to prevent the server's timezone from affecting
1274
+ * the localized format
1275
+ */ const DateFormatter = ({ options: _options, value, children: render })=>{
1276
+ const options = {
1277
+ timeZone: 'Europe/Berlin',
1278
+ ..._options
1279
+ };
1280
+ const formatter = useDateFormatter(options);
1281
+ const date = typeof value === 'string' ? new Date(value) : value;
1282
+ const formatted = formatter.format(date);
1283
+ return render ? render(formatted) : formatted;
1284
+ };
1285
+
1286
+ const VideoLoop = ({ src, format, alt, className })=>{
1287
+ // Control the video playback state, so that the user can pause and play the video at will, also control the video autoplay
1288
+ const [shouldPlay, setShouldPlay] = useState(false);
1289
+ // Needed to show the pause button when the video is actually playing (refer to google's autoplay policy: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes)
1290
+ const [isPlaying, setIsPlaying] = useState(false);
1291
+ // We need to check if the user prefers reduced motion, so that we can prevent the video from autoplaying if so
1292
+ const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(null);
1293
+ const videoRef = useRef(null);
1294
+ useEffect(()=>{
1295
+ const { matches: userPrefersReducedMotion } = matchMedia('(prefers-reduced-motion: reduce)');
1296
+ setUserPrefersReducedMotion(userPrefersReducedMotion);
1297
+ // Autoplay the video if the user does not prefer reduced motion
1298
+ setShouldPlay(!userPrefersReducedMotion);
1299
+ }, []);
1300
+ // Follow google's autoplay policy: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
1301
+ // "Don't assume a video will play, and don't show a pause button when the video is not actually playing."
1302
+ // "You should always look at the Promise returned by the play function to see if it was rejected:"
1303
+ // This is why we use the promise returned by the play function, and an extra state variable to determine if the video is actually playing or not
1304
+ useEffect(()=>{
1305
+ if (!videoRef.current) return;
1306
+ if (shouldPlay) {
1307
+ videoRef.current.play().then(()=>setIsPlaying(true)).catch(()=>setIsPlaying(false));
1308
+ } else {
1309
+ videoRef.current.pause();
1310
+ setIsPlaying(false);
1311
+ }
1312
+ }, [
1313
+ shouldPlay
1314
+ ]);
1315
+ return /*#__PURE__*/ jsxs("div", {
1316
+ className: cx(className, 'relative', userPrefersReducedMotion === null && 'opacity-0'),
1317
+ children: [
1318
+ /*#__PURE__*/ jsx("video", {
1319
+ "aria-hidden": true,
1320
+ ref: videoRef,
1321
+ // cursor-pointer is not working on the button below, so we add it here for the same effect
1322
+ className: "h-full w-full cursor-pointer object-cover",
1323
+ playsInline: true,
1324
+ loop: userPrefersReducedMotion === false,
1325
+ autoPlay: userPrefersReducedMotion === false,
1326
+ muted: true,
1327
+ onEnded: (event)=>{
1328
+ if (userPrefersReducedMotion) {
1329
+ // Reset the video to the beginning if the user prefers reduced motion, since the video will not loop
1330
+ event.currentTarget.currentTime = 0;
1331
+ setShouldPlay(false);
1332
+ setIsPlaying(false);
1333
+ }
1334
+ },
1335
+ children: /*#__PURE__*/ jsx("source", {
1336
+ src: src,
1337
+ type: `video/${format}`
1338
+ })
1339
+ }),
1340
+ userPrefersReducedMotion !== null && /*#__PURE__*/ jsx("button", {
1341
+ "aria-hidden": true,
1342
+ type: "button",
1343
+ onClick: ()=>setShouldPlay((prevState)=>!prevState),
1344
+ className: cx('absolute top-0 right-0 bottom-0 left-0 m-auto grid place-items-center', 'focus-visible:outline-focus focus-visible:outline-focus-offset', // Setting the opacity to 0 before applying the transition below will ensure the button only fades in after the video has started playing
1345
+ shouldPlay && 'opacity-0', isPlaying && [
1346
+ 'transition-opacity duration-200',
1347
+ // Only show the pause button when the video is hovered or focused
1348
+ 'focus-visible:opacity-100',
1349
+ 'hover:opacity-100'
1350
+ ]),
1351
+ children: /*#__PURE__*/ jsx("span", {
1352
+ className: "grid h-12 w-12 place-items-center rounded-full bg-white outline-hidden",
1353
+ children: isPlaying ? /*#__PURE__*/ jsx(PlayerPause, {}) : /*#__PURE__*/ jsx(PlayerPlay, {})
1354
+ })
1355
+ }),
1356
+ alt && /*#__PURE__*/ jsx("p", {
1357
+ className: "sr-only",
1358
+ children: alt
1359
+ })
1360
+ ]
1361
+ });
1362
+ };
1363
+
1364
+ const disclosureButtonVariants = cva({
1365
+ base: [
1366
+ 'inline-flex cursor-pointer items-center justify-between rounded-lg focus-visible:outline-current focus-visible:outline-focus',
1367
+ // Ensure a minimum click area of 44x44px, while making it look like it only has the size of the content
1368
+ '-m-2.5 p-2.5 focus-visible:outline-offset-[-0.625rem]'
1369
+ ],
1370
+ variants: {
1371
+ withChevron: {
1372
+ true: '[&[aria-expanded="true"]_svg]:rotate-180',
1373
+ false: null
1374
+ },
1375
+ /**
1376
+ * When the button is without text, but with a single icon.
1377
+ * @default false
1378
+ */ isIconOnly: {
1379
+ true: '[&>svg]:h-7 [&>svg]:w-7',
1380
+ false: 'gap-2.5'
1381
+ }
1382
+ },
1383
+ defaultVariants: {
1384
+ withChevron: false,
1385
+ isIconOnly: false
1386
+ }
1387
+ });
1388
+ const DisclosureButton = ({ className, withChevron, isIconOnly, children, ref: _ref, ...restProps })=>{
1389
+ const [props, ref] = useContextProps(restProps, _ref, ButtonContext);
1390
+ return /*#__PURE__*/ jsxs(Button$1, {
1391
+ ...props,
1392
+ ref: ref,
1393
+ className: disclosureButtonVariants({
1394
+ className,
1395
+ withChevron,
1396
+ isIconOnly
1397
+ }),
1398
+ slot: "trigger",
1399
+ children: [
1400
+ children,
1401
+ withChevron && /*#__PURE__*/ jsx(ChevronDown, {
1402
+ className: "flex-none transition-transform duration-300 motion-reduce:transition-none"
1403
+ })
1404
+ ]
1405
+ });
1406
+ };
1407
+ const DisclosureStateContext = /*#__PURE__*/ createContext(null);
1408
+ const Disclosure = ({ ref: _ref, children, ..._props })=>{
1409
+ const [props, ref] = useContextProps(_props, _ref, DisclosureContext);
1410
+ const groupState = useContext(DisclosureGroupStateContext);
1411
+ let { id, ...otherProps } = props;
1412
+ const defaultId = useId();
1413
+ id ||= defaultId;
1414
+ const isExpanded = groupState ? groupState.expandedKeys.has(id) : props.isExpanded;
1415
+ const state = useDisclosureState({
1416
+ ...props,
1417
+ isExpanded,
1418
+ onExpandedChange (isExpanded) {
1419
+ if (groupState) {
1420
+ groupState.toggleKey(id);
1421
+ }
1422
+ props.onExpandedChange?.(isExpanded);
1423
+ }
1424
+ });
1425
+ const isDisabled = props.isDisabled || groupState?.isDisabled || false;
1426
+ const domProps = filterDOMProps(otherProps);
1427
+ const { isFocusVisible: isFocusVisibleWithin } = useFocusRing({
1428
+ within: true
1429
+ });
1430
+ const panelRef = useRef(null);
1431
+ const { buttonProps, panelProps } = useDisclosure({
1432
+ ...props,
1433
+ isExpanded,
1434
+ isDisabled
1435
+ }, state, panelRef);
1436
+ const { role: _, ...propsWithoutRole } = panelProps;
1437
+ return /*#__PURE__*/ jsx(Provider, {
1438
+ values: [
1439
+ [
1440
+ DisclosureContext,
1441
+ state
1442
+ ],
1443
+ [
1444
+ ButtonContext,
1445
+ {
1446
+ slots: {
1447
+ [DEFAULT_SLOT]: {},
1448
+ trigger: buttonProps
1449
+ }
1450
+ }
1451
+ ],
1452
+ [
1453
+ DisclosurePanelContext,
1454
+ {
1455
+ ...propsWithoutRole,
1456
+ panelRef
1457
+ }
1458
+ ],
1459
+ [
1460
+ DisclosureStateContext,
1461
+ state
1462
+ ]
1463
+ ],
1464
+ children: /*#__PURE__*/ jsx("div", {
1465
+ ...domProps,
1466
+ className: otherProps.className,
1467
+ ref: ref,
1468
+ "data-focus-visible-within": isFocusVisibleWithin || undefined,
1469
+ "data-expanded": state.isExpanded || undefined,
1470
+ "data-disabled": isDisabled || undefined,
1471
+ children: typeof children === 'function' ? children({
1472
+ isExpanded: state.isExpanded,
1473
+ isFocusVisibleWithin,
1474
+ isDisabled,
1475
+ state,
1476
+ defaultChildren: null
1477
+ }) : children
1478
+ })
1479
+ });
1480
+ };
1481
+ const DisclosurePanelContext = /*#__PURE__*/ createContext({});
1482
+ const DisclosurePanel = ({ ref, children, ...props })=>{
1483
+ const disclosureContext = useContext(DisclosureContext);
1484
+ const { panelProps, panelRef } = useContext(DisclosurePanelContext);
1485
+ const { role: _role = 'group', className, ...restProps } = props;
1486
+ const ariaLabelledby = props['aria-labelledby'] ?? restProps['aria-labelledby'];
1487
+ const isWithoutRole = _role === 'none';
1488
+ const role = isWithoutRole ? undefined : _role;
1489
+ const { isFocusVisible: isFocusVisibleWithin, focusProps: focusWithinProps } = useFocusRing({
1490
+ within: true
1491
+ });
1492
+ const domProps = filterDOMProps(props);
1493
+ return /*#__PURE__*/ jsx("div", {
1494
+ className: cx('grid transition-all duration-300 motion-reduce:transition-none', disclosureContext?.isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'),
1495
+ children: /*#__PURE__*/ jsx("div", {
1496
+ className: "overflow-hidden",
1497
+ children: /*#__PURE__*/ jsx("div", {
1498
+ ref: mergeRefs(ref, panelRef),
1499
+ ...mergeProps(panelProps, focusWithinProps),
1500
+ ...restProps,
1501
+ ...domProps,
1502
+ "data-focus-visible-within": isFocusVisibleWithin || undefined,
1503
+ className: cx(className, '[content-visibility:visible]'),
1504
+ role: role,
1505
+ "aria-labelledby": isWithoutRole ? undefined : ariaLabelledby,
1506
+ children: /*#__PURE__*/ jsx(Provider, {
1507
+ values: [
1508
+ // Reset the context to avoid passing the same context to children, in case of nested Disclosures
1509
+ [
1510
+ DisclosureContext,
1511
+ null
1512
+ ],
1513
+ [
1514
+ ButtonContext,
1515
+ null
1516
+ ]
1517
+ ],
1518
+ children: children
1519
+ })
1520
+ })
1521
+ })
1522
+ });
1523
+ };
1524
+
1525
+ /**
1526
+ * A FileTrigger allows a user to access the file system with any pressable React Aria or React Spectrum component, or custom components built with usePress.
1527
+ */ const FileTrigger = (props)=>{
1528
+ const { onSelect, acceptedFileTypes, allowsMultiple, defaultCamera, children, acceptDirectory, ref, isInvalid, isRequired, name, value, ...rest } = props;
1529
+ const inputRef = useObjectRef(ref);
1530
+ return /*#__PURE__*/ jsxs(Fragment, {
1531
+ children: [
1532
+ /*#__PURE__*/ jsx(PressResponder, {
1533
+ onPress: ()=>{
1534
+ if (inputRef.current?.value) {
1535
+ inputRef.current.value = '';
1536
+ }
1537
+ inputRef.current?.click();
1538
+ },
1539
+ children: children
1540
+ }),
1541
+ /*#__PURE__*/ jsx(Input, {
1542
+ ...rest,
1543
+ required: isRequired,
1544
+ "aria-invalid": isInvalid,
1545
+ "data-invalid": isInvalid,
1546
+ "data-rac": true,
1547
+ name: Array.isArray(name) ? name.join(' ') : name,
1548
+ type: "file",
1549
+ ref: inputRef,
1550
+ accept: acceptedFileTypes?.toString(),
1551
+ onChange: (e)=>onSelect?.(e.target.files),
1552
+ capture: defaultCamera,
1553
+ multiple: allowsMultiple,
1554
+ // @ts-expect-error
1555
+ webkitdirectory: acceptDirectory ? '' : undefined,
1556
+ // This is a work around to prevent error in the console when attempting to submit a form with a required and empty file input
1557
+ // RAC uses display: none, which prevents the file input from being focused.
1558
+ // What we do instead is to hide it visually using custom CSS, so that the native HTML validation messages are still hidden. Which is why
1559
+ // we don't use the sr-only class.
1560
+ className: "absolute left-[-1000vw] opacity-0",
1561
+ // Finally, we add aria-hidden to prevent the file input from being read by screen readers
1562
+ "aria-hidden": true,
1563
+ // Prevent focus trap when tabbing (since focus is delegated to the button)
1564
+ tabIndex: -1
1565
+ })
1566
+ ]
1567
+ });
1568
+ };
1569
+
1570
+ const translations = {
1571
+ remove: {
1572
+ nb: 'Fjern',
1573
+ sv: 'Ta bort',
1574
+ en: 'Remove'
1575
+ }
1576
+ };
1577
+ /**
1578
+ * Converts an array of files to a DataTransfer object which can be used as a FileList.
1579
+ * This is necessary for setting the files on a native file input.
1580
+ * @param files An array of files
1581
+ * @returns The files as a DataTransfer object which can be used as a FileList
1582
+ */ function filesToDataTransfer(files) {
1583
+ const dataTransfer = new DataTransfer();
1584
+ for (const file of files){
1585
+ dataTransfer.items.add(file);
1586
+ }
1587
+ return dataTransfer.files;
1588
+ }
1589
+ /**
1590
+ * Generates unique file names for files with the same original name.
1591
+ * Any duplicate files will have a number in parentheses appended to their name.
1592
+ * @param files An array of files
1593
+ * @returns An array of files with unique names
1594
+ */ function uniqueFileNames(files) {
1595
+ const fileNameCounts = {};
1596
+ return files.map((file)=>{
1597
+ const fileName = file.name;
1598
+ // Filter out the extension and any trailing numbers in parentheses
1599
+ const baseName = fileName.replace(/\s*\(\d+\)|(\.[^.]+)$/g, '');
1600
+ // Extract the file extension
1601
+ const extension = fileName.match(/(\.[^.]+)$/)?.[0] || '';
1602
+ if (!fileNameCounts[baseName]) {
1603
+ // Extract any number from the file name (if any, otherwise default to 0)
1604
+ const baseNameCount = Number.parseInt(fileName.match(/\((\d+)\)/)?.[1] ?? '0');
1605
+ fileNameCounts[baseName] = baseNameCount;
1606
+ }
1607
+ fileNameCounts[baseName]++;
1608
+ if (fileNameCounts[baseName] > 1) {
1609
+ return new File([
1610
+ file
1611
+ ], // Follow the pattern of adding a number in parentheses to the base name (e.g. "file (1).txt")
1612
+ `${baseName} (${fileNameCounts[baseName] - 1})${extension}`);
1613
+ }
1614
+ return file;
1615
+ });
1616
+ }
1617
+ const FileUpload = ({ children, files: _files, onChange, validate, isInvalid: _isInvalid, errorMessage, isRequired, allowsMultiple, ref, ...fileTriggerProps })=>{
1618
+ const [files, setFiles] = useState(_files ?? []);
1619
+ const isInvalid = !!errorMessage || _isInvalid;
1620
+ const id = useId();
1621
+ const locale = _useLocale();
1622
+ const _inputRef = useRef(null);
1623
+ const inputRef = ref ?? _inputRef;
1624
+ const slottedContext = useSlottedContext(FormContext) || {};
1625
+ const validationBehavior = fileTriggerProps.validationBehavior ?? slottedContext.validationBehavior ?? 'native';
1626
+ const validateFiles = useCallback((files)=>{
1627
+ if (validate === undefined) return true;
1628
+ const errors = [];
1629
+ for (const file of files){
1630
+ if (!validate) continue;
1631
+ const validation = validate(file);
1632
+ if (typeof validation === 'string') errors.push(validation);
1633
+ }
1634
+ if (errors.length === 0) return true;
1635
+ return errors;
1636
+ }, [
1637
+ validate
1638
+ ]);
1639
+ const validationState = useFormValidationState({
1640
+ ...fileTriggerProps,
1641
+ validationBehavior,
1642
+ validate: validateFiles,
1643
+ isRequired,
1644
+ isInvalid,
1645
+ value: _files ?? files ?? null
1646
+ });
1647
+ const controlledOrUncontrolledFiles = // Use controlled files if they are provided, otherwise use internal files - but map them to File objects (remove validation prop)
1648
+ _files ?? files;
1649
+ useEffect(()=>{
1650
+ // Keep the native file input in sync with the internal file state
1651
+ if (inputRef.current) {
1652
+ inputRef.current.files = filesToDataTransfer(controlledOrUncontrolledFiles);
1653
+ }
1654
+ }, [
1655
+ controlledOrUncontrolledFiles,
1656
+ inputRef
1657
+ ]);
1658
+ const { fieldProps } = useField({
1659
+ ...fileTriggerProps,
1660
+ validate: validateFiles,
1661
+ validationBehavior,
1662
+ isInvalid,
1663
+ errorMessage
1664
+ });
1665
+ const [value, setValue] = useControlledState(_files, [], onChange);
1666
+ useFormReset(inputRef, value, setValue);
1667
+ useFormValidation({
1668
+ ...fileTriggerProps,
1669
+ validationBehavior,
1670
+ validate: validateFiles,
1671
+ isRequired,
1672
+ isInvalid
1673
+ }, validationState, inputRef);
1674
+ const buttonRef = useRef(null);
1675
+ const { displayValidation } = validationState;
1676
+ useUpdateEffect(()=>{
1677
+ // Fixes a bug where validation state is not reset after being set to customError
1678
+ // This happens if the file upload ends up in an invalid state and then is emptied: the old valdiations is still lingering
1679
+ if (controlledOrUncontrolledFiles.length === 0 && validationState.displayValidation.validationDetails.customError) {
1680
+ validationState.commitValidation();
1681
+ }
1682
+ }, [
1683
+ controlledOrUncontrolledFiles
1684
+ ]);
1685
+ return /*#__PURE__*/ jsx(Provider, {
1686
+ values: [
1687
+ [
1688
+ FieldErrorContext,
1689
+ displayValidation
1690
+ ]
1691
+ ],
1692
+ children: /*#__PURE__*/ jsxs("div", {
1693
+ "data-slot": "file-upload",
1694
+ className: "group grid w-72 max-w-full gap-2",
1695
+ "data-required": isRequired,
1696
+ children: [
1697
+ /*#__PURE__*/ jsx(Provider, {
1698
+ values: [
1699
+ [
1700
+ LabelContext,
1701
+ {
1702
+ htmlFor: id
1703
+ }
1704
+ ],
1705
+ [
1706
+ ButtonContext,
1707
+ {
1708
+ // The button acts as the trigger for the file input, which is why we connect the label to the button id
1709
+ id,
1710
+ // Needed for RAC auto-focusing behavior to work
1711
+ ref: buttonRef,
1712
+ className: 'w-fit'
1713
+ }
1714
+ ],
1715
+ [
1716
+ InputContext,
1717
+ fieldProps
1718
+ ]
1719
+ ],
1720
+ children: /*#__PURE__*/ jsx(FileTrigger, {
1721
+ ...fileTriggerProps,
1722
+ isRequired: isRequired,
1723
+ allowsMultiple: allowsMultiple,
1724
+ onSelect: (selectedFiles)=>{
1725
+ if (selectedFiles === null) return;
1726
+ const newFiles = Array.from(selectedFiles);
1727
+ // For controlled component
1728
+ onChange?.((prevFiles)=>allowsMultiple ? uniqueFileNames(prevFiles.concat(newFiles)) : newFiles);
1729
+ // For internal file state
1730
+ setFiles((prevFiles)=>allowsMultiple ? uniqueFileNames(prevFiles.concat(newFiles)) : newFiles);
1731
+ },
1732
+ isInvalid: isInvalid || validationState.displayValidation.isInvalid,
1733
+ ref: inputRef,
1734
+ // Delegate focus to the button when the hidden file input is focused (for RAC auto-focusing behavior)
1735
+ onFocus: ()=>buttonRef.current?.focus(),
1736
+ children: children
1737
+ })
1738
+ }),
1739
+ /*#__PURE__*/ jsx("ul", {
1740
+ className: "mt-4 grid gap-y-2",
1741
+ children: controlledOrUncontrolledFiles.map((file, fileIndex)=>{
1742
+ let fileName = file.name;
1743
+ if (fileTriggerProps.acceptDirectory && file.webkitRelativePath !== '') {
1744
+ fileName = file.webkitRelativePath;
1745
+ }
1746
+ const validation = validate?.(file) ?? true;
1747
+ const hasError = validation !== true;
1748
+ return /*#__PURE__*/ jsxs("li", {
1749
+ children: [
1750
+ /*#__PURE__*/ jsxs("div", {
1751
+ className: cx('flex items-center justify-between gap-2 rounded-lg border-2 px-4 py-2', hasError ? 'border-red bg-red-light' : 'border-gray bg-gray-lightest'),
1752
+ children: [
1753
+ fileName,
1754
+ ' ',
1755
+ /*#__PURE__*/ jsx("button", {
1756
+ className: cx('-m-2 grid h-11 w-11 shrink-0 cursor-pointer place-items-center rounded-xl', // Focus styles
1757
+ 'focus-visible:-outline-offset-8 focus-visible:outline-focus'),
1758
+ onClick: ()=>{
1759
+ // For controlled component
1760
+ onChange?.((prevFiles)=>prevFiles.filter((_, index)=>index !== fileIndex));
1761
+ // For internal file state
1762
+ setFiles((prevFiles)=>prevFiles.filter((_, index)=>index !== fileIndex));
1763
+ // Make sure screen readers doesn't loose track of focus
1764
+ // (without this, the focus will be set to the top of the page for screen readers)
1765
+ buttonRef.current?.focus();
1766
+ },
1767
+ "aria-label": translations.remove[locale],
1768
+ type: "button",
1769
+ children: /*#__PURE__*/ jsx(Trash, {})
1770
+ })
1771
+ ]
1772
+ }),
1773
+ hasError && /*#__PURE__*/ jsx(ErrorMessage, {
1774
+ className: "mt-1 block w-full",
1775
+ children: validation
1776
+ })
1777
+ ]
1778
+ }, fileName);
1779
+ })
1780
+ }),
1781
+ (controlledOrUncontrolledFiles.length === 0 || !!errorMessage) && /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
1782
+ errorMessage: errorMessage
1783
+ })
1784
+ ]
1785
+ })
1786
+ });
1787
+ };
1788
+
1789
+ const baseClassName = 'h-20 w-20 shrink-0 rounded-full';
1790
+ const Avatar = ({ src, alt = '', className, onError, loading = 'lazy', ...rest })=>{
1791
+ const [hasError, setHasError] = useState(false);
1792
+ const hasValidImage = !hasError && src;
1793
+ return hasValidImage ? /*#__PURE__*/ jsx("img", {
1794
+ ...rest,
1795
+ src: src,
1796
+ alt: alt,
1797
+ loading: loading,
1798
+ className: cx(className, baseClassName, 'object-cover'),
1799
+ onError: (event)=>{
1800
+ onError?.(event);
1801
+ setHasError(true);
1802
+ }
1803
+ }) : /*#__PURE__*/ jsx("div", {
1804
+ className: cx(className, baseClassName, 'grid place-items-center bg-gray-light text-gray-dark'),
1805
+ children: /*#__PURE__*/ jsx(User, {
1806
+ className: "scale-[2.25]"
1807
+ })
1808
+ });
1809
+ };
1810
+
1811
+ const DialogTrigger = (props)=>/*#__PURE__*/ jsx(DialogTrigger$1, {
1812
+ ...props
1813
+ });
1814
+ const ModalOverlay = (props)=>/*#__PURE__*/ jsx(ModalOverlay$1, {
1815
+ ...props,
1816
+ isDismissable: true,
1817
+ className: ({ isEntering, isExiting })=>cx('fixed inset-0 z-10 flex min-h-full items-center justify-center overflow-y-auto bg-black/25 p-4 text-center backdrop-blur-sm', isEntering && 'fade-in animate-in duration-300 ease-out', isExiting && 'fade-out animate-out duration-200 ease-in', // Using the motion-safe class does not work, so we use motion-reduce to overwrite instead
1818
+ 'motion-reduce:animate-none')
1819
+ });
1820
+ const Modal = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ModalOverlay, {
1821
+ children: /*#__PURE__*/ jsx(Modal$1, {
1822
+ ...restProps,
1823
+ className: ({ isEntering, isExiting })=>cx(className, 'w-full max-w-md overflow-hidden rounded-2xl bg-white p-4 text-left align-middle shadow-xl', isEntering && 'zoom-in-95 animate-in duration-300 ease-out', isExiting && 'zoom-out-95 animate-out duration-200 ease-in', // Using the motion-safe class does not work, so we use motion-reduce to overwrite instead
1824
+ 'motion-reduce:animate-none')
1825
+ })
1826
+ });
1827
+ const Dialog = ({ className, children, ...restProps })=>{
1828
+ const locale = _useLocale();
1829
+ return /*#__PURE__*/ jsx(Dialog$1, {
1830
+ ...restProps,
1831
+ className: cx('relative grid gap-y-5 outline-none', // Footer
1832
+ '[&_[data-slot="footer"]]:flex [&_[data-slot="footer"]]:gap-x-2'),
1833
+ children: ({ close })=>/*#__PURE__*/ jsx(Fragment, {
1834
+ children: /*#__PURE__*/ jsx(Provider, {
1835
+ values: [
1836
+ [
1837
+ HeadingContext,
1838
+ {
1839
+ slots: {
1840
+ [DEFAULT_SLOT]: {},
1841
+ title: {
1842
+ className: 'heading-s',
1843
+ _outerWrapper: (children)=>/*#__PURE__*/ jsxs("div", {
1844
+ className: "flex items-center justify-between gap-x-2",
1845
+ children: [
1846
+ children,
1847
+ /*#__PURE__*/ jsx(Button, {
1848
+ slot: "close" // RAC Dialog suppors one close button out of the box, so we utilize that here. For other close buttons we use ButtonContext
1849
+ ,
1850
+ variant: "tertiary",
1851
+ className: "!px-2.5 data-focus-visible:outline-focus-inset",
1852
+ "aria-label": translations$1.close[locale],
1853
+ children: /*#__PURE__*/ jsx(Close, {})
1854
+ })
1855
+ ]
1856
+ })
1857
+ }
1858
+ }
1859
+ }
1860
+ ],
1861
+ [
1862
+ ButtonContext,
1863
+ {
1864
+ // This is necessary to support multiple close buttons
1865
+ slots: {
1866
+ // We need to define default slot in order to also support non-slotted buttons (i.e. buttons without slot prop)
1867
+ [DEFAULT_SLOT]: {
1868
+ className: 'w-fit'
1869
+ },
1870
+ close: {
1871
+ onPress: close,
1872
+ className: 'w-fit'
1873
+ }
1874
+ }
1875
+ }
1876
+ ]
1877
+ ],
1878
+ children: children
1879
+ })
1880
+ })
1881
+ });
1882
+ };
1883
+
1884
+ const tagVariants = cva({
1885
+ base: [
1886
+ 'relative flex cursor-pointer items-center gap-1 rounded-lg px-2 py-1 font-medium text-sm transition-colors duration-200',
1887
+ //Focus
1888
+ 'focus-visible:outline-focus-offset',
1889
+ //Border
1890
+ 'border-2 border-blue-dark',
1891
+ //Backgrounds
1892
+ 'data-hovered:!bg-sky bg-white text-black aria-selected:bg-sky-light data-allows-removing:bg-sky-light',
1893
+ //Icons
1894
+ '[&_svg]:h-4 [&_svg]:w-4'
1895
+ ]
1896
+ });
1897
+ /**
1898
+ * A group component for Tag components that enables selection and organization of options.
1899
+ */ function TagGroup(props) {
1900
+ const { onRemove, selectionMode = 'single', className, children, ...restProps } = props;
1901
+ return /*#__PURE__*/ jsx(TagGroup$1, {
1902
+ ...restProps,
1903
+ className: className,
1904
+ selectionMode: onRemove ? 'none' : selectionMode,
1905
+ onRemove: onRemove,
1906
+ children: children
1907
+ });
1908
+ }
1909
+ /**
1910
+ * A container component for Tag components within a TagGroup.
1911
+ */ function TagList(props) {
1912
+ const { className, children, ...restProps } = props;
1913
+ return /*#__PURE__*/ jsx(TagList$1, {
1914
+ ...restProps,
1915
+ className: cx('flex flex-wrap gap-2', className),
1916
+ children: children
1917
+ });
1918
+ }
1919
+ /**
1920
+ * Interactive tag component for selections, filtering, and categorization.
1921
+ */ function Tag(props) {
1922
+ const { className, children, ...restProps } = props;
1923
+ const textValue = typeof children === 'string' ? children : undefined;
1924
+ return /*#__PURE__*/ jsx(Tag$1, {
1925
+ className: tagVariants({
1926
+ className
1927
+ }),
1928
+ textValue: textValue,
1929
+ ...restProps,
1930
+ children: ({ allowsRemoving })=>allowsRemoving ? /*#__PURE__*/ jsxs(Fragment, {
1931
+ children: [
1932
+ children,
1933
+ /*#__PURE__*/ jsx(Button$1, {
1934
+ className: "cursor-pointer outline-none after:absolute after:top-0 after:right-0 after:bottom-0 after:left-0",
1935
+ slot: "remove",
1936
+ children: /*#__PURE__*/ jsx(Close, {
1937
+ className: "ml-1"
1938
+ })
1939
+ })
1940
+ ]
1941
+ }) : children
533
1942
  });
534
1943
  }
535
- const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
536
1944
 
537
- export { _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ComboboxItem, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, SelectItem, _TextArea as TextArea, _TextField as TextField };
1945
+ export { Accordion, AccordionItem, Alertbox, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Label, Media, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, TextArea, TextField, Avatar as UNSAFE_Avatar, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, Disclosure as UNSAFE_Disclosure, DisclosureButton as UNSAFE_DisclosureButton, DisclosurePanel as UNSAFE_DisclosurePanel, FileUpload as UNSAFE_FileUpload, Modal as UNSAFE_Modal, Tag as UNSAFE_Tag, TagGroup as UNSAFE_TagGroup, TagList as UNSAFE_TagList, VideoLoop, _useLocale as useLocale };