@obosbbl/grunnmuren-react 2.0.0-canary.4 → 2.0.0-canary.41

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,17 +1,212 @@
1
- import { Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ListBoxItem, ComboBox, Group, Input, Button as Button$1, Popover, ListBox, RadioGroup as RadioGroup$1, Radio as Radio$1, Select as Select$1, SelectValue, TextField as TextField$1, TextArea as TextArea$1 } from 'react-aria-components';
2
- export { Form, I18nProvider } from 'react-aria-components';
1
+ 'use client';
2
+ import { I18nProvider, RouterProvider, 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, 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 } from 'react-aria-components';
3
+ export { Form } from 'react-aria-components';
3
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { forwardRef, useState, useRef, useId } from 'react';
5
- import { cva, cx, compose } from 'cva';
6
- import { LoadingSpinner, Check, ChevronDown } from '@obosbbl/grunnmuren-icons-react';
7
- import { mergeRefs } from '@react-aria/utils';
8
- import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5nawgR.js';
5
+ import { ChevronDown, LoadingSpinner, Check, Close, InfoCircle, CheckCircle, Warning, CloseCircle, ChevronRight, ChevronLeft } from '@obosbbl/grunnmuren-icons-react';
6
+ import { useLayoutEffect } from '@react-aria/utils';
7
+ import { cx, cva, compose } from 'cva';
8
+ import { createContext, forwardRef, Children, useId, useState } from 'react';
9
+ import { useProgressBar, useDateFormatter } from 'react-aria';
10
+
11
+ function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
12
+ return /*#__PURE__*/ jsx(I18nProvider, {
13
+ locale: locale,
14
+ children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
15
+ navigate: navigate,
16
+ useHref: useHref,
17
+ children: children
18
+ }) : children
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Returns the locale set in `<GrunnmurenProvider />`
24
+ */ function _useLocale() {
25
+ // a small wrapper around react-arias useLocale with a simpler return type with only the locales that we actually support
26
+ const locale = useLocale();
27
+ return locale.locale;
28
+ }
29
+
30
+ const HeadingContext = /*#__PURE__*/ createContext({});
31
+ const Heading = (props, ref)=>{
32
+ // biome-ignore lint/style/noParameterAssign: fix when removing refs for React 19
33
+ [props, ref] = useContextProps(props, ref, HeadingContext);
34
+ const { children, level, className, _innerWrapper: innerWrapper, ...restProps } = props;
35
+ const Element = `h${level}`;
36
+ return /*#__PURE__*/ jsx(Element, {
37
+ ...restProps,
38
+ className: className,
39
+ "data-slot": "heading",
40
+ children: innerWrapper ? innerWrapper(children) : children
41
+ });
42
+ };
43
+ const ContentContext = /*#__PURE__*/ createContext({});
44
+ const Content = (props, ref)=>{
45
+ // biome-ignore lint/style/noParameterAssign: fix when removing refs for React 19
46
+ [props, ref] = useContextProps(props, ref, ContentContext);
47
+ const { _outerWrapper: outerWrapper, ...restProps } = props;
48
+ const content = /*#__PURE__*/ jsx("div", {
49
+ ...restProps,
50
+ "data-slot": "content"
51
+ });
52
+ return outerWrapper ? outerWrapper(content) : content;
53
+ };
54
+ const Media = (props)=>/*#__PURE__*/ jsx("div", {
55
+ ...props,
56
+ "data-slot": "media"
57
+ });
58
+ const Footer = (props)=>/*#__PURE__*/ jsx("div", {
59
+ ...props,
60
+ "data-slot": "footer"
61
+ });
62
+
63
+ function Accordion(props, ref) {
64
+ const { children, className, ...restProps } = props;
65
+ const childCount = Children.count(children);
66
+ return /*#__PURE__*/ jsx("div", {
67
+ ...restProps,
68
+ ref: ref,
69
+ className: cx('rounded-lg bg-white', className),
70
+ children: Children.map(children, (child, index)=>/*#__PURE__*/ jsxs(Fragment, {
71
+ children: [
72
+ child,
73
+ index < childCount - 1 && // Margin is added to enable support for containers with a background color
74
+ /*#__PURE__*/ jsx("hr", {
75
+ className: "mx-2 border-gray-light",
76
+ "aria-hidden": true
77
+ })
78
+ ]
79
+ }))
80
+ });
81
+ }
82
+ function AccordionItem(props, ref) {
83
+ const { className, children, defaultOpen = false, isOpen: controlledIsOpen, onOpenChange, ...restProps } = props;
84
+ const contentId = useId();
85
+ const buttonId = useId();
86
+ const isControlled = controlledIsOpen != null;
87
+ // This component has internal state that controls whether it is open or not,
88
+ // regardless if we are controlled or uncontrolled.
89
+ // If we are controlled, we use a layout effect to sync the controlled state
90
+ // with the internal state.
91
+ //
92
+ const [isOpen, setIsOpen] = useState(// If we are controlled, use that open state, otherwise use the uncontrolled
93
+ isControlled ? controlledIsOpen : defaultOpen);
94
+ useLayoutEffect(()=>{
95
+ if (isControlled) {
96
+ setIsOpen(controlledIsOpen);
97
+ }
98
+ }, [
99
+ controlledIsOpen,
100
+ isControlled
101
+ ]);
102
+ const handleOpenChange = ()=>{
103
+ const newOpenState = !isOpen;
104
+ if (!isControlled) {
105
+ setIsOpen(newOpenState);
106
+ }
107
+ // Always call the change handler, even if we're uncontrolled.
108
+ // Easier to add stuff such as tracking etc.
109
+ if (onOpenChange) {
110
+ onOpenChange(newOpenState);
111
+ }
112
+ };
113
+ return /*#__PURE__*/ jsx("div", {
114
+ ...restProps,
115
+ className: cx('relative px-2', className),
116
+ ref: ref,
117
+ "data-open": isOpen,
118
+ children: /*#__PURE__*/ jsx(Provider, {
119
+ values: [
120
+ [
121
+ HeadingContext,
122
+ {
123
+ // Negative margin to strech the button to the entire with of the accordion (to support containers with a background color)
124
+ className: 'font-semibold leading-7 -mx-2 text-base',
125
+ // Supply a default level here to make this typecheck ok. Will be overwritten with the consumers set heading level anyways
126
+ level: 3,
127
+ _innerWrapper: (children)=>/*#__PURE__*/ jsxs("button", {
128
+ "aria-controls": contentId,
129
+ "aria-expanded": isOpen,
130
+ // 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
131
+ 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 focus-visible:outline-focus-inset",
132
+ id: buttonId,
133
+ onClick: handleOpenChange,
134
+ type: "button",
135
+ children: [
136
+ children,
137
+ /*#__PURE__*/ jsx(ChevronDown, {
138
+ className: cx('flex-none transition-transform duration-300 motion-reduce:transition-none', isOpen && 'rotate-180')
139
+ })
140
+ ]
141
+ })
142
+ }
143
+ ],
144
+ [
145
+ ContentContext,
146
+ {
147
+ className: // Uses pseudo element for vertical padding, since that doesn't affect the height when the accordion is closed
148
+ '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',
149
+ role: 'region',
150
+ // @ts-expect-error TODO: remove this expect-error when we're on React 19 https://github.com/facebook/react/issues/17157#issuecomment-2003750544
151
+ inert: isOpen ? undefined : 'true',
152
+ 'aria-labelledby': buttonId,
153
+ _outerWrapper: (children)=>/*#__PURE__*/ jsx("div", {
154
+ 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] '),
155
+ children: children
156
+ })
157
+ }
158
+ ]
159
+ ],
160
+ children: children
161
+ })
162
+ });
163
+ }
164
+ const _Accordion = /*#__PURE__*/ forwardRef(Accordion);
165
+ const _AccordionItem = /*#__PURE__*/ forwardRef(AccordionItem);
166
+
167
+ const badgeVariants = cva({
168
+ base: [
169
+ 'inline-flex w-fit items-center justify-center gap-1.5 rounded-lg [&_svg]:shrink-0'
170
+ ],
171
+ variants: {
172
+ color: {
173
+ 'gray-dark': 'bg-gray-dark text-white',
174
+ mint: 'bg-mint',
175
+ sky: 'bg-sky',
176
+ white: 'bg-white',
177
+ 'blue-dark': 'bg-blue-dark text-white',
178
+ 'green-dark': 'bg-green-dark text-white'
179
+ },
180
+ size: {
181
+ small: 'description px-2 py-0.5 [&_svg]:h-4 [&_svg]:w-4',
182
+ medium: 'description px-2.5 py-1.5 [&_svg]:h-4 [&_svg]:w-4',
183
+ large: 'paragraph px-3 py-2 [&_svg]:h-5 [&_svg]:w-5'
184
+ }
185
+ },
186
+ defaultVariants: {
187
+ size: 'medium'
188
+ }
189
+ });
190
+ function Badge(props, ref) {
191
+ const { className: _className, color, size, ...restProps } = props;
192
+ const className = badgeVariants({
193
+ className: _className,
194
+ color,
195
+ size
196
+ });
197
+ return /*#__PURE__*/ jsx("span", {
198
+ className: className,
199
+ ...restProps,
200
+ ref: ref
201
+ });
202
+ }
203
+ const _Badge = /*#__PURE__*/ forwardRef(Badge);
9
204
 
