@obosbbl/grunnmuren-react 3.0.15 → 3.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +454 -454
- package/dist/index.mjs +1594 -1592
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,37 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useContextProps, Provider, useLocale, Link, Button as Button$1, Breadcrumb as Breadcrumb$1, Breadcrumbs as Breadcrumbs$1, DEFAULT_SLOT, 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, ButtonContext as ButtonContext$1, DisclosureContext, DisclosureGroupStateContext, useSlottedContext, FormContext, FieldErrorContext, LabelContext, InputContext, I18nProvider, RouterProvider, GroupContext, DialogTrigger as DialogTrigger$1, Modal as Modal$1, Dialog as Dialog$1, ModalOverlay as ModalOverlay$1, NumberField as NumberField$1, Radio as Radio$1, RadioGroup as RadioGroup$1, Select as Select$1, SelectValue, Table as Table$1, TableHeader as TableHeader$1, Column, TableBody as TableBody$1, Row, Cell, Tabs as Tabs$1, TabListStateContext, TabList as TabList$1, Tab as Tab$1, TabPanel as TabPanel$1, TagGroup as TagGroup$1, TagList as TagList$1, Tag as Tag$1, TextField as TextField$1, TextArea as TextArea$1 } from 'react-aria-components';
|
|
3
3
|
export { Form, Group, DisclosureGroup as UNSAFE_DisclosureGroup } from 'react-aria-components';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import { ChevronDown,
|
|
6
|
-
import { useLayoutEffect, filterDOMProps, mergeRefs, mergeProps, useObjectRef, useFormReset
|
|
5
|
+
import { ChevronDown, Close, InfoCircle, CheckCircle, Warning, Error, User, ChevronLeft, ChevronRight, LoadingSpinner, Check, Trash, ArrowRight, Download, LinkExternal, PlayerPause, PlayerPlay } from '@obosbbl/grunnmuren-icons-react';
|
|
6
|
+
import { useLayoutEffect, useUpdateEffect, filterDOMProps, mergeRefs, mergeProps, useObjectRef, useFormReset } from '@react-aria/utils';
|
|
7
7
|
import { cva, cx, compose } from 'cva';
|
|
8
8
|
import { createContext, Children, useId, useState, useRef, useEffect, useContext, useCallback } from 'react';
|
|
9
9
|
import { useProgressBar, useDateFormatter, useFocusRing, useDisclosure, useField } from 'react-aria';
|
|
10
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
10
11
|
import { useDisclosureState } from 'react-stately';
|
|
11
12
|
import { useFormValidation } from '@react-aria/form';
|
|
12
13
|
import { useFormValidationState } from '@react-stately/form';
|
|
13
14
|
import { useControlledState } from '@react-stately/utils';
|
|
14
15
|
import { PressResponder } from '@react-aria/interactions';
|
|
15
|
-
import { useDebouncedCallback } from 'use-debounce';
|
|
16
|
-
|
|
17
|
-
function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
|
|
18
|
-
return /*#__PURE__*/ jsx(I18nProvider, {
|
|
19
|
-
locale: locale,
|
|
20
|
-
children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
|
|
21
|
-
navigate: navigate,
|
|
22
|
-
useHref: useHref,
|
|
23
|
-
children: children
|
|
24
|
-
}) : children
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns the locale set in `<GrunnmurenProvider />`
|
|
30
|
-
*/ function _useLocale() {
|
|
31
|
-
// a small wrapper around react-arias useLocale with a simpler return type with only the locales that we actually support
|
|
32
|
-
const locale = useLocale();
|
|
33
|
-
return locale.locale;
|
|
34
|
-
}
|
|
35
16
|
|
|
36
17
|
const HeadingContext = /*#__PURE__*/ createContext({});
|
|
37
18
|
const headingVariants = cva({
|
|
@@ -208,43 +189,6 @@ function AccordionItem(props) {
|
|
|
208
189
|
});
|
|
209
190
|
}
|
|
210
191
|
|
|
211
|
-
const badgeVariants = cva({
|
|
212
|
-
base: [
|
|
213
|
-
'inline-flex w-fit items-center justify-center gap-1.5 rounded-lg [&_svg]:shrink-0'
|
|
214
|
-
],
|
|
215
|
-
variants: {
|
|
216
|
-
color: {
|
|
217
|
-
'gray-dark': 'bg-gray-dark text-white',
|
|
218
|
-
mint: 'bg-mint',
|
|
219
|
-
sky: 'bg-sky',
|
|
220
|
-
white: 'bg-white',
|
|
221
|
-
'blue-dark': 'bg-blue-dark text-white',
|
|
222
|
-
'green-dark': 'bg-green-dark text-white'
|
|
223
|
-
},
|
|
224
|
-
size: {
|
|
225
|
-
small: 'description px-2 py-0.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
226
|
-
medium: 'description px-2.5 py-1.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
227
|
-
large: 'paragraph px-3 py-2 [&_svg]:h-5 [&_svg]:w-5'
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
defaultVariants: {
|
|
231
|
-
size: 'medium'
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
function Badge(props) {
|
|
235
|
-
const { className: _className, color, size, ...restProps } = props;
|
|
236
|
-
const className = badgeVariants({
|
|
237
|
-
className: _className,
|
|
238
|
-
color,
|
|
239
|
-
size
|
|
240
|
-
});
|
|
241
|
-
return /*#__PURE__*/ jsx("span", {
|
|
242
|
-
className: className,
|
|
243
|
-
...restProps,
|
|
244
|
-
"data-slot": "badge"
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
192
|
const translations$1 = {
|
|
249
193
|
close: {
|
|
250
194
|
nb: 'Lukk',
|
|
@@ -279,1065 +223,1045 @@ const translations$1 = {
|
|
|
279
223
|
};
|
|
280
224
|
|
|
281
225
|
/**
|
|
282
|
-
*
|
|
283
|
-
*/
|
|
226
|
+
* Returns the locale set in `<GrunnmurenProvider />`
|
|
227
|
+
*/ function _useLocale() {
|
|
228
|
+
// a small wrapper around react-arias useLocale with a simpler return type with only the locales that we actually support
|
|
229
|
+
const locale = useLocale();
|
|
230
|
+
return locale.locale;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const iconMap = {
|
|
234
|
+
info: InfoCircle,
|
|
235
|
+
success: CheckCircle,
|
|
236
|
+
warning: Warning,
|
|
237
|
+
danger: Error
|
|
238
|
+
};
|
|
239
|
+
const alertVariants = cva({
|
|
284
240
|
base: [
|
|
285
|
-
'
|
|
241
|
+
'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
|
|
242
|
+
// Heading styles:
|
|
243
|
+
'[&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:leading-7',
|
|
244
|
+
// Content styles:
|
|
245
|
+
'[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
|
|
246
|
+
// Footer styles:
|
|
247
|
+
'[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:leading-6'
|
|
286
248
|
],
|
|
287
249
|
variants: {
|
|
288
250
|
/**
|
|
289
|
-
* The variant of the
|
|
290
|
-
* @default
|
|
251
|
+
* The variant of the alert
|
|
252
|
+
* @default info
|
|
291
253
|
*/ variant: {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
},
|
|
297
|
-
/**
|
|
298
|
-
* Adjusts the color of the button for usage on different backgrounds.
|
|
299
|
-
* @default blue
|
|
300
|
-
*/ color: {
|
|
301
|
-
blue: 'focus-visible:outline-focus',
|
|
302
|
-
mint: 'focus-visible:outline-focus focus-visible:outline-mint',
|
|
303
|
-
white: 'focus-visible:outline-focus focus-visible:outline-white'
|
|
304
|
-
},
|
|
305
|
-
/**
|
|
306
|
-
* When the button is without text, but with a single icon.
|
|
307
|
-
* @default false
|
|
308
|
-
*/ isIconOnly: {
|
|
309
|
-
true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
|
|
310
|
-
false: 'gap-2.5 px-4 py-2'
|
|
311
|
-
},
|
|
312
|
-
// Make the content of the button transparent to hide it's content, but keep the button width
|
|
313
|
-
isPending: {
|
|
314
|
-
true: '!text-transparent relative',
|
|
315
|
-
false: null
|
|
254
|
+
info: 'border-[#1A7FA7] bg-sky-light',
|
|
255
|
+
success: 'border-[#0F9B6E] bg-mint-light',
|
|
256
|
+
warning: 'border-[#C57C13] bg-[#FFF2DE]',
|
|
257
|
+
danger: 'border-[#C0385D] bg-red-light'
|
|
316
258
|
}
|
|
317
259
|
},
|
|
318
|
-
compoundVariants: [
|
|
319
|
-
{
|
|
320
|
-
color: 'blue',
|
|
321
|
-
variant: 'primary',
|
|
322
|
-
// Darken bg by 20% on hover. The color is manually crafted
|
|
323
|
-
className: 'bg-blue-dark text-white hover:bg-blue active:bg-[#0536A0] active:text-white [&_[role="progressbar"]]:text-white'
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
color: 'blue',
|
|
327
|
-
variant: 'secondary',
|
|
328
|
-
className: 'text-blue-dark hover:border-transparent hover:bg-blue hover:text-blue-dark hover:text-white active:bg-[#0536A0] [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-blue-dark'
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
color: 'blue',
|
|
332
|
-
variant: 'tertiary',
|
|
333
|
-
className: '[&_[role="progressbar"]]:text-black'
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
color: 'mint',
|
|
337
|
-
variant: 'primary',
|
|
338
|
-
// Darken bg by 20% on hover. The color is manually crafted
|
|
339
|
-
className: 'bg-mint text-black hover:bg-[#8dd4bd] active:[#9ddac6] [&_[role="progressbar"]]:text-black'
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
color: 'mint',
|
|
343
|
-
variant: 'secondary',
|
|
344
|
-
className: 'text-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint'
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
color: 'mint',
|
|
348
|
-
variant: 'tertiary',
|
|
349
|
-
className: 'text-mint [&_[role="progressbar"]]:text-mint'
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
color: 'white',
|
|
353
|
-
variant: 'primary',
|
|
354
|
-
className: 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black'
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
color: 'white',
|
|
358
|
-
variant: 'secondary',
|
|
359
|
-
className: 'text-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white'
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
color: 'white',
|
|
363
|
-
variant: 'tertiary',
|
|
364
|
-
className: 'text-white [&_[role="progressbar"]]:text-white'
|
|
365
|
-
}
|
|
366
|
-
],
|
|
367
260
|
defaultVariants: {
|
|
368
|
-
variant: '
|
|
369
|
-
color: 'blue',
|
|
370
|
-
isIconOnly: false,
|
|
371
|
-
isPending: false
|
|
261
|
+
variant: 'info'
|
|
372
262
|
}
|
|
373
263
|
});
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
return !!props.href;
|
|
377
|
-
}
|
|
378
|
-
function Button({ ref = null, ...props }) {
|
|
379
|
-
[props, ref] = useContextProps(props, ref, ButtonContext);
|
|
380
|
-
const { children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
|
|
381
|
-
const className = buttonVariants({
|
|
382
|
-
className: props.className,
|
|
383
|
-
color,
|
|
384
|
-
isIconOnly,
|
|
385
|
-
variant,
|
|
386
|
-
isPending
|
|
387
|
-
});
|
|
264
|
+
const Alertbox = ({ children, role, className, icon, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
|
|
265
|
+
const Icon = icon ?? iconMap[variant];
|
|
388
266
|
const locale = _useLocale();
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
267
|
+
const id = useId();
|
|
268
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
269
|
+
const isCollapsed = isExpandable && !isExpanded;
|
|
270
|
+
const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
|
|
271
|
+
const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
|
|
272
|
+
if (!isVisible) return;
|
|
273
|
+
const close = ()=>{
|
|
274
|
+
setIsUncontrolledVisible(false);
|
|
275
|
+
if (onDismiss) onDismiss();
|
|
276
|
+
};
|
|
277
|
+
const isInDevMode = process.env.NODE_ENV !== 'production';
|
|
278
|
+
if (isInDevMode && onDismiss && !isDismissable) {
|
|
279
|
+
console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
|
|
280
|
+
}
|
|
281
|
+
if (isInDevMode && !children) {
|
|
282
|
+
console.error('`No children was passed to the <AlertBox/>` component.');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const [firstChild, ...restChildren] = Children.toArray(children);
|
|
286
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
287
|
+
className: alertVariants({
|
|
288
|
+
className,
|
|
289
|
+
variant
|
|
290
|
+
}),
|
|
291
|
+
// The role prop is required to force consumers to consider and choose the appropriate alertbox role.
|
|
292
|
+
// role="none" will not have any effect on a div, so it can be omitted.
|
|
293
|
+
role: role === 'none' ? undefined : role,
|
|
394
294
|
children: [
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
295
|
+
/*#__PURE__*/ jsx(Icon, {}),
|
|
296
|
+
firstChild,
|
|
297
|
+
isDismissable && /*#__PURE__*/ jsx("button", {
|
|
298
|
+
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'),
|
|
299
|
+
onClick: close,
|
|
300
|
+
"aria-label": translations$1.close[locale],
|
|
301
|
+
type: "button",
|
|
302
|
+
children: /*#__PURE__*/ jsx(Close, {})
|
|
303
|
+
}),
|
|
304
|
+
isExpandable && /*#__PURE__*/ jsxs("button", {
|
|
305
|
+
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:
|
|
306
|
+
'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'),
|
|
307
|
+
onClick: ()=>setIsExpanded((prevState)=>!prevState),
|
|
308
|
+
"aria-expanded": isExpanded,
|
|
309
|
+
"aria-controls": id,
|
|
310
|
+
type: "button",
|
|
311
|
+
children: [
|
|
312
|
+
isExpanded ? translations$1.showLess[locale] : translations$1.showMore[locale],
|
|
313
|
+
/*#__PURE__*/ jsx(ChevronDown, {
|
|
314
|
+
className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
|
|
315
|
+
})
|
|
316
|
+
]
|
|
317
|
+
}),
|
|
318
|
+
restChildren?.length > 0 && /*#__PURE__*/ jsx("div", {
|
|
319
|
+
className: cx('col-span-full grid gap-y-4', isCollapsed && '[&>*:not([data-slot="footer"])]:hidden'),
|
|
320
|
+
id: id,
|
|
321
|
+
children: restChildren
|
|
399
322
|
})
|
|
400
323
|
]
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const baseClassName = 'h-20 w-20 shrink-0 rounded-full';
|
|
328
|
+
const Avatar = ({ src, alt = '', className, onError, loading = 'lazy', ...rest })=>{
|
|
329
|
+
const [hasError, setHasError] = useState(false);
|
|
330
|
+
const hasValidImage = !hasError && src;
|
|
331
|
+
return hasValidImage ? /*#__PURE__*/ jsx("img", {
|
|
332
|
+
...rest,
|
|
333
|
+
src: src,
|
|
334
|
+
alt: alt,
|
|
335
|
+
loading: loading,
|
|
336
|
+
className: cx(className, baseClassName, 'object-cover'),
|
|
337
|
+
onError: (event)=>{
|
|
338
|
+
onError?.(event);
|
|
339
|
+
setHasError(true);
|
|
340
|
+
}
|
|
341
|
+
}) : /*#__PURE__*/ jsx("div", {
|
|
342
|
+
className: cx(className, baseClassName, 'grid place-items-center bg-gray-light text-gray-dark'),
|
|
343
|
+
children: /*#__PURE__*/ jsx(User, {
|
|
344
|
+
className: "scale-[2.25]"
|
|
345
|
+
})
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
function isLinkProps$1(props) {
|
|
350
|
+
return !!props.href;
|
|
351
|
+
}
|
|
352
|
+
function Backlink(props) {
|
|
353
|
+
const { className, style, children, withUnderline, ref, ...restProps } = props;
|
|
354
|
+
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');
|
|
355
|
+
const content = /*#__PURE__*/ jsxs(Fragment, {
|
|
356
|
+
children: [
|
|
357
|
+
/*#__PURE__*/ jsx(ChevronLeft, {
|
|
358
|
+
className: cx('-ml-[0.5em] group-hover:-translate-x-1 shrink-0 transition-transform duration-300')
|
|
359
|
+
}),
|
|
360
|
+
/*#__PURE__*/ jsx("span", {
|
|
361
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
362
|
+
className: cx('border-transparent border-t-[1px] border-b-[1px] transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
|
|
363
|
+
children: children
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
]
|
|
367
|
+
});
|
|
368
|
+
if (isLinkProps$1(props)) {
|
|
369
|
+
return /*#__PURE__*/ jsx(Link, {
|
|
370
|
+
...restProps,
|
|
371
|
+
className: _className,
|
|
372
|
+
style: style,
|
|
373
|
+
ref: ref,
|
|
374
|
+
children: content
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return /*#__PURE__*/ jsx(Button$1, {
|
|
378
|
+
...restProps,
|
|
379
|
+
className: _className,
|
|
380
|
+
style: style,
|
|
411
381
|
ref: ref,
|
|
412
|
-
children:
|
|
382
|
+
children: content
|
|
413
383
|
});
|
|
414
384
|
}
|
|
415
385
|
|
|
416
|
-
const
|
|
417
|
-
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');
|
|
418
|
-
const input = cva({
|
|
386
|
+
const badgeVariants = cva({
|
|
419
387
|
base: [
|
|
420
|
-
|
|
421
|
-
'bg-white',
|
|
422
|
-
// Use box-content to enable auto width based on number of characters (size)
|
|
423
|
-
// Setting min-height to prevent the input from collapsing in Safari
|
|
424
|
-
// Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
|
|
425
|
-
'box-content min-h-6 py-2.5',
|
|
426
|
-
'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
|
|
427
|
-
// invalid styles
|
|
428
|
-
'group-data-invalid:ring-focus group-data-invalid:ring-red',
|
|
429
|
-
// Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
|
|
430
|
-
'appearance-none'
|
|
388
|
+
'inline-flex w-fit items-center justify-center gap-1.5 rounded-lg [&_svg]:shrink-0'
|
|
431
389
|
],
|
|
432
390
|
variants: {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
391
|
+
color: {
|
|
392
|
+
'gray-dark': 'bg-gray-dark text-white',
|
|
393
|
+
mint: 'bg-mint',
|
|
394
|
+
sky: 'bg-sky',
|
|
395
|
+
white: 'bg-white',
|
|
396
|
+
'blue-dark': 'bg-blue-dark text-white',
|
|
397
|
+
'green-dark': 'bg-green-dark text-white'
|
|
437
398
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
399
|
+
size: {
|
|
400
|
+
small: 'description px-2 py-0.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
401
|
+
medium: 'description px-2.5 py-1.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
402
|
+
large: 'paragraph px-3 py-2 [&_svg]:h-5 [&_svg]:w-5'
|
|
441
403
|
}
|
|
442
404
|
},
|
|
443
405
|
defaultVariants: {
|
|
444
|
-
|
|
445
|
-
isGrouped: false
|
|
406
|
+
size: 'medium'
|
|
446
407
|
}
|
|
447
408
|
});
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
|
|
455
|
-
listbox: cx('max-h-[25rem] overflow-x-hidden text-sm outline-hidden'),
|
|
456
|
-
chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
function ErrorMessage(props) {
|
|
460
|
-
const { children, className, ...restProps } = props;
|
|
461
|
-
return /*#__PURE__*/ jsx(Text, {
|
|
462
|
-
...restProps,
|
|
463
|
-
className: cx(className, formFieldError),
|
|
464
|
-
slot: "errorMessage",
|
|
465
|
-
children: children
|
|
409
|
+
function Badge(props) {
|
|
410
|
+
const { className: _className, color, size, ...restProps } = props;
|
|
411
|
+
const className = badgeVariants({
|
|
412
|
+
className: _className,
|
|
413
|
+
color,
|
|
414
|
+
size
|
|
466
415
|
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const defaultClasses$1 = cx([
|
|
470
|
-
'group -mx-2.5 relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 p-2.5 leading-7'
|
|
471
|
-
]);
|
|
472
|
-
// Pulling this out into it's own component. Will probably export it in the future
|
|
473
|
-
// so it can be used in other views, outside of an input of type checkbox, like in table rows.
|
|
474
|
-
function CheckmarkBox() {
|
|
475
416
|
return /*#__PURE__*/ jsx("span", {
|
|
476
|
-
className:
|
|
477
|
-
'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
|
|
478
|
-
// 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.
|
|
479
|
-
// For the ::before psuedo element the line height of the label is always 1em.
|
|
480
|
-
// When we know the height of the label we use the height of the radio to push it down to align with the label's first line
|
|
481
|
-
// 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?
|
|
482
|
-
'mt-[calc((1em_*_1.75_-_24px)_/_2)] h-[24px] w-[24px]',
|
|
483
|
-
// selected
|
|
484
|
-
'group-data-selected:group-not-data-hovered:group-not-data-invalid:border-blue group-data-selected:group-not-data-hovered:group-not-data-invalid:bg-blue',
|
|
485
|
-
'group-data-selected:group-not-data-hovered:group-data-invalid:border-red group-data-selected:group-not-data-hovered:group-data-invalid:bg-red',
|
|
486
|
-
// focus
|
|
487
|
-
'group-data-focus-visible:outline-focus-offset',
|
|
488
|
-
// hovered
|
|
489
|
-
'group-data-hovered:group-data-invalid:bg-red-light',
|
|
490
|
-
'group-data-hovered:border-blue',
|
|
491
|
-
'group-data-hovered:bg-sky',
|
|
492
|
-
'group-data-hovered:group-data-selected:group-not-data-invalid:border-blue-dark',
|
|
493
|
-
'group-data-hovered:group-data-selected:group-not-data-invalid:bg-blue-dark',
|
|
494
|
-
// 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
|
|
495
|
-
// so we use an inner shadow of 1 px instead to pad the actual border
|
|
496
|
-
'group-data-invalid:border-red group-data-invalid:shadow-[inset_0_0_0_1px] group-data-invalid:shadow-red'
|
|
497
|
-
]),
|
|
498
|
-
children: /*#__PURE__*/ jsx(Check, {
|
|
499
|
-
className: "h-full w-full opacity-0 group-data-invalid:group-data-hovered:group-data-selected:text-red group-data-selected:opacity-100"
|
|
500
|
-
})
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
function Checkbox(props) {
|
|
504
|
-
const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
|
|
505
|
-
const id = useId();
|
|
506
|
-
const descriptionId = `desc${id}`;
|
|
507
|
-
const errorMessageId = `error${id}`;
|
|
508
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
509
|
-
return /*#__PURE__*/ jsx("div", {
|
|
510
|
-
children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
|
|
511
|
-
value: {
|
|
512
|
-
'aria-describedby': description ? descriptionId : undefined,
|
|
513
|
-
'aria-errormessage': errorMessage ? errorMessageId : undefined
|
|
514
|
-
},
|
|
515
|
-
children: [
|
|
516
|
-
/*#__PURE__*/ jsxs(Checkbox$1, {
|
|
517
|
-
...restProps,
|
|
518
|
-
className: cx(className, defaultClasses$1),
|
|
519
|
-
isInvalid: isInvalid,
|
|
520
|
-
children: [
|
|
521
|
-
/*#__PURE__*/ jsx(CheckmarkBox, {}),
|
|
522
|
-
children
|
|
523
|
-
]
|
|
524
|
-
}),
|
|
525
|
-
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 */}
|
|
526
|
-
/*#__PURE__*/ jsx("div", {
|
|
527
|
-
id: descriptionId,
|
|
528
|
-
slot: "description",
|
|
529
|
-
className: "description block",
|
|
530
|
-
children: description
|
|
531
|
-
}),
|
|
532
|
-
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
533
|
-
className: "mt-2 block",
|
|
534
|
-
id: errorMessageId,
|
|
535
|
-
children: errorMessage
|
|
536
|
-
})
|
|
537
|
-
]
|
|
538
|
-
})
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function Description(props) {
|
|
543
|
-
const { className, ...restProps } = props;
|
|
544
|
-
return /*#__PURE__*/ jsx(Text, {
|
|
545
|
-
...restProps,
|
|
546
|
-
className: cx(className, 'description'),
|
|
547
|
-
slot: "description"
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
|
|
553
|
-
* In other words, this handles controlled and uncontrolled form errors.
|
|
554
|
-
*/ function ErrorMessageOrFieldError({ errorMessage }) {
|
|
555
|
-
return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
|
|
556
|
-
children: errorMessage
|
|
557
|
-
}) : /*#__PURE__*/ jsx(FieldError, {
|
|
558
|
-
className: formFieldError
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function Label(props) {
|
|
563
|
-
const { children, className, ...restProps } = props;
|
|
564
|
-
return /*#__PURE__*/ jsx(Label$1, {
|
|
565
|
-
className: cx(className, 'font-semibold leading-7'),
|
|
417
|
+
className: className,
|
|
566
418
|
...restProps,
|
|
567
|
-
|
|
419
|
+
"data-slot": "badge"
|
|
568
420
|
});
|
|
569
421
|
}
|
|
570
422
|
|
|
571
|
-
function
|
|
572
|
-
const {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
576
|
-
return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
|
|
423
|
+
function Breadcrumb(props) {
|
|
424
|
+
const { className, children, href, ...restProps } = props;
|
|
425
|
+
return /*#__PURE__*/ jsxs(Breadcrumb$1, {
|
|
426
|
+
className: cx(className, 'group flex items-center'),
|
|
577
427
|
...restProps,
|
|
578
|
-
className: cx(className, 'flex flex-col gap-2'),
|
|
579
|
-
isInvalid: isInvalid,
|
|
580
|
-
isRequired: isRequired,
|
|
581
428
|
children: [
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
children:
|
|
587
|
-
}),
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
errorMessage: errorMessage
|
|
429
|
+
href ? /*#__PURE__*/ jsx(Link, {
|
|
430
|
+
href: href,
|
|
431
|
+
// 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
|
|
432
|
+
className: "rounded-xs focus-visible:outline-focus group-last:no-underline",
|
|
433
|
+
children: children
|
|
434
|
+
}) : children,
|
|
435
|
+
/*#__PURE__*/ jsx(ChevronRight, {
|
|
436
|
+
className: "px-1 group-last:hidden"
|
|
591
437
|
})
|
|
592
438
|
]
|
|
593
439
|
});
|
|
594
440
|
}
|
|
595
441
|
|
|
596
|
-
|
|
442
|
+
function Breadcrumbs(props) {
|
|
443
|
+
const { className, children, ...restProps } = props;
|
|
444
|
+
return /*#__PURE__*/ jsx(Breadcrumbs$1, {
|
|
597
445
|
...restProps,
|
|
598
|
-
className: cx(
|
|
599
|
-
|
|
600
|
-
const ListBoxItem = (props)=>{
|
|
601
|
-
let textValue = props.textValue;
|
|
602
|
-
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
603
|
-
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
604
|
-
// This condition adds back that behaviour
|
|
605
|
-
if (textValue == null && typeof props.children === 'string') {
|
|
606
|
-
textValue = props.children;
|
|
607
|
-
}
|
|
608
|
-
return /*#__PURE__*/ jsx(ListBoxItem$1, {
|
|
609
|
-
...props,
|
|
610
|
-
className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-focused:bg-sky-lightest'),
|
|
611
|
-
textValue: textValue,
|
|
612
|
-
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
613
|
-
children: [
|
|
614
|
-
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
615
|
-
className: "-ml-6 text-base"
|
|
616
|
-
}),
|
|
617
|
-
props.children
|
|
618
|
-
]
|
|
619
|
-
})
|
|
620
|
-
});
|
|
621
|
-
};
|
|
622
|
-
/**
|
|
623
|
-
* This component can be used to group items in a listbox
|
|
624
|
-
*/ const ListBoxSection = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBoxSection$1, {
|
|
625
|
-
...restProps,
|
|
626
|
-
// The :not(:first-child) selector adds extra spacing to all the options, but not the section (group) headings
|
|
627
|
-
// This way we get the desired extra indent on all options within a group
|
|
628
|
-
className: cx(className, 'pb-1 [&>:not(:first-child)]:pl-10')
|
|
446
|
+
className: cx(className, 'flex flex-wrap text-sm leading-6'),
|
|
447
|
+
children: children
|
|
629
448
|
});
|
|
449
|
+
}
|
|
450
|
+
|
|
630
451
|
/**
|
|
631
|
-
*
|
|
632
|
-
*/ const
|
|
633
|
-
|
|
634
|
-
|
|
452
|
+
* Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
|
|
453
|
+
*/ const buttonVariants = cva({
|
|
454
|
+
base: [
|
|
455
|
+
'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'
|
|
456
|
+
],
|
|
457
|
+
variants: {
|
|
458
|
+
/**
|
|
459
|
+
* The variant of the button
|
|
460
|
+
* @default primary
|
|
461
|
+
*/ variant: {
|
|
462
|
+
primary: 'no-underline',
|
|
463
|
+
// 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
|
|
464
|
+
secondary: 'border-2 border-current no-underline hover:border-transparent',
|
|
465
|
+
tertiary: 'underline hover:no-underline'
|
|
466
|
+
},
|
|
467
|
+
/**
|
|
468
|
+
* Adjusts the color of the button for usage on different backgrounds.
|
|
469
|
+
* @default blue
|
|
470
|
+
*/ color: {
|
|
471
|
+
blue: 'focus-visible:outline-focus',
|
|
472
|
+
mint: 'focus-visible:outline-focus focus-visible:outline-mint',
|
|
473
|
+
white: 'focus-visible:outline-focus focus-visible:outline-white'
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* When the button is without text, but with a single icon.
|
|
477
|
+
* @default false
|
|
478
|
+
*/ isIconOnly: {
|
|
479
|
+
true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
|
|
480
|
+
false: 'gap-2.5 px-4 py-2'
|
|
481
|
+
},
|
|
482
|
+
// Make the content of the button transparent to hide it's content, but keep the button width
|
|
483
|
+
isPending: {
|
|
484
|
+
true: '!text-transparent relative',
|
|
485
|
+
false: null
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
compoundVariants: [
|
|
489
|
+
{
|
|
490
|
+
color: 'blue',
|
|
491
|
+
variant: 'primary',
|
|
492
|
+
// Darken bg by 20% on hover. The color is manually crafted
|
|
493
|
+
className: 'bg-blue-dark text-white hover:bg-blue active:bg-[#0536A0] active:text-white [&_[role="progressbar"]]:text-white'
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
color: 'blue',
|
|
497
|
+
variant: 'secondary',
|
|
498
|
+
className: 'text-blue-dark hover:border-transparent hover:bg-blue hover:text-blue-dark hover:text-white active:bg-[#0536A0] [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-blue-dark'
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
color: 'blue',
|
|
502
|
+
variant: 'tertiary',
|
|
503
|
+
className: '[&_[role="progressbar"]]:text-black'
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
color: 'mint',
|
|
507
|
+
variant: 'primary',
|
|
508
|
+
// Darken bg by 20% on hover. The color is manually crafted
|
|
509
|
+
className: 'bg-mint text-black hover:bg-[#8dd4bd] active:[#9ddac6] [&_[role="progressbar"]]:text-black'
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
color: 'mint',
|
|
513
|
+
variant: 'secondary',
|
|
514
|
+
className: 'text-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint'
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
color: 'mint',
|
|
518
|
+
variant: 'tertiary',
|
|
519
|
+
className: 'text-mint [&_[role="progressbar"]]:text-mint'
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
color: 'white',
|
|
523
|
+
variant: 'primary',
|
|
524
|
+
className: 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black'
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
color: 'white',
|
|
528
|
+
variant: 'secondary',
|
|
529
|
+
className: 'text-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white'
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
color: 'white',
|
|
533
|
+
variant: 'tertiary',
|
|
534
|
+
className: 'text-white [&_[role="progressbar"]]:text-white'
|
|
535
|
+
}
|
|
536
|
+
],
|
|
537
|
+
defaultVariants: {
|
|
538
|
+
variant: 'primary',
|
|
539
|
+
color: 'blue',
|
|
540
|
+
isIconOnly: false,
|
|
541
|
+
isPending: false
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
const ButtonContext = /*#__PURE__*/ createContext({});
|
|
545
|
+
function isLinkProps(props) {
|
|
546
|
+
return !!props.href;
|
|
547
|
+
}
|
|
548
|
+
function Button({ ref = null, ...props }) {
|
|
549
|
+
[props, ref] = useContextProps(props, ref, ButtonContext);
|
|
550
|
+
const { children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
|
|
551
|
+
const className = buttonVariants({
|
|
552
|
+
className: props.className,
|
|
553
|
+
color,
|
|
554
|
+
isIconOnly,
|
|
555
|
+
variant,
|
|
556
|
+
isPending
|
|
635
557
|
});
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
558
|
+
const locale = _useLocale();
|
|
559
|
+
const { progressBarProps } = useProgressBar({
|
|
560
|
+
isIndeterminate: true,
|
|
561
|
+
'aria-label': translations$1.pending[locale]
|
|
640
562
|
});
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
function Combobox(props) {
|
|
644
|
-
const { className, children, description, errorMessage, isPending, label, isInvalid: _isInvalid, ref, ...restProps } = props;
|
|
645
|
-
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
646
|
-
// which will override any built in validation
|
|
647
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
648
|
-
return /*#__PURE__*/ jsxs(ComboBox, {
|
|
649
|
-
...restProps,
|
|
650
|
-
className: cx(className, formField),
|
|
651
|
-
isInvalid: isInvalid,
|
|
563
|
+
const children = isPending ? /*#__PURE__*/ jsxs(Fragment, {
|
|
652
564
|
children: [
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
children: description
|
|
658
|
-
}),
|
|
659
|
-
/*#__PURE__*/ jsxs(Group, {
|
|
660
|
-
className: inputGroup,
|
|
661
|
-
children: [
|
|
662
|
-
/*#__PURE__*/ jsx(Input, {
|
|
663
|
-
className: input({
|
|
664
|
-
isGrouped: true
|
|
665
|
-
}),
|
|
666
|
-
ref: ref
|
|
667
|
-
}),
|
|
668
|
-
/*#__PURE__*/ jsx(Button$1, {
|
|
669
|
-
children: isPending ? /*#__PURE__*/ jsx(LoadingSpinner, {
|
|
670
|
-
className: "animate-spin"
|
|
671
|
-
}) : /*#__PURE__*/ jsx(ChevronDown, {
|
|
672
|
-
className: dropdown.chevronIcon
|
|
673
|
-
})
|
|
674
|
-
})
|
|
675
|
-
]
|
|
676
|
-
}),
|
|
677
|
-
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
678
|
-
errorMessage: errorMessage
|
|
679
|
-
}),
|
|
680
|
-
/*#__PURE__*/ jsx(Popover, {
|
|
681
|
-
// FIXME: The trigger width doesn't include the padding of the group, so for now we have to apply this workaround.
|
|
682
|
-
// Also... the combobox border gets a pixel wider when focused, so we account for that as well when calculating the width
|
|
683
|
-
// and the offset.
|
|
684
|
-
// The input gutter should probably be moved to a theme variable instead of using the hardcoded value as here.
|
|
685
|
-
className: cx(dropdown.popover, 'min-w-[calc(var(--trigger-width)+26px)]'),
|
|
686
|
-
crossOffset: -13,
|
|
687
|
-
children: /*#__PURE__*/ jsx(ListBox, {
|
|
688
|
-
className: dropdown.listbox,
|
|
689
|
-
children: children
|
|
690
|
-
})
|
|
565
|
+
_children,
|
|
566
|
+
/*#__PURE__*/ jsx(LoadingSpinner, {
|
|
567
|
+
className: "absolute m-auto motion-safe:animate-spin",
|
|
568
|
+
...progressBarProps
|
|
691
569
|
})
|
|
692
570
|
]
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
function RadioGroup(props) {
|
|
697
|
-
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, value, ...restProps } = props;
|
|
698
|
-
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
699
|
-
// which will override any built in validation
|
|
700
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
701
|
-
return /*#__PURE__*/ jsxs(RadioGroup$1, {
|
|
571
|
+
}) : _children;
|
|
572
|
+
return isLinkProps(restProps) ? /*#__PURE__*/ jsx(Link, {
|
|
702
573
|
...restProps,
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
description && /*#__PURE__*/ jsx(Description, {
|
|
713
|
-
children: description
|
|
714
|
-
}),
|
|
715
|
-
children,
|
|
716
|
-
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
717
|
-
errorMessage: errorMessage
|
|
718
|
-
})
|
|
719
|
-
]
|
|
574
|
+
className: className,
|
|
575
|
+
ref: ref,
|
|
576
|
+
children: children
|
|
577
|
+
}) : /*#__PURE__*/ jsx(Button$1, {
|
|
578
|
+
...restProps,
|
|
579
|
+
className: className,
|
|
580
|
+
isPending: isPending,
|
|
581
|
+
ref: ref,
|
|
582
|
+
children: children
|
|
720
583
|
});
|
|
721
584
|
}
|
|
722
585
|
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
]
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
586
|
+
const cardVariants = cva({
|
|
587
|
+
base: [
|
|
588
|
+
'group/card',
|
|
589
|
+
'rounded-[inherit]',
|
|
590
|
+
'border p-3',
|
|
591
|
+
'flex flex-col gap-y-4',
|
|
592
|
+
'relative',
|
|
593
|
+
// **** Content ****
|
|
594
|
+
'[&_[data-slot="content"]]:flex [&_[data-slot="content"]]:flex-col [&_[data-slot="content"]]:gap-y-4',
|
|
595
|
+
// **** Media ****
|
|
596
|
+
'[&_[data-slot="media"]_*]:pointer-events-none',
|
|
597
|
+
'[&_[data-slot="media"]]:overflow-hidden',
|
|
598
|
+
'[&_[data-slot="media"]]:relative',
|
|
599
|
+
// 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)
|
|
600
|
+
'[&_[data-slot="media"]]:mx-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))] [&_[data-slot="media"]]:mt-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
|
|
601
|
+
// Sets the aspect ratio of the media content (width: 100% is necessary to make aspect ratio work on images in FF)
|
|
602
|
+
'[&_[data-slot="media"]>*:not([data-slot="badge"])]:aspect-3/2 [&_[data-slot="media"]>img]:w-full [&_[data-slot="media"]>img]:object-cover',
|
|
603
|
+
// 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.
|
|
604
|
+
'[&_[data-slot="media"]>*]:duration-300 [&_[data-slot="media"]>*]:ease-in-out [&_[data-slot="media"]>*]:motion-safe:transition-transform',
|
|
605
|
+
// **** Card link ****
|
|
606
|
+
// **** Hover ****
|
|
607
|
+
// 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)
|
|
608
|
+
'[&:has([data-slot="card-link"]_a:hover)_[data-slot="media"]>img]:scale-110',
|
|
609
|
+
'[&:has([data-slot="heading"]_[data-slot="card-link"]:hover)_[data-slot="media"]>img]:scale-110',
|
|
610
|
+
// **** Fail-safe for interactive elements ****
|
|
611
|
+
// Make interactive elements clickable by themselves, while the rest of the card is clickable as a whole
|
|
612
|
+
// The card is made clickable by a pseudo-element on the heading that covers the entire card
|
|
613
|
+
'[&:not(:has([data-slot="card-link"]_a))_a:not([data-slot="card-link"])]:relative [&_button]:relative [&_input]:relative',
|
|
614
|
+
// 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)
|
|
615
|
+
'[&_[data-slot="card-link"]_a]:static',
|
|
616
|
+
// Place other interactive on top of the pseudo-element that makes the entire card clickable
|
|
617
|
+
// by setting a higher z-index than the pseudo-element (which implicitly z-index 0)
|
|
618
|
+
'[&_a:not([data-slot="card-link"])]:z-[1] [&_button]:z-[1] [&_input]:z-[1]',
|
|
619
|
+
// **** Badge ****
|
|
620
|
+
'[&_[data-slot="media"]_[data-slot="badge"]]:absolute [&_[data-slot="media"]_[data-slot="badge"]]:top-0',
|
|
621
|
+
// 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)
|
|
622
|
+
'[&_[data-slot="media"]_[data-slot="badge"]]:z-[1]',
|
|
623
|
+
// Left aligned - override default corner radius of the badge
|
|
624
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tl-2xl',
|
|
625
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-br-2xl',
|
|
626
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tr-none',
|
|
627
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-bl-none',
|
|
628
|
+
// Right aligned - override default corner radius of the badge
|
|
629
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tl-none',
|
|
630
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-br-none',
|
|
631
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tr-2xl',
|
|
632
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-bl-2xl',
|
|
633
|
+
// ... and position the badge at the right edge of the media content
|
|
634
|
+
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:right-0'
|
|
635
|
+
],
|
|
636
|
+
variants: {
|
|
637
|
+
/**
|
|
638
|
+
* The variant of the card
|
|
639
|
+
* @default subtle
|
|
640
|
+
*/ variant: {
|
|
641
|
+
subtle: [
|
|
642
|
+
'border-transparent',
|
|
643
|
+
// **** Media styles ****
|
|
644
|
+
'[&_[data-slot="media"]]:rounded-2xl'
|
|
645
|
+
],
|
|
646
|
+
outlined: 'border border-black'
|
|
647
|
+
},
|
|
648
|
+
/**
|
|
649
|
+
* The layout of the card
|
|
650
|
+
* @default vertical
|
|
651
|
+
*/ layout: {
|
|
652
|
+
vertical: [
|
|
653
|
+
'flex-col',
|
|
654
|
+
// **** Media ****
|
|
655
|
+
'[&_[data-slot="media"]]:rounded-t-2xl'
|
|
656
|
+
],
|
|
657
|
+
horizontal: [
|
|
658
|
+
// Use more gap for horizontal cards that have media
|
|
659
|
+
// Since this does not affect the layout before the flex direction is set (at breakpoint @2xl for Card with Media), we can set it here
|
|
660
|
+
'has-data-[slot=media]:layout-gap-x not-has-data-[slot=media]:gap-x-4',
|
|
661
|
+
// **** With Media ****
|
|
662
|
+
'[&:has(>[data-slot="media"]:last-child)]:flex-col-reverse',
|
|
663
|
+
'has-data-[slot=media]:@2xl:!flex-row',
|
|
664
|
+
'*:data-[slot=media]:@2xl:h-fit',
|
|
665
|
+
'has-data-[slot=media]:*:@2xl:basis-1/2',
|
|
666
|
+
// Position media at the edges of the card
|
|
667
|
+
'*:data-[slot=media]:@2xl:mb-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
|
|
668
|
+
'*:data-[slot=media]:first:@2xl:mr-0',
|
|
669
|
+
'*:data-[slot=media]:last:@2xl:ml-0',
|
|
670
|
+
// Make sure the card link is clickable when the media is on the right side
|
|
671
|
+
// This is necessary because the media content is positioned after the card link in the DOM
|
|
672
|
+
'[&:has(>[data-slot="media"]:last-child)_[data-slot="card-link"]]:z-[1]',
|
|
673
|
+
// **** Without Media ****
|
|
674
|
+
'not-has-data-[slot=media]:@md:flex-row',
|
|
675
|
+
// 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.
|
|
676
|
+
'not-has-data-[slot=media]:**:data-[slot=content]:grow',
|
|
677
|
+
// Make sure svg's etc. are not shrinkable
|
|
678
|
+
'[&>:not([data-slot="content"],[data-slot="media"])]:shrink-0'
|
|
754
679
|
]
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
defaultVariants: {
|
|
683
|
+
variant: 'subtle',
|
|
684
|
+
layout: 'vertical'
|
|
685
|
+
},
|
|
686
|
+
compoundVariants: [
|
|
687
|
+
{
|
|
688
|
+
variant: 'outlined',
|
|
689
|
+
layout: 'horizontal',
|
|
690
|
+
className: [
|
|
691
|
+
// **** Media ****
|
|
692
|
+
// Some rounded corners are removed when the card is outlined
|
|
693
|
+
'[&_[data-slot="media"]]:rounded-t-2xl',
|
|
694
|
+
'*:data-[slot=media]:first:@2xl:rounded-tr-none *:data-[slot=media]:first:@2xl:rounded-bl-2xl',
|
|
695
|
+
'*:data-[slot=media]:last:@2xl:rounded-tl-none *:data-[slot=media]:last:@2xl:rounded-br-2xl',
|
|
696
|
+
// **** Badge ****
|
|
697
|
+
// Override default corner radius of the badge to match the media border radius
|
|
698
|
+
'[&_[data-slot="media"]:first-child_[data-slot="badge"]:last-child]:@2xl:rounded-tr-none',
|
|
699
|
+
'[&_[data-slot="media"]:last-child_[data-slot="badge"]:first-child]:@2xl:rounded-tl-none'
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
]
|
|
703
|
+
});
|
|
704
|
+
const Card = ({ children, className, variant, layout, ...restProps })=>{
|
|
705
|
+
const cardClassName = cardVariants({
|
|
706
|
+
variant,
|
|
707
|
+
layout
|
|
708
|
+
});
|
|
709
|
+
return(// The border-radius is set on the outer container to make it act as an invisible wrapper, only used for container queries
|
|
710
|
+
// Since passing the className prop to this container is necessary to make custom styles behave as expected, we need to apply the border-radius here incase the consumer passes a custom background color
|
|
711
|
+
/*#__PURE__*/ jsx("div", {
|
|
712
|
+
...restProps,
|
|
713
|
+
className: cx(className, '@container rounded-2xl'),
|
|
714
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
715
|
+
className: cardClassName,
|
|
716
|
+
children: /*#__PURE__*/ jsx(Provider, {
|
|
717
|
+
values: [
|
|
718
|
+
[
|
|
719
|
+
HeadingContext,
|
|
720
|
+
{
|
|
721
|
+
size: 's',
|
|
722
|
+
className: cx([
|
|
723
|
+
'inline',
|
|
724
|
+
'w-fit',
|
|
725
|
+
'text-pretty',
|
|
726
|
+
'hyphens-auto',
|
|
727
|
+
'[word-break:break-word]',
|
|
728
|
+
// **** Card link in Heading ****
|
|
729
|
+
// Border (bottom/top) is set to transparent to make sure the bottom underline is not visible when the card is hovered
|
|
730
|
+
// Border top is set to even out the border bottom used for the underline
|
|
731
|
+
'*:data-[slot="card-link"]:no-underline',
|
|
732
|
+
'*:data-[slot="card-link"]:border-y-2',
|
|
733
|
+
'*:data-[slot="card-link"]:border-y-transparent',
|
|
734
|
+
'*:data-[slot="card-link"]:transition-colors',
|
|
735
|
+
'*:data-[slot="card-link"]:hover:border-b-current',
|
|
736
|
+
// 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
|
|
737
|
+
'*:data-[slot="card-link"]:font-inherit',
|
|
738
|
+
'*:data-[slot="card-link"]:text-pretty',
|
|
739
|
+
'*:data-[slot="card-link"]:hyphens-auto',
|
|
740
|
+
'*:data-[slot="card-link"]:[word-break:break-word]'
|
|
741
|
+
])
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
],
|
|
745
|
+
children: children
|
|
746
|
+
})
|
|
755
747
|
})
|
|
748
|
+
}));
|
|
749
|
+
};
|
|
750
|
+
const cardLinkVariants = cva({
|
|
751
|
+
base: 'w-fit max-w-full',
|
|
752
|
+
variants: {
|
|
753
|
+
withHref: {
|
|
754
|
+
true: [
|
|
755
|
+
// **** Clickarea ****
|
|
756
|
+
'cursor-pointer',
|
|
757
|
+
'after:absolute',
|
|
758
|
+
'after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
759
|
+
'after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
760
|
+
// **** Focus ****
|
|
761
|
+
'focus-visible:outline-none',
|
|
762
|
+
'data-focus-visible:after:outline-focus',
|
|
763
|
+
'data-focus-visible:after:outline-offset-2',
|
|
764
|
+
// **** Hover ****
|
|
765
|
+
// Links are underlined by default, and the underline is removed on hover.
|
|
766
|
+
// So we make sure that also happens when the user hovers the clickable area.
|
|
767
|
+
'hover:no-underline'
|
|
768
|
+
],
|
|
769
|
+
false: [
|
|
770
|
+
// **** Clickarea ****
|
|
771
|
+
'[&_a]:after:cursor-pointer',
|
|
772
|
+
'[&_a]:after:absolute',
|
|
773
|
+
'[&_a]:after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
774
|
+
'[&_a]:after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
775
|
+
// **** Focus ****
|
|
776
|
+
'[&_a[data-focus-visible]]:outline-none',
|
|
777
|
+
'[&_a[data-focus-visible]]:after:outline-focus',
|
|
778
|
+
'[&_a[data-focus-visible]]:after:outline-offset-2',
|
|
779
|
+
// **** Hover ****
|
|
780
|
+
// Links are underlined by default, and the underline is removed on hover.
|
|
781
|
+
// So we make sure that also happens when the user hovers the card.
|
|
782
|
+
// The group-hover ensures that the hover effect also applies when this component is used as a wrapper around a link.
|
|
783
|
+
'[&_a]:group-hover/card:no-underline'
|
|
784
|
+
]
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
/**
|
|
789
|
+
* A component that creates a clickable area on a card.
|
|
790
|
+
* It can be used either as a wrapper around a link or as a standalone link.
|
|
791
|
+
*/ const CardLink = ({ className: _className, href, ...restProps })=>{
|
|
792
|
+
const className = cardLinkVariants({
|
|
793
|
+
className: _className,
|
|
794
|
+
withHref: !!href
|
|
756
795
|
});
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
function Select(props) {
|
|
760
|
-
const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ref, ...restProps } = props;
|
|
761
|
-
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
762
|
-
// which will override any built in validation
|
|
763
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
764
|
-
return /*#__PURE__*/ jsxs(Select$1, {
|
|
796
|
+
return href ? /*#__PURE__*/ jsx(Link, {
|
|
797
|
+
"data-slot": "card-link",
|
|
765
798
|
...restProps,
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
799
|
+
href: href,
|
|
800
|
+
className: className
|
|
801
|
+
}) : // We can't utilize that the `Link` component from react-aria-components renders as a span if it doesn't have an href,
|
|
802
|
+
// because it still renders with role="link" and tabindex="0" which makes it focusable.
|
|
803
|
+
// So we need to render a div instead.
|
|
804
|
+
/*#__PURE__*/ jsx("div", {
|
|
805
|
+
...restProps,
|
|
806
|
+
"data-slot": "card-link",
|
|
807
|
+
className: className
|
|
808
|
+
});
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const Carousel = ({ className, children, onChange })=>{
|
|
812
|
+
const ref = useRef(null);
|
|
813
|
+
const locale = _useLocale();
|
|
814
|
+
const { previous, next } = translations$1;
|
|
815
|
+
const [scrollTargetIndex, setScrollTargetIndex] = useState(0);
|
|
816
|
+
const [hasReachedScrollStart, setHasReachedScrollStart] = useState(scrollTargetIndex === 0);
|
|
817
|
+
const [hasReachedScrollEnd, setHasReachedScrollEnd] = useState(!ref.current || ref.current.children.length - 1 === scrollTargetIndex);
|
|
818
|
+
useEffect(()=>{
|
|
819
|
+
setHasReachedScrollStart(scrollTargetIndex === 0);
|
|
820
|
+
setHasReachedScrollEnd(!ref.current || ref.current.children.length - 1 === scrollTargetIndex);
|
|
821
|
+
}, [
|
|
822
|
+
scrollTargetIndex
|
|
823
|
+
]);
|
|
824
|
+
// Keep track of the previous index to determine if the user is scrolling forward or backward
|
|
825
|
+
// This is used to determine which callback to call (onPrev or onNext)
|
|
826
|
+
const prevIndex = useRef(0);
|
|
827
|
+
// Handle scrolling when user clicks the arrow icons
|
|
828
|
+
useUpdateEffect(()=>{
|
|
829
|
+
if (!ref.current) return;
|
|
830
|
+
ref.current.children[scrollTargetIndex]?.scrollIntoView({
|
|
831
|
+
behavior: 'smooth',
|
|
832
|
+
inline: 'start',
|
|
833
|
+
block: 'nearest'
|
|
834
|
+
});
|
|
835
|
+
if (prevIndex.current !== scrollTargetIndex && onChange) {
|
|
836
|
+
onChange({
|
|
837
|
+
index: scrollTargetIndex,
|
|
838
|
+
id: ref.current.children[scrollTargetIndex]?.id,
|
|
839
|
+
prevIndex: prevIndex.current,
|
|
840
|
+
prevId: ref.current.children[prevIndex.current]?.id
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
prevIndex.current = scrollTargetIndex;
|
|
844
|
+
}, [
|
|
845
|
+
scrollTargetIndex
|
|
846
|
+
]);
|
|
847
|
+
const onScroll = useDebouncedCallback((event)=>{
|
|
848
|
+
const target = event.target;
|
|
849
|
+
// Calculate the index of the item that is currently in view
|
|
850
|
+
const newScrollTargetIndex = Array.from(target.children).findIndex((child)=>{
|
|
851
|
+
const rect = child.getBoundingClientRect();
|
|
852
|
+
return rect.left >= 0 && rect.right <= window.innerWidth && rect.top >= 0;
|
|
853
|
+
});
|
|
854
|
+
if (newScrollTargetIndex !== -1) {
|
|
855
|
+
setScrollTargetIndex(newScrollTargetIndex);
|
|
856
|
+
}
|
|
857
|
+
}, 100);
|
|
858
|
+
return /*#__PURE__*/ jsx("div", {
|
|
859
|
+
"data-slot": "carousel",
|
|
860
|
+
children: /*#__PURE__*/ jsx(Provider, {
|
|
861
|
+
values: [
|
|
862
|
+
[
|
|
863
|
+
CarouselItemsContext,
|
|
864
|
+
{
|
|
865
|
+
ref,
|
|
866
|
+
onScroll
|
|
867
|
+
}
|
|
868
|
+
],
|
|
869
|
+
[
|
|
870
|
+
ButtonContext,
|
|
871
|
+
{
|
|
872
|
+
slots: {
|
|
873
|
+
[DEFAULT_SLOT]: {},
|
|
874
|
+
prev: {
|
|
875
|
+
'aria-label': previous[locale],
|
|
876
|
+
onPress: ()=>{
|
|
877
|
+
if (scrollTargetIndex > 0) {
|
|
878
|
+
setScrollTargetIndex((prev)=>prev - 1);
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
isDisabled: hasReachedScrollStart
|
|
882
|
+
},
|
|
883
|
+
next: {
|
|
884
|
+
isIconOnly: true,
|
|
885
|
+
'aria-label': next[locale],
|
|
886
|
+
onPress: ()=>{
|
|
887
|
+
if (!ref.current) return;
|
|
888
|
+
if (scrollTargetIndex < ref.current.children.length - 1) {
|
|
889
|
+
setScrollTargetIndex((prev)=>prev + 1);
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
isDisabled: hasReachedScrollEnd
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
]
|
|
897
|
+
],
|
|
898
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
899
|
+
className: cx(className, 'relative rounded-3xl', // If any <CarouselItems/> (the scroll-snap container) or <VideoLoop/> component is focused, apply custom focus styles around the carousel, this makes ensures that the focus outline is visible around the carousel in all cases
|
|
900
|
+
'[&:has([data-slot="carousel-items"]:focus-visible,[data-slot="video-loop-button"]:focus-visible)]:outline-focus', '[&:has([data-slot="carousel-items"]:focus-visible,[data-slot="video-loop-button"]:focus-visible)]:outline-focus-offset', // Unset the default focus outline for potential video loop buttons, as it interferes with the custom focus styles for the carousel
|
|
901
|
+
'**:data-[slot="video-loop-button"]:focus-visible:outline-none'),
|
|
902
|
+
children: [
|
|
903
|
+
children,
|
|
904
|
+
/*#__PURE__*/ jsxs(_CarouselControls, {
|
|
905
|
+
children: [
|
|
906
|
+
/*#__PURE__*/ jsx(Button, {
|
|
907
|
+
isIconOnly: true,
|
|
908
|
+
slot: "prev",
|
|
909
|
+
variant: "primary",
|
|
910
|
+
color: "white",
|
|
911
|
+
className: cx('group/carousel-previous', hasReachedScrollStart && 'invisible'),
|
|
912
|
+
children: /*#__PURE__*/ jsx(ChevronLeft, {
|
|
913
|
+
className: "group-hover/carousel-previous:motion-safe:-translate-x-1 transition-transform"
|
|
914
|
+
})
|
|
915
|
+
}),
|
|
916
|
+
/*#__PURE__*/ jsx(Button, {
|
|
917
|
+
isIconOnly: true,
|
|
918
|
+
slot: "next",
|
|
919
|
+
variant: "primary",
|
|
920
|
+
color: "white",
|
|
921
|
+
className: cx('group/carousel-next', hasReachedScrollEnd && 'invisible'),
|
|
922
|
+
children: /*#__PURE__*/ jsx(ChevronRight, {
|
|
923
|
+
className: "transition-transform group-hover/carousel-next:motion-safe:translate-x-1"
|
|
924
|
+
})
|
|
925
|
+
})
|
|
926
|
+
]
|
|
788
927
|
})
|
|
789
928
|
]
|
|
790
|
-
}),
|
|
791
|
-
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
792
|
-
errorMessage: errorMessage
|
|
793
|
-
}),
|
|
794
|
-
/*#__PURE__*/ jsx(Popover, {
|
|
795
|
-
className: dropdown.popover,
|
|
796
|
-
children: /*#__PURE__*/ jsx(ListBox, {
|
|
797
|
-
className: dropdown.listbox,
|
|
798
|
-
children: children
|
|
799
|
-
})
|
|
800
929
|
})
|
|
801
|
-
|
|
930
|
+
})
|
|
802
931
|
});
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
children: [
|
|
813
|
-
label && /*#__PURE__*/ jsx(Label, {
|
|
814
|
-
children: label
|
|
815
|
-
}),
|
|
816
|
-
description && /*#__PURE__*/ jsx(Description, {
|
|
817
|
-
children: description
|
|
818
|
-
}),
|
|
819
|
-
/*#__PURE__*/ jsx(TextArea$1, {
|
|
820
|
-
className: input(),
|
|
821
|
-
rows: rows,
|
|
822
|
-
ref: ref
|
|
823
|
-
}),
|
|
824
|
-
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
825
|
-
errorMessage: errorMessage
|
|
826
|
-
})
|
|
827
|
-
]
|
|
932
|
+
};
|
|
933
|
+
/**
|
|
934
|
+
* This is internal for now, but we will expose it in the future when we support more flexible positioning of prev/next and other actions.
|
|
935
|
+
* It is used to render the prev/next buttons in the carousel for now.
|
|
936
|
+
*/ const _CarouselControls = ({ children, className })=>/*#__PURE__*/ jsx("div", {
|
|
937
|
+
className: cx(className, 'absolute right-6 bottom-6 flex gap-x-2', // Make it easier to position in full-bleed hero variants (these style have no other side effects)
|
|
938
|
+
'items-end *:h-fit'),
|
|
939
|
+
"data-slot": "carousel-controls",
|
|
940
|
+
children: children
|
|
828
941
|
});
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
function TextField(props) {
|
|
845
|
-
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
|
|
846
|
-
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
847
|
-
// which will override any built in validation
|
|
848
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
849
|
-
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
850
|
-
...restProps,
|
|
851
|
-
className: cx(className, formField),
|
|
852
|
-
isInvalid: isInvalid,
|
|
853
|
-
children: [
|
|
854
|
-
label && /*#__PURE__*/ jsx(Label, {
|
|
855
|
-
children: label
|
|
856
|
-
}),
|
|
857
|
-
description && /*#__PURE__*/ jsx(Description, {
|
|
858
|
-
children: description
|
|
859
|
-
}),
|
|
860
|
-
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
861
|
-
className: cx(inputGroup, {
|
|
862
|
-
'w-fit': !!size
|
|
863
|
-
}),
|
|
864
|
-
children: [
|
|
865
|
-
leftAddon,
|
|
866
|
-
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
867
|
-
/*#__PURE__*/ jsx(Input, {
|
|
868
|
-
className: inputVariants$1({
|
|
869
|
-
textAlign,
|
|
870
|
-
isGrouped: true,
|
|
871
|
-
autoWidth: !!size
|
|
872
|
-
}),
|
|
873
|
-
ref: ref,
|
|
874
|
-
size: size
|
|
875
|
-
}),
|
|
876
|
-
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
877
|
-
rightAddon
|
|
878
|
-
]
|
|
879
|
-
}) : /*#__PURE__*/ jsx(Input, {
|
|
880
|
-
className: inputVariants$1({
|
|
881
|
-
textAlign,
|
|
882
|
-
autoWidth: !!size
|
|
883
|
-
}),
|
|
942
|
+
const CarouselItemsContext = /*#__PURE__*/ createContext({
|
|
943
|
+
ref: null
|
|
944
|
+
});
|
|
945
|
+
const CarouselItems = ({ className, children })=>/*#__PURE__*/ jsx(CarouselItemsContext.Consumer, {
|
|
946
|
+
children: ({ ref, onScroll })=>/*#__PURE__*/ jsx("div", {
|
|
947
|
+
"data-slot": "carousel-items",
|
|
948
|
+
className: cx(className, [
|
|
949
|
+
'scrollbar-hidden',
|
|
950
|
+
'flex',
|
|
951
|
+
'snap-x',
|
|
952
|
+
'snap-mandatory',
|
|
953
|
+
'overflow-x-auto',
|
|
954
|
+
'outline-none',
|
|
955
|
+
'rounded-[inherit]'
|
|
956
|
+
]),
|
|
884
957
|
ref: ref,
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
958
|
+
// When the SnapEvent is supported: https://developer.mozilla.org/en-US/docs/Web/API/SnapEvent
|
|
959
|
+
// We can use the scrollsnapchange event to detect when the user has scrolled to a new item.
|
|
960
|
+
// We can then use Array.from(event.target.children).indexOf(event.snapTargetInline) to calculate the index of the item that is currently in view.
|
|
961
|
+
// Another option is to use the scrollEnd event, when Safiri supports it: https://developer.apple.com/documentation/webkitjs/snap_event/scrollend_event
|
|
962
|
+
onScroll: onScroll,
|
|
963
|
+
children: children
|
|
889
964
|
})
|
|
890
|
-
]
|
|
891
965
|
});
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}));
|
|
908
|
-
function NumberField(props) {
|
|
909
|
-
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
|
|
910
|
-
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
911
|
-
// which will override any built in validation
|
|
912
|
-
const isInvalid = !!errorMessage || _isInvalid;
|
|
913
|
-
return /*#__PURE__*/ jsxs(NumberField$1, {
|
|
914
|
-
...restProps,
|
|
915
|
-
className: cx(className, formField),
|
|
916
|
-
isInvalid: isInvalid,
|
|
917
|
-
children: [
|
|
918
|
-
label && /*#__PURE__*/ jsx(Label, {
|
|
919
|
-
children: label
|
|
920
|
-
}),
|
|
921
|
-
description && /*#__PURE__*/ jsx(Description, {
|
|
922
|
-
children: description
|
|
923
|
-
}),
|
|
924
|
-
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
925
|
-
className: cx(inputGroup, {
|
|
926
|
-
'w-fit': !!size
|
|
927
|
-
}),
|
|
928
|
-
children: [
|
|
929
|
-
leftAddon,
|
|
930
|
-
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
931
|
-
/*#__PURE__*/ jsx(Input, {
|
|
932
|
-
className: inputVariants({
|
|
933
|
-
textAlign,
|
|
934
|
-
isGrouped: true,
|
|
935
|
-
autoWidth: !!size
|
|
936
|
-
}),
|
|
937
|
-
ref: ref,
|
|
938
|
-
size: size
|
|
939
|
-
}),
|
|
940
|
-
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
941
|
-
rightAddon
|
|
966
|
+
const CarouselItem = ({ className, children, id })=>{
|
|
967
|
+
return /*#__PURE__*/ jsx("div", {
|
|
968
|
+
className: cx(className, 'shrink-0 basis-full snap-start'),
|
|
969
|
+
"data-slot": "carousel-item",
|
|
970
|
+
id: id,
|
|
971
|
+
children: /*#__PURE__*/ jsx(Provider, {
|
|
972
|
+
values: [
|
|
973
|
+
[
|
|
974
|
+
MediaContext,
|
|
975
|
+
{
|
|
976
|
+
fit: 'cover',
|
|
977
|
+
className: cx('data-[fit="contain"]:bg-blue-dark', '*:h-full *:w-full', 'aspect-1/1 max-sm:data-[fit="contain"]:*:object-cover sm:aspect-4/3 md:aspect-3/2 lg:aspect-2/1')
|
|
978
|
+
}
|
|
942
979
|
]
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
autoWidth: !!size
|
|
947
|
-
}),
|
|
948
|
-
ref: ref,
|
|
949
|
-
size: size
|
|
950
|
-
}),
|
|
951
|
-
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
952
|
-
errorMessage: errorMessage
|
|
953
|
-
})
|
|
954
|
-
]
|
|
980
|
+
],
|
|
981
|
+
children: children
|
|
982
|
+
})
|
|
955
983
|
});
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
const iconMap = {
|
|
959
|
-
info: InfoCircle,
|
|
960
|
-
success: CheckCircle,
|
|
961
|
-
warning: Warning,
|
|
962
|
-
danger: Error
|
|
963
984
|
};
|
|
964
|
-
|
|
985
|
+
|
|
986
|
+
const formField = cx('group flex flex-col gap-2');
|
|
987
|
+
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');
|
|
988
|
+
const input = cva({
|
|
965
989
|
base: [
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
//
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
'
|
|
990
|
+
// All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
|
|
991
|
+
'bg-white',
|
|
992
|
+
// Use box-content to enable auto width based on number of characters (size)
|
|
993
|
+
// Setting min-height to prevent the input from collapsing in Safari
|
|
994
|
+
// Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
|
|
995
|
+
'box-content min-h-6 py-2.5',
|
|
996
|
+
'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
|
|
997
|
+
// invalid styles
|
|
998
|
+
'group-data-invalid:ring-focus group-data-invalid:ring-red',
|
|
999
|
+
// Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
|
|
1000
|
+
'appearance-none'
|
|
973
1001
|
],
|
|
974
1002
|
variants: {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1003
|
+
// Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
|
|
1004
|
+
focusModifier: {
|
|
1005
|
+
focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
|
|
1006
|
+
visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
|
|
1007
|
+
},
|
|
1008
|
+
isGrouped: {
|
|
1009
|
+
false: 'px-3',
|
|
1010
|
+
true: '!ring-0 flex-1'
|
|
983
1011
|
}
|
|
984
1012
|
},
|
|
985
1013
|
defaultVariants: {
|
|
986
|
-
|
|
1014
|
+
focusModifier: 'focus',
|
|
1015
|
+
isGrouped: false
|
|
987
1016
|
}
|
|
988
1017
|
});
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const close = ()=>{
|
|
999
|
-
setIsUncontrolledVisible(false);
|
|
1000
|
-
if (onDismiss) onDismiss();
|
|
1001
|
-
};
|
|
1002
|
-
const isInDevMode = process.env.NODE_ENV !== 'production';
|
|
1003
|
-
if (isInDevMode && onDismiss && !isDismissable) {
|
|
1004
|
-
console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
|
|
1005
|
-
}
|
|
1006
|
-
if (isInDevMode && !children) {
|
|
1007
|
-
console.error('`No children was passed to the <AlertBox/>` component.');
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
const [firstChild, ...restChildren] = Children.toArray(children);
|
|
1011
|
-
return /*#__PURE__*/ jsxs("div", {
|
|
1012
|
-
className: alertVariants({
|
|
1013
|
-
className,
|
|
1014
|
-
variant
|
|
1015
|
-
}),
|
|
1016
|
-
// The role prop is required to force consumers to consider and choose the appropriate alertbox role.
|
|
1017
|
-
// role="none" will not have any effect on a div, so it can be omitted.
|
|
1018
|
-
role: role === 'none' ? undefined : role,
|
|
1019
|
-
children: [
|
|
1020
|
-
/*#__PURE__*/ jsx(Icon, {}),
|
|
1021
|
-
firstChild,
|
|
1022
|
-
isDismissable && /*#__PURE__*/ jsx("button", {
|
|
1023
|
-
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'),
|
|
1024
|
-
onClick: close,
|
|
1025
|
-
"aria-label": translations$1.close[locale],
|
|
1026
|
-
type: "button",
|
|
1027
|
-
children: /*#__PURE__*/ jsx(Close, {})
|
|
1028
|
-
}),
|
|
1029
|
-
isExpandable && /*#__PURE__*/ jsxs("button", {
|
|
1030
|
-
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:
|
|
1031
|
-
'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'),
|
|
1032
|
-
onClick: ()=>setIsExpanded((prevState)=>!prevState),
|
|
1033
|
-
"aria-expanded": isExpanded,
|
|
1034
|
-
"aria-controls": id,
|
|
1035
|
-
type: "button",
|
|
1036
|
-
children: [
|
|
1037
|
-
isExpanded ? translations$1.showLess[locale] : translations$1.showMore[locale],
|
|
1038
|
-
/*#__PURE__*/ jsx(ChevronDown, {
|
|
1039
|
-
className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
|
|
1040
|
-
})
|
|
1041
|
-
]
|
|
1042
|
-
}),
|
|
1043
|
-
restChildren?.length > 0 && /*#__PURE__*/ jsx("div", {
|
|
1044
|
-
className: cx('col-span-full grid gap-y-4', isCollapsed && '[&>*:not([data-slot="footer"])]:hidden'),
|
|
1045
|
-
id: id,
|
|
1046
|
-
children: restChildren
|
|
1047
|
-
})
|
|
1048
|
-
]
|
|
1049
|
-
});
|
|
1018
|
+
const inputGroup = cx([
|
|
1019
|
+
'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
|
|
1020
|
+
'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
|
|
1021
|
+
]);
|
|
1022
|
+
const dropdown = {
|
|
1023
|
+
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'),
|
|
1024
|
+
// overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
|
|
1025
|
+
listbox: cx('max-h-[25rem] overflow-x-hidden text-sm outline-hidden'),
|
|
1026
|
+
chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
|
|
1050
1027
|
};
|
|
1051
1028
|
|
|
1052
|
-
function
|
|
1053
|
-
const {
|
|
1054
|
-
return /*#__PURE__*/ jsx(
|
|
1029
|
+
function ErrorMessage(props) {
|
|
1030
|
+
const { children, className, ...restProps } = props;
|
|
1031
|
+
return /*#__PURE__*/ jsx(Text, {
|
|
1055
1032
|
...restProps,
|
|
1056
|
-
className: cx(className,
|
|
1033
|
+
className: cx(className, formFieldError),
|
|
1034
|
+
slot: "errorMessage",
|
|
1057
1035
|
children: children
|
|
1058
1036
|
});
|
|
1059
1037
|
}
|
|
1060
1038
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1039
|
+
const defaultClasses$1 = cx([
|
|
1040
|
+
'group -mx-2.5 relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 p-2.5 leading-7'
|
|
1041
|
+
]);
|
|
1042
|
+
// Pulling this out into it's own component. Will probably export it in the future
|
|
1043
|
+
// so it can be used in other views, outside of an input of type checkbox, like in table rows.
|
|
1044
|
+
function CheckmarkBox() {
|
|
1045
|
+
return /*#__PURE__*/ jsx("span", {
|
|
1046
|
+
className: cx([
|
|
1047
|
+
'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
|
|
1048
|
+
// 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.
|
|
1049
|
+
// For the ::before psuedo element the line height of the label is always 1em.
|
|
1050
|
+
// When we know the height of the label we use the height of the radio to push it down to align with the label's first line
|
|
1051
|
+
// 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?
|
|
1052
|
+
'mt-[calc((1em_*_1.75_-_24px)_/_2)] h-[24px] w-[24px]',
|
|
1053
|
+
// selected
|
|
1054
|
+
'group-data-selected:group-not-data-hovered:group-not-data-invalid:border-blue group-data-selected:group-not-data-hovered:group-not-data-invalid:bg-blue',
|
|
1055
|
+
'group-data-selected:group-not-data-hovered:group-data-invalid:border-red group-data-selected:group-not-data-hovered:group-data-invalid:bg-red',
|
|
1056
|
+
// focus
|
|
1057
|
+
'group-data-focus-visible:outline-focus-offset',
|
|
1058
|
+
// hovered
|
|
1059
|
+
'group-data-hovered:group-data-invalid:bg-red-light',
|
|
1060
|
+
'group-data-hovered:border-blue',
|
|
1061
|
+
'group-data-hovered:bg-sky',
|
|
1062
|
+
'group-data-hovered:group-data-selected:group-not-data-invalid:border-blue-dark',
|
|
1063
|
+
'group-data-hovered:group-data-selected:group-not-data-invalid:bg-blue-dark',
|
|
1064
|
+
// 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
|
|
1065
|
+
// so we use an inner shadow of 1 px instead to pad the actual border
|
|
1066
|
+
'group-data-invalid:border-red group-data-invalid:shadow-[inset_0_0_0_1px] group-data-invalid:shadow-red'
|
|
1067
|
+
]),
|
|
1068
|
+
children: /*#__PURE__*/ jsx(Check, {
|
|
1069
|
+
className: "h-full w-full opacity-0 group-data-invalid:group-data-hovered:group-data-selected:text-red group-data-selected:opacity-100"
|
|
1070
|
+
})
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
function Checkbox(props) {
|
|
1074
|
+
const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
|
|
1075
|
+
const id = useId();
|
|
1076
|
+
const descriptionId = `desc${id}`;
|
|
1077
|
+
const errorMessageId = `error${id}`;
|
|
1078
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
1079
|
+
return /*#__PURE__*/ jsx("div", {
|
|
1080
|
+
children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
|
|
1081
|
+
value: {
|
|
1082
|
+
'aria-describedby': description ? descriptionId : undefined,
|
|
1083
|
+
'aria-errormessage': errorMessage ? errorMessageId : undefined
|
|
1084
|
+
},
|
|
1085
|
+
children: [
|
|
1086
|
+
/*#__PURE__*/ jsxs(Checkbox$1, {
|
|
1087
|
+
...restProps,
|
|
1088
|
+
className: cx(className, defaultClasses$1),
|
|
1089
|
+
isInvalid: isInvalid,
|
|
1090
|
+
children: [
|
|
1091
|
+
/*#__PURE__*/ jsx(CheckmarkBox, {}),
|
|
1092
|
+
children
|
|
1093
|
+
]
|
|
1094
|
+
}),
|
|
1095
|
+
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 */}
|
|
1096
|
+
/*#__PURE__*/ jsx("div", {
|
|
1097
|
+
id: descriptionId,
|
|
1098
|
+
slot: "description",
|
|
1099
|
+
className: "description block",
|
|
1100
|
+
children: description
|
|
1101
|
+
}),
|
|
1102
|
+
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
1103
|
+
className: "mt-2 block",
|
|
1104
|
+
id: errorMessageId,
|
|
1105
|
+
children: errorMessage
|
|
1106
|
+
})
|
|
1107
|
+
]
|
|
1108
|
+
})
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function Description(props) {
|
|
1113
|
+
const { className, ...restProps } = props;
|
|
1114
|
+
return /*#__PURE__*/ jsx(Text, {
|
|
1065
1115
|
...restProps,
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
href: href,
|
|
1069
|
-
// 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
|
|
1070
|
-
className: "rounded-xs focus-visible:outline-focus group-last:no-underline",
|
|
1071
|
-
children: children
|
|
1072
|
-
}) : children,
|
|
1073
|
-
/*#__PURE__*/ jsx(ChevronRight, {
|
|
1074
|
-
className: "px-1 group-last:hidden"
|
|
1075
|
-
})
|
|
1076
|
-
]
|
|
1116
|
+
className: cx(className, 'description'),
|
|
1117
|
+
slot: "description"
|
|
1077
1118
|
});
|
|
1078
1119
|
}
|
|
1079
1120
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1121
|
+
/**
|
|
1122
|
+
* This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
|
|
1123
|
+
* In other words, this handles controlled and uncontrolled form errors.
|
|
1124
|
+
*/ function ErrorMessageOrFieldError({ errorMessage }) {
|
|
1125
|
+
return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
|
|
1126
|
+
children: errorMessage
|
|
1127
|
+
}) : /*#__PURE__*/ jsx(FieldError, {
|
|
1128
|
+
className: formFieldError
|
|
1129
|
+
});
|
|
1082
1130
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}),
|
|
1091
|
-
/*#__PURE__*/ jsx("span", {
|
|
1092
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
1093
|
-
className: cx('border-transparent border-t-[1px] border-b-[1px] transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
|
|
1094
|
-
children: children
|
|
1095
|
-
})
|
|
1096
|
-
})
|
|
1097
|
-
]
|
|
1131
|
+
|
|
1132
|
+
function Label(props) {
|
|
1133
|
+
const { children, className, ...restProps } = props;
|
|
1134
|
+
return /*#__PURE__*/ jsx(Label$1, {
|
|
1135
|
+
className: cx(className, 'font-semibold leading-7'),
|
|
1136
|
+
...restProps,
|
|
1137
|
+
children: children
|
|
1098
1138
|
});
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1108
|
-
return /*#__PURE__*/ jsx(Button$1, {
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function CheckboxGroup(props) {
|
|
1142
|
+
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
1143
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
1144
|
+
// which will override any built in validation
|
|
1145
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
1146
|
+
return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
|
|
1109
1147
|
...restProps,
|
|
1110
|
-
className:
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
children:
|
|
1148
|
+
className: cx(className, 'flex flex-col gap-2'),
|
|
1149
|
+
isInvalid: isInvalid,
|
|
1150
|
+
isRequired: isRequired,
|
|
1151
|
+
children: [
|
|
1152
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
1153
|
+
children: label
|
|
1154
|
+
}),
|
|
1155
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
1156
|
+
children: description
|
|
1157
|
+
}),
|
|
1158
|
+
children,
|
|
1159
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
1160
|
+
errorMessage: errorMessage
|
|
1161
|
+
})
|
|
1162
|
+
]
|
|
1114
1163
|
});
|
|
1115
1164
|
}
|
|
1116
1165
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
'rounded-[inherit]',
|
|
1121
|
-
'border p-3',
|
|
1122
|
-
'flex flex-col gap-y-4',
|
|
1123
|
-
'relative',
|
|
1124
|
-
// **** Content ****
|
|
1125
|
-
'[&_[data-slot="content"]]:flex [&_[data-slot="content"]]:flex-col [&_[data-slot="content"]]:gap-y-4',
|
|
1126
|
-
// **** Media ****
|
|
1127
|
-
'[&_[data-slot="media"]_*]:pointer-events-none',
|
|
1128
|
-
'[&_[data-slot="media"]]:overflow-hidden',
|
|
1129
|
-
'[&_[data-slot="media"]]:relative',
|
|
1130
|
-
// 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)
|
|
1131
|
-
'[&_[data-slot="media"]]:mx-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))] [&_[data-slot="media"]]:mt-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
|
|
1132
|
-
// Sets the aspect ratio of the media content (width: 100% is necessary to make aspect ratio work on images in FF)
|
|
1133
|
-
'[&_[data-slot="media"]>*:not([data-slot="badge"])]:aspect-3/2 [&_[data-slot="media"]>img]:w-full [&_[data-slot="media"]>img]:object-cover',
|
|
1134
|
-
// 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.
|
|
1135
|
-
'[&_[data-slot="media"]>*]:duration-300 [&_[data-slot="media"]>*]:ease-in-out [&_[data-slot="media"]>*]:motion-safe:transition-transform',
|
|
1136
|
-
// **** Card link ****
|
|
1137
|
-
// **** Hover ****
|
|
1138
|
-
// 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)
|
|
1139
|
-
'[&:has([data-slot="card-link"]_a:hover)_[data-slot="media"]>img]:scale-110',
|
|
1140
|
-
'[&:has([data-slot="heading"]_[data-slot="card-link"]:hover)_[data-slot="media"]>img]:scale-110',
|
|
1141
|
-
// **** Fail-safe for interactive elements ****
|
|
1142
|
-
// Make interactive elements clickable by themselves, while the rest of the card is clickable as a whole
|
|
1143
|
-
// The card is made clickable by a pseudo-element on the heading that covers the entire card
|
|
1144
|
-
'[&:not(:has([data-slot="card-link"]_a))_a:not([data-slot="card-link"])]:relative [&_button]:relative [&_input]:relative',
|
|
1145
|
-
// 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)
|
|
1146
|
-
'[&_[data-slot="card-link"]_a]:static',
|
|
1147
|
-
// Place other interactive on top of the pseudo-element that makes the entire card clickable
|
|
1148
|
-
// by setting a higher z-index than the pseudo-element (which implicitly z-index 0)
|
|
1149
|
-
'[&_a:not([data-slot="card-link"])]:z-[1] [&_button]:z-[1] [&_input]:z-[1]',
|
|
1150
|
-
// **** Badge ****
|
|
1151
|
-
'[&_[data-slot="media"]_[data-slot="badge"]]:absolute [&_[data-slot="media"]_[data-slot="badge"]]:top-0',
|
|
1152
|
-
// 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)
|
|
1153
|
-
'[&_[data-slot="media"]_[data-slot="badge"]]:z-[1]',
|
|
1154
|
-
// Left aligned - override default corner radius of the badge
|
|
1155
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tl-2xl',
|
|
1156
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-br-2xl',
|
|
1157
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-tr-none',
|
|
1158
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:first-child]:rounded-bl-none',
|
|
1159
|
-
// Right aligned - override default corner radius of the badge
|
|
1160
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tl-none',
|
|
1161
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-br-none',
|
|
1162
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-tr-2xl',
|
|
1163
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:rounded-bl-2xl',
|
|
1164
|
-
// ... and position the badge at the right edge of the media content
|
|
1165
|
-
'[&_[data-slot="media"]_[data-slot="badge"]:last-child]:right-0'
|
|
1166
|
-
],
|
|
1167
|
-
variants: {
|
|
1168
|
-
/**
|
|
1169
|
-
* The variant of the card
|
|
1170
|
-
* @default subtle
|
|
1171
|
-
*/ variant: {
|
|
1172
|
-
subtle: [
|
|
1173
|
-
'border-transparent',
|
|
1174
|
-
// **** Media styles ****
|
|
1175
|
-
'[&_[data-slot="media"]]:rounded-2xl'
|
|
1176
|
-
],
|
|
1177
|
-
outlined: 'border border-black'
|
|
1178
|
-
},
|
|
1179
|
-
/**
|
|
1180
|
-
* The layout of the card
|
|
1181
|
-
* @default vertical
|
|
1182
|
-
*/ layout: {
|
|
1183
|
-
vertical: [
|
|
1184
|
-
'flex-col',
|
|
1185
|
-
// **** Media ****
|
|
1186
|
-
'[&_[data-slot="media"]]:rounded-t-2xl'
|
|
1187
|
-
],
|
|
1188
|
-
horizontal: [
|
|
1189
|
-
// Use more gap for horizontal cards that have media
|
|
1190
|
-
// Since this does not affect the layout before the flex direction is set (at breakpoint @2xl for Card with Media), we can set it here
|
|
1191
|
-
'has-data-[slot=media]:layout-gap-x not-has-data-[slot=media]:gap-x-4',
|
|
1192
|
-
// **** With Media ****
|
|
1193
|
-
'[&:has(>[data-slot="media"]:last-child)]:flex-col-reverse',
|
|
1194
|
-
'has-data-[slot=media]:@2xl:!flex-row',
|
|
1195
|
-
'*:data-[slot=media]:@2xl:h-fit',
|
|
1196
|
-
'has-data-[slot=media]:*:@2xl:basis-1/2',
|
|
1197
|
-
// Position media at the edges of the card
|
|
1198
|
-
'*:data-[slot=media]:@2xl:mb-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
|
|
1199
|
-
'*:data-[slot=media]:first:@2xl:mr-0',
|
|
1200
|
-
'*:data-[slot=media]:last:@2xl:ml-0',
|
|
1201
|
-
// Make sure the card link is clickable when the media is on the right side
|
|
1202
|
-
// This is necessary because the media content is positioned after the card link in the DOM
|
|
1203
|
-
'[&:has(>[data-slot="media"]:last-child)_[data-slot="card-link"]]:z-[1]',
|
|
1204
|
-
// **** Without Media ****
|
|
1205
|
-
'not-has-data-[slot=media]:@md:flex-row',
|
|
1206
|
-
// 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.
|
|
1207
|
-
'not-has-data-[slot=media]:**:data-[slot=content]:grow',
|
|
1208
|
-
// Make sure svg's etc. are not shrinkable
|
|
1209
|
-
'[&>:not([data-slot="content"],[data-slot="media"])]:shrink-0'
|
|
1210
|
-
]
|
|
1211
|
-
}
|
|
1212
|
-
},
|
|
1213
|
-
defaultVariants: {
|
|
1214
|
-
variant: 'subtle',
|
|
1215
|
-
layout: 'vertical'
|
|
1216
|
-
},
|
|
1217
|
-
compoundVariants: [
|
|
1218
|
-
{
|
|
1219
|
-
variant: 'outlined',
|
|
1220
|
-
layout: 'horizontal',
|
|
1221
|
-
className: [
|
|
1222
|
-
// **** Media ****
|
|
1223
|
-
// Some rounded corners are removed when the card is outlined
|
|
1224
|
-
'[&_[data-slot="media"]]:rounded-t-2xl',
|
|
1225
|
-
'*:data-[slot=media]:first:@2xl:rounded-tr-none *:data-[slot=media]:first:@2xl:rounded-bl-2xl',
|
|
1226
|
-
'*:data-[slot=media]:last:@2xl:rounded-tl-none *:data-[slot=media]:last:@2xl:rounded-br-2xl',
|
|
1227
|
-
// **** Badge ****
|
|
1228
|
-
// Override default corner radius of the badge to match the media border radius
|
|
1229
|
-
'[&_[data-slot="media"]:first-child_[data-slot="badge"]:last-child]:@2xl:rounded-tr-none',
|
|
1230
|
-
'[&_[data-slot="media"]:last-child_[data-slot="badge"]:first-child]:@2xl:rounded-tl-none'
|
|
1231
|
-
]
|
|
1232
|
-
}
|
|
1233
|
-
]
|
|
1234
|
-
});
|
|
1235
|
-
const Card = ({ children, className, variant, layout, ...restProps })=>{
|
|
1236
|
-
const cardClassName = cardVariants({
|
|
1237
|
-
variant,
|
|
1238
|
-
layout
|
|
1166
|
+
function InputAddonDivider() {
|
|
1167
|
+
return /*#__PURE__*/ jsx("span", {
|
|
1168
|
+
className: "block h-6 w-px flex-none bg-black"
|
|
1239
1169
|
});
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
|
|
1243
1173
|
...restProps,
|
|
1244
|
-
className: cx(
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
'*:data-[slot="card-link"]:transition-colors',
|
|
1266
|
-
'*:data-[slot="card-link"]:hover:border-b-current',
|
|
1267
|
-
// 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
|
|
1268
|
-
'*:data-[slot="card-link"]:font-inherit',
|
|
1269
|
-
'*:data-[slot="card-link"]:text-pretty',
|
|
1270
|
-
'*:data-[slot="card-link"]:hyphens-auto',
|
|
1271
|
-
'*:data-[slot="card-link"]:[word-break:break-word]'
|
|
1272
|
-
])
|
|
1273
|
-
}
|
|
1274
|
-
]
|
|
1275
|
-
],
|
|
1276
|
-
children: children
|
|
1174
|
+
className: cx(dropdown.listbox, className)
|
|
1175
|
+
});
|
|
1176
|
+
const ListBoxItem = (props)=>{
|
|
1177
|
+
let textValue = props.textValue;
|
|
1178
|
+
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
1179
|
+
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
1180
|
+
// This condition adds back that behaviour
|
|
1181
|
+
if (textValue == null && typeof props.children === 'string') {
|
|
1182
|
+
textValue = props.children;
|
|
1183
|
+
}
|
|
1184
|
+
return /*#__PURE__*/ jsx(ListBoxItem$1, {
|
|
1185
|
+
...props,
|
|
1186
|
+
className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-focused:bg-sky-lightest'),
|
|
1187
|
+
textValue: textValue,
|
|
1188
|
+
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
1189
|
+
children: [
|
|
1190
|
+
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
1191
|
+
className: "-ml-6 text-base"
|
|
1192
|
+
}),
|
|
1193
|
+
props.children
|
|
1194
|
+
]
|
|
1277
1195
|
})
|
|
1278
|
-
|
|
1279
|
-
}));
|
|
1196
|
+
});
|
|
1280
1197
|
};
|
|
1281
|
-
const cardLinkVariants = cva({
|
|
1282
|
-
base: 'w-fit max-w-full',
|
|
1283
|
-
variants: {
|
|
1284
|
-
withHref: {
|
|
1285
|
-
true: [
|
|
1286
|
-
// **** Clickarea ****
|
|
1287
|
-
'cursor-pointer',
|
|
1288
|
-
'after:absolute',
|
|
1289
|
-
'after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
1290
|
-
'after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
1291
|
-
// **** Focus ****
|
|
1292
|
-
'focus-visible:outline-none',
|
|
1293
|
-
'data-focus-visible:after:outline-focus',
|
|
1294
|
-
'data-focus-visible:after:outline-offset-2',
|
|
1295
|
-
// **** Hover ****
|
|
1296
|
-
// Links are underlined by default, and the underline is removed on hover.
|
|
1297
|
-
// So we make sure that also happens when the user hovers the clickable area.
|
|
1298
|
-
'hover:no-underline'
|
|
1299
|
-
],
|
|
1300
|
-
false: [
|
|
1301
|
-
// **** Clickarea ****
|
|
1302
|
-
'[&_a]:after:cursor-pointer',
|
|
1303
|
-
'[&_a]:after:absolute',
|
|
1304
|
-
'[&_a]:after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
1305
|
-
'[&_a]:after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
1306
|
-
// **** Focus ****
|
|
1307
|
-
'[&_a[data-focus-visible]]:outline-none',
|
|
1308
|
-
'[&_a[data-focus-visible]]:after:outline-focus',
|
|
1309
|
-
'[&_a[data-focus-visible]]:after:outline-offset-2',
|
|
1310
|
-
// **** Hover ****
|
|
1311
|
-
// Links are underlined by default, and the underline is removed on hover.
|
|
1312
|
-
// So we make sure that also happens when the user hovers the card.
|
|
1313
|
-
// The group-hover ensures that the hover effect also applies when this component is used as a wrapper around a link.
|
|
1314
|
-
'[&_a]:group-hover/card:no-underline'
|
|
1315
|
-
]
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
});
|
|
1319
1198
|
/**
|
|
1320
|
-
*
|
|
1321
|
-
|
|
1322
|
-
*/ const CardLink = ({ className: _className, href, ...restProps })=>{
|
|
1323
|
-
const className = cardLinkVariants({
|
|
1324
|
-
className: _className,
|
|
1325
|
-
withHref: !!href
|
|
1326
|
-
});
|
|
1327
|
-
return href ? /*#__PURE__*/ jsx(Link, {
|
|
1328
|
-
"data-slot": "card-link",
|
|
1199
|
+
* This component can be used to group items in a listbox
|
|
1200
|
+
*/ const ListBoxSection = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBoxSection$1, {
|
|
1329
1201
|
...restProps,
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1202
|
+
// The :not(:first-child) selector adds extra spacing to all the options, but not the section (group) headings
|
|
1203
|
+
// This way we get the desired extra indent on all options within a group
|
|
1204
|
+
className: cx(className, 'pb-1 [&>:not(:first-child)]:pl-10')
|
|
1205
|
+
});
|
|
1206
|
+
/**
|
|
1207
|
+
* This component can be used to label grouped items in a `ListBoxSection` with a heading
|
|
1208
|
+
*/ const ListBoxHeader = (props)=>/*#__PURE__*/ jsx(Header, {
|
|
1209
|
+
...props,
|
|
1210
|
+
className: cx(props.className, 'mx-6 cursor-default py-2 font-medium text-blue-dark leading-6')
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
function Combobox(props) {
|
|
1214
|
+
const { className, children, description, errorMessage, isPending, label, isInvalid: _isInvalid, ref, ...restProps } = props;
|
|
1215
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
1216
|
+
// which will override any built in validation
|
|
1217
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
1218
|
+
return /*#__PURE__*/ jsxs(ComboBox, {
|
|
1336
1219
|
...restProps,
|
|
1337
|
-
|
|
1338
|
-
|
|
1220
|
+
className: cx(className, formField),
|
|
1221
|
+
isInvalid: isInvalid,
|
|
1222
|
+
children: [
|
|
1223
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
1224
|
+
children: label
|
|
1225
|
+
}),
|
|
1226
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
1227
|
+
children: description
|
|
1228
|
+
}),
|
|
1229
|
+
/*#__PURE__*/ jsxs(Group, {
|
|
1230
|
+
className: inputGroup,
|
|
1231
|
+
children: [
|
|
1232
|
+
/*#__PURE__*/ jsx(Input, {
|
|
1233
|
+
className: input({
|
|
1234
|
+
isGrouped: true
|
|
1235
|
+
}),
|
|
1236
|
+
ref: ref
|
|
1237
|
+
}),
|
|
1238
|
+
/*#__PURE__*/ jsx(Button$1, {
|
|
1239
|
+
children: isPending ? /*#__PURE__*/ jsx(LoadingSpinner, {
|
|
1240
|
+
className: "animate-spin"
|
|
1241
|
+
}) : /*#__PURE__*/ jsx(ChevronDown, {
|
|
1242
|
+
className: dropdown.chevronIcon
|
|
1243
|
+
})
|
|
1244
|
+
})
|
|
1245
|
+
]
|
|
1246
|
+
}),
|
|
1247
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
1248
|
+
errorMessage: errorMessage
|
|
1249
|
+
}),
|
|
1250
|
+
/*#__PURE__*/ jsx(Popover, {
|
|
1251
|
+
// FIXME: The trigger width doesn't include the padding of the group, so for now we have to apply this workaround.
|
|
1252
|
+
// Also... the combobox border gets a pixel wider when focused, so we account for that as well when calculating the width
|
|
1253
|
+
// and the offset.
|
|
1254
|
+
// The input gutter should probably be moved to a theme variable instead of using the hardcoded value as here.
|
|
1255
|
+
className: cx(dropdown.popover, 'min-w-[calc(var(--trigger-width)+26px)]'),
|
|
1256
|
+
crossOffset: -13,
|
|
1257
|
+
children: /*#__PURE__*/ jsx(ListBox, {
|
|
1258
|
+
className: dropdown.listbox,
|
|
1259
|
+
children: children
|
|
1260
|
+
})
|
|
1261
|
+
})
|
|
1262
|
+
]
|
|
1339
1263
|
});
|
|
1340
|
-
}
|
|
1264
|
+
}
|
|
1341
1265
|
|
|
1342
1266
|
/**
|
|
1343
1267
|
* A React component that wraps https://react-spectrum.adobe.com/react-aria/useDateFormatter.html
|
|
@@ -1354,85 +1278,6 @@ const cardLinkVariants = cva({
|
|
|
1354
1278
|
return render ? render(formatted) : formatted;
|
|
1355
1279
|
};
|
|
1356
1280
|
|
|
1357
|
-
const VideoLoop = ({ src, format, alt, className })=>{
|
|
1358
|
-
// Control the video playback state, so that the user can pause and play the video at will, also control the video autoplay
|
|
1359
|
-
const [shouldPlay, setShouldPlay] = useState(false);
|
|
1360
|
-
// 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)
|
|
1361
|
-
const [isPlaying, setIsPlaying] = useState(false);
|
|
1362
|
-
// We need to check if the user prefers reduced motion, so that we can prevent the video from autoplaying if so
|
|
1363
|
-
const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(null);
|
|
1364
|
-
const videoRef = useRef(null);
|
|
1365
|
-
useEffect(()=>{
|
|
1366
|
-
const { matches: userPrefersReducedMotion } = matchMedia('(prefers-reduced-motion: reduce)');
|
|
1367
|
-
setUserPrefersReducedMotion(userPrefersReducedMotion);
|
|
1368
|
-
// Autoplay the video if the user does not prefer reduced motion
|
|
1369
|
-
setShouldPlay(!userPrefersReducedMotion);
|
|
1370
|
-
}, []);
|
|
1371
|
-
// Follow google's autoplay policy: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
|
|
1372
|
-
// "Don't assume a video will play, and don't show a pause button when the video is not actually playing."
|
|
1373
|
-
// "You should always look at the Promise returned by the play function to see if it was rejected:"
|
|
1374
|
-
// 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
|
|
1375
|
-
useEffect(()=>{
|
|
1376
|
-
if (!videoRef.current) return;
|
|
1377
|
-
if (shouldPlay) {
|
|
1378
|
-
videoRef.current.play().then(()=>setIsPlaying(true)).catch(()=>setIsPlaying(false));
|
|
1379
|
-
} else {
|
|
1380
|
-
videoRef.current.pause();
|
|
1381
|
-
setIsPlaying(false);
|
|
1382
|
-
}
|
|
1383
|
-
}, [
|
|
1384
|
-
shouldPlay
|
|
1385
|
-
]);
|
|
1386
|
-
return /*#__PURE__*/ jsxs("div", {
|
|
1387
|
-
className: cx(className, 'relative', userPrefersReducedMotion === null && 'opacity-0'),
|
|
1388
|
-
children: [
|
|
1389
|
-
/*#__PURE__*/ jsx("video", {
|
|
1390
|
-
"aria-hidden": true,
|
|
1391
|
-
ref: videoRef,
|
|
1392
|
-
// cursor-pointer is not working on the button below, so we add it here for the same effect
|
|
1393
|
-
className: "h-full max-h-[inherit] w-full cursor-pointer rounded-[inherit] object-cover",
|
|
1394
|
-
playsInline: true,
|
|
1395
|
-
loop: userPrefersReducedMotion === false,
|
|
1396
|
-
autoPlay: userPrefersReducedMotion === false,
|
|
1397
|
-
muted: true,
|
|
1398
|
-
onEnded: (event)=>{
|
|
1399
|
-
if (userPrefersReducedMotion) {
|
|
1400
|
-
// Reset the video to the beginning if the user prefers reduced motion, since the video will not loop
|
|
1401
|
-
event.currentTarget.currentTime = 0;
|
|
1402
|
-
setShouldPlay(false);
|
|
1403
|
-
setIsPlaying(false);
|
|
1404
|
-
}
|
|
1405
|
-
},
|
|
1406
|
-
children: /*#__PURE__*/ jsx("source", {
|
|
1407
|
-
src: src,
|
|
1408
|
-
type: `video/${format}`
|
|
1409
|
-
})
|
|
1410
|
-
}),
|
|
1411
|
-
userPrefersReducedMotion !== null && /*#__PURE__*/ jsx("button", {
|
|
1412
|
-
"data-slot": "video-loop-button",
|
|
1413
|
-
"aria-hidden": true,
|
|
1414
|
-
type: "button",
|
|
1415
|
-
onClick: ()=>setShouldPlay((prevState)=>!prevState),
|
|
1416
|
-
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', 'rounded-[inherit]', // Setting the opacity to 0 before applying the transition below will ensure the button only fades in after the video has started playing
|
|
1417
|
-
shouldPlay && 'opacity-0', isPlaying && [
|
|
1418
|
-
'transition-opacity duration-200',
|
|
1419
|
-
// Only show the pause button when the video is hovered or focused
|
|
1420
|
-
'focus-visible:opacity-100',
|
|
1421
|
-
'hover:opacity-100'
|
|
1422
|
-
]),
|
|
1423
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
1424
|
-
className: "grid h-12 w-12 place-items-center rounded-full bg-white outline-hidden",
|
|
1425
|
-
children: isPlaying ? /*#__PURE__*/ jsx(PlayerPause, {}) : /*#__PURE__*/ jsx(PlayerPlay, {})
|
|
1426
|
-
})
|
|
1427
|
-
}),
|
|
1428
|
-
alt && /*#__PURE__*/ jsx("p", {
|
|
1429
|
-
className: "sr-only",
|
|
1430
|
-
children: alt
|
|
1431
|
-
})
|
|
1432
|
-
]
|
|
1433
|
-
});
|
|
1434
|
-
};
|
|
1435
|
-
|
|
1436
1281
|
const disclosureButtonVariants = cva({
|
|
1437
1282
|
base: [
|
|
1438
1283
|
'inline-flex cursor-pointer items-center justify-between rounded-lg focus-visible:outline-current focus-visible:outline-focus',
|
|
@@ -1831,198 +1676,42 @@ const FileUpload = ({ children, files: _files, onChange, validate, isInvalid: _i
|
|
|
1831
1676
|
onClick: ()=>{
|
|
1832
1677
|
// For controlled component
|
|
1833
1678
|
onChange?.((prevFiles)=>prevFiles.filter((_, index)=>index !== fileIndex));
|
|
1834
|
-
// For internal file state
|
|
1835
|
-
setFiles((prevFiles)=>prevFiles.filter((_, index)=>index !== fileIndex));
|
|
1836
|
-
// Make sure screen readers doesn't loose track of focus
|
|
1837
|
-
// (without this, the focus will be set to the top of the page for screen readers)
|
|
1838
|
-
buttonRef.current?.focus();
|
|
1839
|
-
},
|
|
1840
|
-
"aria-label": translations.remove[locale],
|
|
1841
|
-
type: "button",
|
|
1842
|
-
children: /*#__PURE__*/ jsx(Trash, {})
|
|
1843
|
-
})
|
|
1844
|
-
]
|
|
1845
|
-
}),
|
|
1846
|
-
hasError && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
1847
|
-
className: "mt-1 block w-full",
|
|
1848
|
-
children: validation
|
|
1849
|
-
})
|
|
1850
|
-
]
|
|
1851
|
-
}, fileName);
|
|
1852
|
-
})
|
|
1853
|
-
}),
|
|
1854
|
-
(controlledOrUncontrolledFiles.length === 0 || !!errorMessage) && /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
1855
|
-
errorMessage: errorMessage
|
|
1856
|
-
})
|
|
1857
|
-
]
|
|
1858
|
-
})
|
|
1859
|
-
});
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
const baseClassName = 'h-20 w-20 shrink-0 rounded-full';
|
|
1863
|
-
const Avatar = ({ src, alt = '', className, onError, loading = 'lazy', ...rest })=>{
|
|
1864
|
-
const [hasError, setHasError] = useState(false);
|
|
1865
|
-
const hasValidImage = !hasError && src;
|
|
1866
|
-
return hasValidImage ? /*#__PURE__*/ jsx("img", {
|
|
1867
|
-
...rest,
|
|
1868
|
-
src: src,
|
|
1869
|
-
alt: alt,
|
|
1870
|
-
loading: loading,
|
|
1871
|
-
className: cx(className, baseClassName, 'object-cover'),
|
|
1872
|
-
onError: (event)=>{
|
|
1873
|
-
onError?.(event);
|
|
1874
|
-
setHasError(true);
|
|
1875
|
-
}
|
|
1876
|
-
}) : /*#__PURE__*/ jsx("div", {
|
|
1877
|
-
className: cx(className, baseClassName, 'grid place-items-center bg-gray-light text-gray-dark'),
|
|
1878
|
-
children: /*#__PURE__*/ jsx(User, {
|
|
1879
|
-
className: "scale-[2.25]"
|
|
1880
|
-
})
|
|
1881
|
-
});
|
|
1882
|
-
};
|
|
1883
|
-
|
|
1884
|
-
const DialogTrigger = (props)=>/*#__PURE__*/ jsx(DialogTrigger$1, {
|
|
1885
|
-
...props
|
|
1886
|
-
});
|
|
1887
|
-
const ModalOverlay = (props)=>/*#__PURE__*/ jsx(ModalOverlay$1, {
|
|
1888
|
-
...props,
|
|
1889
|
-
isDismissable: true,
|
|
1890
|
-
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
|
|
1891
|
-
'motion-reduce:animate-none')
|
|
1892
|
-
});
|
|
1893
|
-
const Modal = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ModalOverlay, {
|
|
1894
|
-
children: /*#__PURE__*/ jsx(Modal$1, {
|
|
1895
|
-
...restProps,
|
|
1896
|
-
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
|
|
1897
|
-
'motion-reduce:animate-none')
|
|
1898
|
-
})
|
|
1899
|
-
});
|
|
1900
|
-
const Dialog = ({ className, children, ...restProps })=>{
|
|
1901
|
-
const locale = _useLocale();
|
|
1902
|
-
return /*#__PURE__*/ jsx(Dialog$1, {
|
|
1903
|
-
...restProps,
|
|
1904
|
-
className: cx('relative grid gap-y-5 outline-none', // Footer
|
|
1905
|
-
'[&_[data-slot="footer"]]:flex [&_[data-slot="footer"]]:gap-x-2'),
|
|
1906
|
-
children: ({ close })=>/*#__PURE__*/ jsx(Fragment, {
|
|
1907
|
-
children: /*#__PURE__*/ jsx(Provider, {
|
|
1908
|
-
values: [
|
|
1909
|
-
[
|
|
1910
|
-
HeadingContext,
|
|
1911
|
-
{
|
|
1912
|
-
slots: {
|
|
1913
|
-
[DEFAULT_SLOT]: {},
|
|
1914
|
-
title: {
|
|
1915
|
-
className: 'heading-s',
|
|
1916
|
-
_outerWrapper: (children)=>/*#__PURE__*/ jsxs("div", {
|
|
1917
|
-
className: "flex items-center justify-between gap-x-2",
|
|
1918
|
-
children: [
|
|
1919
|
-
children,
|
|
1920
|
-
/*#__PURE__*/ jsx(Button, {
|
|
1921
|
-
slot: "close" // RAC Dialog suppors one close button out of the box, so we utilize that here. For other close buttons we use ButtonContext
|
|
1922
|
-
,
|
|
1923
|
-
variant: "tertiary",
|
|
1924
|
-
className: "!px-2.5 data-focus-visible:outline-focus-inset",
|
|
1925
|
-
"aria-label": translations$1.close[locale],
|
|
1926
|
-
children: /*#__PURE__*/ jsx(Close, {})
|
|
1927
|
-
})
|
|
1928
|
-
]
|
|
1929
|
-
})
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
],
|
|
1934
|
-
[
|
|
1935
|
-
ButtonContext$1,
|
|
1936
|
-
{
|
|
1937
|
-
// This is necessary to support multiple close buttons
|
|
1938
|
-
slots: {
|
|
1939
|
-
// We need to define default slot in order to also support non-slotted buttons (i.e. buttons without slot prop)
|
|
1940
|
-
[DEFAULT_SLOT]: {
|
|
1941
|
-
className: 'w-fit'
|
|
1942
|
-
},
|
|
1943
|
-
close: {
|
|
1944
|
-
onPress: close,
|
|
1945
|
-
className: 'w-fit'
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
]
|
|
1950
|
-
],
|
|
1951
|
-
children: children
|
|
1679
|
+
// For internal file state
|
|
1680
|
+
setFiles((prevFiles)=>prevFiles.filter((_, index)=>index !== fileIndex));
|
|
1681
|
+
// Make sure screen readers doesn't loose track of focus
|
|
1682
|
+
// (without this, the focus will be set to the top of the page for screen readers)
|
|
1683
|
+
buttonRef.current?.focus();
|
|
1684
|
+
},
|
|
1685
|
+
"aria-label": translations.remove[locale],
|
|
1686
|
+
type: "button",
|
|
1687
|
+
children: /*#__PURE__*/ jsx(Trash, {})
|
|
1688
|
+
})
|
|
1689
|
+
]
|
|
1690
|
+
}),
|
|
1691
|
+
hasError && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
1692
|
+
className: "mt-1 block w-full",
|
|
1693
|
+
children: validation
|
|
1694
|
+
})
|
|
1695
|
+
]
|
|
1696
|
+
}, fileName);
|
|
1697
|
+
})
|
|
1698
|
+
}),
|
|
1699
|
+
(controlledOrUncontrolledFiles.length === 0 || !!errorMessage) && /*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
1700
|
+
errorMessage: errorMessage
|
|
1952
1701
|
})
|
|
1953
|
-
|
|
1702
|
+
]
|
|
1703
|
+
})
|
|
1954
1704
|
});
|
|
1955
1705
|
};
|
|
1956
1706
|
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
' data-hovered:bg-sky',
|
|
1966
|
-
// Selected
|
|
1967
|
-
// Allows removing
|
|
1968
|
-
'data-allows-removing:border-transparent',
|
|
1969
|
-
'data-allows-removing:bg-blue',
|
|
1970
|
-
'data-allows-removing:data-hovered:bg-blue-dark',
|
|
1971
|
-
'data-allows-removing:text-white',
|
|
1972
|
-
// Selected
|
|
1973
|
-
'aria-selected:border-transparent',
|
|
1974
|
-
'aria-selected:bg-blue',
|
|
1975
|
-
'aria-selected:data-hovered:bg-blue-dark',
|
|
1976
|
-
'aria-selected:text-white',
|
|
1977
|
-
//Icons
|
|
1978
|
-
'[&_svg]:h-4 [&_svg]:w-4'
|
|
1979
|
-
]
|
|
1980
|
-
});
|
|
1981
|
-
/**
|
|
1982
|
-
* A group component for Tag components that enables selection and organization of options.
|
|
1983
|
-
*/ function TagGroup(props) {
|
|
1984
|
-
const { onRemove, selectionMode = 'single', className, children, ...restProps } = props;
|
|
1985
|
-
return /*#__PURE__*/ jsx(TagGroup$1, {
|
|
1986
|
-
...restProps,
|
|
1987
|
-
className: className,
|
|
1988
|
-
selectionMode: onRemove ? 'none' : selectionMode,
|
|
1989
|
-
onRemove: onRemove,
|
|
1990
|
-
children: children
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1993
|
-
/**
|
|
1994
|
-
* A container component for Tag components within a TagGroup.
|
|
1995
|
-
*/ function TagList(props) {
|
|
1996
|
-
const { className, children, ...restProps } = props;
|
|
1997
|
-
return /*#__PURE__*/ jsx(TagList$1, {
|
|
1998
|
-
...restProps,
|
|
1999
|
-
className: cx('flex flex-wrap gap-2', className),
|
|
2000
|
-
children: children
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* Interactive tag component for selections, filtering, and categorization.
|
|
2005
|
-
*/ function Tag(props) {
|
|
2006
|
-
const { className, children, ...restProps } = props;
|
|
2007
|
-
const textValue = typeof children === 'string' ? children : undefined;
|
|
2008
|
-
return /*#__PURE__*/ jsx(Tag$1, {
|
|
2009
|
-
className: tagVariants({
|
|
2010
|
-
className
|
|
2011
|
-
}),
|
|
2012
|
-
textValue: textValue,
|
|
2013
|
-
...restProps,
|
|
2014
|
-
children: ({ allowsRemoving })=>allowsRemoving ? /*#__PURE__*/ jsxs(Fragment, {
|
|
2015
|
-
children: [
|
|
2016
|
-
children,
|
|
2017
|
-
/*#__PURE__*/ jsx(Button$1, {
|
|
2018
|
-
className: "cursor-pointer outline-none after:absolute after:top-0 after:right-0 after:bottom-0 after:left-0",
|
|
2019
|
-
slot: "remove",
|
|
2020
|
-
children: /*#__PURE__*/ jsx(Close, {
|
|
2021
|
-
className: "ml-1"
|
|
2022
|
-
})
|
|
2023
|
-
})
|
|
2024
|
-
]
|
|
2025
|
-
}) : children
|
|
1707
|
+
function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
|
|
1708
|
+
return /*#__PURE__*/ jsx(I18nProvider, {
|
|
1709
|
+
locale: locale,
|
|
1710
|
+
children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
|
|
1711
|
+
navigate: navigate,
|
|
1712
|
+
useHref: useHref,
|
|
1713
|
+
children: children
|
|
1714
|
+
}) : children
|
|
2026
1715
|
});
|
|
2027
1716
|
}
|
|
2028
1717
|
|
|
@@ -2136,183 +1825,312 @@ const Hero = ({ variant, className, children })=>{
|
|
|
2136
1825
|
});
|
|
2137
1826
|
};
|
|
2138
1827
|
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
// Keep track of the previous index to determine if the user is scrolling forward or backward
|
|
2153
|
-
// This is used to determine which callback to call (onPrev or onNext)
|
|
2154
|
-
const prevIndex = useRef(0);
|
|
2155
|
-
// Handle scrolling when user clicks the arrow icons
|
|
2156
|
-
useUpdateEffect(()=>{
|
|
2157
|
-
if (!ref.current) return;
|
|
2158
|
-
ref.current.children[scrollTargetIndex]?.scrollIntoView({
|
|
2159
|
-
behavior: 'smooth',
|
|
2160
|
-
inline: 'start',
|
|
2161
|
-
block: 'nearest'
|
|
2162
|
-
});
|
|
2163
|
-
if (prevIndex.current !== scrollTargetIndex && onChange) {
|
|
2164
|
-
onChange({
|
|
2165
|
-
index: scrollTargetIndex,
|
|
2166
|
-
id: ref.current.children[scrollTargetIndex]?.id,
|
|
2167
|
-
prevIndex: prevIndex.current,
|
|
2168
|
-
prevId: ref.current.children[prevIndex.current]?.id
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
prevIndex.current = scrollTargetIndex;
|
|
2172
|
-
}, [
|
|
2173
|
-
scrollTargetIndex
|
|
2174
|
-
]);
|
|
2175
|
-
const onScroll = useDebouncedCallback((event)=>{
|
|
2176
|
-
const target = event.target;
|
|
2177
|
-
// Calculate the index of the item that is currently in view
|
|
2178
|
-
const newScrollTargetIndex = Array.from(target.children).findIndex((child)=>{
|
|
2179
|
-
const rect = child.getBoundingClientRect();
|
|
2180
|
-
return rect.left >= 0 && rect.right <= window.innerWidth && rect.top >= 0;
|
|
2181
|
-
});
|
|
2182
|
-
if (newScrollTargetIndex !== -1) {
|
|
2183
|
-
setScrollTargetIndex(newScrollTargetIndex);
|
|
2184
|
-
}
|
|
2185
|
-
}, 100);
|
|
1828
|
+
/**
|
|
1829
|
+
* A basic link component that extends react-aria-components Link with consistent styling.
|
|
1830
|
+
* Provides accessible focus styles and maintains design system consistency.
|
|
1831
|
+
*/ const CustomLink = ({ children, className, ...restProps })=>{
|
|
1832
|
+
return /*#__PURE__*/ jsx(Link, {
|
|
1833
|
+
...restProps,
|
|
1834
|
+
className: cx(className, 'inline-flex cursor-pointer items-center gap-1 font-medium hover:no-underline focus-visible:outline-current focus-visible:outline-focus-offset [&>svg]:shrink-0'),
|
|
1835
|
+
children: children
|
|
1836
|
+
});
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
const LinkList = ({ className, children, ...restProps })=>{
|
|
1840
|
+
const numberofLinks = Children.count(children);
|
|
2186
1841
|
return /*#__PURE__*/ jsx("div", {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
1842
|
+
className: cx(className, '@container'),
|
|
1843
|
+
...restProps,
|
|
1844
|
+
children: /*#__PURE__*/ jsx("ul", {
|
|
1845
|
+
className: cx('min-w-fit', // Hide dividers at the top of the list (overflow-y) and prevents arrow icon from overflowing container when animated to the right (overflow-x)
|
|
1846
|
+
'overflow-hidden', // Add a small gap between items that fits the divider lines (this way the divider line don't take up any space in each item)
|
|
1847
|
+
'grid auto-rows-max gap-y-0.25', // Gaps for when the list is displayed in multiple columns
|
|
1848
|
+
'@lg:gap-x-12 @md:gap-x-9 @sm:gap-x-4 @xl:gap-x-16', numberofLinks > 5 && [
|
|
1849
|
+
'@xl:grid-cols-2',
|
|
1850
|
+
(numberofLinks === 9 || numberofLinks > 10) && '@4xl:grid-cols-3'
|
|
1851
|
+
]),
|
|
1852
|
+
children: children
|
|
1853
|
+
})
|
|
1854
|
+
});
|
|
1855
|
+
};
|
|
1856
|
+
const LinkListItem = ({ children, isExternal, className, ...restProps })=>{
|
|
1857
|
+
let Icon = ArrowRight;
|
|
1858
|
+
let iconTransition = 'group-hover:motion-safe:translate-x-1';
|
|
1859
|
+
if (restProps.download) {
|
|
1860
|
+
Icon = Download;
|
|
1861
|
+
iconTransition = 'group-hover:motion-safe:translate-y-1';
|
|
1862
|
+
} else if (isExternal) {
|
|
1863
|
+
iconTransition = 'group-hover:motion-safe:-translate-y-0.5 group-hover:motion-safe:translate-x-0.5';
|
|
1864
|
+
Icon = LinkExternal;
|
|
1865
|
+
}
|
|
1866
|
+
return /*#__PURE__*/ jsx("li", {
|
|
1867
|
+
// Creates divider lines that works in any grid layout and with the focus ring
|
|
1868
|
+
className: "after:-top-0.25 relative p-0.75 after:absolute after:right-0 after:left-0 after:h-0.25 after:w-full after:bg-gray-light",
|
|
1869
|
+
children: /*#__PURE__*/ jsxs(Link, {
|
|
1870
|
+
...restProps,
|
|
1871
|
+
className: cx(className, 'group paragraph flex cursor-pointer justify-between gap-x-2 py-3.5 font-medium no-underline focus-visible:outline-focus'),
|
|
1872
|
+
children: [
|
|
1873
|
+
/*#__PURE__*/ jsx("span", {
|
|
1874
|
+
children: children
|
|
1875
|
+
}),
|
|
1876
|
+
/*#__PURE__*/ jsx(Icon, {
|
|
1877
|
+
className: cx('shrink-0 motion-safe:transition-transform', iconTransition)
|
|
1878
|
+
})
|
|
1879
|
+
]
|
|
1880
|
+
})
|
|
1881
|
+
});
|
|
1882
|
+
};
|
|
1883
|
+
|
|
1884
|
+
const DialogTrigger = (props)=>/*#__PURE__*/ jsx(DialogTrigger$1, {
|
|
1885
|
+
...props
|
|
1886
|
+
});
|
|
1887
|
+
const ModalOverlay = (props)=>/*#__PURE__*/ jsx(ModalOverlay$1, {
|
|
1888
|
+
...props,
|
|
1889
|
+
isDismissable: true,
|
|
1890
|
+
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
|
|
1891
|
+
'motion-reduce:animate-none')
|
|
1892
|
+
});
|
|
1893
|
+
const Modal = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ModalOverlay, {
|
|
1894
|
+
children: /*#__PURE__*/ jsx(Modal$1, {
|
|
1895
|
+
...restProps,
|
|
1896
|
+
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
|
|
1897
|
+
'motion-reduce:animate-none')
|
|
1898
|
+
})
|
|
1899
|
+
});
|
|
1900
|
+
const Dialog = ({ className, children, ...restProps })=>{
|
|
1901
|
+
const locale = _useLocale();
|
|
1902
|
+
return /*#__PURE__*/ jsx(Dialog$1, {
|
|
1903
|
+
...restProps,
|
|
1904
|
+
className: cx('relative grid gap-y-5 outline-none', // Footer
|
|
1905
|
+
'[&_[data-slot="footer"]]:flex [&_[data-slot="footer"]]:gap-x-2'),
|
|
1906
|
+
children: ({ close })=>/*#__PURE__*/ jsx(Fragment, {
|
|
1907
|
+
children: /*#__PURE__*/ jsx(Provider, {
|
|
1908
|
+
values: [
|
|
1909
|
+
[
|
|
1910
|
+
HeadingContext,
|
|
1911
|
+
{
|
|
1912
|
+
slots: {
|
|
1913
|
+
[DEFAULT_SLOT]: {},
|
|
1914
|
+
title: {
|
|
1915
|
+
className: 'heading-s',
|
|
1916
|
+
_outerWrapper: (children)=>/*#__PURE__*/ jsxs("div", {
|
|
1917
|
+
className: "flex items-center justify-between gap-x-2",
|
|
1918
|
+
children: [
|
|
1919
|
+
children,
|
|
1920
|
+
/*#__PURE__*/ jsx(Button, {
|
|
1921
|
+
slot: "close" // RAC Dialog suppors one close button out of the box, so we utilize that here. For other close buttons we use ButtonContext
|
|
1922
|
+
,
|
|
1923
|
+
variant: "tertiary",
|
|
1924
|
+
className: "!px-2.5 data-focus-visible:outline-focus-inset",
|
|
1925
|
+
"aria-label": translations$1.close[locale],
|
|
1926
|
+
children: /*#__PURE__*/ jsx(Close, {})
|
|
1927
|
+
})
|
|
1928
|
+
]
|
|
1929
|
+
})
|
|
2206
1930
|
}
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
],
|
|
1934
|
+
[
|
|
1935
|
+
ButtonContext$1,
|
|
1936
|
+
{
|
|
1937
|
+
// This is necessary to support multiple close buttons
|
|
1938
|
+
slots: {
|
|
1939
|
+
// We need to define default slot in order to also support non-slotted buttons (i.e. buttons without slot prop)
|
|
1940
|
+
[DEFAULT_SLOT]: {
|
|
1941
|
+
className: 'w-fit'
|
|
1942
|
+
},
|
|
1943
|
+
close: {
|
|
1944
|
+
onPress: close,
|
|
1945
|
+
className: 'w-fit'
|
|
2217
1946
|
}
|
|
2218
|
-
}
|
|
2219
|
-
isDisabled: hasReachedScrollEnd
|
|
1947
|
+
}
|
|
2220
1948
|
}
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
]
|
|
2224
|
-
],
|
|
2225
|
-
children: /*#__PURE__*/ jsxs("div", {
|
|
2226
|
-
className: cx(className, 'relative rounded-3xl', // If any <CarouselItems/> (the scroll-snap container) or <VideoLoop/> component is focused, apply custom focus styles around the carousel, this makes ensures that the focus outline is visible around the carousel in all cases
|
|
2227
|
-
'[&:has([data-slot="carousel-items"]:focus-visible,[data-slot="video-loop-button"]:focus-visible)]:outline-focus', '[&:has([data-slot="carousel-items"]:focus-visible,[data-slot="video-loop-button"]:focus-visible)]:outline-focus-offset', // Unset the default focus outline for potential video loop buttons, as it interferes with the custom focus styles for the carousel
|
|
2228
|
-
'**:data-[slot="video-loop-button"]:focus-visible:outline-none'),
|
|
2229
|
-
children: [
|
|
2230
|
-
children,
|
|
2231
|
-
/*#__PURE__*/ jsxs(_CarouselControls, {
|
|
2232
|
-
children: [
|
|
2233
|
-
/*#__PURE__*/ jsx(Button, {
|
|
2234
|
-
isIconOnly: true,
|
|
2235
|
-
slot: "prev",
|
|
2236
|
-
variant: "primary",
|
|
2237
|
-
color: "white",
|
|
2238
|
-
className: cx('group/carousel-previous', hasReachedScrollStart && 'invisible'),
|
|
2239
|
-
children: /*#__PURE__*/ jsx(ChevronLeft, {
|
|
2240
|
-
className: "group-hover/carousel-previous:motion-safe:-translate-x-1 transition-transform"
|
|
2241
|
-
})
|
|
2242
|
-
}),
|
|
2243
|
-
/*#__PURE__*/ jsx(Button, {
|
|
2244
|
-
isIconOnly: true,
|
|
2245
|
-
slot: "next",
|
|
2246
|
-
variant: "primary",
|
|
2247
|
-
color: "white",
|
|
2248
|
-
className: cx('group/carousel-next', hasReachedScrollEnd && 'invisible'),
|
|
2249
|
-
children: /*#__PURE__*/ jsx(ChevronRight, {
|
|
2250
|
-
className: "transition-transform group-hover/carousel-next:motion-safe:translate-x-1"
|
|
2251
|
-
})
|
|
2252
|
-
})
|
|
2253
1949
|
]
|
|
2254
|
-
|
|
2255
|
-
|
|
1950
|
+
],
|
|
1951
|
+
children: children
|
|
1952
|
+
})
|
|
2256
1953
|
})
|
|
2257
|
-
})
|
|
2258
1954
|
});
|
|
2259
1955
|
};
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
1956
|
+
|
|
1957
|
+
// This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
|
|
1958
|
+
const inputVariants$1 = compose(input, cva({
|
|
1959
|
+
base: '',
|
|
1960
|
+
variants: {
|
|
1961
|
+
textAlign: {
|
|
1962
|
+
right: 'text-right',
|
|
1963
|
+
left: ''
|
|
1964
|
+
},
|
|
1965
|
+
autoWidth: {
|
|
1966
|
+
true: 'max-w-fit',
|
|
1967
|
+
false: ''
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
}));
|
|
1971
|
+
function NumberField(props) {
|
|
1972
|
+
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
|
|
1973
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
1974
|
+
// which will override any built in validation
|
|
1975
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
1976
|
+
return /*#__PURE__*/ jsxs(NumberField$1, {
|
|
1977
|
+
...restProps,
|
|
1978
|
+
className: cx(className, formField),
|
|
1979
|
+
isInvalid: isInvalid,
|
|
1980
|
+
children: [
|
|
1981
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
1982
|
+
children: label
|
|
1983
|
+
}),
|
|
1984
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
1985
|
+
children: description
|
|
1986
|
+
}),
|
|
1987
|
+
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
1988
|
+
className: cx(inputGroup, {
|
|
1989
|
+
'w-fit': !!size
|
|
1990
|
+
}),
|
|
1991
|
+
children: [
|
|
1992
|
+
leftAddon,
|
|
1993
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
1994
|
+
/*#__PURE__*/ jsx(Input, {
|
|
1995
|
+
className: inputVariants$1({
|
|
1996
|
+
textAlign,
|
|
1997
|
+
isGrouped: true,
|
|
1998
|
+
autoWidth: !!size
|
|
1999
|
+
}),
|
|
2000
|
+
ref: ref,
|
|
2001
|
+
size: size
|
|
2002
|
+
}),
|
|
2003
|
+
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
2004
|
+
rightAddon
|
|
2005
|
+
]
|
|
2006
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
2007
|
+
className: inputVariants$1({
|
|
2008
|
+
textAlign,
|
|
2009
|
+
autoWidth: !!size
|
|
2010
|
+
}),
|
|
2284
2011
|
ref: ref,
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
onScroll: onScroll,
|
|
2290
|
-
children: children
|
|
2012
|
+
size: size
|
|
2013
|
+
}),
|
|
2014
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
2015
|
+
errorMessage: errorMessage
|
|
2291
2016
|
})
|
|
2017
|
+
]
|
|
2292
2018
|
});
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
const defaultClasses = cx([
|
|
2022
|
+
'-ml-2.5 relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
|
|
2023
|
+
// the radio button itself
|
|
2024
|
+
'before:flex-none before:rounded-full before:border-2 before:border-black',
|
|
2025
|
+
// 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.
|
|
2026
|
+
// For the ::before psuedo element the line height of the label is always 1em.
|
|
2027
|
+
// When we know the height of the label we use the height of the radio to push it down to align with the label's first line
|
|
2028
|
+
// 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?
|
|
2029
|
+
'before:mt-[calc((1em_*_1.75_-_24px)_/_2)] before:h-[24px] before:w-[24px]',
|
|
2030
|
+
// selected
|
|
2031
|
+
'data-selected:before:border-black data-selected:before:bg-blue data-selected:before:shadow-[inset_0_0_0_4px_rgb(255,255,255)]',
|
|
2032
|
+
// hover
|
|
2033
|
+
'data-hovered:data-selected:before:border-blue-dark data-hovered:data-invalid:before:bg-red-light data-hovered:data-selected:before:bg-blue-dark data-hovered:before:border-blue data-hovered:before:bg-sky',
|
|
2034
|
+
// focus
|
|
2035
|
+
'data-focus-visible:before:ring-focus-offset',
|
|
2036
|
+
// 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
|
|
2037
|
+
// so we use an inner outline to artifically pad the border
|
|
2038
|
+
'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]'
|
|
2039
|
+
]);
|
|
2040
|
+
function Radio(props) {
|
|
2041
|
+
const { children, className, description, ...restProps } = props;
|
|
2042
|
+
return /*#__PURE__*/ jsx(Radio$1, {
|
|
2043
|
+
...restProps,
|
|
2044
|
+
className: cx(className, defaultClasses),
|
|
2045
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
2046
|
+
children: [
|
|
2047
|
+
children,
|
|
2048
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
2049
|
+
className: "mt-2 block",
|
|
2050
|
+
children: description
|
|
2051
|
+
})
|
|
2052
|
+
]
|
|
2053
|
+
})
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function RadioGroup(props) {
|
|
2058
|
+
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, value, ...restProps } = props;
|
|
2059
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
2060
|
+
// which will override any built in validation
|
|
2061
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
2062
|
+
return /*#__PURE__*/ jsxs(RadioGroup$1, {
|
|
2063
|
+
...restProps,
|
|
2064
|
+
// Tabindex is set to -1 when the value is an empty string, which makes the radio input not focusable
|
|
2065
|
+
value: value === '' ? undefined : value,
|
|
2066
|
+
className: cx(className, 'flex flex-col gap-2'),
|
|
2067
|
+
isInvalid: isInvalid,
|
|
2068
|
+
isRequired: isRequired,
|
|
2069
|
+
children: [
|
|
2070
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
2071
|
+
children: label
|
|
2072
|
+
}),
|
|
2073
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
2074
|
+
children: description
|
|
2075
|
+
}),
|
|
2076
|
+
children,
|
|
2077
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
2078
|
+
errorMessage: errorMessage
|
|
2079
|
+
})
|
|
2080
|
+
]
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
function Select(props) {
|
|
2085
|
+
const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ref, ...restProps } = props;
|
|
2086
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
2087
|
+
// which will override any built in validation
|
|
2088
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
2089
|
+
return /*#__PURE__*/ jsxs(Select$1, {
|
|
2090
|
+
...restProps,
|
|
2091
|
+
className: cx(className, formField),
|
|
2092
|
+
isInvalid: isInvalid,
|
|
2093
|
+
children: [
|
|
2094
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
2095
|
+
children: label
|
|
2096
|
+
}),
|
|
2097
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
2098
|
+
children: description
|
|
2099
|
+
}),
|
|
2100
|
+
/*#__PURE__*/ jsxs(Button$1, {
|
|
2101
|
+
className: cx(input({
|
|
2102
|
+
focusModifier: 'visible'
|
|
2103
|
+
}), // How to reuse placeholder text?
|
|
2104
|
+
'inline-flex cursor-default items-center gap-2'),
|
|
2105
|
+
// See https://github.com/adobe/react-spectrum/discussions/4792#discussioncomment-6492305
|
|
2106
|
+
ref: ref,
|
|
2107
|
+
children: [
|
|
2108
|
+
/*#__PURE__*/ jsx(SelectValue, {
|
|
2109
|
+
className: "flex-1 truncate text-left data-[placeholder]:text-[#727070]"
|
|
2110
|
+
}),
|
|
2111
|
+
/*#__PURE__*/ jsx(ChevronDown, {
|
|
2112
|
+
className: dropdown.chevronIcon
|
|
2113
|
+
})
|
|
2306
2114
|
]
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2115
|
+
}),
|
|
2116
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
2117
|
+
errorMessage: errorMessage
|
|
2118
|
+
}),
|
|
2119
|
+
/*#__PURE__*/ jsx(Popover, {
|
|
2120
|
+
className: dropdown.popover,
|
|
2121
|
+
children: /*#__PURE__*/ jsx(ListBox, {
|
|
2122
|
+
className: dropdown.listbox,
|
|
2123
|
+
children: children
|
|
2124
|
+
})
|
|
2125
|
+
})
|
|
2126
|
+
]
|
|
2310
2127
|
});
|
|
2311
|
-
}
|
|
2128
|
+
}
|
|
2312
2129
|
|
|
2313
2130
|
function ScrollButton({ direction, onClick, isVisible, hasScrollingOccurred, className, iconClassName }) {
|
|
2314
2131
|
const Icon = direction === 'left' ? ChevronLeft : ChevronRight;
|
|
2315
2132
|
return(// biome-ignore lint/a11y/useKeyWithClickEvents: This button is only for mouse interaction to help users scroll. Keyboard and screen reader users can navigate the content directly without needing these scroll helpers.
|
|
2133
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: This button is only for mouse interaction to help users scroll. Keyboard and screen reader users can navigate the content directly without needing these scroll helpers.
|
|
2316
2134
|
/*#__PURE__*/ jsx("div", {
|
|
2317
2135
|
onClick: onClick,
|
|
2318
2136
|
className: cx(// Base scroll button styling
|
|
@@ -2375,6 +2193,126 @@ function ScrollButton({ direction, onClick, isVisible, hasScrollingOccurred, cla
|
|
|
2375
2193
|
};
|
|
2376
2194
|
}
|
|
2377
2195
|
|
|
2196
|
+
const tableVariants = cva({
|
|
2197
|
+
base: [
|
|
2198
|
+
'relative'
|
|
2199
|
+
],
|
|
2200
|
+
variants: {
|
|
2201
|
+
variant: {
|
|
2202
|
+
default: '',
|
|
2203
|
+
'zebra-striped': ''
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
const tableRowVariants = cva({
|
|
2208
|
+
base: [
|
|
2209
|
+
'data-focus-visible:outline-focus-inset',
|
|
2210
|
+
'group-data-[variant=zebra-striped]:odd:bg-white',
|
|
2211
|
+
'group-data-[variant=zebra-striped]:even:bg-sky-lightest'
|
|
2212
|
+
]
|
|
2213
|
+
});
|
|
2214
|
+
/**
|
|
2215
|
+
* A container component for displaying tabular data with horizontal scrolling support.
|
|
2216
|
+
*/ function Table(props) {
|
|
2217
|
+
const { className, children, variant = 'default', ...restProps } = props;
|
|
2218
|
+
const { scrollContainerRef, canScrollLeft, canScrollRight, hasScrollingOccurred } = useHorizontalScroll();
|
|
2219
|
+
const handleScroll = useCallback((direction)=>{
|
|
2220
|
+
const container = scrollContainerRef.current;
|
|
2221
|
+
if (!container) return;
|
|
2222
|
+
const scrollAmount = container.clientWidth * 0.8;
|
|
2223
|
+
container.scrollBy({
|
|
2224
|
+
left: direction === 'left' ? -scrollAmount : scrollAmount,
|
|
2225
|
+
behavior: 'smooth'
|
|
2226
|
+
});
|
|
2227
|
+
}, [
|
|
2228
|
+
scrollContainerRef
|
|
2229
|
+
]);
|
|
2230
|
+
return /*#__PURE__*/ jsx("div", {
|
|
2231
|
+
className: tableVariants({
|
|
2232
|
+
className,
|
|
2233
|
+
variant
|
|
2234
|
+
}),
|
|
2235
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
2236
|
+
className: "relative overflow-hidden",
|
|
2237
|
+
children: [
|
|
2238
|
+
/*#__PURE__*/ jsx(ScrollButton, {
|
|
2239
|
+
direction: "left",
|
|
2240
|
+
onClick: ()=>handleScroll('left'),
|
|
2241
|
+
isVisible: canScrollLeft,
|
|
2242
|
+
hasScrollingOccurred: hasScrollingOccurred,
|
|
2243
|
+
className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
|
|
2244
|
+
iconClassName: "h-5 w-5"
|
|
2245
|
+
}),
|
|
2246
|
+
/*#__PURE__*/ jsx(ScrollButton, {
|
|
2247
|
+
direction: "right",
|
|
2248
|
+
onClick: ()=>handleScroll('right'),
|
|
2249
|
+
isVisible: canScrollRight,
|
|
2250
|
+
hasScrollingOccurred: hasScrollingOccurred,
|
|
2251
|
+
className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
|
|
2252
|
+
iconClassName: "h-5 w-5"
|
|
2253
|
+
}),
|
|
2254
|
+
/*#__PURE__*/ jsx("div", {
|
|
2255
|
+
ref: scrollContainerRef,
|
|
2256
|
+
className: "scrollbar-hidden overflow-x-auto",
|
|
2257
|
+
style: {
|
|
2258
|
+
WebkitOverflowScrolling: 'touch'
|
|
2259
|
+
},
|
|
2260
|
+
children: /*#__PURE__*/ jsx(Table$1, {
|
|
2261
|
+
...restProps,
|
|
2262
|
+
className: "group w-full min-w-fit",
|
|
2263
|
+
"data-variant": variant,
|
|
2264
|
+
children: children
|
|
2265
|
+
})
|
|
2266
|
+
})
|
|
2267
|
+
]
|
|
2268
|
+
})
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Container for table column headers.
|
|
2273
|
+
*/ function TableHeader({ className, children, ...restProps }) {
|
|
2274
|
+
return /*#__PURE__*/ jsx(TableHeader$1, {
|
|
2275
|
+
...restProps,
|
|
2276
|
+
className: cx(className, 'border-black border-b'),
|
|
2277
|
+
children: children
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
function TableColumn(props) {
|
|
2281
|
+
const { className, children, ...restProps } = props;
|
|
2282
|
+
return /*#__PURE__*/ jsx(Column, {
|
|
2283
|
+
...restProps,
|
|
2284
|
+
className: cx(className, 'px-4 py-3 text-left font-medium text-black text-sm', 'data-focus-visible:outline-focus-inset', 'min-w-fit whitespace-nowrap'),
|
|
2285
|
+
children: children
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Container for table rows.
|
|
2290
|
+
*/ function TableBody({ className, children, ...restProps }) {
|
|
2291
|
+
return /*#__PURE__*/ jsx(TableBody$1, {
|
|
2292
|
+
...restProps,
|
|
2293
|
+
className: className,
|
|
2294
|
+
children: children
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
function TableRow(props) {
|
|
2298
|
+
const { className, children, ...restProps } = props;
|
|
2299
|
+
return /*#__PURE__*/ jsx(Row, {
|
|
2300
|
+
...restProps,
|
|
2301
|
+
className: tableRowVariants({
|
|
2302
|
+
className
|
|
2303
|
+
}),
|
|
2304
|
+
children: children
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
function TableCell(props) {
|
|
2308
|
+
const { className, children, ...restProps } = props;
|
|
2309
|
+
return /*#__PURE__*/ jsx(Cell, {
|
|
2310
|
+
...restProps,
|
|
2311
|
+
className: cx(className, 'px-4 py-3 text-black text-sm leading-relaxed', 'min-w-fit whitespace-nowrap', 'align-top', 'data-focus-visible:outline-focus-inset'),
|
|
2312
|
+
children: children
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2378
2316
|
const tabsVariants = cva({
|
|
2379
2317
|
base: [
|
|
2380
2318
|
'grid gap-4'
|
|
@@ -2529,179 +2467,243 @@ const tabsVariants = cva({
|
|
|
2529
2467
|
});
|
|
2530
2468
|
}
|
|
2531
2469
|
|
|
2532
|
-
const
|
|
2533
|
-
base: [
|
|
2534
|
-
'relative'
|
|
2535
|
-
],
|
|
2536
|
-
variants: {
|
|
2537
|
-
variant: {
|
|
2538
|
-
default: '',
|
|
2539
|
-
'zebra-striped': ''
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
});
|
|
2543
|
-
const tableRowVariants = cva({
|
|
2470
|
+
const tagVariants = cva({
|
|
2544
2471
|
base: [
|
|
2545
|
-
'
|
|
2546
|
-
|
|
2547
|
-
'
|
|
2472
|
+
'relative flex cursor-pointer items-center gap-1 rounded-lg px-2 py-1 font-medium text-sm transition-colors duration-200',
|
|
2473
|
+
// Resting
|
|
2474
|
+
'border-2 border-black bg-white text-black',
|
|
2475
|
+
//Focus
|
|
2476
|
+
'focus-visible:outline-focus-offset',
|
|
2477
|
+
// Hover
|
|
2478
|
+
'data-hovered:bg-sky',
|
|
2479
|
+
// Selected
|
|
2480
|
+
// Allows removing
|
|
2481
|
+
'data-allows-removing:border-transparent',
|
|
2482
|
+
'data-allows-removing:bg-blue',
|
|
2483
|
+
'data-allows-removing:data-hovered:bg-blue-dark',
|
|
2484
|
+
'data-allows-removing:text-white',
|
|
2485
|
+
// Selected
|
|
2486
|
+
'aria-selected:border-transparent',
|
|
2487
|
+
'aria-selected:bg-blue',
|
|
2488
|
+
'aria-selected:data-hovered:bg-blue-dark',
|
|
2489
|
+
'aria-selected:text-white',
|
|
2490
|
+
//Icons
|
|
2491
|
+
'[&_svg]:h-4 [&_svg]:w-4'
|
|
2548
2492
|
]
|
|
2549
2493
|
});
|
|
2550
2494
|
/**
|
|
2551
|
-
* A
|
|
2552
|
-
*/ function
|
|
2553
|
-
const {
|
|
2554
|
-
|
|
2555
|
-
const handleScroll = useCallback((direction)=>{
|
|
2556
|
-
const container = scrollContainerRef.current;
|
|
2557
|
-
if (!container) return;
|
|
2558
|
-
const scrollAmount = container.clientWidth * 0.8;
|
|
2559
|
-
container.scrollBy({
|
|
2560
|
-
left: direction === 'left' ? -scrollAmount : scrollAmount,
|
|
2561
|
-
behavior: 'smooth'
|
|
2562
|
-
});
|
|
2563
|
-
}, [
|
|
2564
|
-
scrollContainerRef
|
|
2565
|
-
]);
|
|
2566
|
-
return /*#__PURE__*/ jsx("div", {
|
|
2567
|
-
className: tableVariants({
|
|
2568
|
-
className,
|
|
2569
|
-
variant
|
|
2570
|
-
}),
|
|
2571
|
-
children: /*#__PURE__*/ jsxs("div", {
|
|
2572
|
-
className: "relative overflow-hidden",
|
|
2573
|
-
children: [
|
|
2574
|
-
/*#__PURE__*/ jsx(ScrollButton, {
|
|
2575
|
-
direction: "left",
|
|
2576
|
-
onClick: ()=>handleScroll('left'),
|
|
2577
|
-
isVisible: canScrollLeft,
|
|
2578
|
-
hasScrollingOccurred: hasScrollingOccurred,
|
|
2579
|
-
className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
|
|
2580
|
-
iconClassName: "h-5 w-5"
|
|
2581
|
-
}),
|
|
2582
|
-
/*#__PURE__*/ jsx(ScrollButton, {
|
|
2583
|
-
direction: "right",
|
|
2584
|
-
onClick: ()=>handleScroll('right'),
|
|
2585
|
-
isVisible: canScrollRight,
|
|
2586
|
-
hasScrollingOccurred: hasScrollingOccurred,
|
|
2587
|
-
className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
|
|
2588
|
-
iconClassName: "h-5 w-5"
|
|
2589
|
-
}),
|
|
2590
|
-
/*#__PURE__*/ jsx("div", {
|
|
2591
|
-
ref: scrollContainerRef,
|
|
2592
|
-
className: "scrollbar-hidden overflow-x-auto",
|
|
2593
|
-
style: {
|
|
2594
|
-
WebkitOverflowScrolling: 'touch'
|
|
2595
|
-
},
|
|
2596
|
-
children: /*#__PURE__*/ jsx(Table$1, {
|
|
2597
|
-
...restProps,
|
|
2598
|
-
className: "group w-full min-w-fit",
|
|
2599
|
-
"data-variant": variant,
|
|
2600
|
-
children: children
|
|
2601
|
-
})
|
|
2602
|
-
})
|
|
2603
|
-
]
|
|
2604
|
-
})
|
|
2605
|
-
});
|
|
2606
|
-
}
|
|
2607
|
-
/**
|
|
2608
|
-
* Container for table column headers.
|
|
2609
|
-
*/ function TableHeader({ className, children, ...restProps }) {
|
|
2610
|
-
return /*#__PURE__*/ jsx(TableHeader$1, {
|
|
2495
|
+
* A group component for Tag components that enables selection and organization of options.
|
|
2496
|
+
*/ function TagGroup(props) {
|
|
2497
|
+
const { onRemove, selectionMode = 'single', className, children, ...restProps } = props;
|
|
2498
|
+
return /*#__PURE__*/ jsx(TagGroup$1, {
|
|
2611
2499
|
...restProps,
|
|
2612
|
-
className:
|
|
2500
|
+
className: className,
|
|
2501
|
+
selectionMode: onRemove ? 'none' : selectionMode,
|
|
2502
|
+
onRemove: onRemove,
|
|
2613
2503
|
children: children
|
|
2614
2504
|
});
|
|
2615
2505
|
}
|
|
2616
|
-
|
|
2506
|
+
/**
|
|
2507
|
+
* A container component for Tag components within a TagGroup.
|
|
2508
|
+
*/ function TagList(props) {
|
|
2617
2509
|
const { className, children, ...restProps } = props;
|
|
2618
|
-
return /*#__PURE__*/ jsx(
|
|
2510
|
+
return /*#__PURE__*/ jsx(TagList$1, {
|
|
2619
2511
|
...restProps,
|
|
2620
|
-
className: cx(
|
|
2512
|
+
className: cx('flex flex-wrap gap-2', className),
|
|
2621
2513
|
children: children
|
|
2622
2514
|
});
|
|
2623
2515
|
}
|
|
2624
2516
|
/**
|
|
2625
|
-
*
|
|
2626
|
-
*/ function
|
|
2627
|
-
return /*#__PURE__*/ jsx(TableBody$1, {
|
|
2628
|
-
...restProps,
|
|
2629
|
-
className: className,
|
|
2630
|
-
children: children
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
function TableRow(props) {
|
|
2517
|
+
* Interactive tag component for selections, filtering, and categorization.
|
|
2518
|
+
*/ function Tag(props) {
|
|
2634
2519
|
const { className, children, ...restProps } = props;
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
className:
|
|
2520
|
+
const textValue = typeof children === 'string' ? children : undefined;
|
|
2521
|
+
return /*#__PURE__*/ jsx(Tag$1, {
|
|
2522
|
+
className: tagVariants({
|
|
2638
2523
|
className
|
|
2639
2524
|
}),
|
|
2640
|
-
|
|
2641
|
-
});
|
|
2642
|
-
}
|
|
2643
|
-
function TableCell(props) {
|
|
2644
|
-
const { className, children, ...restProps } = props;
|
|
2645
|
-
return /*#__PURE__*/ jsx(Cell, {
|
|
2525
|
+
textValue: textValue,
|
|
2646
2526
|
...restProps,
|
|
2647
|
-
|
|
2648
|
-
|
|
2527
|
+
children: ({ allowsRemoving })=>allowsRemoving ? /*#__PURE__*/ jsxs(Fragment, {
|
|
2528
|
+
children: [
|
|
2529
|
+
children,
|
|
2530
|
+
/*#__PURE__*/ jsx(Button$1, {
|
|
2531
|
+
className: "cursor-pointer outline-none after:absolute after:top-0 after:right-0 after:bottom-0 after:left-0",
|
|
2532
|
+
slot: "remove",
|
|
2533
|
+
children: /*#__PURE__*/ jsx(Close, {
|
|
2534
|
+
className: "ml-1"
|
|
2535
|
+
})
|
|
2536
|
+
})
|
|
2537
|
+
]
|
|
2538
|
+
}) : children
|
|
2649
2539
|
});
|
|
2650
2540
|
}
|
|
2651
2541
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
*/
|
|
2656
|
-
return /*#__PURE__*/ jsx(Link, {
|
|
2542
|
+
function TextArea(props) {
|
|
2543
|
+
const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ref, ...restProps } = props;
|
|
2544
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
2545
|
+
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
2657
2546
|
...restProps,
|
|
2658
|
-
className: cx(className,
|
|
2659
|
-
|
|
2547
|
+
className: cx(className, formField),
|
|
2548
|
+
isInvalid: isInvalid,
|
|
2549
|
+
children: [
|
|
2550
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
2551
|
+
children: label
|
|
2552
|
+
}),
|
|
2553
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
2554
|
+
children: description
|
|
2555
|
+
}),
|
|
2556
|
+
/*#__PURE__*/ jsx(TextArea$1, {
|
|
2557
|
+
className: input(),
|
|
2558
|
+
rows: rows,
|
|
2559
|
+
ref: ref
|
|
2560
|
+
}),
|
|
2561
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
2562
|
+
errorMessage: errorMessage
|
|
2563
|
+
})
|
|
2564
|
+
]
|
|
2660
2565
|
});
|
|
2661
|
-
}
|
|
2566
|
+
}
|
|
2662
2567
|
|
|
2663
|
-
const
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
'
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
(numberofLinks === 9 || numberofLinks > 10) && '@4xl:grid-cols-3'
|
|
2675
|
-
]),
|
|
2676
|
-
children: children
|
|
2677
|
-
})
|
|
2678
|
-
});
|
|
2679
|
-
};
|
|
2680
|
-
const LinkListItem = ({ children, isExternal, className, ...restProps })=>{
|
|
2681
|
-
let Icon = ArrowRight;
|
|
2682
|
-
let iconTransition = 'group-hover:motion-safe:translate-x-1';
|
|
2683
|
-
if (restProps.download) {
|
|
2684
|
-
Icon = Download;
|
|
2685
|
-
iconTransition = 'group-hover:motion-safe:translate-y-1';
|
|
2686
|
-
} else if (isExternal) {
|
|
2687
|
-
iconTransition = 'group-hover:motion-safe:-translate-y-0.5 group-hover:motion-safe:translate-x-0.5';
|
|
2688
|
-
Icon = LinkExternal;
|
|
2568
|
+
const inputVariants = compose(input, cva({
|
|
2569
|
+
base: '',
|
|
2570
|
+
variants: {
|
|
2571
|
+
textAlign: {
|
|
2572
|
+
right: 'text-right',
|
|
2573
|
+
left: ''
|
|
2574
|
+
},
|
|
2575
|
+
autoWidth: {
|
|
2576
|
+
true: 'max-w-fit',
|
|
2577
|
+
false: ''
|
|
2578
|
+
}
|
|
2689
2579
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2580
|
+
}));
|
|
2581
|
+
function TextField(props) {
|
|
2582
|
+
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ref, ...restProps } = props;
|
|
2583
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
2584
|
+
// which will override any built in validation
|
|
2585
|
+
const isInvalid = !!errorMessage || _isInvalid;
|
|
2586
|
+
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
2587
|
+
...restProps,
|
|
2588
|
+
className: cx(className, formField),
|
|
2589
|
+
isInvalid: isInvalid,
|
|
2590
|
+
children: [
|
|
2591
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
2592
|
+
children: label
|
|
2593
|
+
}),
|
|
2594
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
2595
|
+
children: description
|
|
2596
|
+
}),
|
|
2597
|
+
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
2598
|
+
className: cx(inputGroup, {
|
|
2599
|
+
'w-fit': !!size
|
|
2699
2600
|
}),
|
|
2700
|
-
|
|
2701
|
-
|
|
2601
|
+
children: [
|
|
2602
|
+
leftAddon,
|
|
2603
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
2604
|
+
/*#__PURE__*/ jsx(Input, {
|
|
2605
|
+
className: inputVariants({
|
|
2606
|
+
textAlign,
|
|
2607
|
+
isGrouped: true,
|
|
2608
|
+
autoWidth: !!size
|
|
2609
|
+
}),
|
|
2610
|
+
ref: ref,
|
|
2611
|
+
size: size
|
|
2612
|
+
}),
|
|
2613
|
+
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
2614
|
+
rightAddon
|
|
2615
|
+
]
|
|
2616
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
2617
|
+
className: inputVariants({
|
|
2618
|
+
textAlign,
|
|
2619
|
+
autoWidth: !!size
|
|
2620
|
+
}),
|
|
2621
|
+
ref: ref,
|
|
2622
|
+
size: size
|
|
2623
|
+
}),
|
|
2624
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
2625
|
+
errorMessage: errorMessage
|
|
2626
|
+
})
|
|
2627
|
+
]
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
const VideoLoop = ({ src, format, alt, className })=>{
|
|
2632
|
+
// Control the video playback state, so that the user can pause and play the video at will, also control the video autoplay
|
|
2633
|
+
const [shouldPlay, setShouldPlay] = useState(false);
|
|
2634
|
+
// 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)
|
|
2635
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
2636
|
+
// We need to check if the user prefers reduced motion, so that we can prevent the video from autoplaying if so
|
|
2637
|
+
const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(null);
|
|
2638
|
+
const videoRef = useRef(null);
|
|
2639
|
+
useEffect(()=>{
|
|
2640
|
+
const { matches: userPrefersReducedMotion } = matchMedia('(prefers-reduced-motion: reduce)');
|
|
2641
|
+
setUserPrefersReducedMotion(userPrefersReducedMotion);
|
|
2642
|
+
// Autoplay the video if the user does not prefer reduced motion
|
|
2643
|
+
setShouldPlay(!userPrefersReducedMotion);
|
|
2644
|
+
}, []);
|
|
2645
|
+
// Follow google's autoplay policy: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
|
|
2646
|
+
// "Don't assume a video will play, and don't show a pause button when the video is not actually playing."
|
|
2647
|
+
// "You should always look at the Promise returned by the play function to see if it was rejected:"
|
|
2648
|
+
// 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
|
|
2649
|
+
useEffect(()=>{
|
|
2650
|
+
if (!videoRef.current) return;
|
|
2651
|
+
if (shouldPlay) {
|
|
2652
|
+
videoRef.current.play().then(()=>setIsPlaying(true)).catch(()=>setIsPlaying(false));
|
|
2653
|
+
} else {
|
|
2654
|
+
videoRef.current.pause();
|
|
2655
|
+
setIsPlaying(false);
|
|
2656
|
+
}
|
|
2657
|
+
}, [
|
|
2658
|
+
shouldPlay
|
|
2659
|
+
]);
|
|
2660
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
2661
|
+
className: cx(className, 'relative', userPrefersReducedMotion === null && 'opacity-0'),
|
|
2662
|
+
children: [
|
|
2663
|
+
/*#__PURE__*/ jsx("video", {
|
|
2664
|
+
"aria-hidden": true,
|
|
2665
|
+
ref: videoRef,
|
|
2666
|
+
// cursor-pointer is not working on the button below, so we add it here for the same effect
|
|
2667
|
+
className: "h-full max-h-[inherit] w-full cursor-pointer rounded-[inherit] object-cover",
|
|
2668
|
+
playsInline: true,
|
|
2669
|
+
loop: userPrefersReducedMotion === false,
|
|
2670
|
+
autoPlay: userPrefersReducedMotion === false,
|
|
2671
|
+
muted: true,
|
|
2672
|
+
onEnded: (event)=>{
|
|
2673
|
+
if (userPrefersReducedMotion) {
|
|
2674
|
+
// Reset the video to the beginning if the user prefers reduced motion, since the video will not loop
|
|
2675
|
+
event.currentTarget.currentTime = 0;
|
|
2676
|
+
setShouldPlay(false);
|
|
2677
|
+
setIsPlaying(false);
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
children: /*#__PURE__*/ jsx("source", {
|
|
2681
|
+
src: src,
|
|
2682
|
+
type: `video/${format}`
|
|
2702
2683
|
})
|
|
2703
|
-
|
|
2704
|
-
|
|
2684
|
+
}),
|
|
2685
|
+
userPrefersReducedMotion !== null && /*#__PURE__*/ jsx("button", {
|
|
2686
|
+
"data-slot": "video-loop-button",
|
|
2687
|
+
"aria-hidden": true,
|
|
2688
|
+
type: "button",
|
|
2689
|
+
onClick: ()=>setShouldPlay((prevState)=>!prevState),
|
|
2690
|
+
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', 'rounded-[inherit]', // Setting the opacity to 0 before applying the transition below will ensure the button only fades in after the video has started playing
|
|
2691
|
+
shouldPlay && 'opacity-0', isPlaying && [
|
|
2692
|
+
'transition-opacity duration-200',
|
|
2693
|
+
// Only show the pause button when the video is hovered or focused
|
|
2694
|
+
'focus-visible:opacity-100',
|
|
2695
|
+
'hover:opacity-100'
|
|
2696
|
+
]),
|
|
2697
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
2698
|
+
className: "grid h-12 w-12 place-items-center rounded-full bg-white outline-hidden",
|
|
2699
|
+
children: isPlaying ? /*#__PURE__*/ jsx(PlayerPause, {}) : /*#__PURE__*/ jsx(PlayerPlay, {})
|
|
2700
|
+
})
|
|
2701
|
+
}),
|
|
2702
|
+
alt && /*#__PURE__*/ jsx("p", {
|
|
2703
|
+
className: "sr-only",
|
|
2704
|
+
children: alt
|
|
2705
|
+
})
|
|
2706
|
+
]
|
|
2705
2707
|
});
|
|
2706
2708
|
};
|
|
2707
2709
|
|