@obosbbl/grunnmuren-react 3.3.5 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
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, Radio as Radio$1, RadioGroup as RadioGroup$1, Select as Select$1, SelectValue, ProgressBar as ProgressBar$1, LinkContext, 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, useMemo, useCallback, isValidElement, cloneElement } from 'react';
7
- import { ChevronDown, Error, Warning, CheckCircle, InfoCircle, Close, User, ChevronLeft, ChevronRight, LoadingSpinner, Check, Trash, ArrowRight, Download, LinkExternal, PlayerPause, PlayerPlay } from '@obosbbl/grunnmuren-icons-react';
6
+ import { createContext, useContext, useId, useRef, Children, useState, useEffect, useMemo, useCallback, isValidElement, cloneElement, use } from 'react';
7
+ import { ChevronDown, Error, Warning, CheckCircle, InfoCircle, Close, User, ChevronLeft, ChevronRight, LoadingSpinner, Check, Trash, Download, LinkExternal, ArrowRight, Edit, PlayerPause, PlayerPlay } from '@obosbbl/grunnmuren-icons-react';
8
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';
@@ -357,15 +357,20 @@ const translations$1 = {
357
357
  sv: 'Nästa',
358
358
  en: 'Next'
359
359
  },
360
- carousel: {
361
- nb: 'Karusell',
362
- sv: 'Karusell',
363
- en: 'Carousel'
364
- },
365
360
  externalLink: {
366
361
  nb: '(ekstern lenke)',
367
362
  sv: '(extern länk)',
368
363
  en: '(external link)'
364
+ },
365
+ stepper: {
366
+ nb: 'Steg',
367
+ sv: 'Steg',
368
+ en: 'Steps'
369
+ },
370
+ completed: {
371
+ nb: 'Fullført',
372
+ sv: 'Slutförd',
373
+ en: 'Completed'
369
374
  }
370
375
  };
371
376
 
@@ -495,44 +500,73 @@ const Avatar = ({ src, alt = '', className, onError, loading = 'lazy', ...rest }
495
500
  });
496
501
  };
497
502
 