10
205
  /**
11
206
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
12
207
  */ const buttonVariants = cva({
13
208
  base: [
14
- 'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2'
209
+ '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 [&:not([data-focus-visible])]:outline-none'
15
210
  ],
16
211
  variants: {
17
212
  /**
@@ -27,17 +222,21 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
27
222
  * Adjusts the color of the button for usage on different backgrounds.
28
223
  * @default green
29
224
  */ color: {
30
- green: 'focus-visible:ring-black',
31
- mint: 'focus-visible:ring-mint focus-visible:ring-offset-green-dark',
32
- white: 'focus-visible:ring-white focus-visible:ring-offset-blue'
225
+ green: 'data-[focus-visible]:outline-focus',
226
+ mint: 'data-[focus-visible]:outline-focus data-[focus-visible]:outline-mint',
227
+ white: 'data-[focus-visible]:outline-focus data-[focus-visible]:outline-white'
33
228
  },
34
229
  /**
35
230
  * When the button is without text, but with a single icon.
36
231
  * @default false
37
232
  */ isIconOnly: {
38
233
  true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
39
- false: // The of-type classes takes care to add spacing when the button is used with icons
40
- 'px-4 py-2 [&>svg]:first-of-type:mr-2.5 [&>svg]:last-of-type:ml-2.5'
234
+ false: 'gap-2.5 px-4 py-2'
235
+ },
236
+ // Make the content of the button transparent to hide it's content, but keep the button width
237
+ isPending: {
238
+ true: 'relative !text-transparent',
239
+ false: null
41
240
  }
42
241
  },
43
242
  compoundVariants: [
@@ -45,96 +244,103 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
45
244
  color: 'green',
46
245
  variant: 'primary',
47
246
  // Darken bg by 20% on hover. The color is manually crafted
48
- className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352]'
247
+ className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352] [&_[role="progressbar"]]:text-white'
49
248
  },
50
249
  {
51
250
  color: 'green',
52
251
  variant: 'secondary',
53
- className: 'bg-white text-black shadow-green hover:bg-green hover:text-white active:bg-green'
252
+ className: 'text-black shadow-green hover:bg-green hover:text-white active:bg-green [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-black'
253
+ },
254
+ {
255
+ color: 'green',
256
+ variant: 'tertiary',
257
+ className: '[&_[role="progressbar"]]:text-black'
54
258
  },
55
259
  {
56
260
  color: 'mint',
57
261
  variant: 'primary',
58
262
  // Darken bg by 20% on hover. The color is manually crafted
59
- className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]'
263
+ className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd] [&_[role="progressbar"]]:text-black'
60
264
  },
61
265
  {
62
266
  color: 'mint',
63
267
  variant: 'secondary',
64
- className: 'text-mint shadow-mint hover:bg-mint hover:text-black'
268
+ className: 'text-mint shadow-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint'
65
269
  },
66
270
  {
67
271
  color: 'mint',
68
272
  variant: 'tertiary',
69
- className: 'text-mint'
273
+ className: 'text-mint [&_[role="progressbar"]]:text-mint'
70
274
  },
71
275
  {
72
276
  color: 'white',
73
277
  variant: 'primary',
74
- className: 'bg-white text-black hover:bg-sky active:bg-sky-light'
278
+ className: 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black'
75
279
  },
76
280
  {
77
281
  color: 'white',
78
282
  variant: 'secondary',
79
- className: 'text-white shadow-white hover:bg-white hover:text-black'
283
+ className: 'text-white shadow-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white'
80
284
  },
