@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/README.md +122 -10
- package/dist/index.d.mts +421 -81
- package/dist/index.mjs +1597 -189
- package/package.json +16 -6
- package/dist/Button-client-wuoyidfi.js +0 -144
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,392 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
23
|
-
visible: 'data-
|
|
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(
|
|
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-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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-
|
|
433
|
+
'group-data-selected:!border-green group-data-selected:!bg-green',
|
|
78
434
|
// focus
|
|
79
|
-
'group-data-
|
|
435
|
+
'group-data-focus-visible:outline-focus-offset',
|
|
80
436
|
// hovered
|
|
81
|
-
'group-data-
|
|
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-
|
|
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-
|
|
443
|
+
className: "h-full w-full opacity-0 group-data-selected:opacity-100"
|
|
88
444
|
})
|
|
89
445
|
});
|
|
90
446
|
}
|
|
91
|
-
function Checkbox(props
|
|
447
|
+
function Checkbox(props) {
|
|
92
448
|
const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
|
|
93
449
|
const id = useId();
|
|
94
|
-
const descriptionId =
|
|
95
|
-
const errorMessageId =
|
|
96
|
-
const isInvalid =
|
|
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 &&
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
515
|
+
function CheckboxGroup(props) {
|
|
143
516
|
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
144
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
179
|
-
const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
180
|
-
const
|
|
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:
|
|
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
|
|
641
|
+
function RadioGroup(props) {
|
|
253
642
|
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
254
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
685
|
+
function Radio(props) {
|
|
297
686
|
const { children, className, description, ...restProps } = props;
|
|
298
|
-
return /*#__PURE__*/
|
|
687
|
+
return /*#__PURE__*/ jsx(Radio$1, {
|
|
299
688
|
...restProps,
|
|
300
689
|
className: cx(className, defaultClasses),
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
321
|
-
const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
322
|
-
|
|
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
|
|
388
|
-
const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
|
|
389
|
-
const isInvalid =
|
|
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
|
|
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
|
|
424
|
-
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
|
|
425
|
-
|
|
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(
|
|
442
|
-
className: "ml-3"
|
|
443
|
-
}),
|
|
809
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
444
810
|
/*#__PURE__*/ jsx(Input, {
|
|
445
|
-
className:
|
|
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:
|
|
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
|
|
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
|
|
486
|
-
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
|
|
487
|
-
|
|
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(
|
|
504
|
-
className: "ml-3"
|
|
505
|
-
}),
|
|
873
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
506
874
|
/*#__PURE__*/ jsx(Input, {
|
|
507
|
-
className:
|
|
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:
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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 {
|
|
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 };
|