@payfit/unity-components 2.9.8 → 2.10.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.
Files changed (28) hide show
  1. package/dist/esm/components/carousel/Carousel.context.d.ts +60 -0
  2. package/dist/esm/components/carousel/Carousel.context.js +14 -0
  3. package/dist/esm/components/carousel/Carousel.d.ts +72 -0
  4. package/dist/esm/components/carousel/Carousel.js +106 -0
  5. package/dist/esm/components/carousel/Carousel.options.d.ts +24 -0
  6. package/dist/esm/components/carousel/Carousel.options.js +64 -0
  7. package/dist/esm/components/carousel/hooks/useCarouselAccessibility.d.ts +21 -0
  8. package/dist/esm/components/carousel/hooks/useCarouselAccessibility.js +87 -0
  9. package/dist/esm/components/carousel/hooks/useCarouselState.d.ts +14 -0
  10. package/dist/esm/components/carousel/hooks/useCarouselState.js +62 -0
  11. package/dist/esm/components/carousel/parts/CarouselContent.d.ts +103 -0
  12. package/dist/esm/components/carousel/parts/CarouselContent.js +69 -0
  13. package/dist/esm/components/carousel/parts/CarouselHeader.d.ts +87 -0
  14. package/dist/esm/components/carousel/parts/CarouselHeader.js +58 -0
  15. package/dist/esm/components/carousel/parts/CarouselNav.d.ts +59 -0
  16. package/dist/esm/components/carousel/parts/CarouselNav.js +80 -0
  17. package/dist/esm/components/carousel/parts/CarouselSlide.d.ts +38 -0
  18. package/dist/esm/components/carousel/parts/CarouselSlide.js +35 -0
  19. package/dist/esm/components/carousel/types.d.ts +8 -0
  20. package/dist/esm/components/icon-button/IconButton.d.ts +1 -0
  21. package/dist/esm/hooks/use-responsive-value.d.ts +11 -0
  22. package/dist/esm/hooks/use-responsive-value.js +29 -0
  23. package/dist/esm/index.d.ts +5 -0
  24. package/dist/esm/index.js +87 -72
  25. package/i18n/en-GB.json +13 -0
  26. package/i18n/es-ES.json +13 -0
  27. package/i18n/fr-FR.json +13 -0
  28. package/package.json +10 -7