498
- const _LinkContext = /*#__PURE__*/ createContext({});
499
- const Link = ({ ref: _ref, ..._props })=>{
500
- const [props, ref] = useContextProps(_props, _ref, _LinkContext);
501
- const { className, _innerWrapper, children: _children, ...restProps } = props;
502
- const locale = _useLocale();
503
- const externalLinkSR = props.rel?.includes('external') ? /*#__PURE__*/ jsx("span", {
504
- className: "sr-only",
505
- children: translations$1.externalLink[locale]
506
- }) : null;
507
- const reactNodeChildren = /*#__PURE__*/ jsxs(Fragment, {
508
- children: [
509
- _children,
510
- externalLinkSR
511
- ]
512
- });
513
- const children = _innerWrapper ? _innerWrapper({
514
- ...restProps,
515
- children: typeof _children === 'function' ? (values)=>_children(values) : reactNodeChildren
516
- }) : reactNodeChildren;
517
- return /*#__PURE__*/ jsx(Link$1, {
518
- ...restProps,
519
- ref: ref,
520
- "data-slot": "link",
521
- 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'),
522
- children: children
523
- });
503
+ const formField = cx('group flex flex-col gap-2');
504
+ 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');
505
+ const input = cva({
506
+ base: [
507
+ // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
508
+ 'bg-white',
509
+ // Use box-content to enable auto width based on number of characters (size)
510
+ // Setting min-height to prevent the input from collapsing in Safari
511
+ // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
512
+ 'box-content min-h-6 py-2.5',
513
+ 'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
514
+ // invalid styles
515
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red',
516
+ // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
517
+ 'appearance-none'
518
+ ],
519
+ variants: {
520
+ // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
521
+ focusModifier: {
522
+ focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
523
+ visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
524
+ },
525
+ isGrouped: {
526
+ false: 'px-3',
527
+ true: '!ring-0 flex-1'
528
+ }
529
+ },
530
+ defaultVariants: {
531
+ focusModifier: 'focus',
532
+ isGrouped: false
533
+ }
534
+ });
535
+ const inputGroup = cx([
536
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
537
+ 'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
538
+ ]);
539
+ const dropdown = {
540
+ 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'),
541
+ // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
542
+ listbox: cx('max-h-100 overflow-x-hidden text-sm outline-hidden'),
543
+ chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
524
544
  };
545
+ const animateIconVariants = cva({
546
+ base: '*:[svg]:shrink-0 *:[svg]:transition-transform',
547
+ variants: {
548
+ animateIcon: {
549
+ right: 'hover:*:[svg]:motion-safe:translate-x-1',
550
+ left: 'hover:*:[svg]:motion-safe:-translate-x-1',
551
+ down: 'hover:*:[svg]:motion-safe:translate-y-1',
552
+ up: 'hover:*:[svg]:motion-safe:-translate-y-1',
553
+ 'up-right': 'hover:*:[svg]:motion-safe:-translate-y-0.5 hover:*:[svg]:motion-safe:translate-x-0.5'
554
+ }
555
+ }
556
+ });
525
557
 
526
558
  function isLinkProps$1(props) {
527
559
  return !!props.href;
528
560
  }
529
561
  function Backlink(props) {
530
- const { className, style, children, withUnderline, ref, ...restProps } = props;
531
- 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');
562
+ const { className, children, withUnderline, ref, ...restProps } = props;
563
+ const _className = cx(className, animateIconVariants({
564
+ animateIcon: 'left'
565
+ }), 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 font-normal no-underline focus-visible:outline-focus');
532
566
  const content = /*#__PURE__*/ jsxs(Fragment, {
533
567
  children: [
534
568
  /*#__PURE__*/ jsx(ChevronLeft, {
535
- className: cx('-ml-[0.5em] group-hover:-translate-x-1 duration-300')
569
+ className: cx('-ml-[0.5em] duration-300')
536
570
  }),
537
571
  /*#__PURE__*/ jsx("span", {
538
572
  children: /*#__PURE__*/ jsx("span", {
@@ -543,10 +577,9 @@ function Backlink(props) {
543
577
  ]
544
578
  });
545
579
  if (isLinkProps$1(props)) {
546
- return /*#__PURE__*/ jsx(Link, {
580
+ return /*#__PURE__*/ jsx(Link$1, {
547
581
  ...restProps,
548
582
  className: _className,
549
- style: style,
550
583
  ref: ref,
551
584
  children: content
552
585
  });
@@ -554,7 +587,6 @@ function Backlink(props) {
554
587
  return /*#__PURE__*/ jsx(Button$1, {
555
588
  ...restProps,
556
589
  className: _className,
557
- style: style,
558
590
  ref: ref,
559
591
  children: content
560
592
  });
@@ -597,6 +629,30 @@ function Badge(props) {
597
629
  });
598
630
  }
599
631
 
632
+ const linkVariants = compose(animateIconVariants, cva({
633
+ 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 data-disabled:font-normal data-disabled:no-underline'
634
+ }));
635
+ const Link = ({ animateIcon, children, className, '~iconRight': iconRight, ...props })=>{
636
+ const locale = _useLocale();
637
+ const externalLinkText = translations$1.externalLink[locale];
638
+ return /*#__PURE__*/ jsxs(Link$1, {
639
+ ...props,
640
+ "data-slot": "link",
641
+ className: linkVariants({
642
+ className,
643
+ animateIcon
644
+ }),
645
+ children: [
646
+ children,
647
+ props.rel?.includes('external') && /*#__PURE__*/ jsx("span", {
648
+ className: "sr-only",
649
+ children: externalLinkText
650
+ }),
651
+ iconRight
652
+ ]
653
+ });
654
+ };
655
+
600
656
  function Breadcrumb(props) {
601
657
  const { className, children, href, ...restProps } = props;
602
658
  return /*#__PURE__*/ jsxs(Breadcrumb$1, {
@@ -627,38 +683,38 @@ function Breadcrumbs(props) {
627
683
 
628
684
  /**
629
685
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
630
- */ const buttonVariants = cva({
686
+ */ const buttonVariants = compose(animateIconVariants, cva({
631
687
  base: [
632
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'
633
689
  ],
634
690
  variants: {
635
691
  /**
636
- * The variant of the button
637
- * @default primary
638
- */ variant: {
692
+ * The variant of the button
693
+ * @default primary
694
+ */ variant: {
639
695
  primary: 'no-underline',
640
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
641
697
  secondary: 'border-2 border-current no-underline hover:border-transparent',
642
698
  tertiary: 'underline hover:no-underline'
643
699
  },
644
700
  /**
645
- * Adjusts the color of the button for usage on different backgrounds.
646
- * @default blue
647
- */ color: {
701
+ * Adjusts the color of the button for usage on different backgrounds.
702
+ * @default blue
703
+ */ color: {
648
704
  blue: 'focus-visible:outline-focus',
649
705
  mint: 'focus-visible:outline-focus focus-visible:outline-mint',
650
706
  white: 'focus-visible:outline-focus focus-visible:outline-white'
651
707
  },
652
708
  /**
653
- * When the button is without text, but with a single icon.
654
- * @default false
655
- */ isIconOnly: {
709
+ * When the button is without text, but with a single icon.
710
+ * @default false
711
+ */ isIconOnly: {
656
712
  true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
657
713
  false: 'gap-2.5 px-4 py-2'
658
714
  },
659
715
  // Make the content of the button transparent to hide it's content, but keep the button width
660
716
  isPending: {
661
- true: '!text-transparent relative',
717
+ true: 'relative text-transparent!',
662
718
  false: null
663
719
  }
664
720
  },
@@ -717,15 +773,17 @@ function Breadcrumbs(props) {
717
773
  isIconOnly: false,
718
774
  isPending: false
719
775
  }
720
- });
776
+ }));
721
777
  const ButtonContext = /*#__PURE__*/ createContext({});
722
778
  function isLinkProps(props) {
723
779
  return !!props.href;
724
780
  }
725
781
  function Button({ ref = null, ...props }) {
726
782
  [props, ref] = useContextProps(props, ref, ButtonContext);
727
- const { children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
783
+ const { animateIcon, children: _children, color, isIconOnly, variant, isPending, ...restProps } = props;
728
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,
729
787
  className: props.className,
730
788
  color,
731
789
  isIconOnly,
@@ -985,6 +1043,137 @@ const cardLinkVariants = cva({
985
1043
  });
986
1044
  };
987
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
+
988
1177
  /**
989
1178
  * Hook that detects the user's preference for reduced motion.
990
1179
  *
@@ -1008,6 +1197,32 @@ const cardLinkVariants = cva({
1008
1197
  return prefersReducedMotion;
1009
1198
  }
1010
1199
 
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
+ };
1011
1226
  const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0, orientation = 'horizontal', onSelect, onSettled, loop = false, scrollGestures = false, ref, ...rest })=>{
1012
1227
  const carouselRef = useRef(null);
1013
1228
  const prefersReducedMotion = usePrefersReducedMotion() ?? false;
@@ -1040,6 +1255,10 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1040
1255
  const [slidesInView, setSlidesInView] = useState([
1041
1256
  initialIndex
1042
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);
1043
1262
  const previousSettledScrollIndex = useRef(initialIndex);
1044
1263
  useEffect(()=>{
1045
1264
  if (!emblaApi) {
@@ -1050,6 +1269,8 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1050
1269
  switch(type){
1051
1270
  case 'select':
1052
1271
  onSelect?.(scrollSnapIndex);
1272
+ setCanScrollNext(emblaApi.canScrollNext());
1273
+ setCanScrollPrev(emblaApi.canScrollPrev());
1053
1274
  break;
1054
1275
  case 'settle':
1055
1276
  {
@@ -1066,15 +1287,23 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1066
1287
  setSlidesInView(emblaApi.slidesInView());
1067
1288
  break;
1068
1289
  }
1290
+ case 'init':
1291
+ {
1292
+ setCanScrollNext(emblaApi.canScrollNext());
1293
+ setCanScrollPrev(emblaApi.canScrollPrev());
1294
+ break;
1295
+ }
1069
1296
  }
1070
1297
  };
1071
1298
  emblaApi.on('select', emblaHandler);
1072
1299
  emblaApi.on('slidesInView', emblaHandler);
1073
1300
  emblaApi.on('settle', emblaHandler);
1301
+ emblaApi.on('init', emblaHandler);
1074
1302
  return ()=>{
1075
1303
  emblaApi.off('select', emblaHandler);
1076
1304
  emblaApi.off('settle', emblaHandler);
1077
1305
  emblaApi.off('slidesInView', emblaHandler);
1306
+ emblaApi.off('init', emblaHandler);
1078
1307
  };
1079
1308
  }, [
1080
1309
  emblaApi,
@@ -1088,8 +1317,8 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1088
1317
  emblaApi.plugins().autoplay?.stop();
1089
1318
  emblaApi.scrollNext(prefersReducedMotion);
1090
1319
  // we need to move focus if we are about to disable this button due to start/end of carousel
1091
- if (!loop && !emblaApi.canScrollNext() && carouselRef.current?.querySelector('button[slot="next"]')?.matches(':focus-visible')) {
1092
- carouselRef.current?.querySelector('button[slot="prev"]')?.focus();
1320
+ if (!loop && !emblaApi.canScrollNext() && getCarouselButton(carouselRef, 'next')?.matches(':focus-visible')) {
1321
+ getCarouselButton(carouselRef, 'prev')?.focus();
1093
1322
  }
1094
1323
  }, [
1095
1324
  emblaApi,
@@ -1103,8 +1332,8 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1103
1332
  emblaApi.plugins().autoplay?.stop();
1104
1333
  emblaApi.scrollPrev(prefersReducedMotion);
1105
1334
  // we need to move focus if we are about to disable this button due to start/end of carousel
1106
- if (!loop && !emblaApi.canScrollPrev() && carouselRef.current?.querySelector('button[slot="prev"]')?.matches(':focus-visible')) {
1107
- carouselRef.current?.querySelector('button[slot="next"]')?.focus();
1335
+ if (!loop && !emblaApi.canScrollPrev() && getCarouselButton(carouselRef, 'prev')?.matches(':focus-visible')) {
1336
+ getCarouselButton(carouselRef, 'next')?.focus();
1108
1337
  }
1109
1338
  }, [
1110
1339
  emblaApi,
@@ -1113,24 +1342,40 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1113
1342
  ]);
1114
1343
  const locale = _useLocale();
1115
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');
1116
1347
  if (e.key === 'ArrowRight' && !e.repeat) {
1117
1348
  handleNextPress();
1349
+ // Focus first focusable element in the next slide, unless a carousel button has focus
1350
+ if (!carouselButtonHasFocus) {
1351
+ focusElementInSnappedSlide(emblaApi);
1352
+ }
1118
1353
  } else if (e.key === 'ArrowLeft' && !e.repeat) {
1119
1354
  handlePrevPress();
1355
+ // Focus first focusable element in the previous slide, unless a carousel button has focus
1356
+ if (!carouselButtonHasFocus) {
1357
+ focusElementInSnappedSlide(emblaApi);
1358
+ }
1120
1359
  }
1121
1360
  }, [
1122
1361
  handleNextPress,
1123
- handlePrevPress
1362
+ handlePrevPress,
1363
+ emblaApi
1124
1364
  ]);
1125
- return(// biome-ignore lint/a11y/useSemanticElements: we want to use a div
1365
+ const hasHeroContext = !!useContext(HeroContext);
1366
+ const nextPrevStyles = hasHeroContext ? {
1367
+ color: 'white',
1368
+ variant: 'primary'
1369
+ } : {
1370
+ variant: 'tertiary'
1371
+ };
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
1126
1373
  /*#__PURE__*/ jsx("div", {
1127
1374
  ...rest,
1128
1375
  "data-orientation": orientation,
1129
1376
  "data-slot": "carousel",
1130
1377
  ref: mergeRefs(ref, carouselRef),
1131
1378
  onKeyDown: handleKeyDown,
1132
- role: "region",
1133
- "aria-label": translations$1.carousel[locale],
1134
1379
  children: /*#__PURE__*/ jsx(Provider, {
1135
1380
  values: [
1136
1381
  [
@@ -1148,13 +1393,15 @@ const Carousel = ({ autoPlayDelay, align = 'center', children, initialIndex = 0,
1148
1393
  [DEFAULT_SLOT]: {},
1149
1394
  prev: {
1150
1395
  'aria-label': translations$1.previous[locale],
1151
- isDisabled: !emblaApi?.canScrollPrev(),
1152
- onPress: handlePrevPress
1396
+ isDisabled: !canScrollPrev,
1397
+ onPress: handlePrevPress,
1398
+ ...nextPrevStyles
1153
1399
  },
1154
1400
  next: {
1155
1401
  'aria-label': translations$1.next[locale],
1156
- isDisabled: !emblaApi?.canScrollNext(),
1157
- onPress: handleNextPress
1402
+ isDisabled: !canScrollNext,
1403
+ onPress: handleNextPress,
1404
+ ...nextPrevStyles
1158
1405
  }
1159
1406
  }
1160
1407
  }
@@ -1180,17 +1427,11 @@ const CarouselItemsContainer = ({ children, className, ...rest })=>{
1180
1427
  });
1181
1428
  };
1182
1429
  const CarouselItems = ({ className, children })=>{
1183
- const { slidesInView, orientation } = useContext(CarouselContext);
1430
+ const { orientation } = useContext(CarouselContext);
1184
1431
  return /*#__PURE__*/ jsx("div", {
1185
1432
  className: cx(className, 'flex', orientation === 'vertical' && 'flex-col'),
1186
1433
  "data-slot": "carousel-items",
1187
- children: Children.map(children, (child, index)=>{
1188
- if (/*#__PURE__*/ isValidElement(child)) {
1189
- return /*#__PURE__*/ cloneElement(child, {
1190
- inert: slidesInView.includes(index) ? undefined : true
1191
- });
1192
- }
1193
- })
1434
+ children: children
1194
1435
  });
1195
1436
  };
1196
1437
  /**
@@ -1200,6 +1441,8 @@ const CarouselItems = ({ className, children })=>{
1200
1441
  className: cx(className, 'flex justify-end gap-x-2'),
1201
1442
  "data-slot": "carousel-controls",
1202
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",
1203
1446
  children: children
1204
1447
  });
1205
1448
  const carouselButtonVariants = cva({
@@ -1242,7 +1485,7 @@ const carouselButtonIconSlotVariants = cva({
1242
1485
  }
1243
1486
  ]
1244
1487
  });
1245
- const CarouselButton = ({ className, isIconOnly = true, color = 'white', variant = 'primary', slot, ...rest })=>{
1488
+ const CarouselButton = ({ className, isIconOnly = true, slot, ...rest })=>{
1246
1489
  const { orientation } = useContext(CarouselContext);
1247
1490
  return /*#__PURE__*/ jsx(Button, {
1248
1491
  className: carouselButtonVariants({
@@ -1250,8 +1493,6 @@ const CarouselButton = ({ className, isIconOnly = true, color = 'white', variant
1250
1493
  }),
1251
1494
  isIconOnly: isIconOnly,
1252
1495
  slot: slot,
1253
- variant: variant,
1254
- color: color,
1255
1496
  ...rest,
1256
1497
  children: /*#__PURE__*/ jsx(ChevronRight, {
1257
1498
  className: carouselButtonIconSlotVariants({
@@ -1281,49 +1522,6 @@ const CarouselItem = ({ className, children, ...rest })=>{
1281
1522
  });
1282
1523
  };
1283
1524
 
1284
- const formField = cx('group flex flex-col gap-2');
1285
- 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');
1286
- const input = cva({
1287
- base: [
1288
- // All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
1289
- 'bg-white',
1290
- // Use box-content to enable auto width based on number of characters (size)
1291
- // Setting min-height to prevent the input from collapsing in Safari
1292
- // Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
1293
- 'box-content min-h-6 py-2.5',
1294
- 'rounded-md font-normal text-base leading-6 placeholder-[#727070] outline-hidden ring-1 ring-black',
1295
- // invalid styles
1296
- 'group-data-invalid:ring-focus group-data-invalid:ring-red',
1297
- // Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
1298
- 'appearance-none'
1299
- ],
1300
- variants: {
1301
- // Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
1302
- focusModifier: {
1303
- focus: 'focus:ring-focus group-data-invalid:focus:ring-3 group-data-invalid:focus:ring-red',
1304
- visible: 'data-focus-visible:ring-focus group-data-invalid:data-focus-visible:ring-3 group-data-invalid:data-focus-visible:ring-red'
1305
- },
1306
- isGrouped: {
1307
- false: 'px-3',
1308
- true: '!ring-0 flex-1'
1309
- }
1310
- },
1311
- defaultVariants: {
1312
- focusModifier: 'focus',
1313
- isGrouped: false
1314
- }
1315
- });
1316
- const inputGroup = cx([
1317
- 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
1318
- 'group-data-invalid:ring-focus group-data-invalid:ring-red group-data-invalid:focus-within:ring-3 group-data-invalid:focus-within:ring-red'
1319
- ]);
1320
- const dropdown = {
1321
- 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'),
1322
- // overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
1323
- listbox: cx('max-h-100 overflow-x-hidden text-sm outline-hidden'),
1324
- chevronIcon: cx('text-base transition-transform duration-150 group-data-open:rotate-180 motion-reduce:transition-none')
1325
- };
1326
-
1327
1525
  function ErrorMessage(props) {
1328
1526
  const { children, className, ...restProps } = props;
1329
1527
  return /*#__PURE__*/ jsx(Text, {
@@ -1430,7 +1628,7 @@ function Description(props) {
1430
1628
  function Label(props) {
1431
1629
  const { children, className, ...restProps } = props;
1432
1630
  return /*#__PURE__*/ jsx(Label$1, {
1433
- className: cx(className, 'font-semibold leading-7'),
1631
+ className: cx(className, 'font-medium leading-7'),
1434
1632
  ...restProps,
1435
1633
  children: children
1436
1634
  });
@@ -1913,185 +2111,77 @@ function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
1913
2111
  });
1914
2112
  }
1915
2113
 
1916
- // Common variant for "standard" and "full-bleed" Hero variants
1917
- const oneColumnLayout = [
1918
- // Vertical spacing in the <Content>
1919
- 'lg:*:data-[slot="content"]:gap-y-4',
1920
- // Main text content takes up 9 columns on medium screens and above
1921
- 'lg:*:data-[slot="content"]:col-span-9',
1922
- // Make sure other elements than <Content> and <Media> (i.e CTA) does not span the full width on small screens
1923
- '*:not-data-[slot="content"]:not-data-[slot="media"]:w-fit',
1924
- // 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
1925
- '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',
1926
- // <Media> and <Carousel> content takes up the full width on medium screens and above
1927
- 'lg:*:data-[slot="media"]:col-span-full *:data-[slot="media"]:*:w-full',
1928
- 'lg:*:data-[slot="carousel"]:col-span-full',
1929
- // Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
1930
- 'lg:items-end'
2114
+ const linkStyles = [
2115
+ '*:data-[slot=link]:flex',
2116
+ '*:data-[slot=link]:w-full',
2117
+ '*:data-[slot=link]:justify-between',
2118
+ '*:data-[slot=link]:gap-x-2',
2119
+ '*:data-[slot=link]:py-3.5',
2120
+ '*:data-[slot=link]:no-underline',
2121
+ '*:data-[slot=link]:focus-visible:outline-focus'
1931
2122
  ];
1932
- const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot="media"]:*:aspect-[1/1] sm:*:data-[slot="media"]:*:aspect-4/3 md:*:data-[slot="media"]:*:aspect-3/2';
1933
- const variants = cva({
2123
+ const linkListContainerVariants = cva({
1934
2124
  base: [
1935
- 'container px-0',
1936
- // Grid variant to position the Hero's content
1937
- 'grid lg:grid-cols-12 lg:gap-x-12 xl:gap-x-16',
1938
- 'gap-y-10 lg:gap-y-12',
1939
- // Enable vertical gap within <Content>
1940
- '*:data-[slot="content"]:grid',
1941
- // Vertical spacing in the <Content>
1942
- '*:data-[slot="content"]:gap-y-3',
1943
- // Make sure <Media> content fills any available vertical and horizontal space
1944
- '*:data-[slot="media"]:*:object-cover',
1945
- '*:data-[slot="carousel"]:overflow-hidden *:data-[slot="carousel"]:rounded-3xl',
1946
- // Make the carousel items full width, so we scroll one at a time
1947
- '**:data-[slot="carousel-item"]:basis-full'
2125
+ '*:data-[slot=link-list]:overflow-visible',
2126
+ '*:data-[slot=heading]:p-1.25',
2127
+ '*:data-[slot=heading]:*:data-[slot=link]:py-2.25',
2128
+ '**:[svg]:text-base',
2129
+ 'has-data-[slot=heading]:*:data-[slot=link-list]:overflow-visible',
2130
+ '*:data-[slot=heading]:has-not:*:data-[slot=link]:my-2.25'
1948
2131
  ],
1949
2132
  variants: {
1950
- /**
1951
- * Defines the variant of the Hero
1952
- * @default standard
1953
- * */ variant: {
1954
- standard: [
1955
- oneColumnLayout,
1956
- nonFullBleedAspectRatiosForSmallScreens,
1957
- 'lg:*:data-[slot="media"]:*:aspect-2/1'
1958
- ],
1959
- 'full-bleed': [
1960
- oneColumnLayout,
1961
- // Position the media and carousel content to fill the entire viewport width
1962
- '*:data-[slot="media"]:*:absolute *:data-[slot="media"]:*:left-0',
1963
- // Special case for Carousel, where the Media is nested inside a CarouselItem
1964
- '*:data-[slot="carousel"]:**:data-[slot="media"]:w-full',
1965
- // Match the heights of the <Media> or <Carousel> wrapper for the Media content (e.g. image, VideoLoop, video etc.)
1966
- // This is necessary due to the absolute positioning of the media and carousel containers in this variant
1967
- // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1968
- '**: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]',
1969
- '**:data-[slot="media"]:*:h-[inherit]',
1970
- // biome-ignore lint/nursery/useSortedClasses: biome is unable to sort the custom classes for 3xl and 4xl breakpoints
1971
- '*: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]',
1972
- '*:data-[slot="carousel"]:w-full!',
1973
- // Override aspect ratio of the media and carousel-item slots (since we can not use aspect for full-bleed layout)
1974
- '**:data-[slot="carousel-item"]:data-[slot="media"]:*:aspect-none',
1975
- // break out the carousel out of the container
1976
- '**: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]',
1977
- // Positions the carousel controls inside the carousel
1978
- '**: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'
1979
- ],
1980
- 'two-column': [
1981
- 'lg:items-center lg:*:col-span-6',
1982
- // Vertical spacing in the <Content>
1983
- 'lg:*:data-[slot="content"]:gap-y-7',
1984
- nonFullBleedAspectRatiosForSmallScreens,
1985
- // Set media aspect ratio to 1:1 (square)
1986
- 'lg:*:data-[slot="media"]:*:aspect-square'
1987
- ]
2133
+ layout: {
2134
+ stack: null,
2135
+ grid: '@container'
1988
2136
  }
1989
2137
  },
1990
- compoundVariants: [
1991
- {
1992
- variant: [
1993
- 'standard',
1994
- 'two-column'
1995
- ],
1996
- className: [
1997
- '*:data-[slot="media"]:*:rounded-3xl',
1998
- '**: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'
1999
- ]
2000
- }
2001
- ],
2002
2138
  defaultVariants: {
2003
- variant: 'standard'
2139
+ layout: 'stack'
2004
2140
  }
2005
2141
  });
2006
- const Hero = ({ variant, className, children, ...rest })=>{
2007
- const variantsClassName = variants({
2008
- variant,
2009
- className
2010
- });
2011
- return /*#__PURE__*/ jsx(Provider, {
2142
+ const LinkListContainer = ({ className, layout = 'stack', ...props })=>/*#__PURE__*/ jsx(Provider, {
2012
2143
  values: [
2013
2144
  [
2014
2145
  HeadingContext,
2015
2146
  {
2016
- // Sets the default heading size for the Hero based on the variant
2017
- size: variant === 'two-column' ? 'xl' : 'l',
2018
- className: // word-break:break-word to allow long words to break (this is necessary to make hyphens work in grid containers in Safari)
2019
- 'hyphens-auto text-pretty [word-break:break-word]'
2020
- }
2021
- ],
2022
- [
2023
- GroupContext,
2024
- {
2025
- // Prevents the group from being announced as a group by screen readers
2026
- // The Group component is used to group the Hero's CTA buttons together visually, and has no semantic meaning
2027
- role: 'presentation',
2028
- className: 'flex flex-wrap gap-3 *:w-fit'
2147
+ size: 'm',
2148
+ className: cx(linkStyles)
2029
2149
  }
2030
2150
  ]
2031
2151
  ],
2032
2152
  children: /*#__PURE__*/ jsx("div", {
2033
- className: cx(variantsClassName, className),
2034
- ...rest,
2035
- children: children
2036
- })
2037
- });
2038
- };
2039
-
2040
- // Sets the correct icons for each link in the link list
2041
- const _LinkProvider = ({ children })=>/*#__PURE__*/ jsx(Provider, {
2042
- values: [
2043
- [
2044
- _LinkContext,
2045
- {
2046
- _innerWrapper: ({ children, download, rel })=>(values)=>{
2047
- let Icon = ArrowRight;
2048
- if (download) {
2049
- Icon = Download;
2050
- } else if (rel?.includes('external')) {
2051
- Icon = LinkExternal;
2052
- }
2053
- return /*#__PURE__*/ jsxs(Fragment, {
2054
- children: [
2055
- typeof children === 'function' ? children({
2056
- ...values,
2057
- defaultChildren: null
2058
- }) : children,
2059
- /*#__PURE__*/ jsx(Icon, {})
2060
- ]
2061
- });
2062
- }
2063
- }
2064
- ]
2065
- ],
2066
- children: children
2067
- });
2068
- const LinkListContainer = ({ className, ...restProps })=>// Dual providers makes for easier typing and more readable code
2069
- /*#__PURE__*/ jsx(Provider, {
2070
- values: [
2071
- [
2072
- HeadingContext,
2073
- {
2074
- size: 'm'
2075
- }
2076
- ]
2077
- ],
2078
- children: /*#__PURE__*/ jsx(_LinkProvider, {
2079
- children: /*#__PURE__*/ jsx("div", {
2080
- className: cx(className, 'link-list-container'),
2081
- ...restProps
2082
- })
2083
- })
2084
- });
2085
- const LinkList = (props)=>/*#__PURE__*/ jsx(_LinkProvider, {
2086
- children: /*#__PURE__*/ jsx("ul", {
2087
2153
  ...props,
2088
- "data-slot": "link-list"
2154
+ className: linkListContainerVariants({
2155
+ className,
2156
+ layout
2157
+ }),
2158
+ "data-layout": layout,
2159
+ "data-slot": "link-list-container"
2089
2160
  })
2090
2161
  });
2091
- const LinkListItem = (props)=>/*#__PURE__*/ jsx("li", {
2162
+ const LinkList = ({ className, ...restProps })=>/*#__PURE__*/ jsx("ul", {
2163
+ ...restProps,
2164
+ "data-slot": "link-list",
2165
+ className: cx(/**
2166
+ * Hides dividers at the top of the list (overflow-y)
2167
+ * while preventing arrow icons from overflowing container when animated to the right (overflow-x)
2168
+ */ 'grid min-w-fit auto-rows-max gap-y-px overflow-hidden', className)
2169
+ });
2170
+ const LinkListItem = ({ children, className, ...props })=>{
2171
+ const child = Children.only(children);
2172
+ const childProps = /*#__PURE__*/ isValidElement(child) ? child.props : {};
2173
+ const animateIcon = childProps.animateIcon || childProps.download ? 'down' : childProps.rel?.includes('external') ? 'up-right' : 'right';
2174
+ const iconRight = childProps['~iconRight'] || childProps.download ? /*#__PURE__*/ jsx(Download, {}) : childProps.rel?.includes('external') ? /*#__PURE__*/ jsx(LinkExternal, {}) : /*#__PURE__*/ jsx(ArrowRight, {});
2175
+ return /*#__PURE__*/ jsx("li", {
2092
2176
  ...props,
2093
- "data-slot": "link-list-item"
2177
+ className: cx(className, 'after:-top-px relative p-1.25 after:absolute after:right-0 after:left-0 after:h-px after:w-full after:bg-gray-light', '*:data-[slot=link]:paragraph', ...linkStyles),
2178
+ "data-slot": "link-list-item",
2179
+ children: /*#__PURE__*/ isValidElement(child) && /*#__PURE__*/ cloneElement(child, {
2180
+ animateIcon,
2181
+ '~iconRight': iconRight
2182
+ })
2094
2183
  });
2184
+ };
2095
2185
 
2096
2186
  const DialogTrigger = (props)=>/*#__PURE__*/ jsx(DialogTrigger$1, {
2097
2187
  ...props
@@ -2240,44 +2330,6 @@ function NumberField(props) {
2240
2330
  });
2241
2331
  }
2242
2332
 
2243
- const _ProgressBarValueTextContext = /*#__PURE__*/ createContext(undefined);
2244
- const _ProgressBarValueTextProvider = _ProgressBarValueTextContext.Provider;
2245
- const ProgressBarValueText = ({ className, ...restProps })=>{
2246
- const value = useContext(_ProgressBarValueTextContext);
2247
- return /*#__PURE__*/ jsx("span", {
2248
- ...restProps,
2249
- className: cx(className, 'px-2 leading-7'),
2250
- "data-slot": "progress-bar-value-text",
2251
- children: value
2252
- });
2253
- };
2254
- const ProgressBar = ({ children, className, ...restProps })=>{
2255
- return /*#__PURE__*/ jsx(ProgressBar$1, {
2256
- "data-slot": "progress-bar",
2257
- ...restProps,
2258
- className: cx(className, 'max-w-full'),
2259
- children: ({ percentage, valueText, ...restArgs })=>/*#__PURE__*/ jsxs(_ProgressBarValueTextProvider, {
2260
- value: valueText,
2261
- children: [
2262
- typeof children === 'function' ? children({
2263
- percentage,
2264
- valueText,
2265
- ...restArgs
2266
- }) : children,
2267
- /*#__PURE__*/ jsx("div", {
2268
- className: "relative h-0.75 rounded bg-gray-light",
2269
- children: /*#__PURE__*/ jsx("div", {
2270
- className: "h-0.75 rounded bg-blue-dark transition-all duration-300 ease-in-out",
2271
- style: {
2272
- width: `${percentage}%`
2273
- }
2274
- })
2275
- })
2276
- ]
2277
- })
2278
- });
2279
- };
2280
-
2281
2333
  const defaultClasses = cx([
2282
2334
  '-ml-2.5 relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
2283
2335
  // the radio button itself
@@ -2387,18 +2439,47 @@ function Select(props) {
2387
2439
  });
2388
2440
  }
2389
2441
 
2390
- function ScrollButton({ direction, onClick, isVisible, hasScrollingOccurred, className, iconClassName }) {
2442
+ const _ProgressBarValueTextContext = /*#__PURE__*/ createContext(undefined);
2443
+ const _ProgressBarValueTextProvider = _ProgressBarValueTextContext.Provider;
2444
+ const ProgressBar = ({ children, className, ...restProps })=>{
2445
+ return /*#__PURE__*/ jsx(ProgressBar$1, {
2446
+ "data-slot": "progress-bar",
2447
+ ...restProps,
2448
+ className: cx(className, 'max-w-full'),
2449
+ children: ({ percentage, valueText, ...restArgs })=>/*#__PURE__*/ jsxs(_ProgressBarValueTextProvider, {
2450
+ value: valueText,
2451
+ children: [
2452
+ typeof children === 'function' ? children({
2453
+ percentage,
2454
+ valueText,
2455
+ ...restArgs
2456
+ }) : children,
2457
+ /*#__PURE__*/ jsx("div", {
2458
+ className: "relative rounded border border-blue-dark bg-gray-light",
2459
+ children: /*#__PURE__*/ jsx("div", {
2460
+ className: "h-1 rounded bg-blue-dark transition-all duration-300 ease-in-out",
2461
+ style: {
2462
+ width: `${percentage}%`
2463
+ }
2464
+ })
2465
+ })
2466
+ ]
2467
+ })
2468
+ });
2469
+ };
2470
+
2471
+ function ScrollButton({ direction, onClick, isVisible, hasScrollingOccurred, className }) {
2391
2472
  const Icon = direction === 'left' ? ChevronLeft : ChevronRight;
2392
2473
  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.
2393
2474
  // 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.
2394
2475
  /*#__PURE__*/ jsx("div", {
2395
2476
  onClick: onClick,
2396
2477
  className: cx(// Base scroll button styling
2397
- 'flex cursor-pointer items-center justify-center', 'text-black hover:bg-white', direction === 'left' ? 'bg-[linear-gradient(90deg,white,white_calc(100%-10px),transparent)]' : 'bg-[linear-gradient(90deg,transparent,white_calc(10px),white)]', // Animation
2478
+ 'flex cursor-pointer items-center justify-center', 'absolute top-0 size-11', 'group/scroll-button text-black', direction === 'left' ? 'bg-[linear-gradient(90deg,white,white_calc(100%-10px),transparent)]' : 'bg-[linear-gradient(90deg,transparent,white_calc(10px),white)]', // Animation
2398
2479
  hasScrollingOccurred && 'duration-100 ease-in motion-safe:transition-transform', // Hide/show animation
2399
2480
  direction === 'left' ? !isVisible && '-translate-x-full pointer-events-none' : !isVisible && 'pointer-events-none translate-x-full', direction === 'left' ? '-left-3' : '-right-3', className),
2400
2481
  children: /*#__PURE__*/ jsx(Icon, {
2401
- className: iconClassName
2482
+ className: cx('motion-safe:transition-all', direction === 'left' ? 'group-hover/scroll-button:-translate-x-1' : 'group-hover/scroll-button:translate-x-1')
2402
2483
  })
2403
2484
  }));
2404
2485
  }
@@ -2455,6 +2536,132 @@ function ScrollButton({ direction, onClick, isVisible, hasScrollingOccurred, cla
2455
2536
  };
2456
2537
  }
2457
2538
 
2539
+ const StepperContext = /*#__PURE__*/ createContext({
2540
+ activeStep: 0,
2541
+ stepsCount: 0
2542
+ });
2543
+ const Stepper = ({ children, activeStep, onStepChange, ...restProps })=>{
2544
+ const locale = _useLocale();
2545
+ const stepsCount = Children.count(children);
2546
+ const [emblaRef, emblaApi] = useEmblaCarousel({
2547
+ startIndex: activeStep,
2548
+ dragFree: true,
2549
+ breakpoints: {
2550
+ // disable the carousel on large screens
2551
+ '(width >= 64rem)': {
2552
+ active: false
2553
+ }
2554
+ }
2555
+ });
2556
+ return /*#__PURE__*/ jsxs("div", {
2557
+ ...restProps,
2558
+ "data-slot": "stepper-container",
2559
+ ref: emblaRef,
2560
+ children: [
2561
+ /*#__PURE__*/ jsx("ol", {
2562
+ "aria-label": translations$1.stepper[locale],
2563
+ "data-slot": "stepper",
2564
+ children: /*#__PURE__*/ jsx(StepperContext.Provider, {
2565
+ value: {
2566
+ onStepChange,
2567
+ activeStep,
2568
+ stepsCount
2569
+ },
2570
+ children: Children.map(children, (child, index)=>{
2571
+ return /*#__PURE__*/ isValidElement(child) && /*#__PURE__*/ cloneElement(child, {
2572
+ '~stepIndex': index
2573
+ });
2574
+ })
2575
+ })
2576
+ }),
2577
+ /*#__PURE__*/ jsx(MobileScrollButtons, {
2578
+ emblaApi: emblaApi
2579
+ })
2580
+ ]
2581
+ });
2582
+ };
2583
+ const MobileScrollButtons = ({ emblaApi })=>{
2584
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
2585
+ const [canScrollRight, setCanScrollRight] = useState(false);
2586
+ useEffect(()=>{
2587
+ if (!emblaApi) {
2588
+ return;
2589
+ }
2590
+ const handleSlidesInView = ()=>{
2591
+ setCanScrollLeft(emblaApi.canScrollPrev());
2592
+ setCanScrollRight(emblaApi.canScrollNext());
2593
+ };
2594
+ emblaApi.on('slidesInView', handleSlidesInView);
2595
+ return ()=>{
2596
+ emblaApi.off('slidesInView', handleSlidesInView);
2597
+ };
2598
+ }, [
2599
+ emblaApi
2600
+ ]);
2601
+ return /*#__PURE__*/ jsxs(Fragment, {
2602
+ children: [
2603
+ /*#__PURE__*/ jsx(ScrollButton, {
2604
+ className: "lg:hidden",
2605
+ direction: "left",
2606
+ onClick: ()=>emblaApi?.scrollPrev(),
2607
+ isVisible: canScrollLeft,
2608
+ hasScrollingOccurred: false
2609
+ }),
2610
+ /*#__PURE__*/ jsx(ScrollButton, {
2611
+ className: "lg:hidden",
2612
+ direction: "right",
2613
+ isVisible: canScrollRight,
2614
+ onClick: ()=>emblaApi?.scrollNext(),
2615
+ hasScrollingOccurred: false
2616
+ })
2617
+ ]
2618
+ });
2619
+ };
2620
+ const Step = ({ isDisabled = false, state, children, '~stepIndex': stepIndex, progress, ...restProps })=>{
2621
+ const locale = _useLocale();
2622
+ const id = useId();
2623
+ const { onStepChange, activeStep, stepsCount } = use(StepperContext);
2624
+ const isLastStep = stepIndex === stepsCount - 1;
2625
+ const isCurrent = stepIndex === activeStep;
2626
+ const iconText = state === 'completed' ? translations$1.completed[locale] : undefined;
2627
+ const icon = isCurrent ? /*#__PURE__*/ jsx(Edit, {
2628
+ "aria-label": iconText
2629
+ }) : state === 'completed' && /*#__PURE__*/ jsx(Check, {
2630
+ "aria-label": iconText
2631
+ });
2632
+ return /*#__PURE__*/ jsx("li", {
2633
+ ...restProps,
2634
+ "data-slot": "step",
2635
+ "data-state": state,
2636
+ "data-current": isCurrent ? true : undefined,
2637
+ "data-disabled": isDisabled ? true : undefined,
2638
+ id: id,
2639
+ children: /*#__PURE__*/ jsxs(Provider, {
2640
+ values: [
2641
+ [
2642
+ LinkContext,
2643
+ {
2644
+ // @ts-expect-error this works even though it's a type error
2645
+ 'aria-current': isCurrent ? 'step' : undefined,
2646
+ isDisabled,
2647
+ className: 'underline',
2648
+ onPress: ()=>onStepChange?.(stepIndex)
2649
+ }
2650
+ ]
2651
+ ],
2652
+ children: [
2653
+ icon,
2654
+ children,
2655
+ !isLastStep && /*#__PURE__*/ jsx(ProgressBar, {
2656
+ "aria-labelledby": id,
2657
+ // Make sure that if the step is completed, the progress value is 100%
2658
+ value: state === 'completed' ? 100 : progress
2659
+ })
2660
+ ]
2661
+ })
2662
+ });
2663
+ };
2664
+
2458
2665
  const tableVariants = cva({
2459
2666
  base: [
2460
2667
  'relative'
@@ -2504,22 +2711,6 @@ const TableScrollContainerContext = /*#__PURE__*/ createContext({
2504
2711
  children: /*#__PURE__*/ jsxs("div", {
2505
2712
  className: "relative overflow-hidden",
2506
2713
  children: [
2507
- /*#__PURE__*/ jsx(ScrollButton, {
2508
- direction: "left",
2509
- onClick: ()=>handleScroll('left'),
2510
- isVisible: canScrollLeft,
2511
- hasScrollingOccurred: hasScrollingOccurred,
2512
- className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
2513
- iconClassName: "h-5 w-5"
2514
- }),
2515
- /*#__PURE__*/ jsx(ScrollButton, {
2516
- direction: "right",
2517
- onClick: ()=>handleScroll('right'),
2518
- isVisible: canScrollRight,
2519
- hasScrollingOccurred: hasScrollingOccurred,
2520
- className: "-translate-y-1/2 absolute top-5 z-10 h-11 w-11",
2521
- iconClassName: "h-5 w-5"
2522
- }),
2523
2714
  /*#__PURE__*/ jsx("div", {
2524
2715
  ref: scrollContainerRef,
2525
2716
  className: "scrollbar-hidden overflow-x-auto",
@@ -2532,6 +2723,18 @@ const TableScrollContainerContext = /*#__PURE__*/ createContext({
2532
2723
  "data-variant": variant,
2533
2724
  children: children
2534
2725
  })
2726
+ }),
2727
+ /*#__PURE__*/ jsx(ScrollButton, {
2728
+ direction: "left",
2729
+ onClick: ()=>handleScroll('left'),
2730
+ isVisible: canScrollLeft,
2731
+ hasScrollingOccurred: hasScrollingOccurred
2732
+ }),
2733
+ /*#__PURE__*/ jsx(ScrollButton, {
2734
+ direction: "right",
2735
+ onClick: ()=>handleScroll('right'),
2736
+ isVisible: canScrollRight,
2737
+ hasScrollingOccurred: hasScrollingOccurred
2535
2738
  })
2536
2739
  ]
2537
2740
  })
@@ -2719,17 +2922,13 @@ const tabsVariants = cva({
2719
2922
  direction: "left",
2720
2923
  onClick: onPrev,
2721
2924
  isVisible: canScrollLeft,
2722
- hasScrollingOccurred: hasScrollingOccurred,
2723
- className: "absolute bottom-0.25 size-11",
2724
- iconClassName: "mt-0.25 h-6 w-full text-black"
2925
+ hasScrollingOccurred: hasScrollingOccurred
2725
2926
  }),
2726
2927
  /*#__PURE__*/ jsx(ScrollButton, {
2727
2928
  direction: "right",
2728
2929
  onClick: onNext,
2729
2930
  isVisible: canScrollRight,
2730
- hasScrollingOccurred: hasScrollingOccurred,
2731
- className: "absolute bottom-0.25 size-11",
2732
- iconClassName: "mt-0.25 h-6 w-full text-black"
2931
+ hasScrollingOccurred: hasScrollingOccurred
2733
2932
  })
2734
2933
  ]
2735
2934
  });
@@ -2740,7 +2939,7 @@ const tabsVariants = cva({
2740
2939
  const { className, children, ...restProps } = props;
2741
2940
  return /*#__PURE__*/ jsx(Tab$1, {
2742
2941
  ...restProps,
2743
- 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
2942
+ className: cx(className, 'data-focus-visible:-outline-offset-10 data-focus-visible:outline-2 data-focus-visible:outline-black', 'description h-11 cursor-pointer border-transparent px-4 py-[0.71875rem] font-light', // Transition
2744
2943
  'transition-colors duration-150 ease-out', // TODO: Should disabled tabs just be hidden?
2745
2944
  'data-disabled:cursor-not-allowed data-disabled:opacity-50', // Selection
2746
2945
  'data-selected:font-medium data-selected:text-blue-dark', // Hover with layout shift prevention using pseudo-element
@@ -3001,4 +3200,4 @@ const VideoLoop = ({ src, format, alt, className })=>{
3001
3200
  });
3002
3201
  };
3003
3202
 
3004
- 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, 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, 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 };
3203
+ 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, Step as UNSAFE_Step, Stepper as UNSAFE_Stepper, 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, _useLocale as useLocale };