@obosbbl/grunnmuren-react 3.3.4 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,18 +1,21 @@
1
1
  'use client';
2
- import { useContextProps, DisclosureContext, DisclosureGroupStateContext, Provider, ButtonContext as ButtonContext$1, DEFAULT_SLOT, Button as Button$1, useLocale, Link as Link$1, Breadcrumb as Breadcrumb$1, Breadcrumbs as Breadcrumbs$1, Text, CheckboxContext, Checkbox as Checkbox$1, FieldError, Label as Label$1, CheckboxGroup as CheckboxGroup$1, Header, ListBoxItem as ListBoxItem$1, ListBoxSection as ListBoxSection$1, ListBox as ListBox$1, ComboBox, Group, Input, Popover, useSlottedContext, FormContext, FieldErrorContext, LabelContext, InputContext, I18nProvider, RouterProvider, GroupContext, Dialog as Dialog$1, DialogTrigger as DialogTrigger$1, Modal as Modal$1, ModalOverlay, NumberField as NumberField$1, ProgressBar as ProgressBar$1, Radio as Radio$1, RadioGroup as RadioGroup$1, Select as Select$1, SelectValue, ColumnResizer, Table as Table$1, TableBody as TableBody$1, Cell, Column, ResizableTableContainer, TableHeader as TableHeader$1, Row, Tab as Tab$1, TabListStateContext, TabList as TabList$1, TabPanel as TabPanel$1, Tabs as Tabs$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';
2
+ import { useContextProps, DisclosureContext, DisclosureGroupStateContext, Provider, ButtonContext as ButtonContext$1, DEFAULT_SLOT, Button as Button$1, useLocale, Link as Link$1, Breadcrumb as Breadcrumb$1, Breadcrumbs as Breadcrumbs$1, GroupContext, Text, CheckboxContext, Checkbox as Checkbox$1, FieldError, Label as Label$1, CheckboxGroup as CheckboxGroup$1, Header, ListBoxItem as ListBoxItem$1, ListBoxSection as ListBoxSection$1, ListBox as ListBox$1, ComboBox, Group, Input, Popover, useSlottedContext, FormContext, FieldErrorContext, LabelContext, InputContext, I18nProvider, RouterProvider, Dialog as Dialog$1, DialogTrigger as DialogTrigger$1, Modal as Modal$1, ModalOverlay, NumberField as NumberField$1, ProgressBar as ProgressBar$1, Radio as Radio$1, RadioGroup as RadioGroup$1, Select as Select$1, SelectValue, ColumnResizer, Table as Table$1, TableBody as TableBody$1, Cell, Column, ResizableTableContainer, TableHeader as TableHeader$1, Row, Tab as Tab$1, TabListStateContext, TabList as TabList$1, TabPanel as TabPanel$1, Tabs as Tabs$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 { DisclosureGroup, Form, Group } from 'react-aria-components';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { cva, cx, compose } from 'cva';
6
- import { createContext, useContext, useId, useRef, Children, useState, useEffect, isValidElement, cloneElement, useCallback } from 'react';
6
+ import { createContext, useContext, useId, useRef, Children, useState, useEffect, useMemo, useCallback } from 'react';
7
7
  import { ChevronDown, Error, Warning, CheckCircle, InfoCircle, Close, User, ChevronLeft, ChevronRight, LoadingSpinner, Check, Trash, ArrowRight, Download, LinkExternal, PlayerPause, PlayerPlay } from '@obosbbl/grunnmuren-icons-react';
8
- import { filterDOMProps, mergeProps, mergeRefs, useUpdateEffect, useObjectRef, useFormReset } from '@react-aria/utils';
8
+ import { filterDOMProps, mergeProps, mergeRefs, useObjectRef, useFormReset, useUpdateEffect } from '@react-aria/utils';
9
9
  import { useFocusRing, useDisclosure, useProgressBar, useDateFormatter, useField } from 'react-aria';
10
10
  import { useDisclosureState } from 'react-stately';
11
- import { useDebouncedCallback } from 'use-debounce';
11
+ import Autoplay from 'embla-carousel-autoplay';
12
+ import useEmblaCarousel from 'embla-carousel-react';
13
+ import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
12
14
  import { useFormValidation } from '@react-aria/form';
13
15
  import { useFormValidationState } from '@react-stately/form';
14
16
  import { useControlledState } from '@react-stately/utils';
15
17
  import { PressResponder } from '@react-aria/interactions';
18
+ import { useDebouncedCallback } from 'use-debounce';
16
19
 
17
20
  const HeadingContext = /*#__PURE__*/ createContext({});
18
21
  const headingVariants = cva({
@@ -487,44 +490,73 @@ const Avatar = ({ src, alt = '', className, onError, loading = 'lazy', ...rest }
487
490
  });
488
491
  };
489
492
 
490
- const _LinkContext = /*#__PURE__*/ createContext({});
491
- const Link = ({ ref: _ref, ..._props })=>{
492
- const [props, ref] = useContextProps(_props, _ref, _LinkContext);
493
- const { className, _innerWrapper, children: _children, ...restProps } = props;
494
- const locale = _useLocale();
495
- const externalLinkSR = props.rel?.includes('external') ? /*#__PURE__*/ jsx("span", {
496
- className: "sr-only",
497
- children: translations$1.externalLink[locale]
498
- }) : null;
499
- const reactNodeChildren = /*#__PURE__*/ jsxs(Fragment, {
500
- children: [
501
- _children,
502
- externalLinkSR
503
- ]
504
- });
505
- const children = _innerWrapper ? _innerWrapper({
506
- ...restProps,
507
- children: typeof _children === 'function' ? (values)=>_children(values) : reactNodeChildren
508
- }) : reactNodeChildren;
509
- return /*#__PURE__*/ jsx(Link$1, {
510
- ...restProps,
511
- ref: ref,
512
- "data-slot": "link",
513
- 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 [&>svg]:transition-transform'),
514
- children: children
515
- });
493
+ const formField = cx('group flex flex-col gap-2');
494
+ 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');
495
+ const input = cva({
496
+ base: [
497
+ // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
498
+ 'bg-white',
499
+ // Use box-content to enable auto width based on number of characters (size)
500
+ // Setting min-height to prevent the input from collapsing in Safari
501
+ // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
502
+ 'box-content min-h-6 py-2.5',
503
+ 'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
504
+ // invalid styles
505
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red',
506
+ // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
507
+ 'appearance-none'
508
+ ],
509
+ variants: {
510
+ // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
511
+ focusModifier: {
512
+ focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
513
+ visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
514
+ },
515
+ isGrouped: {
516
+ false: 'px-3',
517
+ true: '!ring-0 flex-1'
518
+ }
519
+ },
520
+ defaultVariants: {
521
+ focusModifier: 'focus',
522
+ isGrouped: false
523
+ }
524
+ });
525
+ const inputGroup = cx([
526
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
527
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
528
+ ]);
529
+ const dropdown = {
530
+ 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'),
531
+ // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
532
+ listbox: cx('max-h-100 overflow-x-hidden text-sm outline-hidden'),
533
+ chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
516
534
  };
535
+ const animateIconVariants = cva({
536
+ base: '*:[svg]:shrink-0 *:[svg]:transition-transform',
537
+ variants: {
538
+ animateIcon: {
539
+ right: 'hover:*:[svg]:motion-safe:translate-x-1',
540
+ left: 'hover:*:[svg]:motion-safe:-translate-x-1',
541
+ down: 'hover:*:[svg]:motion-safe:translate-y-1',
542
+ up: 'hover:*:[svg]:motion-safe:-translate-y-1',
543
+ 'up-right': 'hover:*:[svg]:motion-safe:-translate-y-0.5 hover:*:[svg]:motion-safe:translate-x-0.5'
544
+ }
545
+ }
546
+ });
517
547
 
518
548
  function isLinkProps$1(props) {
519
549
  return !!props.href;
520
550
  }
521
551
  function Backlink(props) {
522
- const { className, style, children, withUnderline, ref, ...restProps } = props;
523
- const _className = cx(className, 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 font-normal no-underline focus-visible:outline-focus');
552
+ const { className, children, withUnderline, ref, ...restProps } = props;
553
+ const _className = cx(className, animateIconVariants({
554
+ animateIcon: 'left'
555
+ }), 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 font-normal no-underline focus-visible:outline-focus');
524
556
  const content = /*#__PURE__*/ jsxs(Fragment, {
525
557
  children: [
526
558
  /*#__PURE__*/ jsx(ChevronLeft, {
527
- className: cx('-ml-[0.5em] group-hover:-translate-x-1 duration-300')
559
+ className: cx('-ml-[0.5em] duration-300')
528
560
  }),
529
561
  /*#__PURE__*/ jsx("span", {
530
562
  children: /*#__PURE__*/ jsx("span", {
@@ -535,10 +567,9 @@ function Backlink(props) {
535
567
  ]
536
568
  });
537
569
  if (isLinkProps$1(props)) {
538
- return /*#__PURE__*/ jsx(Link, {
570
+ return /*#__PURE__*/ jsx(Link$1, {
539
571
  ...restProps,
540
572
  className: _className,
541
- style: style,
542
573
  ref: ref,
543
574
  children: content
544
575
  });
@@ -546,7 +577,6 @@ function Backlink(props) {
546
577
  return /*#__PURE__*/ jsx(Button$1, {
547
578
  ...restProps,
548
579
  className: _className,
549
- style: style,
550
580
  ref: ref,
551
581
  children: content
552
582
  });
@@ -589,6 +619,40 @@ function Badge(props) {
589
619
  });
590
620
  }
591
621
 
622
+ const _LinkContext = /*#__PURE__*/ createContext({});
623
+ const linkVariants = compose(animateIconVariants, cva({
624
+ base: 'inline-flex cursor-pointer items-center gap-1 font-medium hover:no-underline focus-visible:outline-current focus-visible:outline-focus-offset data-disabled:cursor-default'
625
+ }));
626
+ const Link = ({ ref: _ref, animateIcon, ..._props })=>{
627
+ const [props, ref] = useContextProps(_props, _ref, _LinkContext);
628
+ const { className, _innerWrapper, children: _children, ...restProps } = props;
629
+ const locale = _useLocale();
630
+ const externalLinkSR = props.rel?.includes('external') ? /*#__PURE__*/ jsx("span", {
631
+ className: "sr-only",
632
+ children: translations$1.externalLink[locale]
633
+ }) : null;
634
+ const reactNodeChildren = /*#__PURE__*/ jsxs(Fragment, {
635
+ children: [
636
+ _children,
637
+ externalLinkSR
638
+ ]
639
+ });
640
+ const children = _innerWrapper ? _innerWrapper({
641
+ ...restProps,
642
+ children: typeof _children === 'function' ? (values)=>_children(values) : reactNodeChildren
643
+ }) : reactNodeChildren;
644
+ return /*#__PURE__*/ jsx(Link$1, {
645
+ ...restProps,
646
+ ref: ref,
647
+ "data-slot": "link",
648
+ className: linkVariants({
649
+ className,
650
+ animateIcon
651
+ }),
652
+ children: children
653
+ });
654
+ };
655
+
592
656
  function Breadcrumb(props) {
593
657
  const { className, children, href, ...restProps } = props;
594
658
  return /*#__PURE__*/ jsxs(Breadcrumb$1, {
@@ -619,38 +683,38 @@ function Breadcrumbs(props) {
619
683
 
620
684
  /**
621
685
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
622
- */ const buttonVariants = cva({
686
+ */ const buttonVariants = compose(animateIconVariants, cva({
623
687
  base: [
624
688
  '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'
625
689
  ],
626
690
  variants: {
627
691
  /**
628
- * The variant of the button
629
- * @default primary
630
- */ variant: {
692
+ * The variant of the button
693
+ * @default primary
694
+ */ variant: {
631
695
  primary: 'no-underline',
632
696
  // 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
633
697
  secondary: 'border-2 border-current no-underline hover:border-transparent',
634
698
  tertiary: 'underline hover:no-underline'
635
699
  },
636
700
  /**
637
- * Adjusts the color of the button for usage on different backgrounds.
638
- * @default blue
639
- */ color: {
701
+ * Adjusts the color of the button for usage on different backgrounds.
702
+ * @default blue
703
+ */ color: {
640
704
  blue: 'focus-visible:outline-focus',
641
705
  mint: 'focus-visible:outline-focus focus-visible:outline-mint',
642
706
  white: 'focus-visible:outline-focus focus-visible:outline-white'
643
707
  },
644
708
  /**
645
- * When the button is without text, but with a single icon.
646
- * @default false
647
- */ isIconOnly: {
709
+ * When the button is without text, but with a single icon.
710
+ * @default false
711
+ */ isIconOnly: {
648
712
  true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
649
713
  false: 'gap-2.5 px-4 py-2'
650
714
  },
651
715
  // Make the content of the button transparent to hide it's content, but keep the button width
652
716
  isPending: {
653
- true: '!text-transparent relative',
717
+ true: 'relative text-transparent!',
654
718
  false: null
655
719
  }
656
720
  },
@@ -709,15 +773,17 @@ function Breadcrumbs(props) {
709
773
  isIconOnly: false,
710
774
  isPending: false
711
775
  }
712
- });
776
+ }));
713
777
  const ButtonContext = /*#__PURE__*/ createContext({});
714
778
  function isLinkProps(props) {
715
779
  return !!props.href;
716
780
  }
717
781
  function Button({ ref = null, ...props }) {
718
782
  [props, ref] = useContextProps(props, ref, ButtonContext);
719
- const { children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
783
+ const { animateIcon, children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
720
784
  const className = buttonVariants({
785
+ // Don't animate the icon when we're pending, as it affects the loading spinner
786
+ animateIcon: isPending ? undefined : animateIcon,
721
787
  className: props.className,
722
788
  color,
723
789
  isIconOnly,
@@ -977,12 +1043,145 @@ const cardLinkVariants = cva({
977
1043
  });
978
1044
  };
979
1045
 
1046
+ const HeroContext = /*#__PURE__*/ createContext(null);
1047
+ // Common variant for "standard" and "full-bleed" Hero variants
1048
+ const oneColumnLayout = [
1049
+ // Vertical spacing in the <Content>
1050
+ 'lg:*:data-[slot="content"]:gap-y-4',
1051
+ // Main text content takes up 9 columns on medium screens and above
1052
+ 'lg:*:data-[slot="content"]:col-span-9',
1053
+ // Make sure other elements than <Content> and <Media> (i.e CTA) does not span the full width on small screens
1054
+ '*:not-data-[slot="content"]:not-data-[slot="media"]:w-fit',
1055
+ // Other elements than <Content> and <Media> (e.g. CTA, SVG logo or Badge) take up 3 columns on medium screens and above, and are right aligned
1056
+ 'lg:*:not-data-[slot="content"]:not-data-[slot="media"]:not-data-[slot="carousel"]:col-span-3 lg:*:not-data-[slot="content"]:not-data-[slot="media"]:justify-self-end',
1057
+ // <Media> and <Carousel> content takes up the full width on medium screens and above
1058
+ 'lg:*:data-[slot="media"]:col-span-full *:data-[slot="media"]:*:w-full',
1059
+ 'lg:*:data-[slot="carousel"]:col-span-full',
1060
+ // Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
1061
+ 'lg:items-end'
1062
+ ];
1063
+ const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot="media"]:*:aspect-[1/1] sm:*:data-[slot="media"]:*:aspect-4/3 md:*:data-[slot="media"]:*:aspect-3/2';
1064
+ const variants = cva({
1065
+ base: [
1066
+ 'container px-0',
1067
+ // Grid variant to position the Hero's content
1068
+ 'grid lg:grid-cols-12 lg:gap-x-12 xl:gap-x-16',
1069
+ 'gap-y-10 lg:gap-y-12',
1070
+ // Enable vertical gap within <Content>
1071
+ '*:data-[slot="content"]:grid',
1072
+ // Vertical spacing in the <Content>
1073
+ '*:data-[slot="content"]:gap-y-3',
1074
+ // Make sure <Media> content fills any available vertical and horizontal space
1075
+ '*:data-[slot="media"]:*:object-cover',
1076
+ '*:data-[slot="carousel"]:overflow-hidden *:data-[slot="carousel"]:rounded-3xl',
1077
+ // Make the carousel items full width, so we scroll one at a time
1078
+ '**:data-[slot="carousel-item"]:basis-full'
1079
+ ],
1080
+ variants: {
1081
+ /**
1082
+ * Defines the variant of the Hero
1083
+ * @default standard
1084
+ * */ variant: {
1085
+ standard: [
1086
+ oneColumnLayout,
1087
+ nonFullBleedAspectRatiosForSmallScreens,
1088
+ 'lg:*:data-[slot="media"]:*:aspect-2/1'
1089
+ ],
1090
+ 'full-bleed': [
1091
+ oneColumnLayout,
1092
+ // Position the media and carousel content to fill the entire viewport width
1093
+ '*:data-[slot="media"]:*:absolute *:data-[slot="media"]:*:left-0',
1094
+ // Special case for Carousel, where the Media is nested inside a CarouselItem
1095
+ '*:data-[slot="carousel"]:**:data-[slot="media"]:w-full',
1096
+ // Match the heights of the <Media> or <Carousel> wrapper for the Media content (e.g. image, VideoLoop, video etc.)
1097
+ // This is necessary due to the absolute positioning of the media and carousel containers in this variant
1098
+ // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1099
+ '**:data-[slot="media"]:h-80 sm:**:data-[slot="media"]:h-[25rem] md:**:data-[slot="media"]:h-[30rem] lg:**:data-[slot="media"]:h-[35rem] xl:**:data-[slot="media"]:h-[40rem] 2xl:**:data-[slot="media"]:h-[42rem] 3xl:**:data-[slot="media"]:h-[48rem] 4xl:**:data-[slot="media"]:h-[53rem]',
1100
+ '**:data-[slot="media"]:*:h-[inherit]',
1101
+ // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1102
+ '*:data-[slot="carousel"]:h-80 sm:*:data-[slot="carousel"]:h-[25rem] md:*:data-[slot="carousel"]:h-[30rem] lg:*:data-[slot="carousel"]:h-[35rem] xl:*:data-[slot="carousel"]:h-[40rem] 2xl:*:data-[slot="carousel"]:h-[42rem] 3xl:*:data-[slot="carousel"]:h-[48rem] 4xl:*:data-[slot="carousel"]:h-[53rem]',
1103
+ '*:data-[slot="carousel"]:w-full!',
1104
+ // Override aspect ratio of the media and carousel-item slots (since we can not use aspect for full-bleed layout)
1105
+ '**:data-[slot="carousel-item"]:data-[slot="media"]:*:aspect-none',
1106
+ // break out the carousel out of the container
1107
+ '**:data-[slot="carousel-items-container"]:absolute **:data-[slot="carousel-items-container"]:right-0 **:data-[slot="carousel-items-container"]:left-0 **:data-[slot="carousel-items-container"]:h-[inherit]',
1108
+ // Positions the carousel controls inside the carousel
1109
+ '**:data-[slot="carousel-controls"]:z-10 **:data-[slot="carousel-controls"]:mb-4 *:data-[slot="carousel"]:flex *:data-[slot="carousel"]:items-end *:data-[slot="carousel"]:justify-end'
1110
+ ],
1111
+ 'two-column': [
1112
+ 'lg:items-center lg:*:col-span-6',
1113
+ // Vertical spacing in the <Content>
1114
+ 'lg:*:data-[slot="content"]:gap-y-7',
1115
+ nonFullBleedAspectRatiosForSmallScreens,
1116
+ // Set media aspect ratio to 1:1 (square)
1117
+ 'lg:*:data-[slot="media"]:*:aspect-square'
1118
+ ]
1119
+ }
1120
+ },
1121
+ compoundVariants: [
1122
+ {
1123
+ variant: [
1124
+ 'standard',
1125
+ 'two-column'
1126
+ ],
1127
+ className: [
1128
+ '*:data-[slot="media"]:*:rounded-3xl',
1129
+ '**:data-[slot="carousel-controls"]:absolute *:data-[slot="carousel"]:relative **:data-[slot="carousel-controls"]:right-4 **:data-[slot="carousel-controls"]:bottom-4 **:data-[slot="carousel-container"]:rounded-3xl'
1130
+ ]
1131
+ }
1132
+ ],
1133
+ defaultVariants: {
1134
+ variant: 'standard'
1135
+ }
1136
+ });
1137
+ const Hero = ({ variant, className, children, ...rest })=>{
1138
+ const variantsClassName = variants({
1139
+ variant,
1140
+ className
1141
+ });
1142
+ return /*#__PURE__*/ jsx(Provider, {
1143
+ values: [
1144
+ [
1145
+ HeroContext,
1146
+ {
1147
+ variant
1148
+ }
1149
+ ],
1150
+ [
1151
+ HeadingContext,
1152
+ {
1153
+ // Sets the default heading size for the Hero based on the variant
1154
+ size: variant === 'two-column' ? 'xl' : 'l',
1155
+ className: // word-break:break-word to allow long words to break (this is necessary to make hyphens work in grid containers in Safari)
1156
+ 'hyphens-auto text-pretty [word-break:break-word]'
1157
+ }
1158
+ ],
1159
+ [
1160
+ GroupContext,
1161
+ {
1162
+ // Prevents the group from being announced as a group by screen readers
1163
+ // The Group component is used to group the Hero's CTA buttons together visually, and has no semantic meaning
1164
+ role: 'presentation',
1165
+ className: 'flex flex-wrap gap-3 *:w-fit'
1166
+ }
1167
+ ]
1168
+ ],
1169
+ children: /*#__PURE__*/ jsx("div", {
1170
+ className: cx(variantsClassName, className),
1171
+ ...rest,
1172
+ children: children
1173
+ })
1174
+ });
1175
+ };
1176
+
980
1177
  /**
981
1178
  * Hook that detects the user's preference for reduced motion.
982
1179
  *
983
1180
  * Keep in mind that this hook relies on a browser API's and doesn't run on the server.
984
1181
  * You can supply an initial value that will be used for server side rendering.
985
- */ function usePrefersReducedMotion(initialValue = true) {
1182
+ *
1183
+ * The default initial value is `false` as this aligns with most users. This is to prevent unecessary rerenders on mount for the common user.
1184
+ */ function usePrefersReducedMotion(initialValue = false) {
986
1185
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(initialValue);
987
1186
  useEffect(()=>{
988
1187
  const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
@@ -998,188 +1197,193 @@ const cardLinkVariants = cva({
998
1197
  return prefersReducedMotion;
999
1198
  }
1000
1199
 
1001
- const Carousel = ({ className, children, onChange, ...rest })=>{
1200
+ /**
1201
+ * A helper to get the prev/next button from the carousel DOM
1202
+ * @param ref The carousel ref
1203
+ * @param slot The slot of the button to get ('prev' or 'next')
1204
+ * @returns The button element, or undefined if not found
1205
+ */ const getCarouselButton = (ref, slot)=>ref.current?.querySelector(`button[slot="${slot}"]`);
1206
+ /**
1207
+ * Focus the first focusable element in the currently snapped slide
1208
+ * @param emblaApi The embla carousel API instance
1209
+ */ const focusElementInSnappedSlide = (emblaApi)=>{
1210
+ if (!emblaApi) {
1211
+ return;
1212
+ }
1213
+ const index = emblaApi.selectedScrollSnap();
1214
+ const targetSlide = emblaApi.slideNodes()[index];
1215
+ if (!targetSlide) {
1216
+ return;
1217
+ }
1218
+ // Find first focusable element in the slide
1219
+ const focusableElement = targetSlide.querySelector('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');
1220
+ // Use preventScroll to avoid the browser's default scroll-into-view behavior
1221
+ // which would conflict with embla's scroll animation
1222
+ focusableElement?.focus({
1223
+ preventScroll: true
1224
+ });
1225
+ };
1226
+ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0, orientation = 'horizontal', onSelect, onSettled, loop = false, scrollGestures = false, ref, ...rest })=>{
1002
1227
  const carouselRef = useRef(null);
1003
- const carouselItemsRef = useRef(null);
1004
- const locale = _useLocale();
1005
- const { previous, next } = translations$1;
1006
- const [scrollTargetIndex, setScrollTargetIndex] = useState(0);
1007
- const isScrollingProgrammatically = useRef(false);
1008
- const scrollTimeoutRef = useRef(null);
1009
- const scrollQueue = useRef([]);
1010
- const [hasReachedScrollStart, setHasReachedScrollStart] = useState(scrollTargetIndex === 0);
1011
- const [hasReachedScrollEnd, setHasReachedScrollEnd] = useState(!carouselItemsRef.current || carouselItemsRef.current.children.length - 1 === scrollTargetIndex);
1012
- const prefersReducedMotion = usePrefersReducedMotion();
1013
- useEffect(()=>{
1014
- setHasReachedScrollStart(scrollTargetIndex === 0);
1015
- setHasReachedScrollEnd(!carouselItemsRef.current || carouselItemsRef.current.children.length - 1 === scrollTargetIndex);
1016
- }, [
1017
- scrollTargetIndex
1018
- ]);
1019
- // Keep track of the previous index to determine if the user is scrolling forward or backward
1020
- // This is used to determine which callback to call (onPrev or onNext)
1021
- const prevIndex = useRef(0);
1022
- // Processes the next scroll action in the queue, if any
1023
- // All clicks on the prev/next buttons are queued while a programmatic scroll is in progress
1024
- // This is to ensure that rapid clicks on the buttons do not cause janky scrolling behavior
1025
- // while still a snappy response to user clicks
1026
- const processQueue = ()=>{
1027
- if (scrollQueue.current.length > 0 && !isScrollingProgrammatically.current) {
1028
- const nextIndex = scrollQueue.current?.shift();
1029
- if (nextIndex !== undefined) {
1030
- setScrollTargetIndex(nextIndex);
1031
- }
1032
- }
1033
- };
1034
- // Handle scrolling when user clicks the arrow icons
1035
- useUpdateEffect(()=>{
1036
- if (!carouselItemsRef.current) {
1037
- return;
1038
- }
1039
- if (scrollTimeoutRef.current) {
1040
- clearTimeout(scrollTimeoutRef.current);
1041
- }
1042
- isScrollingProgrammatically.current = true;
1043
- const elementWithFocusVisible = carouselRef.current?.querySelector(':focus-visible');
1044
- const targetElement = carouselItemsRef.current.children[scrollTargetIndex];
1045
- if (targetElement) {
1046
- // Calculate the scroll position to scroll the target element into view
1047
- const containerRect = carouselItemsRef.current.getBoundingClientRect();
1048
- const targetRect = targetElement.getBoundingClientRect();
1049
- const scrollLeft = carouselItemsRef.current.scrollLeft + (targetRect.left - containerRect.left);
1050
- carouselItemsRef.current.scrollTo({
1051
- left: Math.round(scrollLeft),
1052
- behavior: prefersReducedMotion ? 'instant' : 'smooth'
1053
- });
1228
+ const prefersReducedMotion = usePrefersReducedMotion() ?? false;
1229
+ const emblaPlugins = useMemo(()=>{
1230
+ const plugins = [];
1231
+ if (scrollGestures) {
1232
+ plugins.push(WheelGesturesPlugin());
1054
1233
  }
1055
- if (prevIndex.current !== scrollTargetIndex && onChange) {
1056
- onChange({
1057
- index: scrollTargetIndex,
1058
- id: carouselItemsRef.current.children[scrollTargetIndex]?.id,
1059
- prevIndex: prevIndex.current,
1060
- prevId: carouselItemsRef.current.children[prevIndex.current]?.id
1061
- });
1234
+ if (autoPlayDelay) {
1235
+ plugins.push(Autoplay({
1236
+ delay: autoPlayDelay,
1237
+ stopOnLastSnap: !loop,
1238
+ jump: prefersReducedMotion
1239
+ }));
1062
1240
  }
1063
- prevIndex.current = scrollTargetIndex;
1064
- scrollTimeoutRef.current = setTimeout(()=>{
1065
- isScrollingProgrammatically.current = false;
1066
- scrollTimeoutRef.current = null;
1067
- if (elementWithFocusVisible && !carouselRef.current?.contains(document.activeElement)) {
1068
- // Restore focus to the appropriate element after scrolling
1069
- // First check if the prev or next buttons just got hidden due to reaching the start/end of the carousel
1070
- // If so, move focus to the other button. This is to avoid a scroll jank that occurs if instead focus is restored to the carousel container
1071
- // This jank happens because of the delays used for scrolling with these buttons (debounce, queuing etc.).
1072
- switch(elementWithFocusVisible.slot){
1073
- case 'prev':
1074
- {
1075
- // Focus was lost when the prev button turned invisible, set it to the next button
1076
- const nextButton = carouselRef.current?.querySelector('[slot="next"]');
1077
- nextButton?.focus();
1078
- break;
1079
- }
1080
- case 'next':
1081
- {
1082
- // Focus was lost when the next button turned invisible, set it to the prev button
1083
- const prevButton = carouselRef.current?.querySelector('[slot="prev"]');
1084
- prevButton?.focus();
1085
- break;
1086
- }
1087
- default:
1088
- {
1089
- // Focus was lost during while scrolling with left/right arrows, restore it to the carousel container
1090
- carouselItemsRef.current?.focus();
1091
- break;
1092
- }
1093
- }
1094
- }
1095
- processQueue(); // Process any queued scrolls
1096
- }, 500);
1241
+ return plugins;
1097
1242
  }, [
1098
- scrollTargetIndex,
1243
+ autoPlayDelay,
1244
+ scrollGestures,
1245
+ loop,
1099
1246
  prefersReducedMotion
1100
1247
  ]);
1101
- // Clean up timeout on unmount
1248
+ const [emblaRef, emblaApi] = useEmblaCarousel({
1249
+ align,
1250
+ loop,
1251
+ startIndex: initialIndex,
1252
+ axis: orientation === 'horizontal' ? 'x' : 'y',
1253
+ inViewThreshold: 0.2
1254
+ }, emblaPlugins);
1255
+ const [slidesInView, setSlidesInView] = useState([
1256
+ initialIndex
1257
+ ]);
1258
+ // We need some default values here. The proper initial values will be set by the embla init handler later
1259
+ // for the default values, assume that we can scroll next, but for prev only if looping is enabled
1260
+ const [canScrollNext, setCanScrollNext] = useState(true);
1261
+ const [canScrollPrev, setCanScrollPrev] = useState(loop);
1262
+ const previousSettledScrollIndex = useRef(initialIndex);
1102
1263
  useEffect(()=>{
1103
- return ()=>{
1104
- if (scrollTimeoutRef.current) {
1105
- clearTimeout(scrollTimeoutRef.current);
1264
+ if (!emblaApi) {
1265
+ return;
1266
+ }
1267
+ const emblaHandler = (_, type)=>{
1268
+ const scrollSnapIndex = emblaApi.selectedScrollSnap();
1269
+ switch(type){
1270
+ case 'select':
1271
+ onSelect?.(scrollSnapIndex);
1272
+ setCanScrollNext(emblaApi.canScrollNext());
1273
+ setCanScrollPrev(emblaApi.canScrollPrev());
1274
+ break;
1275
+ case 'settle':
1276
+ {
1277
+ // We only invoke the callback if the scroll index actually changed from the previous settled index
1278
+ // Otherwise this gets triggered if the user does the tiniest bit of scrolling in the carousel, but doesn't transition to the next slide
1279
+ if (scrollSnapIndex !== previousSettledScrollIndex.current) {
1280
+ previousSettledScrollIndex.current = scrollSnapIndex;
1281
+ onSettled?.(scrollSnapIndex);
1282
+ }
1283
+ break;
1284
+ }
1285
+ case 'slidesInView':
1286
+ {
1287
+ setSlidesInView(emblaApi.slidesInView());
1288
+ break;
1289
+ }
1290
+ case 'init':
1291
+ {
1292
+ setCanScrollNext(emblaApi.canScrollNext());
1293
+ setCanScrollPrev(emblaApi.canScrollPrev());
1294
+ break;
1295
+ }
1106
1296
  }
1107
1297
  };
1108
- }, []);
1109
- const onScroll = useDebouncedCallback((event)=>{
1110
- // Ignore scroll events when we're programmatically scrolling
1111
- if (isScrollingProgrammatically.current) {
1298
+ emblaApi.on('select', emblaHandler);
1299
+ emblaApi.on('slidesInView', emblaHandler);
1300
+ emblaApi.on('settle', emblaHandler);
1301
+ emblaApi.on('init', emblaHandler);
1302
+ return ()=>{
1303
+ emblaApi.off('select', emblaHandler);
1304
+ emblaApi.off('settle', emblaHandler);
1305
+ emblaApi.off('slidesInView', emblaHandler);
1306
+ emblaApi.off('init', emblaHandler);
1307
+ };
1308
+ }, [
1309
+ emblaApi,
1310
+ onSelect,
1311
+ onSettled
1312
+ ]);
1313
+ const handleNextPress = useCallback(()=>{
1314
+ if (!emblaApi) {
1112
1315
  return;
1113
1316
  }
1114
- const target = event.target;
1115
- const containerRect = target.getBoundingClientRect();
1116
- // Calculate the index of the item that is currently in view
1117
- const newScrollTargetIndex = Array.from(target.children).findIndex((child)=>{
1118
- const rect = child.getBoundingClientRect();
1119
- // Check if the item is more than 50% visible within the container
1120
- const visibleWidth = Math.min(rect.right, containerRect.right) - Math.max(rect.left, containerRect.left);
1121
- const itemWidth = rect.width;
1122
- return visibleWidth / itemWidth > 0.5;
1123
- });
1124
- if (newScrollTargetIndex !== -1 && newScrollTargetIndex !== scrollTargetIndex) {
1125
- if (onChange) {
1126
- onChange({
1127
- index: newScrollTargetIndex,
1128
- id: target.children[newScrollTargetIndex]?.id,
1129
- prevIndex: prevIndex.current,
1130
- prevId: target.children[prevIndex.current]?.id
1131
- });
1132
- }
1133
- // Update the index and prevIndex
1134
- setScrollTargetIndex(newScrollTargetIndex);
1135
- prevIndex.current = newScrollTargetIndex;
1317
+ emblaApi.plugins().autoplay?.stop();
1318
+ emblaApi.scrollNext(prefersReducedMotion);
1319
+ // we need to move focus if we are about to disable this button due to start/end of carousel
1320
+ if (!loop && !emblaApi.canScrollNext() && getCarouselButton(carouselRef, 'next')?.matches(':focus-visible')) {
1321
+ getCarouselButton(carouselRef, 'prev')?.focus();
1136
1322
  }
1137
- }, 150);
1138
- const handlePrevious = ()=>{
1139
- setScrollTargetIndex((currentTargetIndex)=>{
1140
- const targetIndex = currentTargetIndex - 1;
1141
- if (targetIndex < 0) {
1142
- return currentTargetIndex;
1143
- }
1144
- if (isScrollingProgrammatically.current) {
1145
- // If we're already scrolling, queue this action
1146
- scrollQueue.current = [
1147
- targetIndex
1148
- ];
1149
- return currentTargetIndex;
1150
- }
1151
- return targetIndex;
1152
- });
1153
- };
1154
- const handleNext = ()=>{
1155
- setScrollTargetIndex((currentTargetIndex)=>{
1156
- const targetIndex = currentTargetIndex + 1;
1157
- if (!carouselItemsRef.current || targetIndex >= carouselItemsRef.current.children.length) {
1158
- return currentTargetIndex;
1323
+ }, [
1324
+ emblaApi,
1325
+ prefersReducedMotion,
1326
+ loop
1327
+ ]);
1328
+ const handlePrevPress = useCallback(()=>{
1329
+ if (!emblaApi) {
1330
+ return;
1331
+ }
1332
+ emblaApi.plugins().autoplay?.stop();
1333
+ emblaApi.scrollPrev(prefersReducedMotion);
1334
+ // we need to move focus if we are about to disable this button due to start/end of carousel
1335
+ if (!loop && !emblaApi.canScrollPrev() && getCarouselButton(carouselRef, 'prev')?.matches(':focus-visible')) {
1336
+ getCarouselButton(carouselRef, 'next')?.focus();
1337
+ }
1338
+ }, [
1339
+ emblaApi,
1340
+ prefersReducedMotion,
1341
+ loop
1342
+ ]);
1343
+ const locale = _useLocale();
1344
+ const handleKeyDown = useCallback((e)=>{
1345
+ // Check if either prev or next button has focus - if so, don't override their focus management
1346
+ const carouselButtonHasFocus = getCarouselButton(carouselRef, 'prev')?.matches(':focus-visible') || getCarouselButton(carouselRef, 'next')?.matches(':focus-visible');
1347
+ if (e.key === 'ArrowRight' && !e.repeat) {
1348
+ handleNextPress();
1349
+ // Focus first focusable element in the next slide, unless a carousel button has focus
1350
+ if (!carouselButtonHasFocus) {
1351
+ focusElementInSnappedSlide(emblaApi);
1159
1352
  }
1160
- if (isScrollingProgrammatically.current) {
1161
- // If we're already scrolling, queue this action
1162
- scrollQueue.current = [
1163
- targetIndex
1164
- ];
1165
- return currentTargetIndex;
1353
+ } else if (e.key === 'ArrowLeft' && !e.repeat) {
1354
+ handlePrevPress();
1355
+ // Focus first focusable element in the previous slide, unless a carousel button has focus
1356
+ if (!carouselButtonHasFocus) {
1357
+ focusElementInSnappedSlide(emblaApi);
1166
1358
  }
1167
- return targetIndex;
1168
- });
1359
+ }
1360
+ }, [
1361
+ handleNextPress,
1362
+ handlePrevPress,
1363
+ emblaApi
1364
+ ]);
1365
+ const hasHeroContext = !!useContext(HeroContext);
1366
+ const nextPrevStyles = hasHeroContext ? {
1367
+ color: 'white',
1368
+ variant: 'primary'
1369
+ } : {
1370
+ variant: 'tertiary'
1169
1371
  };
1170
- return /*#__PURE__*/ jsx("div", {
1372
+ return(// biome-ignore lint/a11y/noStaticElementInteractions: This is just to enhance keyboard navigation, this is not a replacement for proper focusable elements inside the carousel
1373
+ /*#__PURE__*/ jsx("div", {
1374
+ ...rest,
1375
+ "data-orientation": orientation,
1171
1376
  "data-slot": "carousel",
1172
- ref: carouselRef,
1377
+ ref: mergeRefs(ref, carouselRef),
1378
+ onKeyDown: handleKeyDown,
1173
1379
  children: /*#__PURE__*/ jsx(Provider, {
1174
1380
  values: [
1175
1381
  [
1176
- CarouselItemsContext,
1382
+ CarouselContext,
1177
1383
  {
1178
- carouselItemsRef,
1179
- onScroll,
1180
- activeIndex: scrollTargetIndex,
1181
- handlePrevious,
1182
- handleNext
1384
+ slidesInView,
1385
+ '~emblaRef': emblaRef,
1386
+ orientation
1183
1387
  }
1184
1388
  ],
1185
1389
  [
@@ -1188,115 +1392,119 @@ const Carousel = ({ className, children, onChange, ...rest })=>{
1188
1392
  slots: {
1189
1393
  [DEFAULT_SLOT]: {},
1190
1394
  prev: {
1191
- 'aria-label': previous[locale],
1192
- onPress: handlePrevious
1395
+ 'aria-label': translations$1.previous[locale],
1396
+ isDisabled: !canScrollPrev,
1397
+ onPress: handlePrevPress,
1398
+ ...nextPrevStyles
1193
1399
  },
1194
1400
  next: {
1195
- isIconOnly: true,
1196
- 'aria-label': next[locale],
1197
- onPress: handleNext
1401
+ 'aria-label': translations$1.next[locale],
1402
+ isDisabled: !canScrollNext,
1403
+ onPress: handleNextPress,
1404
+ ...nextPrevStyles
1198
1405
  }
1199
1406
  }
1200
1407
  }
1201
1408
  ]
1202
1409
  ],
1203
- children: /*#__PURE__*/ jsxs("div", {
1204
- ...rest,
1205
- 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
1206
- '[&: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
1207
- '**:data-[slot="video-loop-button"]:focus-visible:outline-none'),
1208
- children: [
1209
- children,
1210
- /*#__PURE__*/ jsxs(_CarouselControls, {
1211
- children: [
1212
- /*#__PURE__*/ jsx(Button, {
1213
- isIconOnly: true,
1214
- slot: "prev",
1215
- variant: "primary",
1216
- color: "white",
1217
- className: cx('group/carousel-previous', hasReachedScrollStart && 'invisible'),
1218
- children: /*#__PURE__*/ jsx(ChevronLeft, {
1219
- className: "group-hover/carousel-previous:motion-safe:-translate-x-1 transition-transform"
1220
- })
1221
- }),
1222
- /*#__PURE__*/ jsx(Button, {
1223
- isIconOnly: true,
1224
- slot: "next",
1225
- variant: "primary",
1226
- color: "white",
1227
- className: cx('group/carousel-next', hasReachedScrollEnd && 'invisible'),
1228
- children: /*#__PURE__*/ jsx(ChevronRight, {
1229
- className: "transition-transform group-hover/carousel-next:motion-safe:translate-x-1"
1230
- })
1231
- })
1232
- ]
1233
- })
1234
- ]
1235
- })
1410
+ children: children
1236
1411
  })
1412
+ }));
1413
+ };
1414
+ const CarouselContext = /*#__PURE__*/ createContext({
1415
+ '~emblaRef': null,
1416
+ orientation: 'horizontal',
1417
+ slidesInView: []
1418
+ });
1419
+ const CarouselItemsContainer = ({ children, className, ...rest })=>{
1420
+ const { '~emblaRef': emblaRef } = useContext(CarouselContext);
1421
+ return /*#__PURE__*/ jsx("div", {
1422
+ className: cx(className, 'overflow-hidden'),
1423
+ ref: emblaRef,
1424
+ "data-slot": "carousel-items-container",
1425
+ ...rest,
1426
+ children: children
1427
+ });
1428
+ };
1429
+ const CarouselItems = ({ className, children })=>{
1430
+ const { orientation } = useContext(CarouselContext);
1431
+ return /*#__PURE__*/ jsx("div", {
1432
+ className: cx(className, 'flex', orientation === 'vertical' && 'flex-col'),
1433
+ "data-slot": "carousel-items",
1434
+ children: children
1237
1435
  });
1238
1436
  };
1239
1437
  /**
1240
1438
  * 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.
1241
1439
  * It is used to render the prev/next buttons in the carousel for now.
1242
- */ const _CarouselControls = ({ children, className })=>/*#__PURE__*/ jsx("div", {
1243
- 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)
1244
- 'items-end *:h-fit'),
1440
+ */ const CarouselControls = ({ children, className, ...rest })=>/*#__PURE__*/ jsx("div", {
1441
+ className: cx(className, 'flex justify-end gap-x-2'),
1245
1442
  "data-slot": "carousel-controls",
1443
+ ...rest,
1444
+ // All items of the carousel are accessible to the screen reader at all times, so these controls will only confuse screen reader users
1445
+ "aria-hidden": "true",
1246
1446
  children: children
1247
1447
  });
1248
- const CarouselItemsContext = /*#__PURE__*/ createContext({
1249
- carouselItemsRef: null,
1250
- activeIndex: 0
1448
+ const carouselButtonVariants = cva({
1449
+ base: 'group data-disabled:invisible'
1251
1450
  });
1252
- const CarouselItems = ({ className, children })=>{
1253
- const { carouselItemsRef, onScroll, activeIndex, handlePrevious, handleNext } = useContext(CarouselItemsContext);
1254
- const prefersReducedMotion = usePrefersReducedMotion();
1255
- const handleKeyDown = (event)=>{
1256
- // Prevent default behavior when holding down arrow keys (when repeat is true)
1257
- // The default behavior in scroll snapping causes a staggering scroll effect that feels janky
1258
- if (event.repeat && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
1259
- event.preventDefault();
1260
- return;
1451
+ const carouselButtonIconSlotVariants = cva({
1452
+ base: 'transition-transform',
1453
+ variants: {
1454
+ slot: {
1455
+ next: null,
1456
+ prev: null
1457
+ },
1458
+ orientation: {
1459
+ horizontal: null,
1460
+ vertical: null
1261
1461
  }
1262
- // For users with prefers-reduced-motion, trigger button click behavior instead of native scroll
1263
- if (prefersReducedMotion) {
1264
- if (event.key === 'ArrowLeft' && handlePrevious) {
1265
- event.preventDefault();
1266
- handlePrevious();
1267
- } else if (event.key === 'ArrowRight' && handleNext) {
1268
- event.preventDefault();
1269
- handleNext();
1270
- }
1462
+ },
1463
+ compoundVariants: [
1464
+ // horizontal controls
1465
+ {
1466
+ slot: 'next',
1467
+ orientation: 'horizontal',
1468
+ className: 'group-hover:motion-safe:translate-x-1'
1469
+ },
1470
+ {
1471
+ slot: 'prev',
1472
+ orientation: 'horizontal',
1473
+ className: 'group-hover:motion-safe:-translate-x-1 rotate-180'
1474
+ },
1475
+ // vertical controls
1476
+ {
1477
+ slot: 'next',
1478
+ orientation: 'vertical',
1479
+ className: 'rotate-90 group-hover:motion-safe:translate-y-1'
1480
+ },
1481
+ {
1482
+ slot: 'prev',
1483
+ orientation: 'vertical',
1484
+ className: 'group-hover:motion-safe:-translate-y-1 -rotate-90'
1271
1485
  }
1272
- };
1273
- return(// biome-ignore lint/a11y/noStaticElementInteractions: The keydown handler is only to prevent undesired scrolling behavior when using the arrow keys
1274
- /*#__PURE__*/ jsx("div", {
1275
- "data-slot": "carousel-items",
1276
- className: cx(className, [
1277
- 'scrollbar-hidden',
1278
- 'flex',
1279
- 'snap-x',
1280
- 'snap-mandatory',
1281
- 'overflow-x-auto',
1282
- 'outline-none',
1283
- 'rounded-[inherit]'
1284
- ]),
1285
- ref: carouselItemsRef,
1286
- onScroll: onScroll,
1287
- onKeyDown: handleKeyDown,
1288
- children: Children.map(children, (child, index)=>{
1289
- if (/*#__PURE__*/ isValidElement(child)) {
1290
- return /*#__PURE__*/ cloneElement(child, {
1291
- inert: activeIndex === index ? undefined : true
1292
- });
1293
- }
1486
+ ]
1487
+ });
1488
+ const CarouselButton = ({ className, isIconOnly = true, slot, ...rest })=>{
1489
+ const { orientation } = useContext(CarouselContext);
1490
+ return /*#__PURE__*/ jsx(Button, {
1491
+ className: carouselButtonVariants({
1492
+ className
1493
+ }),
1494
+ isIconOnly: isIconOnly,
1495
+ slot: slot,
1496
+ ...rest,
1497
+ children: /*#__PURE__*/ jsx(ChevronRight, {
1498
+ className: carouselButtonIconSlotVariants({
1499
+ orientation,
1500
+ slot
1501
+ })
1294
1502
  })
1295
- }));
1503
+ });
1296
1504
  };
1297
1505
  const CarouselItem = ({ className, children, ...rest })=>{
1298
1506
  return /*#__PURE__*/ jsx("div", {
1299
- className: cx(className, 'shrink-0 basis-full snap-start'),
1507
+ className: cx(className, 'min-w-0 shrink-0 grow-0'),
1300
1508
  "data-slot": "carousel-item",
1301
1509
  ...rest,
1302
1510
  children: /*#__PURE__*/ jsx(Provider, {
@@ -1314,49 +1522,6 @@ const CarouselItem = ({ className, children, ...rest })=>{
1314
1522
  });
1315
1523
  };
1316
1524
 
1317
- const formField = cx('group flex flex-col gap-2');
1318
- 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');
1319
- const input = cva({
1320
- base: [
1321
- // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
1322
- 'bg-white',
1323
- // Use box-content to enable auto width based on number of characters (size)
1324
- // Setting min-height to prevent the input from collapsing in Safari
1325
- // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
1326
- 'box-content min-h-6 py-2.5',
1327
- 'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
1328
- // invalid styles
1329
- 'group-data-invalid:ring-focus group-data-invalid:ring-red',
1330
- // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
1331
- 'appearance-none'
1332
- ],
1333
- variants: {
1334
- // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
1335
- focusModifier: {
1336
- focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
1337
- visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
1338
- },
1339
- isGrouped: {
1340
- false: 'px-3',
1341
- true: '!ring-0 flex-1'
1342
- }
1343
- },
1344
- defaultVariants: {
1345
- focusModifier: 'focus',
1346
- isGrouped: false
1347
- }
1348
- });
1349
- const inputGroup = cx([
1350
- 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
1351
- 'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
1352
- ]);
1353
- const dropdown = {
1354
- 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'),
1355
- // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
1356
- listbox: cx('max-h-100 overflow-x-hidden text-sm outline-hidden'),
1357
- chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
1358
- };
1359
-
1360
1525
  function ErrorMessage(props) {
1361
1526
  const { children, className, ...restProps } = props;
1362
1527
  return /*#__PURE__*/ jsx(Text, {
@@ -1612,7 +1777,7 @@ function Combobox(props) {
1612
1777
  /**
1613
1778
  * A FileTrigger allows a user to access the file system with any pressable React Aria or React Spectrum component, or custom components built with usePress.
1614
1779
  */ const FileTrigger = (props)=>{
1615
- const { onSelect, acceptedFileTypes, allowsMultiple, defaultCamera, children, acceptDirectory, ref, isInvalid, isRequired, name, value, ...rest } = props;
1780
+ const { onSelect, acceptedFileTypes, allowsMultiple, defaultCamera, children, acceptDirectory, ref, isInvalid, isRequired, name, ...rest } = props;
1616
1781
  const inputRef = useObjectRef(ref);
1617
1782
  return /*#__PURE__*/ jsxs(Fragment, {
1618
1783
  children: [
@@ -1661,6 +1826,46 @@ const translations = {
1661
1826
  en: 'Remove'
1662
1827
  }
1663
1828
  };
1829
+ /**
1830
+ * Extracts a simple file extension from a file name or converts MIME type to extension
1831
+ * @param file The file object
1832
+ * @returns A simple file extension (e.g., "pdf", "jpg", "svg")
1833
+ */ function getFileExtension(file) {
1834
+ const match = file.name.match(/\.([^.]+)$/);
1835
+ if (match) {
1836
+ return match[1].toUpperCase();
1837
+ }
1838
+ const mimeType = file.type;
1839
+ if (!mimeType) {
1840
+ return '';
1841
+ }
1842
+ const parts = mimeType.split('/');
1843
+ if (parts.length === 2) {
1844
+ const subtype = parts[1].split('+')[0]; // Handle cases like "svg+xml"
1845
+ return subtype.toUpperCase();
1846
+ }
1847
+ return '';
1848
+ }
1849
+ /**
1850
+ * Formats a file size in bytes to a human-readable string (B, KB, MB, GB, etc.)
1851
+ * @param bytes The file size in bytes
1852
+ * @returns A formatted string with the appropriate unit
1853
+ */ function formatFileSize(bytes) {
1854
+ if (bytes === 0) {
1855
+ return '0 B';
1856
+ }
1857
+ const units = [
1858
+ 'B',
1859
+ 'KB',
1860
+ 'MB',
1861
+ 'GB',
1862
+ 'TB'
1863
+ ];
1864
+ const base = 1024;
1865
+ const unitIndex = Math.floor(Math.log(bytes) / Math.log(base));
1866
+ const size = bytes / base ** unitIndex;
1867
+ return `${size.toFixed(2).replace(/\.?0+$/, '')} ${units[unitIndex]}`;
1868
+ }
1664
1869
  /**
1665
1870
  * Converts an array of files to a DataTransfer object which can be used as a FileList.
1666
1871
  * This is necessary for setting the files on a native file input.
@@ -1688,7 +1893,7 @@ const translations = {
1688
1893
  const extension = fileName.match(/(\.[^.]+)$/)?.[0] || '';
1689
1894
  if (!fileNameCounts[baseName]) {
1690
1895
  // Extract any number from the file name (if any, otherwise default to 0)
1691
- const baseNameCount = Number.parseInt(fileName.match(/\((\d+)\)/)?.[1] ?? '0');
1896
+ const baseNameCount = Number.parseInt(fileName.match(/\((\d+)\)/)?.[1] ?? '0', 10);
1692
1897
  fileNameCounts[baseName] = baseNameCount;
1693
1898
  }
1694
1899
  fileNameCounts[baseName]++;
@@ -1837,11 +2042,34 @@ const FileUpload = ({ children, files: _files, onChange, validate, isInvalid: _i
1837
2042
  return /*#__PURE__*/ jsxs("li", {
1838
2043
  children: [
1839
2044
  /*#__PURE__*/ jsxs("div", {
1840
- className: cx('flex items-center justify-between gap-2 rounded-lg border-2 px-4 py-2', hasError ? 'border-red bg-red-light' : 'border-gray bg-gray-lightest'),
2045
+ className: cx('flex items-center justify-between gap-3 rounded-lg border p-1.5', hasError ? 'border-red bg-red-light' : 'border-gray'),
1841
2046
  children: [
1842
- fileName,
2047
+ /*#__PURE__*/ jsxs("div", {
2048
+ className: "flex items-center gap-3",
2049
+ children: [
2050
+ /*#__PURE__*/ jsx("div", {
2051
+ className: "footnote rounded-md border border-gray-light bg-gray-lightest px-2.5 py-2",
2052
+ children: getFileExtension(file)
2053
+ }),
2054
+ /*#__PURE__*/ jsxs("div", {
2055
+ className: "flex flex-col",
2056
+ children: [
2057
+ /*#__PURE__*/ jsx("span", {
2058
+ className: "description truncate font-medium",
2059
+ children: fileName
2060
+ }),
2061
+ /*#__PURE__*/ jsx("span", {
2062
+ className: "footnote text-gray-dark",
2063
+ children: formatFileSize(file.size)
2064
+ })
2065
+ ]
2066
+ })
2067
+ ]
2068
+ }),
1843
2069
  /*#__PURE__*/ jsx("button", {
1844
- className: cx('self-start', '-m-2 grid h-11 w-11 shrink-0 cursor-pointer place-items-center rounded-xl', // Focus styles
2070
+ type: "button",
2071
+ "aria-label": translations.remove[locale],
2072
+ className: cx('-m-2 grid h-11 w-11 shrink-0 cursor-pointer place-items-center rounded-xl', // Focus styles
1845
2073
  'focus-visible:-outline-offset-8 focus-visible:outline-focus'),
1846
2074
  onClick: ()=>{
1847
2075
  // For controlled component
@@ -1852,8 +2080,6 @@ const FileUpload = ({ children, files: _files, onChange, validate, isInvalid: _i
1852
2080
  // (without this, the focus will be set to the top of the page for screen readers)
1853
2081
  buttonRef.current?.focus();
1854
2082
  },
1855
- "aria-label": translations.remove[locale],
1856
- type: "button",
1857
2083
  children: /*#__PURE__*/ jsx(Trash, {})
1858
2084
  })
1859
2085
  ]
@@ -1885,117 +2111,6 @@ function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
1885
2111
  });
1886
2112
  }
1887
2113
 
1888
- const roundedMediaCorners = '*:data-[slot="media"]:*:rounded-3xl';
1889
- // Common variant for "standard" and "full-bleed" Hero variants
1890
- const oneColumnLayout = [
1891
- // Vertical spacing in the <Content>
1892
- 'lg:*:data-[slot="content"]:gap-y-4',
1893
- // Main text content takes up 9 columns on medium screens and above
1894
- 'lg:*:data-[slot="content"]:col-span-9',
1895
- // Make sure other elements than <Content> and <Media> (i.e CTA) does not span the full width on small screens
1896
- '*:not-data-[slot="content"]:not-data-[slot="media"]:w-fit',
1897
- // Other elements than <Content> and <Media> (e.g. CTA, SVG logo or Badge) take up 3 columns on medium screens and above, and are right aligned
1898
- 'lg:*:not-data-[slot="content"]:not-data-[slot="media"]:not-data-[slot="carousel"]:col-span-3 lg:*:not-data-[slot="content"]:not-data-[slot="media"]:justify-self-end',
1899
- // <Media> and <Carousel> content takes up the full width on medium screens and above
1900
- 'lg:*:data-[slot="media"]:col-span-full *:data-[slot="media"]:*:w-full',
1901
- 'lg:*:data-[slot="carousel"]:col-span-full *:data-[slot="carousel"]:*:w-full',
1902
- // Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
1903
- 'lg:items-end'
1904
- ];
1905
- const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot="media"]:*:aspect-[1/1] sm:*:data-[slot="media"]:*:aspect-4/3 md:*:data-[slot="media"]:*:aspect-3/2';
1906
- const variants = cva({
1907
- base: [
1908
- 'container px-0',
1909
- // Grid variant to position the Hero's content
1910
- 'grid lg:grid-cols-12 lg:gap-x-12 xl:gap-x-16',
1911
- 'gap-y-10 lg:gap-y-12',
1912
- // Enable vertical gap within <Content>
1913
- '*:data-[slot="content"]:grid',
1914
- // Vertical spacing in the <Content>
1915
- '*:data-[slot="content"]:gap-y-3',
1916
- // Make sure <Media> content fills any available vertical and horizontal space
1917
- '*:data-[slot="media"]:*:object-cover'
1918
- ],
1919
- variants: {
1920
- /**
1921
- * Defines the variant of the Hero
1922
- * @default standard
1923
- * */ variant: {
1924
- standard: [
1925
- roundedMediaCorners,
1926
- oneColumnLayout,
1927
- nonFullBleedAspectRatiosForSmallScreens,
1928
- 'lg:*:data-[slot="media"]:*:aspect-2/1'
1929
- ],
1930
- 'full-bleed': [
1931
- oneColumnLayout,
1932
- // Position the media and carousel content to fill the entire viewport width
1933
- '*:data-[slot="media"]:*:absolute *:data-[slot="media"]:*:left-0',
1934
- // Special case for Carousel, where the Media is nested inside a CarouselItem
1935
- '*:data-[slot="carousel"]:**:data-[slot="media"]:w-full *:data-[slot="carousel"]:*:absolute *:data-[slot="carousel"]:*:left-0',
1936
- // Match the heights of the <Media> or <Carousel> wrapper for the Media content (e.g. image, VideoLoop, video etc.)
1937
- // This is necessary due to the absolute positioning of the media and carousel containers in this variant
1938
- // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1939
- '**:data-[slot="media"]:h-80 sm:**:data-[slot="media"]:h-[25rem] md:**:data-[slot="media"]:h-[30rem] lg:**:data-[slot="media"]:h-[35rem] xl:**:data-[slot="media"]:h-[40rem] 2xl:**:data-[slot="media"]:h-[42rem] 3xl:**:data-[slot="media"]:h-[48rem] 4xl:**:data-[slot="media"]:h-[53rem]',
1940
- // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1941
- '**:data-[slot="media"]:*:h-80 sm:**:data-[slot="media"]:*:h-[25rem] md:**:data-[slot="media"]:*:h-[30rem] lg:**:data-[slot="media"]:*:h-[35rem] xl:**:data-[slot="media"]:*:h-[40rem] 2xl:**:data-[slot="media"]:*:h-[42rem] 3xl:**:data-[slot="media"]:*:h-[48rem] 4xl:**:data-[slot="media"]:*:h-[53rem]',
1942
- // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1943
- '*:data-[slot="carousel"]:h-80 sm:*:data-[slot="carousel"]:h-[25rem] md:*:data-[slot="carousel"]:h-[30rem] lg:*:data-[slot="carousel"]:h-[35rem] xl:*:data-[slot="carousel"]:h-[40rem] 2xl:*:data-[slot="carousel"]:h-[42rem] 3xl:*:data-[slot="carousel"]:h-[48rem] 4xl:*:data-[slot="carousel"]:h-[53rem]',
1944
- // Override aspect ratio of the media and carousel-item slots (since we can not use aspect for full-bleed layout)
1945
- '**:data-[slot="carousel-item"]:data-[slot="media"]:*:aspect-none',
1946
- '**:data-[slot="carousel-controls"]:container **:data-[slot="carousel-controls"]:right-0 **:data-[slot="carousel-controls"]:bottom-4 **:data-[slot="carousel-controls"]:left-0 **:data-[slot="carousel-controls"]:justify-end',
1947
- // Override rounded corners of Carousel slots
1948
- '*:data-[slot="carousel"]:*:rounded-none'
1949
- ],
1950
- 'two-column': [
1951
- 'lg:items-center lg:*:col-span-6',
1952
- // Vertical spacing in the <Content>
1953
- 'lg:*:data-[slot="content"]:gap-y-7',
1954
- roundedMediaCorners,
1955
- nonFullBleedAspectRatiosForSmallScreens,
1956
- // Set media aspect ratio to 1:1 (square)
1957
- 'lg:*:data-[slot="media"]:*:aspect-[1/1]'
1958
- ]
1959
- }
1960
- },
1961
- defaultVariants: {
1962
- variant: 'standard'
1963
- }
1964
- });
1965
- const Hero = ({ variant, className, children, ...rest })=>{
1966
- const variantsClassName = variants({
1967
- variant,
1968
- className
1969
- });
1970
- return /*#__PURE__*/ jsx(Provider, {
1971
- values: [
1972
- [
1973
- HeadingContext,
1974
- {
1975
- // Sets the default heading size for the Hero based on the variant
1976
- size: variant === 'two-column' ? 'xl' : 'l',
1977
- className: // word-break:break-word to allow long words to break (this is necessary to make hyphens work in grid containers in Safari)
1978
- 'hyphens-auto text-pretty [word-break:break-word]'
1979
- }
1980
- ],
1981
- [
1982
- GroupContext,
1983
- {
1984
- // Prevents the group from being announced as a group by screen readers
1985
- // The Group component is used to group the Hero's CTA buttons together visually, and has no semantic meaning
1986
- role: 'presentation',
1987
- className: 'flex flex-wrap gap-3 *:w-fit'
1988
- }
1989
- ]
1990
- ],
1991
- children: /*#__PURE__*/ jsx("div", {
1992
- className: cx(variantsClassName, className),
1993
- ...rest,
1994
- children: children
1995
- })
1996
- });
1997
- };
1998
-
1999
2114
  // Sets the correct icons for each link in the link list
2000
2115
  const _LinkProvider = ({ children })=>/*#__PURE__*/ jsx(Provider, {
2001
2116
  values: [
@@ -2110,30 +2225,28 @@ const Modal = ({ isDismissable = true, isOpen, onOpenChange, defaultOpen, classN
2110
2225
  };
2111
2226
  const Dialog = ({ className, children, ...restProps })=>/*#__PURE__*/ jsx(Dialog$1, {
2112
2227
  ...restProps,
2113
- className: cx('relative grid gap-y-5 outline-none', // Footer
2228
+ className: cx(className, 'relative grid gap-y-5 outline-none', // Footer
2114
2229
  '[&_[data-slot="footer"]]:flex [&_[data-slot="footer"]]:gap-x-2'),
2115
- children: ({ close })=>/*#__PURE__*/ jsx(Fragment, {
2116
- children: /*#__PURE__*/ jsx(Provider, {
2117
- values: [
2118
- [
2119
- ButtonContext$1,
2120
- {
2121
- // This is necessary to support multiple close buttons
2122
- slots: {
2123
- // We need to define default slot in order to also support non-slotted buttons (i.e. buttons without slot prop)
2124
- [DEFAULT_SLOT]: {
2125
- className: 'w-fit'
2126
- },
2127
- close: {
2128
- onPress: close,
2129
- className: 'w-fit'
2130
- }
2230
+ children: ({ close })=>/*#__PURE__*/ jsx(Provider, {
2231
+ values: [
2232
+ [
2233
+ ButtonContext$1,
2234
+ {
2235
+ // This is necessary to support multiple close buttons
2236
+ slots: {
2237
+ // We need to define default slot in order to also support non-slotted buttons (i.e. buttons without slot prop)
2238
+ [DEFAULT_SLOT]: {
2239
+ className: 'w-fit'
2240
+ },
2241
+ close: {
2242
+ onPress: close,
2243
+ className: 'w-fit'
2131
2244
  }
2132
2245
  }
2133
- ]
2134
- ],
2135
- children: children
2136
- })
2246
+ }
2247
+ ]
2248
+ ],
2249
+ children: children
2137
2250
  })
2138
2251
  });
2139
2252
 
@@ -2701,7 +2814,7 @@ const tabsVariants = cva({
2701
2814
  const { className, children, ...restProps } = props;
2702
2815
  return /*#__PURE__*/ jsx(Tab$1, {
2703
2816
  ...restProps,
2704
- className: cx(className, 'data-focus-visible:-outline-offset-10 data-focus-visible:outline-2 data-focus-visible:outline-black', 'cursor-pointer border-transparent px-4 py-2 font-light text-sm', // Transition
2817
+ className: cx(className, 'data-focus-visible:-outline-offset-10 data-focus-visible:outline-2 data-focus-visible:outline-black', 'description cursor-pointer border-transparent px-4 py-2 font-light', // Transition
2705
2818
  'transition-colors duration-150 ease-out', // TODO: Should disabled tabs just be hidden?
2706
2819
  'data-disabled:cursor-not-allowed data-disabled:opacity-50', // Selection
2707
2820
  'data-selected:font-medium data-selected:text-blue-dark', // Hover with layout shift prevention using pseudo-element
@@ -2962,4 +3075,4 @@ const VideoLoop = ({ src, format, alt, className })=>{
2962
3075
  });
2963
3076
  };
2964
3077
 
2965
- export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Label, LinkList, LinkListContainer, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, FileUpload as UNSAFE_FileUpload, Hero as UNSAFE_Hero, Link as UNSAFE_Link, Modal as UNSAFE_Modal, ProgressBar as UNSAFE_ProgressBar, ProgressBarValueText as UNSAFE_ProgressBarValueText, Tab as UNSAFE_Tab, TabList as UNSAFE_TabList, TabPanel as UNSAFE_TabPanel, Table as UNSAFE_Table, TableBody as UNSAFE_TableBody, TableCell as UNSAFE_TableCell, TableColumn as UNSAFE_TableColumn, TableColumnResizer as UNSAFE_TableColumnResizer, TableContainer as UNSAFE_TableContainer, TableHeader as UNSAFE_TableHeader, TableRow as UNSAFE_TableRow, Tabs as UNSAFE_Tabs, VideoLoop, _LinkContext, _useLocale as useLocale };
3078
+ export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Label, LinkList, LinkListContainer, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselButton as UNSAFE_CarouselButton, CarouselContext as UNSAFE_CarouselContext, CarouselControls as UNSAFE_CarouselControls, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, CarouselItemsContainer as UNSAFE_CarouselItemsContainer, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, FileUpload as UNSAFE_FileUpload, Hero as UNSAFE_Hero, HeroContext as UNSAFE_HeroContext, Link as UNSAFE_Link, Modal as UNSAFE_Modal, ProgressBar as UNSAFE_ProgressBar, ProgressBarValueText as UNSAFE_ProgressBarValueText, Tab as UNSAFE_Tab, TabList as UNSAFE_TabList, TabPanel as UNSAFE_TabPanel, Table as UNSAFE_Table, TableBody as UNSAFE_TableBody, TableCell as UNSAFE_TableCell, TableColumn as UNSAFE_TableColumn, TableColumnResizer as UNSAFE_TableColumnResizer, TableContainer as UNSAFE_TableContainer, TableHeader as UNSAFE_TableHeader, TableRow as UNSAFE_TableRow, Tabs as UNSAFE_Tabs, VideoLoop, _LinkContext, _useLocale as useLocale };