@@ -0,0 +1,69 @@
1
+ import { jsx as g } from "react/jsx-runtime";
2
+ import { useState as P, useEffect as C } from "react";
3
+ import { uyTv as w } from "@payfit/unity-themes";
4
+ import { useResponsiveValue as r } from "../../../hooks/use-responsive-value.js";
5
+ import { useCarousel as x } from "../Carousel.context.js";
6
+ const D = w({
7
+ slots: {
8
+ root: ["uy:overflow-hidden ", "uy:-mx-(--uy-carousel-track-offset)"],
9
+ track: [
10
+ "uy:flex uy:touch-pan-y uy:touch-pinch-zoom",
11
+ "uy:gap-(--uy-carousel-slide-gap)",
12
+ "uy:*:first:ms-(--uy-carousel-track-margin-start)",
13
+ "uy:*:last:me-(--uy-carousel-track-margin-end)",
14
+ "uy:data-[grab-state=grab]:cursor-grab",
15
+ "uy:data-[grab-state=grabbing]:cursor-grabbing"
16
+ ]
17
+ }
18
+ });
19
+ function N({
20
+ className: p,
21
+ children: d,
22
+ itemsPerPage: f,
23
+ gap: y,
24
+ trackPadding: o,
25
+ trackOffset: m = 0
26
+ }) {
27
+ const { carouselRef: b, a11yIds: k, api: a } = x(), { root: $, track: v } = D(), [h, s] = P(!1), t = r(f), n = r(y), e = r(m), c = r(o?.start), u = r(o?.end);
28
+ return C(() => {
29
+ if (!a) return;
30
+ const i = () => {
31
+ s(!0);
32
+ }, l = () => {
33
+ s(!1);
34
+ };
35
+ return a.on("pointerdown", i), a.on("pointerup", l), () => {
36
+ a.off("pointerdown", i), a.off("pointerup", l);
37
+ };
38
+ }, [a]), /* @__PURE__ */ g(
39
+ "div",
40
+ {
41
+ ref: b,
42
+ className: $({ className: p }),
43
+ "data-slot": "carousel-content",
44
+ "data-items-per-page": typeof t == "number" ? t : void 0,
45
+ style: {
46
+ "--uy-carousel-slide-width": `calc((1 / ${t ?? 1}) * 100%)`,
47
+ "--uy-carousel-track-offset": typeof e == "number" ? `${e}px` : `var(--uy-spacing-${e.replace("$", "")})`,
48
+ "--uy-carousel-slide-gap": `var(--uy-spacing-${n ? n.replace("$", "") : 0})`,
49
+ "--uy-carousel-track-margin-start": `var(--uy-spacing-${c ? c.replace("$", "") : 0})`,
50
+ "--uy-carousel-track-margin-end": `var(--uy-spacing-${u ? u.replace("$", "") : 0})`
51
+ },
52
+ children: /* @__PURE__ */ g(
53
+ "div",
54
+ {
55
+ className: v(),
56
+ "data-slot": "carousel-track",
57
+ "data-grab-state": h ? "grabbing" : "grab",
58
+ id: k.track,
59
+ children: d
60
+ }
61
+ )
62
+ }
63
+ );
64
+ }
65
+ N.displayName = "CarouselContent";
66
+ export {
67
+ N as CarouselContent,
68
+ D as carouselContent
69
+ };
@@ -0,0 +1,87 @@
1
+ import { ReactNode } from 'react';
2
+ export declare const carouselHeader: import('tailwind-variants').TVReturnType<{
3
+ [key: string]: {
4
+ [key: string]: import('tailwind-merge').ClassNameValue | {
5
+ slot?: import('tailwind-merge').ClassNameValue;
6
+ nav?: import('tailwind-merge').ClassNameValue;
7
+ title?: import('tailwind-merge').ClassNameValue;
8
+ root?: import('tailwind-merge').ClassNameValue;
9
+ };
10
+ };
11
+ } | {
12
+ [x: string]: {
13
+ [x: string]: import('tailwind-merge').ClassNameValue | {
14
+ slot?: import('tailwind-merge').ClassNameValue;
15
+ nav?: import('tailwind-merge').ClassNameValue;
16
+ title?: import('tailwind-merge').ClassNameValue;
17
+ root?: import('tailwind-merge').ClassNameValue;
18
+ };
19
+ };
20
+ } | {}, {
21
+ root: string;
22
+ title: string;
23
+ slot: string;
24
+ nav: string;
25
+ }, undefined, {
26
+ [key: string]: {
27
+ [key: string]: import('tailwind-merge').ClassNameValue | {
28
+ slot?: import('tailwind-merge').ClassNameValue;
29
+ nav?: import('tailwind-merge').ClassNameValue;
30
+ title?: import('tailwind-merge').ClassNameValue;
31
+ root?: import('tailwind-merge').ClassNameValue;
32
+ };
33
+ };
34
+ } | {}, {
35
+ root: string;
36
+ title: string;
37
+ slot: string;
38
+ nav: string;
39
+ }, import('tailwind-variants').TVReturnType<unknown, {
40
+ root: string;
41
+ title: string;
42
+ slot: string;
43
+ nav: string;
44
+ }, undefined, unknown, unknown, undefined>>;
45
+ export interface CarouselHeaderProps {
46
+ /** Title of the carousel. Used as the accessible label for the carousel region. */
47
+ title: string;
48
+ /** Optional content rendered after the title (e.g. a "View all" link). */
49
+ actionSlot?: ReactNode;
50
+ /** Additional CSS classes for the root element. */
51
+ className?: string;
52
+ }
53
+ /**
54
+ * Header area for the Carousel. Renders the title and an optional action slot.
55
+ * The `CarouselHeader` displays a heading for the carousel and provides an optional slot
56
+ * for actions like a "View all" button or link. On medium screens and up (`md+`), it also
57
+ * embeds the `CarouselNav` controls in the top-right corner. On small screens, the nav
58
+ * is rendered below the slides instead.
59
+ * ## Accessibility
60
+ * The title is used as the accessible label for the carousel region via `aria-labelledby`,
61
+ * unless an explicit `aria-label` is provided to the `Carousel` root component.
62
+ * The action slot is wrapped in a `group` role with an appropriate label for screen readers.
63
+ * @example
64
+ * ```tsx
65
+ * <CarouselHeader title="Featured items" />
66
+ * ```
67
+ * @example
68
+ * ```tsx
69
+ * import { Button } from '@payfit/unity-components'
70
+ *
71
+ * <CarouselHeader
72
+ * title="Featured products"
73
+ * actionSlot={
74
+ * <Button variant="ghost" color="neutral">
75
+ * View all
76
+ * </Button>
77
+ * }
78
+ * />
79
+ * ```
80
+ * @param {CarouselHeaderProps} props - Component props
81
+ * @param {CarouselHeaderProps['title']} props.title - Carousel title (used as accessible label)
82
+ * @param {CarouselHeaderProps['actionSlot']} props.actionSlot - Optional action content (e.g., "View all" button)
83
+ * @param {CarouselHeaderProps['className']} props.className - Additional CSS classes
84
+ * @see {@link CarouselHeaderProps} for all available props
85
+ */
86
+ declare const CarouselHeader: import('react').ForwardRefExoticComponent<CarouselHeaderProps & import('react').RefAttributes<HTMLDivElement>>;
87
+ export { CarouselHeader };
@@ -0,0 +1,58 @@
1
+ import { jsxs as c, jsx as e } from "react/jsx-runtime";
2
+ import { forwardRef as d } from "react";
3
+ import { uyTv as y } from "@payfit/unity-themes";
4
+ import { useIntl as f } from "react-intl";
5
+ import { Text as p } from "../../text/Text.js";
6
+ import { useCarousel as h } from "../Carousel.context.js";
7
+ import { CarouselNav as v } from "./CarouselNav.js";
8
+ const x = y({
9
+ slots: {
10
+ root: "uy:flex uy:items-center uy:justify-between",
11
+ title: "uy:flex-1",
12
+ slot: "uy:shrink-0",
13
+ nav: "uy:hidden uy:md:flex"
14
+ }
15
+ }), N = d(
16
+ ({ title: t, actionSlot: r, className: s }, a) => {
17
+ const { a11yIds: o } = h(), l = f(), {
18
+ root: i,
19
+ title: n,
20
+ slot: m,
21
+ nav: u
22
+ } = x();
23
+ return /* @__PURE__ */ c("header", { ref: a, className: i({ class: s }), children: [
24
+ /* @__PURE__ */ e(
25
+ p,
26
+ {
27
+ id: o.title,
28
+ "data-dd-privacy": "allow",
29
+ variant: "h3",
30
+ color: "content.neutral",
31
+ className: n(),
32
+ children: t
33
+ }
34
+ ),
35
+ r && /* @__PURE__ */ e(
36
+ "div",
37
+ {
38
+ role: "group",
39
+ "aria-label": l.formatMessage(
40
+ {
41
+ id: "unity:component:carousel:header:actions:label",
42
+ defaultMessage: "{title} actions"
43
+ },
44
+ { title: t }
45
+ ),
46
+ className: m(),
47
+ children: r
48
+ }
49
+ ),
50
+ /* @__PURE__ */ e(v, { className: u() })
51
+ ] });
52
+ }
53
+ );
54
+ N.displayName = "CarouselHeader";
55
+ export {
56
+ N as CarouselHeader,
57
+ x as carouselHeader
58
+ };
@@ -0,0 +1,59 @@
1
+ export declare const carouselNav: import('tailwind-variants').TVReturnType<{
2
+ [key: string]: {
3
+ [key: string]: import('tailwind-merge').ClassNameValue | {
4
+ indicator?: import('tailwind-merge').ClassNameValue;
5
+ root?: import('tailwind-merge').ClassNameValue;
6
+ };
7
+ };
8
+ } | {
9
+ [x: string]: {
10
+ [x: string]: import('tailwind-merge').ClassNameValue | {
11
+ indicator?: import('tailwind-merge').ClassNameValue;
12
+ root?: import('tailwind-merge').ClassNameValue;
13
+ };
14
+ };
15
+ } | {}, {
16
+ root: string;
17
+ indicator: string;
18
+ }, undefined, {
19
+ [key: string]: {
20
+ [key: string]: import('tailwind-merge').ClassNameValue | {
21
+ indicator?: import('tailwind-merge').ClassNameValue;
22
+ root?: import('tailwind-merge').ClassNameValue;
23
+ };
24
+ };
25
+ } | {}, {
26
+ root: string;
27
+ indicator: string;
28
+ }, import('tailwind-variants').TVReturnType<unknown, {
29
+ root: string;
30
+ indicator: string;
31
+ }, undefined, unknown, unknown, undefined>>;
32
+ export interface CarouselNavProps {
33
+ /** Additional CSS classes for the root element. */
34
+ className?: string;
35
+ }
36
+ /**
37
+ * Internal navigation component for the Carousel.
38
+ * Renders previous/next buttons and a page indicator showing the current position (e.g., "2/5").
39
+ * This component is automatically rendered by `CarouselHeader` on medium screens and up, and
40
+ * below the slides on small screens. You typically don't need to use it directly.
41
+ * ## Behavior
42
+ * - Automatically hidden when there's only one page of content (`snapCount === 1`)
43
+ * - Previous/next buttons are disabled when at the start/end (unless `loop: true`)
44
+ * - Buttons are properly labeled for screen readers
45
+ * - Uses `role="toolbar"` for semantic navigation grouping
46
+ *
47
+ * ## Internal Use
48
+ * This component is wired to the carousel via context and is used internally by the
49
+ * `Carousel` component. You don't typically render it directly.
50
+ * @param {CarouselNavProps} props - Component props
51
+ * @param {CarouselNavProps['className']} props.className - Additional CSS classes
52
+ * @see {@link CarouselNavProps} for all available props
53
+ * @internal
54
+ */
55
+ declare const CarouselNav: {
56
+ ({ className }: CarouselNavProps): import("react/jsx-runtime").JSX.Element | null;
57
+ displayName: string;
58
+ };
59
+ export { CarouselNav };
@@ -0,0 +1,80 @@
1
+ import { jsxs as n, jsx as s } from "react/jsx-runtime";
2
+ import { uyTv as p } from "@payfit/unity-themes";
3
+ import { useIntl as f } from "react-intl";
4
+ import { IconButton as l } from "../../icon-button/IconButton.js";
5
+ import { Text as b } from "../../text/Text.js";
6
+ import { useCarousel as g } from "../Carousel.context.js";
7
+ const y = p({
8
+ slots: {
9
+ root: "uy:flex uy:items-center uy:gap-50",
10
+ indicator: "uy:tabular-nums uy:select-none"
11
+ }
12
+ }), x = ({ className: i }) => {
13
+ const {
14
+ goToNext: u,
15
+ goToPrev: c,
16
+ canGoToPrev: e,
17
+ canGoToNext: t,
18
+ selectedSnap: d,
19
+ snapCount: r,
20
+ a11yIds: o
21
+ } = g(), a = f(), { root: m, indicator: v } = y();
22
+ return r === 1 ? null : /* @__PURE__ */ n(
23
+ "div",
24
+ {
25
+ className: m({ class: i }),
26
+ role: "toolbar",
27
+ "aria-label": a.formatMessage({
28
+ id: "unity:component:carousel:nav:toolbar-label",
29
+ defaultMessage: "Carousel navigation"
30
+ }),
31
+ "data-unity-slot": "carousel-nav",
32
+ children: [
33
+ /* @__PURE__ */ s(
34
+ l,
35
+ {
36
+ variant: "ghost",
37
+ color: "neutral",
38
+ icon: "CaretLeftOutlined",
39
+ label: a.formatMessage({
40
+ id: "unity:component:carousel:nav:previous:label",
41
+ defaultMessage: "Go to previous slide"
42
+ }),
43
+ onClick: c,
44
+ isDisabled: !e(),
45
+ "aria-disabled": !e(),
46
+ "aria-controls": o.track,
47
+ id: o.previousButton
48
+ }
49
+ ),
50
+ /* @__PURE__ */ n(b, { variant: "body", color: "content.neutral", className: v(), children: [
51
+ d + 1,
52
+ "/",
53
+ r
54
+ ] }),
55
+ /* @__PURE__ */ s(
56
+ l,
57
+ {
58
+ variant: "ghost",
59
+ color: "neutral",
60
+ icon: "CaretRightOutlined",
61
+ label: a.formatMessage({
62
+ id: "unity:component:carousel:nav:next:label",
63
+ defaultMessage: "Go to next slide"
64
+ }),
65
+ onClick: u,
66
+ isDisabled: !t(),
67
+ "aria-disabled": !t(),
68
+ "aria-controls": o.track,
69
+ id: o.nextButton
70
+ }
71
+ )
72
+ ]
73
+ }
74
+ );
75
+ };
76
+ x.displayName = "CarouselNav";
77
+ export {
78
+ x as CarouselNav,
79
+ y as carouselNav
80
+ };
@@ -0,0 +1,38 @@
1
+ import { default as React, PropsWithChildren } from 'react';
2
+ import { VariantProps } from '@payfit/unity-themes';
3
+ export declare const carouselSlide: import('tailwind-variants').TVReturnType<{} | {} | {}, undefined, string[], {} | {}, undefined, import('tailwind-variants').TVReturnType<unknown, undefined, string[], unknown, unknown, undefined>>;
4
+ export interface CarouselSlideProps extends PropsWithChildren<VariantProps<typeof carouselSlide>>, React.HTMLAttributes<HTMLDivElement> {
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Individual slide wrapper for carousel content.
9
+ * `CarouselSlide` wraps each item in the carousel and provides the proper semantic structure
10
+ * and accessibility attributes. Each slide is focusable and navigable via keyboard.
11
+ * ## Accessibility
12
+ * - Each slide has `role="group"` and `aria-roledescription="slide"`
13
+ * - Slides are keyboard navigable using Arrow keys, PageUp/PageDown, Home/End
14
+ * - Use the `.uy:group` class on CarouselSlide to style focus states on child content
15
+ * @example
16
+ * ```tsx
17
+ * import { CarouselSlide } from '@payfit/unity-components'
18
+ *
19
+ * <CarouselSlide>
20
+ * <img src="..." alt="Product" />
21
+ * <h3>Product title</h3>
22
+ * </CarouselSlide>
23
+ * ```
24
+ * @example
25
+ * ```tsx
26
+ * <CarouselSlide>
27
+ * <div className="uy:group-focus-visible:outline uy:group-focus-visible:outline-2">
28
+ * Content with custom focus ring
29
+ * </div>
30
+ * </CarouselSlide>
31
+ * ```
32
+ * @param {CarouselSlideProps} props - Component props
33
+ * @param {CarouselSlideProps['className']} props.className - Additional CSS classes
34
+ * @param {CarouselSlideProps['children']} props.children - Slide content
35
+ * @see {@link CarouselSlideProps} for all available props
36
+ */
37
+ declare const CarouselSlide: React.ForwardRefExoticComponent<CarouselSlideProps & React.RefAttributes<HTMLDivElement>>;
38
+ export { CarouselSlide };
@@ -0,0 +1,35 @@
1
+ import { jsx as i } from "react/jsx-runtime";
2
+ import { forwardRef as a } from "react";
3
+ import { uyTv as l } from "@payfit/unity-themes";
4
+ import { useIntl as u } from "react-intl";
5
+ const t = l({
6
+ // add the component styles
7
+ base: [
8
+ "uy:group uy:min-w-0 uy:shrink-0 uy:grow-0",
9
+ "uy:basis-[calc(var(--uy-carousel-slide-width)-var(--uy-carousel-slide-gap))]",
10
+ "uy:focus-visible:outline-0"
11
+ ]
12
+ }), d = a(
13
+ ({ className: e, ...o }, r) => {
14
+ const s = u();
15
+ return /* @__PURE__ */ i(
16
+ "div",
17
+ {
18
+ ...o,
19
+ ref: r,
20
+ role: "group",
21
+ "aria-roledescription": s.formatMessage({
22
+ id: "unity:component:carousel:slide:roledescription",
23
+ defaultMessage: "slide"
24
+ }),
25
+ "data-unity-slot": "carousel-item",
26
+ className: t({ className: e })
27
+ }
28
+ );
29
+ }
30
+ );
31
+ d.displayName = "CarouselSlide";
32
+ export {
33
+ d as CarouselSlide,
34
+ t as carouselSlide
35
+ };
@@ -0,0 +1,8 @@
1
+ import { UseEmblaCarouselType, default as useEmblaCarousel } from 'embla-carousel-react';
2
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
3
+ export type CarouselApi = UseEmblaCarouselType[1];
4
+ export type CarouselOptions = UseCarouselParameters[0];
5
+ export type CarouselPlugin = UseCarouselParameters[1];
6
+ export type EmblaCarouselRef = ReturnType<typeof useEmblaCarousel>[0];
7
+ export type EmblaCarouselApi = ReturnType<typeof useEmblaCarousel>[1];
8
+ export {};
@@ -14,6 +14,7 @@ interface IconButtonBaseProps {
14
14
  className?: string;
15
15
  /** The button type */
16
16
  type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
17
+ id?: ButtonHTMLAttributes<HTMLButtonElement>['id'];
17
18
  }
18
19
  interface IconButtonVariantsPrimaryProps extends IconButtonBaseProps, Omit<IconButtonPrimary, 'variant' | 'color'>, Required<Pick<IconButtonPrimary, 'variant' | 'color'>> {
19
20
  }
@@ -0,0 +1,11 @@
1
+ import { Breakpoint } from './use-breakpoint-listener.js';
2
+ /**
3
+ * A value that can be specified per breakpoint.
4
+ * At minimum, `base` (the mobile-first default) must be provided.
5
+ * @example
6
+ * { base: 1, md: 2, lg: 3 }
7
+ */
8
+ export type ResponsiveValue<TValue> = {
9
+ base: TValue;
10
+ } & Partial<Record<Exclude<Breakpoint, 'xs'>, TValue>>;
11
+ export declare function useResponsiveValue<TValue>(value: TValue | ResponsiveValue<TValue>): TValue;
@@ -0,0 +1,29 @@
1
+ import { useBreakpointListener as s } from "./use-breakpoint-listener.js";
2
+ function p(n) {
3
+ return typeof n == "object" && n !== null && "base" in n;
4
+ }
5
+ const i = [
6
+ "sm",
7
+ "md",
8
+ "lg",
9
+ "xl"
10
+ ];
11
+ function c(n) {
12
+ const r = s();
13
+ if (!p(n))
14
+ return n;
15
+ if (r === "xs")
16
+ return n.base;
17
+ if (r in n)
18
+ return n[r];
19
+ const o = i.indexOf(r);
20
+ for (let t = o; t >= 0; t--) {
21
+ const e = i[t];
22
+ if (e && e in n)
23
+ return n[e];
24
+ }
25
+ return n.base;
26
+ }
27
+ export {
28
+ c as useResponsiveValue
29
+ };
@@ -214,3 +214,8 @@ export { useFieldA11yContext } from './components/form-field/TanstackFormField.c
214
214
  export { fieldRevalidateLogic } from './utils/field-revalidate-logic.js';
215
215
  export type { FieldRevalidateLogicProps } from './utils/field-revalidate-logic.js';
216
216
  export * from './providers/router/RouterProvider.js';
217
+ export * from './components/carousel/Carousel.js';
218
+ export * from './components/carousel/parts/CarouselHeader.js';
219
+ export * from './components/carousel/parts/CarouselNav.js';
220
+ export * from './components/carousel/parts/CarouselContent.js';
221
+ export * from './components/carousel/parts/CarouselSlide.js';