81
285
  {
82
286
  color: 'white',
83
287
  variant: 'tertiary',
84
- className: 'text-white'
288
+ className: 'text-white [&_[role="progressbar"]]:text-white'
85
289
  }
86
290
  ],
87
291
  defaultVariants: {
88
292
  variant: 'primary',
89
293
  color: 'green',
90
- isIconOnly: false
294
+ isIconOnly: false,
295
+ isPending: false
91
296
  }
92
297
  });
93
- function Button(props, forwardedRef) {
94
- const { children, className, color, isIconOnly, isLoading, variant, style, ...restProps } = props;
95
- const [widthOverride, setWidthOverride] = useState();
96
- const ownRef = useRef(null);
97
- const ref = mergeRefs(ownRef, forwardedRef);
98
- useClientLayoutEffect(()=>{
99
- if (isLoading) {
100
- const requestID = window.requestAnimationFrame(()=>{
101
- setWidthOverride(ownRef.current?.getBoundingClientRect()?.width);
102
- });
103
- return ()=>{
104
- setWidthOverride(undefined);
105
- cancelAnimationFrame(requestID);
106
- };
107
- }
108
- }, [
109
- isLoading,
110
- children
111
- ]);
112
- let Component = 'a';
113
- if (props.href == null) {
114
- // If we don't have a href, it's a button, and we add a fallback type button to prevent the button from accidentally submitting when in a form
115
- Component = 'button';
116
- restProps.type ??= 'button';
298
+ function isLinkProps$1(props) {
299
+ return !!props.href;
300
+ }
301
+ const translations$1 = {
302
+ pending: {
303
+ nb: 'venter',
304
+ sv: 'väntar',
305
+ en: 'pending'
117
306
  }
118
- 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
119
- /*#__PURE__*/ jsx(Component, {
120
- "aria-busy": isLoading ? true : undefined,
121
- className: buttonVariants({
122
- className,
123
- color,
124
- isIconOnly,
125
- variant
126
- }),
307
+ };
308
+ function Button(props, ref) {
309
+ const { children: _children, color, isIconOnly, isLoading, variant, isPending: _isPending, ...restProps } = props;
310
+ const isPending = _isPending || isLoading;
311
+ const className = buttonVariants({
312
+ className: props.className,
313
+ color,
314
+ isIconOnly,
315
+ variant,
316
+ isPending
317
+ });
318
+ const locale = _useLocale();
319
+ const { progressBarProps } = useProgressBar({
320
+ isIndeterminate: true,
321
+ 'aria-label': translations$1.pending[locale]
322
+ });
323
+ const children = isPending ? /*#__PURE__*/ jsxs(Fragment, {
324
+ children: [
325
+ _children,
326
+ /*#__PURE__*/ jsx(LoadingSpinner, {
327
+ className: "absolute m-auto motion-safe:animate-spin",
328
+ ...progressBarProps
329
+ })
330
+ ]
331
+ }) : _children;
332
+ return isLinkProps$1(restProps) ? /*#__PURE__*/ jsx(Link, {
333
+ ...restProps,
334
+ className: className,
127
335
  ref: ref,
128
- style: {
129
- ...style,
130
- width: widthOverride
131
- },
336
+ children: children
337
+ }) : /*#__PURE__*/ jsx(Button$1, {
132
338
  ...restProps,
133
- children: widthOverride ? // remove margin for icon alignment
134
- /*#__PURE__*/ jsx(LoadingSpinner, {
135
- className: "!m-0 mx-auto animate-spin"
136
- }) : children
137
- }));
339
+ className: className,
340
+ isPending: isPending,
341
+ ref: ref,
342
+ children: children
343
+ });
138
344
  }
139
345
  const _Button = /*#__PURE__*/ forwardRef(Button);
140
346
 
@@ -142,20 +348,27 @@ const formField = cx('group flex flex-col gap-2');
142
348
  const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
143
349
  const input = cva({
144
350
  base: [
145
- 'rounded-md px-3 py-2.5 text-sm font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
351
+ // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
352
+ 'bg-white',
353
+ // Use box-content to enable auto width based on number of characters (size)
354
+ // Setting min-height to prevent the input from collapsing in Safari
355
+ // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
356
+ 'box-content min-h-6 py-2.5',
357
+ 'rounded-md text-base font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
146
358
  // invalid styles
147
- 'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red'
359
+ 'group-data-[invalid]:ring-focus group-data-[invalid]:ring-red',
360
+ // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
361
+ 'appearance-none'
148
362
  ],
149
363
  variants: {
150
364
  // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
151
365
  focusModifier: {
152
- focus: 'focus:ring-2 group-data-[invalid]:focus:ring',
153
- visible: 'data-[focus-visible]:ring-2 group-data-[invalid]:data-[focus-visible]:ring'
366
+ focus: 'focus:ring-focus group-data-[invalid]:focus:ring',
367
+ visible: 'data-[focus-visible]:ring-focus group-data-[invalid]:data-[focus-visible]:ring'
154
368
  },
155
369
  isGrouped: {
156
- false: '',
157
- //
158
- true: 'flex-1 !ring-0 first:pl-0 last:pr-0'
370
+ false: 'px-3',
371
+ true: 'flex-1 !ring-0'
159
372
  }
160
373
  },
161
374
  defaultVariants: {
@@ -163,10 +376,14 @@ const input = cva({
163
376
  isGrouped: false
164
377
  }
165
378
  });
166
- 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');
379
+ const inputGroup = cx([
380
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
381
+ 'group-data-[invalid]:ring-focus group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring'
382
+ ]);
167
383
  const dropdown = {
168
- 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'),
169
- listbox: cx('text-sm outline-none'),
384
+ popover: cx('min-w-[--trigger-width] overflow-y-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'),
385
+ // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
386
+ listbox: cx('max-h-[25rem] overflow-x-hidden text-sm outline-none'),
170
387
  chevronIcon: cx('text-base transition-transform duration-150 group-data-[open]:rotate-180 motion-reduce:transition-none')
171
388
  };
172
389
 
@@ -180,22 +397,13 @@ function ErrorMessage(props) {
180
397
  });
181
398
  }
182
399
 
183
- function Description(props) {
184
- const { className, ...restProps } = props;
185
- return /*#__PURE__*/ jsx(Text, {
186
- ...restProps,
187
- className: cx(className, 'text-sm font-light leading-6'),
188
- slot: "description"
189
- });
190
- }
191
-
192
400
  const defaultClasses$1 = cx([
193
- 'group relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7'
401
+ 'group relative left-0 -mx-2.5 inline-flex max-w-fit cursor-pointer items-start gap-4 p-2.5 leading-7'
194
402
  ]);
195
403
  // Pulling this out into it's own component. Will probably export it in the future
196
404
  // so it can be used in other views, outside of an input of type checkbox, like in table rows.
197
405
  function CheckmarkBox() {
198
- return /*#__PURE__*/ jsx("div", {
406
+ return /*#__PURE__*/ jsx("span", {
199
407
  className: cx([
200
408
  'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
201
409
  // 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.
@@ -206,7 +414,7 @@ function CheckmarkBox() {
206
414
  // selected
207
415
  'group-data-[selected]:!border-green group-data-[selected]:!bg-green',
208
416
  // focus
209
- 'group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-black group-data-[focus-visible]:ring-offset-[9px]',
417
+ 'group-data-[focus-visible]:outline-focus-offset',
210
418
  // hovered
211
419
  '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',
212
420
  // 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
@@ -221,9 +429,9 @@ function CheckmarkBox() {
221
429
  function Checkbox(props, ref) {
222
430
  const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
223
431
  const id = useId();
224
- const descriptionId = 'desc' + id;
225
- const errorMessageId = 'error' + id;
226
- const isInvalid = _isInvalid || errorMessage != null;
432
+ const descriptionId = `desc${id}`;
433
+ const errorMessageId = `error${id}`;
434
+ const isInvalid = errorMessage != null || _isInvalid;
227
435
  return /*#__PURE__*/ jsx("div", {
228
436
  children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
229
437
  value: {
@@ -237,16 +445,15 @@ function Checkbox(props, ref) {
237
445
  isInvalid: isInvalid,
238
446
  ref: ref,
239
447
  children: [
240
- /*#__PURE__*/ jsx("div", {
241
- className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
242
- }),
243
448
  /*#__PURE__*/ jsx(CheckmarkBox, {}),
244
449
  children
245
450
  ]
246
451
  }),
247
- description && /*#__PURE__*/ jsx(Description, {
248
- className: "block",
452
+ 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 */}
453
+ /*#__PURE__*/ jsx("div", {
249
454
  id: descriptionId,
455
+ slot: "description",
456
+ className: "description block",
250
457
  children: description
251
458
  }),
252
459
  errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
@@ -260,6 +467,26 @@ function Checkbox(props, ref) {
260
467
  }
261
468
  const _Checkbox = /*#__PURE__*/ forwardRef(Checkbox);
262
469
 
470
+ function Description(props) {
471
+ const { className, ...restProps } = props;
472
+ return /*#__PURE__*/ jsx(Text, {
473
+ ...restProps,
474
+ className: cx(className, 'description'),
475
+ slot: "description"
476
+ });
477
+ }
478
+
479
+ /**
480
+ * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
481
+ * In other words, this handles controlled and uncontrolled form errors.
482
+ */ function ErrorMessageOrFieldError({ errorMessage }) {
483
+ return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
484
+ children: errorMessage
485
+ }) : /*#__PURE__*/ jsx(FieldError, {
486
+ className: formFieldError
487
+ });
488
+ }
489
+
263
490
  function Label(props) {
264
491
  const { children, className, ...restProps } = props;
265
492
  return /*#__PURE__*/ jsx(Label$1, {
@@ -271,7 +498,9 @@ function Label(props) {
271
498
 
272
499
  function CheckboxGroup(props, ref) {
273
500
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
274
- const isInvalid = _isInvalid || errorMessage != null;
501
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
502
+ // which will override any built in validation
503
+ const isInvalid = errorMessage != null || _isInvalid;
275
504
  return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
276
505
  ...restProps,
277
506
  className: cx(className, 'flex flex-col gap-2'),
@@ -286,28 +515,53 @@ function CheckboxGroup(props, ref) {
286
515
  children: description
287
516
  }),
288
517
  children,
289
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
290
- children: errorMessage
518
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
519
+ errorMessage: errorMessage
291
520
  })
292
521
  ]
293
522
  });
294
523
  }
295
524
  const _CheckboxGroup = /*#__PURE__*/ forwardRef(CheckboxGroup);
296
525
 
297
- /**
298
- * This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
299
- * In other words, this handles controlled and uncontrolled form errors.
300
- */ function ErrorMessageOrFieldError({ errorMessage }) {
301
- return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
302
- children: errorMessage
303
- }) : /*#__PURE__*/ jsx(FieldError, {
304
- className: formFieldError
526
+ const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
527
+ ...restProps,
528
+ className: cx(dropdown.listbox, className)
529
+ });
530
+ const ListBoxItem = (props)=>{
531
+ let textValue = props.textValue;
532
+ // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
533
+ // Since we use a render function (to handle the selected state) the child is never a string.
534
+ // This condition adds back that behaviour
535
+ if (textValue == null && typeof props.children === 'string') {
536
+ textValue = props.children;
537
+ }
538
+ return /*#__PURE__*/ jsx(ListBoxItem$1, {
539
+ ...props,
540
+ className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-[focused]:bg-sky-lightest'),
541
+ textValue: textValue,
542
+ children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
543
+ children: [
544
+ isSelected && /*#__PURE__*/ jsx(Check, {
545
+ className: "-ml-6 text-base"
546
+ }),
547
+ props.children
548
+ ]
549
+ })
550
+ });
551
+ };
552
+
553
+ function InputAddonDivider() {
554
+ return /*#__PURE__*/ jsx("span", {
555
+ className: "block h-6 w-px flex-none bg-black"
305
556
  });
306
557
  }
307
558
 
308
559
  function Combobox(props, ref) {
309
- const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
310
- const isInvalid = _isInvalid || errorMessage != null;
560
+ const { className, children, description, errorMessage, isLoading, isPending: _isPending, label, isInvalid: _isInvalid, ...restProps } = props;
561
+ const isPending = _isPending || isLoading;
562
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
563
+ // which will override any built in validation
564
+ const isInvalid = errorMessage != null || _isInvalid;
311
565
  return /*#__PURE__*/ jsxs(ComboBox, {
312
566
  ...restProps,
313
567
  className: cx(className, formField),
@@ -329,7 +583,7 @@ function Combobox(props, ref) {
329
583
  ref: ref
330
584
  }),
331
585
  /*#__PURE__*/ jsx(Button$1, {
332
- children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
586
+ children: isPending ? /*#__PURE__*/ jsx(LoadingSpinner, {
333
587
  className: "animate-spin"
334
588
  }) : /*#__PURE__*/ jsx(ChevronDown, {
335
589
  className: dropdown.chevronIcon
@@ -355,33 +609,13 @@ function Combobox(props, ref) {
355
609
  ]
356
610
  });
357
611
  }
358
- const ComboboxItem = (props)=>{
359
- let textValue = props.textValue;
360
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
361
- // Since we use a render function (to handle the selected state) the child is never a string.
362
- // This condition adds back that behaviour
363
- if (textValue == null && typeof props.children === 'string') {
364
- textValue = props.children;
365
- }
366
- return /*#__PURE__*/ jsx(ListBoxItem, {
367
- ...props,
368
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
369
- textValue: textValue,
370
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
371
- children: [
372
- isSelected && /*#__PURE__*/ jsx(Check, {
373
- className: "-ml-6 text-base"
374
- }),
375
- props.children
376
- ]
377
- })
378
- });
379
- };
380
612
  const _Combobox = /*#__PURE__*/ forwardRef(Combobox);
381
613
 
382
614
  function RadioGroup(props, ref) {
383
615
  const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
384
- const isInvalid = _isInvalid || errorMessage != null;
616
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
617
+ // which will override any built in validation
618
+ const isInvalid = errorMessage != null || _isInvalid;
385
619
  return /*#__PURE__*/ jsxs(RadioGroup$1, {
386
620
  ...restProps,
387
621
  className: cx(className, 'flex flex-col gap-2'),
@@ -396,8 +630,8 @@ function RadioGroup(props, ref) {
396
630
  children: description
397
631
  }),
398
632
  children,
399
- errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
400
- children: errorMessage
633
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
634
+ errorMessage: errorMessage
401
635
  })
402
636
  ]
403
637
  });
@@ -405,7 +639,7 @@ function RadioGroup(props, ref) {
405
639
  const _RadioGroup = /*#__PURE__*/ forwardRef(RadioGroup);
406
640
 
407
641
  const defaultClasses = cx([
408
- 'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
642
+ 'relative -ml-2.5 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
409
643
  // the radio button itself
410
644
  'before:flex-none before:rounded-full before:border-2 before:border-black',
411
645
  // 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.
@@ -418,38 +652,35 @@ const defaultClasses = cx([
418
652
  // hover
419
653
  'data-[hovered]:before:border-green data-[hovered]:before:bg-green-lightest data-[hovered]:data-[invalid]:before:bg-red-light',
420
654
  // focus
421
- 'data-[focus-visible]:before:ring data-[focus-visible]:before:ring-black data-[focus-visible]:before:ring-offset-[9px]',
655
+ 'data-[focus-visible]:before:ring-focus-offset',
422
656
  // 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
423
657
  // so we use an inner outline to artifically pad the border
424
658
  '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'
425
659
  ]);
426
660
  function Radio(props, ref) {
427
661
  const { children, className, description, ...restProps } = props;
428
- return /*#__PURE__*/ jsxs(Radio$1, {
662
+ return /*#__PURE__*/ jsx(Radio$1, {
429
663
  ...restProps,
430
664
  className: cx(className, defaultClasses),
431
665
  ref: ref,
432
- children: [
433
- /*#__PURE__*/ jsx("div", {
434
- className: "absolute -left-2.5 top-0 z-10 h-11 w-11 "
435
- }),
436
- /*#__PURE__*/ jsxs("div", {
437
- children: [
438
- children,
439
- description && /*#__PURE__*/ jsx(Description, {
440
- className: "mt-2 block",
441
- children: description
442
- })
443
- ]
444
- })
445
- ]
666
+ children: /*#__PURE__*/ jsxs("div", {
667
+ children: [
668
+ children,
669
+ description && /*#__PURE__*/ jsx(Description, {
670
+ className: "mt-2 block",
671
+ children: description
672
+ })
673
+ ]
674
+ })
446
675
  });
447
676
  }
448
677
  const _Radio = /*#__PURE__*/ forwardRef(Radio);
449
678
 
450
679
  function Select(props, ref) {
451
680
  const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
452
- const isInvalid = _isInvalid || errorMessage != null;
681
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
682
+ // which will override any built in validation
683
+ const isInvalid = errorMessage != null || _isInvalid;
453
684
  return /*#__PURE__*/ jsxs(Select$1, {
454
685
  ...restProps,
455
686
  className: cx(className, formField),
@@ -490,33 +721,11 @@ function Select(props, ref) {
490
721
  ]
491
722
  });
492
723
  }
493
- const SelectItem = (props)=>{
494
- let textValue = props.textValue;
495
- // When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
496
- // Since we use a render function (to handle the selected state) the child is never a string.
497
- // This condition adds back that behaviour
498
- if (textValue == null && typeof props.children === 'string') {
499
- textValue = props.children;
500
- }
501
- return /*#__PURE__*/ jsx(ListBoxItem, {
502
- ...props,
503
- className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
504
- textValue: textValue,
505
- children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
506
- children: [
507
- isSelected && /*#__PURE__*/ jsx(Check, {
508
- className: "-ml-6 text-base"
509
- }),
510
- props.children
511
- ]
512
- })
513
- });
514
- };
515
724
  const _Select = /*#__PURE__*/ forwardRef(Select);
516
725
 
517
726
  function TextArea(props, ref) {
518
727
  const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
519
- const isInvalid = _isInvalid || errorMessage != null;
728
+ const isInvalid = errorMessage != null || _isInvalid;
520
729
  return /*#__PURE__*/ jsxs(TextField$1, {
521
730
  ...restProps,
522
731
  className: cx(className, formField),
@@ -541,18 +750,24 @@ function TextArea(props, ref) {
541
750
  }
542
751
  const _TextArea = /*#__PURE__*/ forwardRef(TextArea);
543
752
 
544
- const inputWithAlignment = compose(input, cva({
753
+ const inputVariants$1 = compose(input, cva({
545
754
  base: '',
546
755
  variants: {
547
756
  textAlign: {
548
757
  right: 'text-right',
549
758
  left: ''
759
+ },
760
+ autoWidth: {
761
+ true: 'max-w-fit',
762
+ false: ''
550
763
  }
551
764
  }
552
765
  }));
553
766
  function TextField(props, ref) {
554
- const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
555
- const isInvalid = _isInvalid || errorMessage != null;
767
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
768
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
769
+ // which will override any built in validation
770
+ const isInvalid = errorMessage != null || _isInvalid;
556
771
  return /*#__PURE__*/ jsxs(TextField$1, {
557
772
  ...restProps,
558
773
  className: cx(className, formField),
@@ -565,29 +780,96 @@ function TextField(props, ref) {
565
780
  children: description
566
781
  }),
567
782
  leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
568
- className: inputGroup,
783
+ className: cx(inputGroup, {
784
+ 'w-fit': !!size
785
+ }),
569
786
  children: [
570
787
  leftAddon,
571
- withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider, {
572
- className: "ml-3"
573
- }),
788
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
574
789
  /*#__PURE__*/ jsx(Input, {
575
- className: inputWithAlignment({
790
+ className: inputVariants$1({
576
791
  textAlign,
577
- isGrouped: true
792
+ isGrouped: true,
793
+ autoWidth: !!size
578
794
  }),
579
- ref: ref
795
+ ref: ref,
796
+ size: size
580
797
  }),
581
- withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider, {
582
- className: "mr-3"
798
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
799
+ rightAddon
800
+ ]
801
+ }) : /*#__PURE__*/ jsx(Input, {
802
+ className: inputVariants$1({
803
+ textAlign,
804
+ autoWidth: !!size
805
+ }),
806
+ ref: ref,
807
+ size: size
808
+ }),
809
+ /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
810
+ errorMessage: errorMessage
811
+ })
812
+ ]
813
+ });
814
+ }
815
+ const _TextField = /*#__PURE__*/ forwardRef(TextField);
816
+
817
+ // This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
818
+ const inputVariants = compose(input, cva({
819
+ base: '',
820
+ variants: {
821
+ textAlign: {
822
+ right: 'text-right',
823
+ left: ''
824
+ },
825
+ autoWidth: {
826
+ true: 'max-w-fit',
827
+ false: ''
828
+ }
829
+ }
830
+ }));
831
+ function NumberField(props, ref) {
832
+ const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
833
+ // the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
834
+ // which will override any built in validation
835
+ const isInvalid = errorMessage != null || _isInvalid;
836
+ return /*#__PURE__*/ jsxs(NumberField$1, {
837
+ ...restProps,
838
+ className: cx(className, formField),
839
+ isInvalid: isInvalid,
840
+ children: [
841
+ label && /*#__PURE__*/ jsx(Label, {
842
+ children: label
843
+ }),
844
+ description && /*#__PURE__*/ jsx(Description, {
845
+ children: description
846
+ }),
847
+ leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
848
+ className: cx(inputGroup, {
849
+ 'w-fit': !!size
850
+ }),
851
+ children: [
852
+ leftAddon,
853
+ withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
854
+ /*#__PURE__*/ jsx(Input, {
855
+ className: inputVariants({
856
+ textAlign,
857
+ isGrouped: true,
858
+ autoWidth: !!size
859
+ }),
860
+ ref: ref,
861
+ size: size
583
862
  }),
863
+ withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
584
864
  rightAddon
585
865
  ]
586
866
  }) : /*#__PURE__*/ jsx(Input, {
587
- className: inputWithAlignment({
588
- textAlign
867
+ className: inputVariants({
868
+ textAlign,
869
+ autoWidth: !!size
589
870
  }),
590
- ref: ref
871
+ ref: ref,
872
+ size: size
591
873
  }),
592
874
  /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
593
875
  errorMessage: errorMessage
@@ -595,11 +877,328 @@ function TextField(props, ref) {
595
877
  ]
596
878
  });
597
879
  }
598
- function Divider({ className }) {
599
- return /*#__PURE__*/ jsx("span", {
600
- className: cx(className, 'block h-6 w-px flex-none bg-black')
880
+ const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
881
+
882
+ // TODO: add new icons
883
+ const iconMap = {
884
+ info: InfoCircle,
885
+ success: CheckCircle,
886
+ warning: Warning,
887
+ danger: CloseCircle
888
+ };
889
+ const alertVariants = cva({
890
+ base: [
891
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
892
+ // Heading styles:
893
+ '[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
894
+ // Content styles:
895
+ '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
896
+ // Footer styles:
897
+ '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
898
+ ],
899
+ variants: {
900
+ /**
901
+ * The variant of the alert
902
+ * @default info
903
+ */ variant: {
904
+ info: 'border-[#1A7FA7] bg-sky-light',
905
+ success: 'border-[#0F9B6E] bg-mint-light',
906
+ warning: 'border-[#C57C13] bg-[#FFF2DE]',
907
+ danger: 'border-[#C0385D] bg-red-light'
908
+ }
909
+ },
910
+ defaultVariants: {
911
+ variant: 'info'
912
+ }
913
+ });
914
+ const translations = {
915
+ close: {
916
+ nb: 'Lukk',
917
+ sv: 'Stäng',
918
+ en: 'Close'
919
+ },
920
+ showMore: {
921
+ nb: 'Les mer',
922
+ sv: 'Läs mer',
923
+ en: 'Read more'
924
+ },
925
+ showLess: {
926
+ nb: 'Vis mindre',
927
+ sv: 'Dölj',
928
+ en: 'Show less'
929
+ }
930
+ };
931
+ const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
932
+ const Icon = iconMap[variant];
933
+ const locale = _useLocale();
934
+ const id = useId();
935
+ const [isExpanded, setIsExpanded] = useState(false);
936
+ const isCollapsed = isExpandable && !isExpanded;
937
+ const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
938
+ const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
939
+ if (!isVisible) return;
940
+ const close = ()=>{
941
+ setIsUncontrolledVisible(false);
942
+ if (onDismiss) onDismiss();
943
+ };
944
+ const isInDevMode = process.env.NODE_ENV !== 'production';
945
+ if (isInDevMode && onDismiss && !isDismissable) {
946
+ console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
947
+ }
948
+ if (isInDevMode && !children) {
949
+ console.error('`No children was passed to the <AlertBox/>` component.');
950
+ return;
951
+ }
952
+ const [firstChild, ...restChildren] = Children.toArray(children);
953
+ return /*#__PURE__*/ jsxs("div", {
954
+ className: alertVariants({
955
+ className,
956
+ variant
957
+ }),
958
+ // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
959
+ // role="none" will not have any effect on a div, so it can be omitted.
960
+ role: role === 'none' ? undefined : role,
961
+ children: [
962
+ /*#__PURE__*/ jsx(Icon, {}),
963
+ firstChild,
964
+ isDismissable && /*#__PURE__*/ jsx("button", {
965
+ className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus-visible:outline-focus focus-visible:-outline-offset-8'),
966
+ onClick: close,
967
+ "aria-label": translations.close[locale],
968
+ type: "button",
969
+ children: /*#__PURE__*/ jsx(Close, {})
970
+ }),
971
+ isExpandable && /*#__PURE__*/ jsxs("button", {
972
+ 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:
973
+ 'outline-none after:absolute after:bottom-3 after:left-0 after:right-0 after:h-0', 'focus-visible:after:h-[2px] focus-visible:after:bg-black'),
974
+ onClick: ()=>setIsExpanded((prevState)=>!prevState),
975
+ "aria-expanded": isExpanded,
976
+ "aria-controls": id,
977
+ type: "button",
978
+ children: [
979
+ isExpanded ? translations.showLess[locale] : translations.showMore[locale],
980
+ /*#__PURE__*/ jsx(ChevronDown, {
981
+ className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
982
+ })
983
+ ]
984
+ }),
985
+ restChildren?.length > 0 && /*#__PURE__*/ jsx("div", {
986
+ className: cx('col-span-full grid gap-y-4', isCollapsed && '[&>*:not([data-slot="footer"])]:hidden'),
987
+ id: id,
988
+ children: restChildren
989
+ })
990
+ ]
991
+ });
992
+ };
993
+
994
+ function Breadcrumbs(props, ref) {
995
+ const { className, children, ...restProps } = props;
996
+ return /*#__PURE__*/ jsx(Breadcrumbs$1, {
997
+ ...restProps,
998
+ className: cx(className, 'flex flex-wrap text-sm leading-6'),
999
+ ref: ref,
1000
+ children: children
601
1001
  });
602
1002
  }
603
- const _TextField = /*#__PURE__*/ forwardRef(TextField);
1003
+ const _Breadcrumbs = /*#__PURE__*/ forwardRef(Breadcrumbs);
1004
+
1005
+ function Breadcrumb(props, ref) {
1006
+ const { className, children, href, ...restProps } = props;
1007
+ return /*#__PURE__*/ jsxs(Breadcrumb$1, {
1008
+ className: cx(className, 'group flex items-center'),
1009
+ ...restProps,
1010
+ ref: ref,
1011
+ children: [
1012
+ href ? /*#__PURE__*/ jsx(Link, {
1013
+ href: href,
1014
+ // use outline instead of ring for focus marker that can be offset without creating a white background between the focus marker and the element content
1015
+ className: "rounded-sm data-[focus-visible]:outline-focus group-last:no-underline [&:not([data-focus-visible])]:outline-none",
1016
+ children: children
1017
+ }) : children,
1018
+ /*#__PURE__*/ jsx(ChevronRight, {
1019
+ className: "px-1 group-last:hidden"
1020
+ })
1021
+ ]
1022
+ });
1023
+ }
1024
+ const _Breadcrumb = /*#__PURE__*/ forwardRef(Breadcrumb);
1025
+
1026
+ function isLinkProps(props) {
1027
+ return !!props.href;
1028
+ }
1029
+ function Backlink(props, ref) {
1030
+ const { className, children, withUnderline, ...restProps } = props;
1031
+ const Component = isLinkProps(props) ? Link : Button$1;
1032
+ return /*#__PURE__*/ jsxs(Component, {
1033
+ className: cx(className, 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 no-underline data-[focus-visible]:outline-focus [&:not([data-focus-visible])]:outline-none'),
1034
+ ...restProps,
1035
+ // @ts-expect-error ignore the type of the ref here
1036
+ ref: ref,
1037
+ children: [
1038
+ /*#__PURE__*/ jsx(ChevronLeft, {
1039
+ className: cx('-ml-[0.5em] flex-shrink-0 transition-transform duration-300 group-hover:-translate-x-1')
1040
+ }),
1041
+ /*#__PURE__*/ jsx("span", {
1042
+ children: /*#__PURE__*/ jsx("span", {
1043
+ className: cx('border-b-[1px] border-t-[1px] border-transparent transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
1044
+ children: children
1045
+ })
1046
+ })
1047
+ ]
1048
+ });
1049
+ }
1050
+ const _Backlink = /*#__PURE__*/ forwardRef(Backlink);
1051
+
1052
+ const cardVariants = cva({
1053
+ base: [
1054
+ 'group/card',
1055
+ 'rounded-2xl border p-3',
1056
+ 'grid auto-rows-max gap-y-4',
1057
+ 'relative',
1058
+ // **** Heading ****
1059
+ '[&_[data-slot="heading"]]:inline',
1060
+ '[&_[data-slot="heading"]]:heading-s',
1061
+ '[&_[data-slot="heading"]]:leading-6',
1062
+ '[&_[data-slot="heading"]]:w-fit',
1063
+ '[&_[data-slot="heading"]]:text-pretty',
1064
+ // **** Content ****
1065
+ '[&_[data-slot="content"]]:grid [&_[data-slot="content"]]:auto-rows-max [&_[data-slot="content"]]:gap-y-4',
1066
+ // **** Media ****
1067
+ '[&_[data-slot="media"]]:overflow-hidden',
1068
+ '[&_[data-slot="media"]]:rounded-t-2xl',
1069
+ // 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)
1070
+ '[&_[data-slot="media"]]:mx-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))] [&_[data-slot="media"]]:mt-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
1071
+ // Sets the aspect ratio of the media content (width: 100% is necessary to make aspect ratio work in FF)
1072
+ '[&_[data-slot="media"]>*]:aspect-[3/2] [&_[data-slot="media"]>*]:w-full [&_[data-slot="media"]_img]:object-cover',
1073
+ // 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.
1074
+ '[&_[data-slot="media"]>*]:duration-300 [&_[data-slot="media"]>*]:ease-in-out [&_[data-slot="media"]>*]:motion-safe:transition-transform',
1075
+ // **** Card link ****
1076
+ // **** Hover ****
1077
+ // 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)
1078
+ '[&:has([data-slot="card-link"]_a:hover)_[data-slot="media"]>*]:scale-110',
1079
+ // **** Card link in Heading ****
1080
+ '[&:has([data-slot="heading"]_[data-slot="card-link"]:hover)_[data-slot="media"]>*]:scale-110',
1081
+ // Border (bottom/top) is set to transparent to make sure the bottom underline is not visible when the card is hovered
1082
+ // Border top is set to even out the border bottom used for the underline
1083
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:no-underline',
1084
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-2',
1085
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-transparent',
1086
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:transition-colors',
1087
+ '[&_[data-slot="heading"]_[data-slot="card-link"]:hover]:border-b-current',
1088
+ // 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
1089
+ '[&_[data-slot="heading"]_[data-slot="card-link"]]:heading-s [&_[data-slot="heading"]_[data-slot="card-link"]]:text-pretty [&_[data-slot="heading"]_[data-slot="card-link"]]:leading-6',
1090
+ // **** Fail-safe for interactive elements ****
1091
+ // Make interactive elements clickable by themselves, while the rest of the card is clickable as a whole
1092
+ // The card is made clickable by a pseudo-element on the heading that covers the entire card
1093
+ '[&:not(:has([data-slot="card-link"]_a))_a:not([data-slot="card-link"])]:relative [&_button]:relative [&_input]:relative',
1094
+ // 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)
1095
+ '[&_[data-slot="card-link"]_a]:static',
1096
+ // Place other interactive on top of the pseudo-element that makes the entire card clickable
1097
+ // by setting a higher z-index than the pseudo-element (which implicitly z-index 0)
1098
+ '[&_a:not([data-slot="card-link"])]:z-[1] [&_button]:z-[1] [&_input]:z-[1]'
1099
+ ],
1100
+ variants: {
1101
+ /**
1102
+ * The variant of the card
1103
+ * @default subtle
1104
+ */ variant: {
1105
+ subtle: [
1106
+ 'border-transparent',
1107
+ // Media styles:
1108
+ '[&_[data-slot="media"]]:rounded-b-2xl'
1109
+ ],
1110
+ outlined: 'border border-black'
1111
+ }
1112
+ },
1113
+ defaultVariants: {
1114
+ variant: 'subtle'
1115
+ }
1116
+ });
1117
+ const Card = ({ children, className: _className, variant, ...restProps })=>{
1118
+ const className = cardVariants({
1119
+ className: _className,
1120
+ variant
1121
+ });
1122
+ return /*#__PURE__*/ jsx("div", {
1123
+ className: className,
1124
+ ...restProps,
1125
+ children: children
1126
+ });
1127
+ };
1128
+ const cardLinkVariants = cva({
1129
+ base: 'w-fit max-w-full',
1130
+ variants: {
1131
+ withHref: {
1132
+ true: [
1133
+ // **** Clickarea ****
1134
+ 'cursor-pointer',
1135
+ 'after:absolute',
1136
+ 'after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
1137
+ 'after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
1138
+ // **** Focus ****
1139
+ 'focus-visible:outline-none',
1140
+ 'data-[focus-visible]:after:outline-focus',
1141
+ 'data-[focus-visible]:after:outline-offset-2',
1142
+ // **** Hover ****
1143
+ // Links are underlined by default, and the underline is removed on hover.
1144
+ // So we make sure that also happens when the user hovers the clickable area.
1145
+ 'hover:no-underline'
1146
+ ],
1147
+ false: [
1148
+ // **** Clickarea ****
1149
+ '[&_a]:after:cursor-pointer',
1150
+ '[&_a]:after:absolute',
1151
+ '[&_a]:after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
1152
+ '[&_a]:after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
1153
+ // **** Focus ****
1154
+ '[&_a[data-focus-visible]]:outline-none',
1155
+ '[&_a[data-focus-visible]]:after:outline-focus',
1156
+ '[&_a[data-focus-visible]]:after:outline-offset-2',
1157
+ // **** Hover ****
1158
+ // Links are underlined by default, and the underline is removed on hover.
1159
+ // So we make sure that also happens when the user hovers the card.
1160
+ // The group-hover ensures that the hover effect also applies when this component is used as a wrapper around a link.
1161
+ '[&_a]:group-hover/card:no-underline'
1162
+ ]
1163
+ }
1164
+ }
1165
+ });
1166
+ /**
1167
+ * A component that creates a clickable area on a card.
1168
+ * It can be used either as a wrapper around a link or as a standalone link.
1169
+ */ const CardLink = ({ className: _className, href, ...restProps })=>{
1170
+ const className = cardLinkVariants({
1171
+ className: _className,
1172
+ withHref: !!href
1173
+ });
1174
+ return href ? /*#__PURE__*/ jsx(Link, {
1175
+ "data-slot": "card-link",
1176
+ ...restProps,
1177
+ href: href,
1178
+ className: className
1179
+ }) : // We can't utilize that the `Link` component from react-aria-components renders as a span if it doesn't have an href,
1180
+ // because it still renders with role="link" and tabindex="0" which makes it focusable.
1181
+ // So we need to render a div instead.
1182
+ /*#__PURE__*/ jsx("div", {
1183
+ "data-slot": "card-link",
1184
+ className: className,
1185
+ ...restProps
1186
+ });
1187
+ };
1188
+
1189
+ /**
1190
+ * A React component that wraps https://react-spectrum.adobe.com/react-aria/useDateFormatter.html
1191
+ * By default it sets the timeZone to `Europe/Berlin` to prevent the server's timezone from affecting
1192
+ * the localized format
1193
+ */ const DateFormatter = ({ options: _options, value, children: render })=>{
1194
+ const options = {
1195
+ timeZone: 'Europe/Berlin',
1196
+ ..._options
1197
+ };
1198
+ const formatter = useDateFormatter(options);
1199
+ const date = typeof value === 'string' ? new Date(value) : value;
1200
+ const formatted = formatter.format(date);
1201
+ return render ? render(formatted) : formatted;
1202
+ };
604
1203
 
605
- export { _Button as Button, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ComboboxItem, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, SelectItem, _TextArea as TextArea, _TextField as TextField };
1204
+ export { _Accordion as Accordion, _AccordionItem as AccordionItem, Alertbox, _Backlink as Backlink, _Badge as Badge, _Breadcrumb as Breadcrumb, _Breadcrumbs as Breadcrumbs, _Button as Button, Card, CardLink, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, Content, ContentContext, DateFormatter, Footer, GrunnmurenProvider, Heading, HeadingContext, Media, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField, _useLocale as useLocale };