@fanvue/ui 3.10.0 → 3.12.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.
@@ -9,16 +9,17 @@ const Button = require("../Button/Button.cjs");
9
9
  const ChevronLeftIcon = require("../Icons/ChevronLeftIcon.cjs");
10
10
  const ChevronRightIcon = require("../Icons/ChevronRightIcon.cjs");
11
11
  function Day({ day, modifiers, className, ...divProps }) {
12
- const { range_start, range_end } = modifiers;
12
+ const { range_start, range_end, range_middle } = modifiers;
13
13
  const isSingleDayRange = range_start && range_end;
14
+ const inRange = (range_start || range_end || range_middle) && !isSingleDayRange;
14
15
  return /* @__PURE__ */ jsxRuntime.jsx(
15
16
  "div",
16
17
  {
17
18
  className: cn.cn(
18
19
  className,
19
- (range_start || range_end) && !isSingleDayRange && "from-50% from-transparent to-50%",
20
- range_start && !isSingleDayRange && "bg-linear-to-r to-brand-primary-muted",
21
- range_end && !isSingleDayRange && "bg-linear-to-l to-brand-primary-muted"
20
+ inRange && "bg-inputs-calendar-range",
21
+ inRange && range_start && "rounded-l-sm",
22
+ inRange && range_end && "rounded-r-sm"
22
23
  ),
23
24
  ...divProps
24
25
  }
@@ -29,21 +30,23 @@ function DayButton({ day, modifiers, className, ...buttonProps }) {
29
30
  React.useEffect(() => {
30
31
  if (modifiers.focused) ref.current?.focus();
31
32
  }, [modifiers.focused]);
33
+ const isSelected = modifiers.selected && !modifiers.range_middle;
32
34
  return /* @__PURE__ */ jsxRuntime.jsx(
33
35
  "button",
34
36
  {
35
37
  ref,
36
38
  type: "button",
37
39
  className: cn.cn(
38
- "relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-xs",
39
- "typography-body-small-14px-regular",
40
- "transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted",
41
- "focus-visible:outline-2 focus-visible:outline-brand-secondary-default focus-visible:outline-offset-[-2px]",
42
- "disabled:cursor-not-allowed disabled:opacity-50",
43
- modifiers.today && !modifiers.selected && "border border-brand-primary-default",
44
- modifiers.selected && !modifiers.range_middle ? "bg-brand-primary-default text-content-always-black hover:bg-brand-primary-default" : "text-content-primary",
45
- modifiers.range_middle && "rounded-none bg-transparent",
46
- modifiers.outside && "pointer-events-none opacity-50"
40
+ "relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-sm text-content-primary",
41
+ "transition-colors hover:bg-inputs-calendar-range not-disabled:active:bg-inputs-calendar-range",
42
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
43
+ "disabled:cursor-not-allowed",
44
+ isSelected ? "typography-body-small-14px-semibold" : "typography-body-small-14px-regular",
45
+ modifiers.today && !isSelected && "bg-inputs-calendar-today text-content-always-white hover:bg-inputs-calendar-today",
46
+ isSelected && "bg-inputs-calendar-selected text-content-primary-inverted hover:bg-inputs-calendar-selected",
47
+ modifiers.range_middle && "bg-transparent hover:bg-transparent",
48
+ modifiers.disabled && "bg-inputs-calendar-disabled text-content-disabled hover:bg-inputs-calendar-disabled",
49
+ modifiers.outside && "pointer-events-none text-content-disabled"
47
50
  ),
48
51
  ...buttonProps
49
52
  }
@@ -91,7 +94,7 @@ const DatePicker = React.forwardRef(
91
94
  {
92
95
  ref,
93
96
  className: cn.cn(
94
- "inline-flex flex-col rounded-md border border-neutral-alphas-200 bg-background-primary shadow-blur-menu backdrop-blur-sm",
97
+ "inline-flex flex-col rounded-xl border border-modal-stroke bg-modal-background p-6 shadow-blur-menu backdrop-blur-sm",
95
98
  className
96
99
  ),
97
100
  children: [
@@ -101,29 +104,23 @@ const DatePicker = React.forwardRef(
101
104
  showOutsideDays: true,
102
105
  numberOfMonths,
103
106
  formatters: {
104
- formatCaption: (date) => date.toLocaleDateString("en-US", { month: "short", year: "numeric" }),
107
+ formatCaption: (date) => date.toLocaleDateString("en-US", { month: "long", year: "numeric" }),
105
108
  ...formatters
106
109
  },
107
110
  classNames: {
108
111
  root: "w-full",
109
- months: "relative flex",
112
+ months: cn.cn("relative flex", isMulti && "gap-6"),
110
113
  month: "flex flex-1 flex-col",
111
- month_caption: cn.cn("flex items-center py-4", isMulti ? "justify-center px-2" : "px-5"),
112
- caption_label: "typography-body-default-16px-semibold text-content-primary",
113
- nav: cn.cn(
114
- "absolute top-4 z-20 flex",
115
- isMulti ? "pointer-events-none inset-x-3 justify-between" : "right-3 gap-1"
116
- ),
117
- button_previous: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50",
118
- // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes
119
- button_next: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50",
120
- // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes
121
- month_grid: cn.cn("mb-4", isMulti ? "mx-2" : "mx-4"),
114
+ month_caption: "mb-4 flex h-8 items-center justify-center",
115
+ caption_label: "typography-body-default-16px-regular text-content-primary",
116
+ nav: "absolute inset-x-0 top-0 z-20 flex justify-between",
117
+ button_previous: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4",
118
+ button_next: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4",
119
+ month_grid: "w-full",
122
120
  weekdays: "flex",
123
121
  weekday: "flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary",
124
- week: "flex overflow-hidden rounded-xs",
125
- day: "relative flex w-10 flex-1 items-center justify-center",
126
- range_middle: "bg-brand-primary-muted",
122
+ week: "flex",
123
+ day: "relative flex flex-1 items-center justify-center p-0.5",
127
124
  hidden: "hidden"
128
125
  },
129
126
  components: {
@@ -142,8 +139,8 @@ const DatePicker = React.forwardRef(
142
139
  ...resolvedDayPickerProps
143
140
  }
144
141
  ),
145
- showFooter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 px-5 pb-4", children: [
146
- /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "secondary", size: "40", className: "flex-1", onClick: onCancel, children: cancelLabel }),
142
+ showFooter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-6", children: [
143
+ /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "outline", size: "40", className: "flex-1", onClick: onCancel, children: cancelLabel }),
147
144
  /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "primary", size: "40", className: "flex-1", onClick: onApply, children: applyLabel })
148
145
  ] })
149
146
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"DatePicker.cjs","sources":["../../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport {\n type ChevronProps,\n type DateRange,\n type DayButtonProps,\n DayPicker,\n type DayPickerProps,\n type DayProps,\n type Modifiers,\n type MonthGridProps,\n type WeekdayProps,\n type WeekdaysProps,\n type WeekProps,\n type WeeksProps,\n} from \"react-day-picker\";\nimport { cn } from \"../../utils/cn\";\nimport type { OmitDistributed } from \"../../utils/types\";\nimport { Button } from \"../Button/Button\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport type { DateRange }; // Needed by consumers when passing props\n\n/** Layout variant — single or side-by-side month display. */\nexport type DatePickerVariant = \"single\" | \"double\";\n\n/** Props specific to the DatePicker wrapper (not inherited from react-day-picker). */\nexport interface DatePickerOwnProps {\n /** Display one month or two side-by-side. @default \"single\" */\n variant?: DatePickerVariant;\n /** Callback fired when the Apply button is clicked. */\n onApply?: () => void;\n /** Callback fired when the Cancel button is clicked. */\n onCancel?: () => void;\n /** Label for the cancel button. @default \"Cancel\" */\n cancelLabel?: string;\n /** Label for the apply button. @default \"Apply\" */\n applyLabel?: string;\n /** Whether to render the cancel / apply footer buttons. @default true */\n showFooter?: boolean;\n /** Additional CSS class name for the outer container. */\n className?: string;\n}\n\nfunction Day({ day, modifiers, className, ...divProps }: DayProps) {\n const { range_start, range_end } = modifiers;\n const isSingleDayRange = range_start && range_end;\n\n return (\n <div\n className={cn(\n className,\n (range_start || range_end) && !isSingleDayRange && \"from-50% from-transparent to-50%\",\n range_start && !isSingleDayRange && \"bg-linear-to-r to-brand-primary-muted\",\n range_end && !isSingleDayRange && \"bg-linear-to-l to-brand-primary-muted\",\n )}\n {...divProps}\n />\n );\n}\n\nfunction DayButton({ day, modifiers, className, ...buttonProps }: DayButtonProps) {\n const ref = useRef<HTMLButtonElement>(null);\n\n useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n \"relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-xs\",\n \"typography-body-small-14px-regular\",\n \"transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted\",\n \"focus-visible:outline-2 focus-visible:outline-brand-secondary-default focus-visible:outline-offset-[-2px]\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n modifiers.today && !modifiers.selected && \"border border-brand-primary-default\",\n modifiers.selected && !modifiers.range_middle\n ? \"bg-brand-primary-default text-content-always-black hover:bg-brand-primary-default\"\n : \"text-content-primary\",\n modifiers.range_middle && \"rounded-none bg-transparent\",\n modifiers.outside && \"pointer-events-none opacity-50\",\n )}\n {...buttonProps}\n />\n );\n}\n\n/** Combined props — own DatePicker options plus all react-day-picker props (except `numberOfMonths`). */\nexport type DatePickerProps = DatePickerOwnProps &\n OmitDistributed<DayPickerProps, \"numberOfMonths\">;\n\n/**\n * A calendar date picker supporting single-date and date-range selection with\n * optional side-by-side month display and footer action buttons.\n *\n * Built on top of [react-day-picker](https://react-day-picker.js.org/) — all\n * `DayPickerProps` (except `numberOfMonths`) are forwarded.\n *\n * @example\n * ```tsx\n * <DatePicker\n * mode=\"range\"\n * type=\"double\"\n * selected={range}\n * onSelect={setRange}\n * onApply={save}\n * onCancel={close}\n * />\n * ```\n */\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n (\n {\n variant = \"single\",\n onApply,\n onCancel,\n cancelLabel = \"Cancel\",\n applyLabel = \"Apply\",\n showFooter = true,\n className,\n formatters,\n ...dayPickerProps\n },\n ref,\n ) => {\n const numberOfMonths = variant === \"double\" ? 2 : 1;\n const isMulti = numberOfMonths > 1;\n\n // Wrap onSelect for range mode: when clicking inside a complete range,\n // move the nearest boundary instead of always resetting the end date.\n const resolvedDayPickerProps = (() => {\n if (dayPickerProps.mode !== \"range\") return dayPickerProps;\n\n const { selected, onSelect } = dayPickerProps as {\n selected?: DateRange;\n onSelect?: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => void;\n };\n\n if (!onSelect || !selected?.from || !selected?.to) return dayPickerProps;\n\n const { from, to } = selected;\n\n return {\n ...dayPickerProps,\n onSelect: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => {\n const clickedTime = triggerDate.getTime();\n const fromTime = from.getTime();\n const toTime = to.getTime();\n\n if (clickedTime > fromTime && clickedTime < toTime) {\n if (clickedTime - fromTime <= toTime - clickedTime) {\n onSelect({ from: triggerDate, to }, triggerDate, modifiers, e);\n } else {\n onSelect({ from, to: triggerDate }, triggerDate, modifiers, e);\n }\n return;\n }\n\n onSelect(range, triggerDate, modifiers, e);\n },\n } as typeof dayPickerProps;\n })();\n\n return (\n <div\n ref={ref}\n className={cn(\n \"inline-flex flex-col rounded-md border border-neutral-alphas-200 bg-background-primary shadow-blur-menu backdrop-blur-sm\",\n className,\n )}\n >\n <DayPicker\n showOutsideDays\n numberOfMonths={numberOfMonths}\n formatters={{\n formatCaption: (date: Date) =>\n date.toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" }),\n ...formatters,\n }}\n classNames={{\n root: \"w-full\",\n months: \"relative flex\",\n month: \"flex flex-1 flex-col\",\n month_caption: cn(\"flex items-center py-4\", isMulti ? \"justify-center px-2\" : \"px-5\"),\n caption_label: \"typography-body-default-16px-semibold text-content-primary\",\n nav: cn(\n \"absolute top-4 z-20 flex\",\n isMulti ? \"pointer-events-none inset-x-3 justify-between\" : \"right-3 gap-1\",\n ),\n button_previous:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n button_next:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n month_grid: cn(\"mb-4\", isMulti ? \"mx-2\" : \"mx-4\"),\n weekdays: \"flex\",\n weekday:\n \"flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary\",\n week: \"flex overflow-hidden rounded-xs\",\n day: \"relative flex w-10 flex-1 items-center justify-center\",\n range_middle: \"bg-brand-primary-muted\",\n hidden: \"hidden\",\n }}\n components={{\n /**\n * !NOTE: We're unable to use semantic elements for the grid due to rdp, as such we've disabled the a11y lint rules for these elements in biome.json.\n */\n Chevron: ({ orientation }: ChevronProps) =>\n orientation === \"left\" ? <ChevronLeftIcon /> : <ChevronRightIcon />,\n MonthGrid: (props: MonthGridProps) => <div role=\"grid\" {...props} />,\n Weekdays: (props: WeekdaysProps) => <div role=\"row\" {...props} />,\n Weekday: (props: WeekdayProps) => <div role=\"columnheader\" {...props} />,\n Weeks: (props: WeeksProps) => <div role=\"rowgroup\" {...props} />,\n Week: ({ week, ...props }: WeekProps) => <div role=\"row\" {...props} />,\n Day,\n DayButton,\n }}\n {...resolvedDayPickerProps}\n />\n\n {showFooter && (\n <div className=\"flex gap-4 px-5 pb-4\">\n <Button variant=\"secondary\" size=\"40\" className=\"flex-1\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant=\"primary\" size=\"40\" className=\"flex-1\" onClick={onApply}>\n {applyLabel}\n </Button>\n </div>\n )}\n </div>\n );\n },\n);\n\nDatePicker.displayName = \"DatePicker\";\n"],"names":["jsx","cn","useRef","useEffect","forwardRef","jsxs","DayPicker","ChevronLeftIcon","ChevronRightIcon","Button"],"mappings":"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"}
1
+ {"version":3,"file":"DatePicker.cjs","sources":["../../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport {\n type ChevronProps,\n type DateRange,\n type DayButtonProps,\n DayPicker,\n type DayPickerProps,\n type DayProps,\n type Modifiers,\n type MonthGridProps,\n type WeekdayProps,\n type WeekdaysProps,\n type WeekProps,\n type WeeksProps,\n} from \"react-day-picker\";\nimport { cn } from \"../../utils/cn\";\nimport type { OmitDistributed } from \"../../utils/types\";\nimport { Button } from \"../Button/Button\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport type { DateRange }; // Needed by consumers when passing props\n\n/** Layout variant — single or side-by-side month display. */\nexport type DatePickerVariant = \"single\" | \"double\";\n\n/** Props specific to the DatePicker wrapper (not inherited from react-day-picker). */\nexport interface DatePickerOwnProps {\n /** Display one month or two side-by-side. @default \"single\" */\n variant?: DatePickerVariant;\n /** Callback fired when the Apply button is clicked. */\n onApply?: () => void;\n /** Callback fired when the Cancel button is clicked. */\n onCancel?: () => void;\n /** Label for the cancel button. @default \"Cancel\" */\n cancelLabel?: string;\n /** Label for the apply button. @default \"Apply\" */\n applyLabel?: string;\n /** Whether to render the cancel / apply footer buttons. @default true */\n showFooter?: boolean;\n /** Additional CSS class name for the outer container. */\n className?: string;\n}\n\nfunction Day({ day, modifiers, className, ...divProps }: DayProps) {\n const { range_start, range_end, range_middle } = modifiers;\n const isSingleDayRange = range_start && range_end;\n const inRange = (range_start || range_end || range_middle) && !isSingleDayRange;\n\n return (\n <div\n className={cn(\n className,\n inRange && \"bg-inputs-calendar-range\",\n inRange && range_start && \"rounded-l-sm\",\n inRange && range_end && \"rounded-r-sm\",\n )}\n {...divProps}\n />\n );\n}\n\nfunction DayButton({ day, modifiers, className, ...buttonProps }: DayButtonProps) {\n const ref = useRef<HTMLButtonElement>(null);\n\n useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n const isSelected = modifiers.selected && !modifiers.range_middle;\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n \"relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-sm text-content-primary\",\n \"transition-colors hover:bg-inputs-calendar-range not-disabled:active:bg-inputs-calendar-range\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed\",\n isSelected ? \"typography-body-small-14px-semibold\" : \"typography-body-small-14px-regular\",\n modifiers.today &&\n !isSelected &&\n \"bg-inputs-calendar-today text-content-always-white hover:bg-inputs-calendar-today\",\n isSelected &&\n \"bg-inputs-calendar-selected text-content-primary-inverted hover:bg-inputs-calendar-selected\",\n modifiers.range_middle && \"bg-transparent hover:bg-transparent\",\n modifiers.disabled &&\n \"bg-inputs-calendar-disabled text-content-disabled hover:bg-inputs-calendar-disabled\",\n modifiers.outside && \"pointer-events-none text-content-disabled\",\n )}\n {...buttonProps}\n />\n );\n}\n\n/** Combined props — own DatePicker options plus all react-day-picker props (except `numberOfMonths`). */\nexport type DatePickerProps = DatePickerOwnProps &\n OmitDistributed<DayPickerProps, \"numberOfMonths\">;\n\n/**\n * A calendar date picker supporting single-date and date-range selection with\n * optional side-by-side month display and footer action buttons.\n *\n * Built on top of [react-day-picker](https://react-day-picker.js.org/) — all\n * `DayPickerProps` (except `numberOfMonths`) are forwarded.\n *\n * @example\n * ```tsx\n * <DatePicker\n * mode=\"range\"\n * variant=\"double\"\n * selected={range}\n * onSelect={setRange}\n * onApply={save}\n * onCancel={close}\n * />\n * ```\n */\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n (\n {\n variant = \"single\",\n onApply,\n onCancel,\n cancelLabel = \"Cancel\",\n applyLabel = \"Apply\",\n showFooter = true,\n className,\n formatters,\n ...dayPickerProps\n },\n ref,\n ) => {\n const numberOfMonths = variant === \"double\" ? 2 : 1;\n const isMulti = numberOfMonths > 1;\n\n // Wrap onSelect for range mode: when clicking inside a complete range,\n // move the nearest boundary instead of always resetting the end date.\n const resolvedDayPickerProps = (() => {\n if (dayPickerProps.mode !== \"range\") return dayPickerProps;\n\n const { selected, onSelect } = dayPickerProps as {\n selected?: DateRange;\n onSelect?: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => void;\n };\n\n if (!onSelect || !selected?.from || !selected?.to) return dayPickerProps;\n\n const { from, to } = selected;\n\n return {\n ...dayPickerProps,\n onSelect: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => {\n const clickedTime = triggerDate.getTime();\n const fromTime = from.getTime();\n const toTime = to.getTime();\n\n if (clickedTime > fromTime && clickedTime < toTime) {\n if (clickedTime - fromTime <= toTime - clickedTime) {\n onSelect({ from: triggerDate, to }, triggerDate, modifiers, e);\n } else {\n onSelect({ from, to: triggerDate }, triggerDate, modifiers, e);\n }\n return;\n }\n\n onSelect(range, triggerDate, modifiers, e);\n },\n } as typeof dayPickerProps;\n })();\n\n return (\n <div\n ref={ref}\n className={cn(\n \"inline-flex flex-col rounded-xl border border-modal-stroke bg-modal-background p-6 shadow-blur-menu backdrop-blur-sm\",\n className,\n )}\n >\n <DayPicker\n showOutsideDays\n numberOfMonths={numberOfMonths}\n formatters={{\n formatCaption: (date: Date) =>\n date.toLocaleDateString(\"en-US\", { month: \"long\", year: \"numeric\" }),\n ...formatters,\n }}\n classNames={{\n root: \"w-full\",\n months: cn(\"relative flex\", isMulti && \"gap-6\"),\n month: \"flex flex-1 flex-col\",\n month_caption: \"mb-4 flex h-8 items-center justify-center\",\n caption_label: \"typography-body-default-16px-regular text-content-primary\",\n nav: \"absolute inset-x-0 top-0 z-20 flex justify-between\",\n button_previous:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4\",\n button_next:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4\",\n month_grid: \"w-full\",\n weekdays: \"flex\",\n weekday:\n \"flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary\",\n week: \"flex\",\n day: \"relative flex flex-1 items-center justify-center p-0.5\",\n hidden: \"hidden\",\n }}\n components={{\n /**\n * !NOTE: We're unable to use semantic elements for the grid due to rdp, as such we've disabled the a11y lint rules for these elements in biome.json.\n */\n Chevron: ({ orientation }: ChevronProps) =>\n orientation === \"left\" ? <ChevronLeftIcon /> : <ChevronRightIcon />,\n MonthGrid: (props: MonthGridProps) => <div role=\"grid\" {...props} />,\n Weekdays: (props: WeekdaysProps) => <div role=\"row\" {...props} />,\n Weekday: (props: WeekdayProps) => <div role=\"columnheader\" {...props} />,\n Weeks: (props: WeeksProps) => <div role=\"rowgroup\" {...props} />,\n Week: ({ week, ...props }: WeekProps) => <div role=\"row\" {...props} />,\n Day,\n DayButton,\n }}\n {...resolvedDayPickerProps}\n />\n\n {showFooter && (\n <div className=\"flex gap-2 pt-6\">\n <Button variant=\"outline\" size=\"40\" className=\"flex-1\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant=\"primary\" size=\"40\" className=\"flex-1\" onClick={onApply}>\n {applyLabel}\n </Button>\n </div>\n )}\n </div>\n );\n },\n);\n\nDatePicker.displayName = \"DatePicker\";\n"],"names":["jsx","cn","useRef","useEffect","forwardRef","jsxs","DayPicker","ChevronLeftIcon","ChevronRightIcon","Button"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const React = require("react");
6
+ const cn = require("../../utils/cn.cjs");
7
+ const ChevronLeftIcon = require("../Icons/ChevronLeftIcon.cjs");
8
+ const ChevronRightIcon = require("../Icons/ChevronRightIcon.cjs");
9
+ function _interopNamespaceDefault(e) {
10
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
11
+ if (e) {
12
+ for (const k in e) {
13
+ if (k !== "default") {
14
+ const d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: () => e[k]
18
+ });
19
+ }
20
+ }
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
26
+ const arrowClasses = cn.cn(
27
+ "flex shrink-0 cursor-pointer items-center justify-center rounded-xs p-1 text-content-primary [&>svg]:size-4",
28
+ "hover:bg-brand-primary-muted disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent",
29
+ "focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150"
30
+ );
31
+ const PageSelector = React__namespace.forwardRef(
32
+ ({
33
+ className,
34
+ totalPages,
35
+ currentPage,
36
+ onPageChange,
37
+ loop = false,
38
+ disabled = false,
39
+ ariaLabel = "Page selector",
40
+ previousLabel = "Previous page",
41
+ nextLabel = "Next page",
42
+ formatLabel = (current, total) => `${current} of ${total}`,
43
+ ...props
44
+ }, ref) => {
45
+ const atStart = currentPage <= 1;
46
+ const atEnd = currentPage >= totalPages;
47
+ const prevDisabled = disabled || atStart && !loop;
48
+ const nextDisabled = disabled || atEnd && !loop;
49
+ const goTo = (page) => {
50
+ const wrapped = loop ? (page - 1 + totalPages) % totalPages + 1 : page;
51
+ if (wrapped < 1 || wrapped > totalPages || wrapped === currentPage) return;
52
+ onPageChange?.(wrapped);
53
+ };
54
+ return /* @__PURE__ */ jsxRuntime.jsxs(
55
+ "nav",
56
+ {
57
+ ref,
58
+ "aria-label": ariaLabel,
59
+ className: cn.cn(
60
+ "inline-flex items-center gap-1 rounded-sm border border-border-primary bg-surface-secondary p-1",
61
+ className
62
+ ),
63
+ ...props,
64
+ children: [
65
+ /* @__PURE__ */ jsxRuntime.jsx(
66
+ "button",
67
+ {
68
+ type: "button",
69
+ "aria-label": previousLabel,
70
+ disabled: prevDisabled,
71
+ onClick: () => goTo(currentPage - 1),
72
+ className: arrowClasses,
73
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon.ChevronLeftIcon, {})
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsxRuntime.jsx(
77
+ "span",
78
+ {
79
+ "aria-live": "polite",
80
+ className: "typography-description-12px-semibold px-2 py-1 text-center text-content-primary tabular-nums",
81
+ children: formatLabel(currentPage, totalPages)
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsxRuntime.jsx(
85
+ "button",
86
+ {
87
+ type: "button",
88
+ "aria-label": nextLabel,
89
+ disabled: nextDisabled,
90
+ onClick: () => goTo(currentPage + 1),
91
+ className: arrowClasses,
92
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon.ChevronRightIcon, {})
93
+ }
94
+ )
95
+ ]
96
+ }
97
+ );
98
+ }
99
+ );
100
+ PageSelector.displayName = "PageSelector";
101
+ exports.PageSelector = PageSelector;
102
+ //# sourceMappingURL=PageSelector.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageSelector.cjs","sources":["../../../../src/components/PageSelector/PageSelector.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport interface PageSelectorProps extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Total number of pages. */\n totalPages: number;\n /** Current active page (1-indexed). */\n currentPage: number;\n /** Callback fired when the active page changes. Receives the new 1-indexed page number. */\n onPageChange?: (page: number) => void;\n /** Whether reaching either end wraps around to the other. @default false */\n loop?: boolean;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n /** Accessible label for the `<nav>` landmark. @default \"Page selector\" */\n ariaLabel?: string;\n /** Accessible label for the previous-page button. @default \"Previous page\" */\n previousLabel?: string;\n /** Accessible label for the next-page button. @default \"Next page\" */\n nextLabel?: string;\n /** Formats the indicator between the arrows. @default (current, total) => \\`${current} of ${total}\\` */\n formatLabel?: (currentPage: number, totalPages: number) => string;\n}\n\nconst arrowClasses = cn(\n \"flex shrink-0 cursor-pointer items-center justify-center rounded-xs p-1 text-content-primary [&>svg]:size-4\",\n \"hover:bg-brand-primary-muted disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150\",\n);\n\n/**\n * A compact control for stepping through a small, known set of pages one at a\n * time, showing the position as \"current of total\" between a back and forward\n * arrow. Use when the total is small and the exact page number matters less\n * than moving between them, such as flicking through generated variants or a\n * short carousel. Reach for `Pagination` instead when consumers need to jump\n * to a specific numbered page.\n *\n * @example\n * ```tsx\n * <PageSelector totalPages={3} currentPage={page} onPageChange={setPage} />\n * ```\n */\nexport const PageSelector = React.forwardRef<HTMLElement, PageSelectorProps>(\n (\n {\n className,\n totalPages,\n currentPage,\n onPageChange,\n loop = false,\n disabled = false,\n ariaLabel = \"Page selector\",\n previousLabel = \"Previous page\",\n nextLabel = \"Next page\",\n formatLabel = (current, total) => `${current} of ${total}`,\n ...props\n },\n ref,\n ) => {\n const atStart = currentPage <= 1;\n const atEnd = currentPage >= totalPages;\n const prevDisabled = disabled || (atStart && !loop);\n const nextDisabled = disabled || (atEnd && !loop);\n\n const goTo = (page: number) => {\n const wrapped = loop ? ((page - 1 + totalPages) % totalPages) + 1 : page;\n if (wrapped < 1 || wrapped > totalPages || wrapped === currentPage) return;\n onPageChange?.(wrapped);\n };\n\n return (\n <nav\n ref={ref}\n aria-label={ariaLabel}\n className={cn(\n \"inline-flex items-center gap-1 rounded-sm border border-border-primary bg-surface-secondary p-1\",\n className,\n )}\n {...props}\n >\n <button\n type=\"button\"\n aria-label={previousLabel}\n disabled={prevDisabled}\n onClick={() => goTo(currentPage - 1)}\n className={arrowClasses}\n >\n <ChevronLeftIcon />\n </button>\n\n <span\n aria-live=\"polite\"\n className=\"typography-description-12px-semibold px-2 py-1 text-center text-content-primary tabular-nums\"\n >\n {formatLabel(currentPage, totalPages)}\n </span>\n\n <button\n type=\"button\"\n aria-label={nextLabel}\n disabled={nextDisabled}\n onClick={() => goTo(currentPage + 1)}\n className={arrowClasses}\n >\n <ChevronRightIcon />\n </button>\n </nav>\n );\n },\n);\n\nPageSelector.displayName = \"PageSelector\";\n"],"names":["cn","React","jsxs","jsx","ChevronLeftIcon","ChevronRightIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,eAAeA,GAAAA;AAAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF;AAeO,MAAM,eAAeC,iBAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,cAAc,CAAC,SAAS,UAAU,GAAG,OAAO,OAAO,KAAK;AAAA,IACxD,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,eAAe;AAC/B,UAAM,QAAQ,eAAe;AAC7B,UAAM,eAAe,YAAa,WAAW,CAAC;AAC9C,UAAM,eAAe,YAAa,SAAS,CAAC;AAE5C,UAAM,OAAO,CAAC,SAAiB;AAC7B,YAAM,UAAU,QAAS,OAAO,IAAI,cAAc,aAAc,IAAI;AACpE,UAAI,UAAU,KAAK,UAAU,cAAc,YAAY,YAAa;AACpE,qBAAe,OAAO;AAAA,IACxB;AAEA,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,cAAY;AAAA,QACZ,WAAWF,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAAG,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,UAAU;AAAA,cACV,SAAS,MAAM,KAAK,cAAc,CAAC;AAAA,cACnC,WAAW;AAAA,cAEX,yCAACC,gBAAAA,iBAAA,CAAA,CAAgB;AAAA,YAAA;AAAA,UAAA;AAAA,UAGnBD,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAET,UAAA,YAAY,aAAa,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAGtCA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,UAAU;AAAA,cACV,SAAS,MAAM,KAAK,cAAc,CAAC;AAAA,cACnC,WAAW;AAAA,cAEX,yCAACE,iBAAAA,kBAAA,CAAA,CAAiB;AAAA,YAAA;AAAA,UAAA;AAAA,QACpB;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA,aAAa,cAAc;;"}
@@ -0,0 +1,121 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const React = require("react");
6
+ const cn = require("../../utils/cn.cjs");
7
+ function _interopNamespaceDefault(e) {
8
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
9
+ if (e) {
10
+ for (const k in e) {
11
+ if (k !== "default") {
12
+ const d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: () => e[k]
16
+ });
17
+ }
18
+ }
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
24
+ const sizeClasses = {
25
+ "32": "px-2 py-1 typography-description-12px-semibold",
26
+ "40": "px-3 py-1.75 typography-body-small-14px-semibold",
27
+ "48": "px-4 py-2 typography-body-default-16px-semibold"
28
+ };
29
+ function warnMissingAccessibleName(ariaLabel, ariaLabelledBy) {
30
+ if (process.env.NODE_ENV !== "production") {
31
+ if (!ariaLabel && !ariaLabelledBy) {
32
+ console.warn(
33
+ "SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop."
34
+ );
35
+ }
36
+ }
37
+ }
38
+ const SegmentedControl = React__namespace.forwardRef(
39
+ ({
40
+ className,
41
+ size = "32",
42
+ variant = "hug",
43
+ options,
44
+ value: controlledValue,
45
+ defaultValue,
46
+ onChange,
47
+ disabled = false,
48
+ ...props
49
+ }, ref) => {
50
+ warnMissingAccessibleName(props["aria-label"], props["aria-labelledby"]);
51
+ const [internalValue, setInternalValue] = React__namespace.useState(defaultValue ?? options[0]?.value);
52
+ const isControlled = controlledValue !== void 0;
53
+ const currentValue = isControlled ? controlledValue : internalValue;
54
+ const anySelected = options.some((o) => o.value === currentValue);
55
+ const buttonRefs = React__namespace.useRef([]);
56
+ const handleSelect = (optionValue) => {
57
+ if (disabled || optionValue === currentValue) return;
58
+ if (!isControlled) {
59
+ setInternalValue(optionValue);
60
+ }
61
+ onChange?.(optionValue);
62
+ };
63
+ const handleKeyDown = (e, index) => {
64
+ const nextIndex = e.key === "ArrowRight" || e.key === "ArrowDown" ? (index + 1) % options.length : e.key === "ArrowLeft" || e.key === "ArrowUp" ? (index - 1 + options.length) % options.length : null;
65
+ if (nextIndex === null) return;
66
+ e.preventDefault();
67
+ const nextOption = options[nextIndex];
68
+ handleSelect(nextOption.value);
69
+ buttonRefs.current[nextIndex]?.focus();
70
+ };
71
+ return /* @__PURE__ */ jsxRuntime.jsx(
72
+ "div",
73
+ {
74
+ ref,
75
+ role: "radiogroup",
76
+ className: cn.cn(
77
+ "relative items-center rounded-full bg-surface-tertiary p-1",
78
+ variant === "fill" ? "flex w-full" : "inline-flex",
79
+ disabled && "cursor-not-allowed opacity-50",
80
+ className
81
+ ),
82
+ ...props,
83
+ children: options.map((option, index) => {
84
+ const isSelected = currentValue === option.value;
85
+ return (
86
+ // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation
87
+ /* @__PURE__ */ jsxRuntime.jsx(
88
+ "button",
89
+ {
90
+ ref: (el) => {
91
+ buttonRefs.current[index] = el;
92
+ },
93
+ type: "button",
94
+ role: "radio",
95
+ "aria-checked": isSelected,
96
+ tabIndex: isSelected || !anySelected && index === 0 ? 0 : -1,
97
+ disabled,
98
+ onClick: () => handleSelect(option.value),
99
+ onKeyDown: (e) => handleKeyDown(e, index),
100
+ className: cn.cn(
101
+ "relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full",
102
+ "motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
103
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
104
+ variant === "fill" ? "flex-1" : "shrink-0",
105
+ sizeClasses[size],
106
+ isSelected ? "bg-buttons-primary-default text-content-primary-inverted shadow-sm" : "text-content-primary hover:bg-buttons-switch-hover",
107
+ disabled && "pointer-events-none"
108
+ ),
109
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: option.label })
110
+ },
111
+ option.value
112
+ )
113
+ );
114
+ })
115
+ }
116
+ );
117
+ }
118
+ );
119
+ SegmentedControl.displayName = "SegmentedControl";
120
+ exports.SegmentedControl = SegmentedControl;
121
+ //# sourceMappingURL=SegmentedControl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SegmentedControl.cjs","sources":["../../../../src/components/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Height of the segmented control in pixels. */\nexport type SegmentedControlSize = \"32\" | \"40\" | \"48\";\n\n/**\n * Segment layout.\n * - `\"hug\"`: each segment is sized to its content.\n * - `\"fill\"`: the control spans the full width of its container and each segment grows equally.\n */\nexport type SegmentedControlVariant = \"hug\" | \"fill\";\n\n/** Describes one selectable segment. */\nexport interface SegmentedControlOption {\n /** Display label for the segment. */\n label: string;\n /** Value identifier returned via `onChange`. */\n value: string;\n}\n\nexport interface SegmentedControlProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Height of the control in pixels. @default \"32\" */\n size?: SegmentedControlSize;\n /** Segment layout. @default \"hug\" */\n variant?: SegmentedControlVariant;\n /** The selectable segments. Designed for two or three mutually exclusive options. */\n options: SegmentedControlOption[];\n /** Currently selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). Defaults to the first option. */\n defaultValue?: string;\n /** Callback fired when the selected value changes. */\n onChange?: (value: string) => void;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n}\n\n/** Padding and typography per size. Combined with the container's `p-1` these hit the 32/40/48px heights. */\nconst sizeClasses: Record<SegmentedControlSize, string> = {\n \"32\": \"px-2 py-1 typography-description-12px-semibold\",\n \"40\": \"px-3 py-1.75 typography-body-small-14px-semibold\",\n \"48\": \"px-4 py-2 typography-body-default-16px-semibold\",\n};\n\nfunction warnMissingAccessibleName(ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A compact selector for choosing between two or three mutually exclusive\n * options where the choice affects the content immediately below it. Use\n * instead of tabs when the options are more like settings or filters than\n * navigation, such as toggling a list/grid view or a monthly/annual price.\n *\n * Rendered as a `radiogroup` with roving-tabindex keyboard navigation. Supports\n * both controlled and uncontrolled usage.\n *\n * @example\n * ```tsx\n * <SegmentedControl\n * options={[\n * { label: \"Net\", value: \"net\" },\n * { label: \"Gross\", value: \"gross\" },\n * ]}\n * value={amount}\n * onChange={setAmount}\n * aria-label=\"Amount type\"\n * />\n * ```\n */\nexport const SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n (\n {\n className,\n size = \"32\",\n variant = \"hug\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n warnMissingAccessibleName(props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const anySelected = options.some((o) => o.value === currentValue);\n const buttonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const handleSelect = (optionValue: string) => {\n if (disabled || optionValue === currentValue) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const nextIndex =\n e.key === \"ArrowRight\" || e.key === \"ArrowDown\"\n ? (index + 1) % options.length\n : e.key === \"ArrowLeft\" || e.key === \"ArrowUp\"\n ? (index - 1 + options.length) % options.length\n : null;\n if (nextIndex === null) return;\n e.preventDefault();\n const nextOption = options[nextIndex] as SegmentedControlOption;\n handleSelect(nextOption.value);\n buttonRefs.current[nextIndex]?.focus();\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative items-center rounded-full bg-surface-tertiary p-1\",\n variant === \"fill\" ? \"flex w-full\" : \"inline-flex\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n {options.map((option, index) => {\n const isSelected = currentValue === option.value;\n return (\n // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation\n <button\n key={option.value}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected || (!anySelected && index === 0) ? 0 : -1}\n disabled={disabled}\n onClick={() => handleSelect(option.value)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n className={cn(\n \"relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n variant === \"fill\" ? \"flex-1\" : \"shrink-0\",\n sizeClasses[size],\n isSelected\n ? \"bg-buttons-primary-default text-content-primary-inverted shadow-sm\"\n : \"text-content-primary hover:bg-buttons-switch-hover\",\n disabled && \"pointer-events-none\",\n )}\n >\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </button>\n );\n })}\n </div>\n );\n },\n);\n\nSegmentedControl.displayName = \"SegmentedControl\";\n"],"names":["React","jsx","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,cAAoD;AAAA,EACxD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,0BAA0B,WAAoB,gBAAyB;AAC9E,MAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,QAAI,CAAC,aAAa,CAAC,gBAAgB;AACjC,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAwBO,MAAM,mBAAmBA,iBAAM;AAAA,EACpC,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,GAEL,QACG;AACH,8BAA0B,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC;AAGvE,UAAM,CAAC,eAAe,gBAAgB,IAAIA,iBAAM,SAAS,gBAAgB,QAAQ,CAAC,GAAG,KAAK;AAC1F,UAAM,eAAe,oBAAoB;AACzC,UAAM,eAAe,eAAe,kBAAkB;AACtD,UAAM,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,YAAY;AAChE,UAAM,aAAaA,iBAAM,OAAqC,EAAE;AAEhE,UAAM,eAAe,CAAC,gBAAwB;AAC5C,UAAI,YAAY,gBAAgB,aAAc;AAC9C,UAAI,CAAC,cAAc;AACjB,yBAAiB,WAAW;AAAA,MAC9B;AACA,iBAAW,WAAW;AAAA,IACxB;AAEA,UAAM,gBAAgB,CAAC,GAAwB,UAAkB;AAC/D,YAAM,YACJ,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eAC/B,QAAQ,KAAK,QAAQ,SACtB,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAChC,QAAQ,IAAI,QAAQ,UAAU,QAAQ,SACvC;AACR,UAAI,cAAc,KAAM;AACxB,QAAE,eAAA;AACF,YAAM,aAAa,QAAQ,SAAS;AACpC,mBAAa,WAAW,KAAK;AAC7B,iBAAW,QAAQ,SAAS,GAAG,MAAA;AAAA,IACjC;AAEA,WACEC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAWC,GAAAA;AAAAA,UACT;AAAA,UACA,YAAY,SAAS,gBAAgB;AAAA,UACrC,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA,QAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,gBAAM,aAAa,iBAAiB,OAAO;AAC3C;AAAA;AAAA,YAEED,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,KAAK,CAAC,OAAO;AACX,6BAAW,QAAQ,KAAK,IAAI;AAAA,gBAC9B;AAAA,gBACA,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,gBAAc;AAAA,gBACd,UAAU,cAAe,CAAC,eAAe,UAAU,IAAK,IAAI;AAAA,gBAC5D;AAAA,gBACA,SAAS,MAAM,aAAa,OAAO,KAAK;AAAA,gBACxC,WAAW,CAAC,MAAM,cAAc,GAAG,KAAK;AAAA,gBACxC,WAAWC,GAAAA;AAAAA,kBACT;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA,YAAY,SAAS,WAAW;AAAA,kBAChC,YAAY,IAAI;AAAA,kBAChB,aACI,uEACA;AAAA,kBACJ,YAAY;AAAA,gBAAA;AAAA,gBAGd,UAAAD,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAoB,iBAAO,MAAA,CAAM;AAAA,cAAA;AAAA,cAvB5C,OAAO;AAAA,YAAA;AAAA;AAAA,QA0BlB,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,iBAAiB,cAAc;;"}
@@ -204,6 +204,7 @@ const Loader = require("./components/Loader/Loader.cjs");
204
204
  const Logo = require("./components/Logo/Logo.cjs");
205
205
  const MobileStepper = require("./components/MobileStepper/MobileStepper.cjs");
206
206
  const OnlineBlinkingIcon = require("./components/OnlineBlinkingIcon/OnlineBlinkingIcon.cjs");
207
+ const PageSelector = require("./components/PageSelector/PageSelector.cjs");
207
208
  const Pagination = require("./components/Pagination/Pagination.cjs");
208
209
  const PasswordField = require("./components/PasswordField/PasswordField.cjs");
209
210
  const Pill = require("./components/Pill/Pill.cjs");
@@ -215,6 +216,7 @@ const RadioGroup = require("./components/RadioGroup/RadioGroup.cjs");
215
216
  const RatingSummary = require("./components/RatingSummary/RatingSummary.cjs");
216
217
  const ReviewCard = require("./components/ReviewCard/ReviewCard.cjs");
217
218
  const SearchField = require("./components/SearchField/SearchField.cjs");
219
+ const SegmentedControl = require("./components/SegmentedControl/SegmentedControl.cjs");
218
220
  const Select = require("./components/Select/Select.cjs");
219
221
  const Skeleton = require("./components/Skeleton/Skeleton.cjs");
220
222
  const Slider = require("./components/Slider/Slider.cjs");
@@ -491,6 +493,7 @@ exports.Loader = Loader.Loader;
491
493
  exports.Logo = Logo.Logo;
492
494
  exports.MobileStepper = MobileStepper.MobileStepper;
493
495
  exports.OnlineBlinkingIcon = OnlineBlinkingIcon.OnlineBlinkingIcon;
496
+ exports.PageSelector = PageSelector.PageSelector;
494
497
  exports.Pagination = Pagination.Pagination;
495
498
  exports.PasswordField = PasswordField.PasswordField;
496
499
  exports.Pill = Pill.Pill;
@@ -502,6 +505,7 @@ exports.RadioGroup = RadioGroup.RadioGroup;
502
505
  exports.RatingSummary = RatingSummary.RatingSummary;
503
506
  exports.ReviewCard = ReviewCard.ReviewCard;
504
507
  exports.SearchField = SearchField.SearchField;
508
+ exports.SegmentedControl = SegmentedControl.SegmentedControl;
505
509
  exports.Select = Select.Select;
506
510
  exports.SelectContent = Select.SelectContent;
507
511
  exports.SelectGroup = Select.SelectGroup;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -7,16 +7,17 @@ import { Button } from "../Button/Button.mjs";
7
7
  import { ChevronLeftIcon } from "../Icons/ChevronLeftIcon.mjs";
8
8
  import { ChevronRightIcon } from "../Icons/ChevronRightIcon.mjs";
9
9
  function Day({ day, modifiers, className, ...divProps }) {
10
- const { range_start, range_end } = modifiers;
10
+ const { range_start, range_end, range_middle } = modifiers;
11
11
  const isSingleDayRange = range_start && range_end;
12
+ const inRange = (range_start || range_end || range_middle) && !isSingleDayRange;
12
13
  return /* @__PURE__ */ jsx(
13
14
  "div",
14
15
  {
15
16
  className: cn(
16
17
  className,
17
- (range_start || range_end) && !isSingleDayRange && "from-50% from-transparent to-50%",
18
- range_start && !isSingleDayRange && "bg-linear-to-r to-brand-primary-muted",
19
- range_end && !isSingleDayRange && "bg-linear-to-l to-brand-primary-muted"
18
+ inRange && "bg-inputs-calendar-range",
19
+ inRange && range_start && "rounded-l-sm",
20
+ inRange && range_end && "rounded-r-sm"
20
21
  ),
21
22
  ...divProps
22
23
  }
@@ -27,21 +28,23 @@ function DayButton({ day, modifiers, className, ...buttonProps }) {
27
28
  useEffect(() => {
28
29
  if (modifiers.focused) ref.current?.focus();
29
30
  }, [modifiers.focused]);
31
+ const isSelected = modifiers.selected && !modifiers.range_middle;
30
32
  return /* @__PURE__ */ jsx(
31
33
  "button",
32
34
  {
33
35
  ref,
34
36
  type: "button",
35
37
  className: cn(
36
- "relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-xs",
37
- "typography-body-small-14px-regular",
38
- "transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted",
39
- "focus-visible:outline-2 focus-visible:outline-brand-secondary-default focus-visible:outline-offset-[-2px]",
40
- "disabled:cursor-not-allowed disabled:opacity-50",
41
- modifiers.today && !modifiers.selected && "border border-brand-primary-default",
42
- modifiers.selected && !modifiers.range_middle ? "bg-brand-primary-default text-content-always-black hover:bg-brand-primary-default" : "text-content-primary",
43
- modifiers.range_middle && "rounded-none bg-transparent",
44
- modifiers.outside && "pointer-events-none opacity-50"
38
+ "relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-sm text-content-primary",
39
+ "transition-colors hover:bg-inputs-calendar-range not-disabled:active:bg-inputs-calendar-range",
40
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
41
+ "disabled:cursor-not-allowed",
42
+ isSelected ? "typography-body-small-14px-semibold" : "typography-body-small-14px-regular",
43
+ modifiers.today && !isSelected && "bg-inputs-calendar-today text-content-always-white hover:bg-inputs-calendar-today",
44
+ isSelected && "bg-inputs-calendar-selected text-content-primary-inverted hover:bg-inputs-calendar-selected",
45
+ modifiers.range_middle && "bg-transparent hover:bg-transparent",
46
+ modifiers.disabled && "bg-inputs-calendar-disabled text-content-disabled hover:bg-inputs-calendar-disabled",
47
+ modifiers.outside && "pointer-events-none text-content-disabled"
45
48
  ),
46
49
  ...buttonProps
47
50
  }
@@ -89,7 +92,7 @@ const DatePicker = forwardRef(
89
92
  {
90
93
  ref,
91
94
  className: cn(
92
- "inline-flex flex-col rounded-md border border-neutral-alphas-200 bg-background-primary shadow-blur-menu backdrop-blur-sm",
95
+ "inline-flex flex-col rounded-xl border border-modal-stroke bg-modal-background p-6 shadow-blur-menu backdrop-blur-sm",
93
96
  className
94
97
  ),
95
98
  children: [
@@ -99,29 +102,23 @@ const DatePicker = forwardRef(
99
102
  showOutsideDays: true,
100
103
  numberOfMonths,
101
104
  formatters: {
102
- formatCaption: (date) => date.toLocaleDateString("en-US", { month: "short", year: "numeric" }),
105
+ formatCaption: (date) => date.toLocaleDateString("en-US", { month: "long", year: "numeric" }),
103
106
  ...formatters
104
107
  },
105
108
  classNames: {
106
109
  root: "w-full",
107
- months: "relative flex",
110
+ months: cn("relative flex", isMulti && "gap-6"),
108
111
  month: "flex flex-1 flex-col",
109
- month_caption: cn("flex items-center py-4", isMulti ? "justify-center px-2" : "px-5"),
110
- caption_label: "typography-body-default-16px-semibold text-content-primary",
111
- nav: cn(
112
- "absolute top-4 z-20 flex",
113
- isMulti ? "pointer-events-none inset-x-3 justify-between" : "right-3 gap-1"
114
- ),
115
- button_previous: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50",
116
- // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes
117
- button_next: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50",
118
- // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes
119
- month_grid: cn("mb-4", isMulti ? "mx-2" : "mx-4"),
112
+ month_caption: "mb-4 flex h-8 items-center justify-center",
113
+ caption_label: "typography-body-default-16px-regular text-content-primary",
114
+ nav: "absolute inset-x-0 top-0 z-20 flex justify-between",
115
+ button_previous: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4",
116
+ button_next: "pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4",
117
+ month_grid: "w-full",
120
118
  weekdays: "flex",
121
119
  weekday: "flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary",
122
- week: "flex overflow-hidden rounded-xs",
123
- day: "relative flex w-10 flex-1 items-center justify-center",
124
- range_middle: "bg-brand-primary-muted",
120
+ week: "flex",
121
+ day: "relative flex flex-1 items-center justify-center p-0.5",
125
122
  hidden: "hidden"
126
123
  },
127
124
  components: {
@@ -140,8 +137,8 @@ const DatePicker = forwardRef(
140
137
  ...resolvedDayPickerProps
141
138
  }
142
139
  ),
143
- showFooter && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 px-5 pb-4", children: [
144
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "40", className: "flex-1", onClick: onCancel, children: cancelLabel }),
140
+ showFooter && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-6", children: [
141
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "40", className: "flex-1", onClick: onCancel, children: cancelLabel }),
145
142
  /* @__PURE__ */ jsx(Button, { variant: "primary", size: "40", className: "flex-1", onClick: onApply, children: applyLabel })
146
143
  ] })
147
144
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"DatePicker.mjs","sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport {\n type ChevronProps,\n type DateRange,\n type DayButtonProps,\n DayPicker,\n type DayPickerProps,\n type DayProps,\n type Modifiers,\n type MonthGridProps,\n type WeekdayProps,\n type WeekdaysProps,\n type WeekProps,\n type WeeksProps,\n} from \"react-day-picker\";\nimport { cn } from \"../../utils/cn\";\nimport type { OmitDistributed } from \"../../utils/types\";\nimport { Button } from \"../Button/Button\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport type { DateRange }; // Needed by consumers when passing props\n\n/** Layout variant — single or side-by-side month display. */\nexport type DatePickerVariant = \"single\" | \"double\";\n\n/** Props specific to the DatePicker wrapper (not inherited from react-day-picker). */\nexport interface DatePickerOwnProps {\n /** Display one month or two side-by-side. @default \"single\" */\n variant?: DatePickerVariant;\n /** Callback fired when the Apply button is clicked. */\n onApply?: () => void;\n /** Callback fired when the Cancel button is clicked. */\n onCancel?: () => void;\n /** Label for the cancel button. @default \"Cancel\" */\n cancelLabel?: string;\n /** Label for the apply button. @default \"Apply\" */\n applyLabel?: string;\n /** Whether to render the cancel / apply footer buttons. @default true */\n showFooter?: boolean;\n /** Additional CSS class name for the outer container. */\n className?: string;\n}\n\nfunction Day({ day, modifiers, className, ...divProps }: DayProps) {\n const { range_start, range_end } = modifiers;\n const isSingleDayRange = range_start && range_end;\n\n return (\n <div\n className={cn(\n className,\n (range_start || range_end) && !isSingleDayRange && \"from-50% from-transparent to-50%\",\n range_start && !isSingleDayRange && \"bg-linear-to-r to-brand-primary-muted\",\n range_end && !isSingleDayRange && \"bg-linear-to-l to-brand-primary-muted\",\n )}\n {...divProps}\n />\n );\n}\n\nfunction DayButton({ day, modifiers, className, ...buttonProps }: DayButtonProps) {\n const ref = useRef<HTMLButtonElement>(null);\n\n useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n \"relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-xs\",\n \"typography-body-small-14px-regular\",\n \"transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted\",\n \"focus-visible:outline-2 focus-visible:outline-brand-secondary-default focus-visible:outline-offset-[-2px]\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n modifiers.today && !modifiers.selected && \"border border-brand-primary-default\",\n modifiers.selected && !modifiers.range_middle\n ? \"bg-brand-primary-default text-content-always-black hover:bg-brand-primary-default\"\n : \"text-content-primary\",\n modifiers.range_middle && \"rounded-none bg-transparent\",\n modifiers.outside && \"pointer-events-none opacity-50\",\n )}\n {...buttonProps}\n />\n );\n}\n\n/** Combined props — own DatePicker options plus all react-day-picker props (except `numberOfMonths`). */\nexport type DatePickerProps = DatePickerOwnProps &\n OmitDistributed<DayPickerProps, \"numberOfMonths\">;\n\n/**\n * A calendar date picker supporting single-date and date-range selection with\n * optional side-by-side month display and footer action buttons.\n *\n * Built on top of [react-day-picker](https://react-day-picker.js.org/) — all\n * `DayPickerProps` (except `numberOfMonths`) are forwarded.\n *\n * @example\n * ```tsx\n * <DatePicker\n * mode=\"range\"\n * type=\"double\"\n * selected={range}\n * onSelect={setRange}\n * onApply={save}\n * onCancel={close}\n * />\n * ```\n */\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n (\n {\n variant = \"single\",\n onApply,\n onCancel,\n cancelLabel = \"Cancel\",\n applyLabel = \"Apply\",\n showFooter = true,\n className,\n formatters,\n ...dayPickerProps\n },\n ref,\n ) => {\n const numberOfMonths = variant === \"double\" ? 2 : 1;\n const isMulti = numberOfMonths > 1;\n\n // Wrap onSelect for range mode: when clicking inside a complete range,\n // move the nearest boundary instead of always resetting the end date.\n const resolvedDayPickerProps = (() => {\n if (dayPickerProps.mode !== \"range\") return dayPickerProps;\n\n const { selected, onSelect } = dayPickerProps as {\n selected?: DateRange;\n onSelect?: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => void;\n };\n\n if (!onSelect || !selected?.from || !selected?.to) return dayPickerProps;\n\n const { from, to } = selected;\n\n return {\n ...dayPickerProps,\n onSelect: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => {\n const clickedTime = triggerDate.getTime();\n const fromTime = from.getTime();\n const toTime = to.getTime();\n\n if (clickedTime > fromTime && clickedTime < toTime) {\n if (clickedTime - fromTime <= toTime - clickedTime) {\n onSelect({ from: triggerDate, to }, triggerDate, modifiers, e);\n } else {\n onSelect({ from, to: triggerDate }, triggerDate, modifiers, e);\n }\n return;\n }\n\n onSelect(range, triggerDate, modifiers, e);\n },\n } as typeof dayPickerProps;\n })();\n\n return (\n <div\n ref={ref}\n className={cn(\n \"inline-flex flex-col rounded-md border border-neutral-alphas-200 bg-background-primary shadow-blur-menu backdrop-blur-sm\",\n className,\n )}\n >\n <DayPicker\n showOutsideDays\n numberOfMonths={numberOfMonths}\n formatters={{\n formatCaption: (date: Date) =>\n date.toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" }),\n ...formatters,\n }}\n classNames={{\n root: \"w-full\",\n months: \"relative flex\",\n month: \"flex flex-1 flex-col\",\n month_caption: cn(\"flex items-center py-4\", isMulti ? \"justify-center px-2\" : \"px-5\"),\n caption_label: \"typography-body-default-16px-semibold text-content-primary\",\n nav: cn(\n \"absolute top-4 z-20 flex\",\n isMulti ? \"pointer-events-none inset-x-3 justify-between\" : \"right-3 gap-1\",\n ),\n button_previous:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n button_next:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full text-content-primary transition-colors hover:bg-brand-primary-muted not-disabled:active:bg-brand-primary-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-secondary-default disabled:cursor-not-allowed disabled:opacity-50\", // !TODO https://linear.app/fanvue/issue/ENG-7301/swap-out-typography-tailwind-utility-classes\n month_grid: cn(\"mb-4\", isMulti ? \"mx-2\" : \"mx-4\"),\n weekdays: \"flex\",\n weekday:\n \"flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary\",\n week: \"flex overflow-hidden rounded-xs\",\n day: \"relative flex w-10 flex-1 items-center justify-center\",\n range_middle: \"bg-brand-primary-muted\",\n hidden: \"hidden\",\n }}\n components={{\n /**\n * !NOTE: We're unable to use semantic elements for the grid due to rdp, as such we've disabled the a11y lint rules for these elements in biome.json.\n */\n Chevron: ({ orientation }: ChevronProps) =>\n orientation === \"left\" ? <ChevronLeftIcon /> : <ChevronRightIcon />,\n MonthGrid: (props: MonthGridProps) => <div role=\"grid\" {...props} />,\n Weekdays: (props: WeekdaysProps) => <div role=\"row\" {...props} />,\n Weekday: (props: WeekdayProps) => <div role=\"columnheader\" {...props} />,\n Weeks: (props: WeeksProps) => <div role=\"rowgroup\" {...props} />,\n Week: ({ week, ...props }: WeekProps) => <div role=\"row\" {...props} />,\n Day,\n DayButton,\n }}\n {...resolvedDayPickerProps}\n />\n\n {showFooter && (\n <div className=\"flex gap-4 px-5 pb-4\">\n <Button variant=\"secondary\" size=\"40\" className=\"flex-1\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant=\"primary\" size=\"40\" className=\"flex-1\" onClick={onApply}>\n {applyLabel}\n </Button>\n </div>\n )}\n </div>\n );\n },\n);\n\nDatePicker.displayName = \"DatePicker\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DatePicker.mjs","sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport {\n type ChevronProps,\n type DateRange,\n type DayButtonProps,\n DayPicker,\n type DayPickerProps,\n type DayProps,\n type Modifiers,\n type MonthGridProps,\n type WeekdayProps,\n type WeekdaysProps,\n type WeekProps,\n type WeeksProps,\n} from \"react-day-picker\";\nimport { cn } from \"../../utils/cn\";\nimport type { OmitDistributed } from \"../../utils/types\";\nimport { Button } from \"../Button/Button\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport type { DateRange }; // Needed by consumers when passing props\n\n/** Layout variant — single or side-by-side month display. */\nexport type DatePickerVariant = \"single\" | \"double\";\n\n/** Props specific to the DatePicker wrapper (not inherited from react-day-picker). */\nexport interface DatePickerOwnProps {\n /** Display one month or two side-by-side. @default \"single\" */\n variant?: DatePickerVariant;\n /** Callback fired when the Apply button is clicked. */\n onApply?: () => void;\n /** Callback fired when the Cancel button is clicked. */\n onCancel?: () => void;\n /** Label for the cancel button. @default \"Cancel\" */\n cancelLabel?: string;\n /** Label for the apply button. @default \"Apply\" */\n applyLabel?: string;\n /** Whether to render the cancel / apply footer buttons. @default true */\n showFooter?: boolean;\n /** Additional CSS class name for the outer container. */\n className?: string;\n}\n\nfunction Day({ day, modifiers, className, ...divProps }: DayProps) {\n const { range_start, range_end, range_middle } = modifiers;\n const isSingleDayRange = range_start && range_end;\n const inRange = (range_start || range_end || range_middle) && !isSingleDayRange;\n\n return (\n <div\n className={cn(\n className,\n inRange && \"bg-inputs-calendar-range\",\n inRange && range_start && \"rounded-l-sm\",\n inRange && range_end && \"rounded-r-sm\",\n )}\n {...divProps}\n />\n );\n}\n\nfunction DayButton({ day, modifiers, className, ...buttonProps }: DayButtonProps) {\n const ref = useRef<HTMLButtonElement>(null);\n\n useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n const isSelected = modifiers.selected && !modifiers.range_middle;\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(\n \"relative z-10 inline-flex size-10 cursor-pointer items-center justify-center rounded-sm text-content-primary\",\n \"transition-colors hover:bg-inputs-calendar-range not-disabled:active:bg-inputs-calendar-range\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed\",\n isSelected ? \"typography-body-small-14px-semibold\" : \"typography-body-small-14px-regular\",\n modifiers.today &&\n !isSelected &&\n \"bg-inputs-calendar-today text-content-always-white hover:bg-inputs-calendar-today\",\n isSelected &&\n \"bg-inputs-calendar-selected text-content-primary-inverted hover:bg-inputs-calendar-selected\",\n modifiers.range_middle && \"bg-transparent hover:bg-transparent\",\n modifiers.disabled &&\n \"bg-inputs-calendar-disabled text-content-disabled hover:bg-inputs-calendar-disabled\",\n modifiers.outside && \"pointer-events-none text-content-disabled\",\n )}\n {...buttonProps}\n />\n );\n}\n\n/** Combined props — own DatePicker options plus all react-day-picker props (except `numberOfMonths`). */\nexport type DatePickerProps = DatePickerOwnProps &\n OmitDistributed<DayPickerProps, \"numberOfMonths\">;\n\n/**\n * A calendar date picker supporting single-date and date-range selection with\n * optional side-by-side month display and footer action buttons.\n *\n * Built on top of [react-day-picker](https://react-day-picker.js.org/) — all\n * `DayPickerProps` (except `numberOfMonths`) are forwarded.\n *\n * @example\n * ```tsx\n * <DatePicker\n * mode=\"range\"\n * variant=\"double\"\n * selected={range}\n * onSelect={setRange}\n * onApply={save}\n * onCancel={close}\n * />\n * ```\n */\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n (\n {\n variant = \"single\",\n onApply,\n onCancel,\n cancelLabel = \"Cancel\",\n applyLabel = \"Apply\",\n showFooter = true,\n className,\n formatters,\n ...dayPickerProps\n },\n ref,\n ) => {\n const numberOfMonths = variant === \"double\" ? 2 : 1;\n const isMulti = numberOfMonths > 1;\n\n // Wrap onSelect for range mode: when clicking inside a complete range,\n // move the nearest boundary instead of always resetting the end date.\n const resolvedDayPickerProps = (() => {\n if (dayPickerProps.mode !== \"range\") return dayPickerProps;\n\n const { selected, onSelect } = dayPickerProps as {\n selected?: DateRange;\n onSelect?: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => void;\n };\n\n if (!onSelect || !selected?.from || !selected?.to) return dayPickerProps;\n\n const { from, to } = selected;\n\n return {\n ...dayPickerProps,\n onSelect: (\n range: DateRange | undefined,\n triggerDate: Date,\n modifiers: Modifiers,\n e: React.MouseEvent | React.KeyboardEvent,\n ) => {\n const clickedTime = triggerDate.getTime();\n const fromTime = from.getTime();\n const toTime = to.getTime();\n\n if (clickedTime > fromTime && clickedTime < toTime) {\n if (clickedTime - fromTime <= toTime - clickedTime) {\n onSelect({ from: triggerDate, to }, triggerDate, modifiers, e);\n } else {\n onSelect({ from, to: triggerDate }, triggerDate, modifiers, e);\n }\n return;\n }\n\n onSelect(range, triggerDate, modifiers, e);\n },\n } as typeof dayPickerProps;\n })();\n\n return (\n <div\n ref={ref}\n className={cn(\n \"inline-flex flex-col rounded-xl border border-modal-stroke bg-modal-background p-6 shadow-blur-menu backdrop-blur-sm\",\n className,\n )}\n >\n <DayPicker\n showOutsideDays\n numberOfMonths={numberOfMonths}\n formatters={{\n formatCaption: (date: Date) =>\n date.toLocaleDateString(\"en-US\", { month: \"long\", year: \"numeric\" }),\n ...formatters,\n }}\n classNames={{\n root: \"w-full\",\n months: cn(\"relative flex\", isMulti && \"gap-6\"),\n month: \"flex flex-1 flex-col\",\n month_caption: \"mb-4 flex h-8 items-center justify-center\",\n caption_label: \"typography-body-default-16px-regular text-content-primary\",\n nav: \"absolute inset-x-0 top-0 z-20 flex justify-between\",\n button_previous:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4\",\n button_next:\n \"pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-full bg-buttons-secondary-default text-content-primary transition-colors hover:bg-buttons-secondary-hover focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-buttons-disabled-default disabled:text-content-disabled [&>svg]:size-4\",\n month_grid: \"w-full\",\n weekdays: \"flex\",\n weekday:\n \"flex h-[30px] w-10 flex-1 items-center justify-center typography-body-small-14px-regular text-content-secondary\",\n week: \"flex\",\n day: \"relative flex flex-1 items-center justify-center p-0.5\",\n hidden: \"hidden\",\n }}\n components={{\n /**\n * !NOTE: We're unable to use semantic elements for the grid due to rdp, as such we've disabled the a11y lint rules for these elements in biome.json.\n */\n Chevron: ({ orientation }: ChevronProps) =>\n orientation === \"left\" ? <ChevronLeftIcon /> : <ChevronRightIcon />,\n MonthGrid: (props: MonthGridProps) => <div role=\"grid\" {...props} />,\n Weekdays: (props: WeekdaysProps) => <div role=\"row\" {...props} />,\n Weekday: (props: WeekdayProps) => <div role=\"columnheader\" {...props} />,\n Weeks: (props: WeeksProps) => <div role=\"rowgroup\" {...props} />,\n Week: ({ week, ...props }: WeekProps) => <div role=\"row\" {...props} />,\n Day,\n DayButton,\n }}\n {...resolvedDayPickerProps}\n />\n\n {showFooter && (\n <div className=\"flex gap-2 pt-6\">\n <Button variant=\"outline\" size=\"40\" className=\"flex-1\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant=\"primary\" size=\"40\" className=\"flex-1\" onClick={onApply}>\n {applyLabel}\n </Button>\n </div>\n )}\n </div>\n );\n },\n);\n\nDatePicker.displayName = \"DatePicker\";\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.mjs";
5
+ import { ChevronLeftIcon } from "../Icons/ChevronLeftIcon.mjs";
6
+ import { ChevronRightIcon } from "../Icons/ChevronRightIcon.mjs";
7
+ const arrowClasses = cn(
8
+ "flex shrink-0 cursor-pointer items-center justify-center rounded-xs p-1 text-content-primary [&>svg]:size-4",
9
+ "hover:bg-brand-primary-muted disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent",
10
+ "focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150"
11
+ );
12
+ const PageSelector = React.forwardRef(
13
+ ({
14
+ className,
15
+ totalPages,
16
+ currentPage,
17
+ onPageChange,
18
+ loop = false,
19
+ disabled = false,
20
+ ariaLabel = "Page selector",
21
+ previousLabel = "Previous page",
22
+ nextLabel = "Next page",
23
+ formatLabel = (current, total) => `${current} of ${total}`,
24
+ ...props
25
+ }, ref) => {
26
+ const atStart = currentPage <= 1;
27
+ const atEnd = currentPage >= totalPages;
28
+ const prevDisabled = disabled || atStart && !loop;
29
+ const nextDisabled = disabled || atEnd && !loop;
30
+ const goTo = (page) => {
31
+ const wrapped = loop ? (page - 1 + totalPages) % totalPages + 1 : page;
32
+ if (wrapped < 1 || wrapped > totalPages || wrapped === currentPage) return;
33
+ onPageChange?.(wrapped);
34
+ };
35
+ return /* @__PURE__ */ jsxs(
36
+ "nav",
37
+ {
38
+ ref,
39
+ "aria-label": ariaLabel,
40
+ className: cn(
41
+ "inline-flex items-center gap-1 rounded-sm border border-border-primary bg-surface-secondary p-1",
42
+ className
43
+ ),
44
+ ...props,
45
+ children: [
46
+ /* @__PURE__ */ jsx(
47
+ "button",
48
+ {
49
+ type: "button",
50
+ "aria-label": previousLabel,
51
+ disabled: prevDisabled,
52
+ onClick: () => goTo(currentPage - 1),
53
+ className: arrowClasses,
54
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx(
58
+ "span",
59
+ {
60
+ "aria-live": "polite",
61
+ className: "typography-description-12px-semibold px-2 py-1 text-center text-content-primary tabular-nums",
62
+ children: formatLabel(currentPage, totalPages)
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsx(
66
+ "button",
67
+ {
68
+ type: "button",
69
+ "aria-label": nextLabel,
70
+ disabled: nextDisabled,
71
+ onClick: () => goTo(currentPage + 1),
72
+ className: arrowClasses,
73
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
74
+ }
75
+ )
76
+ ]
77
+ }
78
+ );
79
+ }
80
+ );
81
+ PageSelector.displayName = "PageSelector";
82
+ export {
83
+ PageSelector
84
+ };
85
+ //# sourceMappingURL=PageSelector.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageSelector.mjs","sources":["../../../src/components/PageSelector/PageSelector.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { ChevronLeftIcon } from \"../Icons/ChevronLeftIcon\";\nimport { ChevronRightIcon } from \"../Icons/ChevronRightIcon\";\n\nexport interface PageSelectorProps extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Total number of pages. */\n totalPages: number;\n /** Current active page (1-indexed). */\n currentPage: number;\n /** Callback fired when the active page changes. Receives the new 1-indexed page number. */\n onPageChange?: (page: number) => void;\n /** Whether reaching either end wraps around to the other. @default false */\n loop?: boolean;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n /** Accessible label for the `<nav>` landmark. @default \"Page selector\" */\n ariaLabel?: string;\n /** Accessible label for the previous-page button. @default \"Previous page\" */\n previousLabel?: string;\n /** Accessible label for the next-page button. @default \"Next page\" */\n nextLabel?: string;\n /** Formats the indicator between the arrows. @default (current, total) => \\`${current} of ${total}\\` */\n formatLabel?: (currentPage: number, totalPages: number) => string;\n}\n\nconst arrowClasses = cn(\n \"flex shrink-0 cursor-pointer items-center justify-center rounded-xs p-1 text-content-primary [&>svg]:size-4\",\n \"hover:bg-brand-primary-muted disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150\",\n);\n\n/**\n * A compact control for stepping through a small, known set of pages one at a\n * time, showing the position as \"current of total\" between a back and forward\n * arrow. Use when the total is small and the exact page number matters less\n * than moving between them, such as flicking through generated variants or a\n * short carousel. Reach for `Pagination` instead when consumers need to jump\n * to a specific numbered page.\n *\n * @example\n * ```tsx\n * <PageSelector totalPages={3} currentPage={page} onPageChange={setPage} />\n * ```\n */\nexport const PageSelector = React.forwardRef<HTMLElement, PageSelectorProps>(\n (\n {\n className,\n totalPages,\n currentPage,\n onPageChange,\n loop = false,\n disabled = false,\n ariaLabel = \"Page selector\",\n previousLabel = \"Previous page\",\n nextLabel = \"Next page\",\n formatLabel = (current, total) => `${current} of ${total}`,\n ...props\n },\n ref,\n ) => {\n const atStart = currentPage <= 1;\n const atEnd = currentPage >= totalPages;\n const prevDisabled = disabled || (atStart && !loop);\n const nextDisabled = disabled || (atEnd && !loop);\n\n const goTo = (page: number) => {\n const wrapped = loop ? ((page - 1 + totalPages) % totalPages) + 1 : page;\n if (wrapped < 1 || wrapped > totalPages || wrapped === currentPage) return;\n onPageChange?.(wrapped);\n };\n\n return (\n <nav\n ref={ref}\n aria-label={ariaLabel}\n className={cn(\n \"inline-flex items-center gap-1 rounded-sm border border-border-primary bg-surface-secondary p-1\",\n className,\n )}\n {...props}\n >\n <button\n type=\"button\"\n aria-label={previousLabel}\n disabled={prevDisabled}\n onClick={() => goTo(currentPage - 1)}\n className={arrowClasses}\n >\n <ChevronLeftIcon />\n </button>\n\n <span\n aria-live=\"polite\"\n className=\"typography-description-12px-semibold px-2 py-1 text-center text-content-primary tabular-nums\"\n >\n {formatLabel(currentPage, totalPages)}\n </span>\n\n <button\n type=\"button\"\n aria-label={nextLabel}\n disabled={nextDisabled}\n onClick={() => goTo(currentPage + 1)}\n className={arrowClasses}\n >\n <ChevronRightIcon />\n </button>\n </nav>\n );\n },\n);\n\nPageSelector.displayName = \"PageSelector\";\n"],"names":[],"mappings":";;;;;;AA0BA,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF;AAeO,MAAM,eAAe,MAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,cAAc,CAAC,SAAS,UAAU,GAAG,OAAO,OAAO,KAAK;AAAA,IACxD,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,eAAe;AAC/B,UAAM,QAAQ,eAAe;AAC7B,UAAM,eAAe,YAAa,WAAW,CAAC;AAC9C,UAAM,eAAe,YAAa,SAAS,CAAC;AAE5C,UAAM,OAAO,CAAC,SAAiB;AAC7B,YAAM,UAAU,QAAS,OAAO,IAAI,cAAc,aAAc,IAAI;AACpE,UAAI,UAAU,KAAK,UAAU,cAAc,YAAY,YAAa;AACpE,qBAAe,OAAO;AAAA,IACxB;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,cAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,UAAU;AAAA,cACV,SAAS,MAAM,KAAK,cAAc,CAAC;AAAA,cACnC,WAAW;AAAA,cAEX,8BAAC,iBAAA,CAAA,CAAgB;AAAA,YAAA;AAAA,UAAA;AAAA,UAGnB;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAET,UAAA,YAAY,aAAa,UAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAGtC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,UAAU;AAAA,cACV,SAAS,MAAM,KAAK,cAAc,CAAC;AAAA,cACnC,WAAW;AAAA,cAEX,8BAAC,kBAAA,CAAA,CAAiB;AAAA,YAAA;AAAA,UAAA;AAAA,QACpB;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA,aAAa,cAAc;"}
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.mjs";
5
+ const sizeClasses = {
6
+ "32": "px-2 py-1 typography-description-12px-semibold",
7
+ "40": "px-3 py-1.75 typography-body-small-14px-semibold",
8
+ "48": "px-4 py-2 typography-body-default-16px-semibold"
9
+ };
10
+ function warnMissingAccessibleName(ariaLabel, ariaLabelledBy) {
11
+ if (process.env.NODE_ENV !== "production") {
12
+ if (!ariaLabel && !ariaLabelledBy) {
13
+ console.warn(
14
+ "SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop."
15
+ );
16
+ }
17
+ }
18
+ }
19
+ const SegmentedControl = React.forwardRef(
20
+ ({
21
+ className,
22
+ size = "32",
23
+ variant = "hug",
24
+ options,
25
+ value: controlledValue,
26
+ defaultValue,
27
+ onChange,
28
+ disabled = false,
29
+ ...props
30
+ }, ref) => {
31
+ warnMissingAccessibleName(props["aria-label"], props["aria-labelledby"]);
32
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);
33
+ const isControlled = controlledValue !== void 0;
34
+ const currentValue = isControlled ? controlledValue : internalValue;
35
+ const anySelected = options.some((o) => o.value === currentValue);
36
+ const buttonRefs = React.useRef([]);
37
+ const handleSelect = (optionValue) => {
38
+ if (disabled || optionValue === currentValue) return;
39
+ if (!isControlled) {
40
+ setInternalValue(optionValue);
41
+ }
42
+ onChange?.(optionValue);
43
+ };
44
+ const handleKeyDown = (e, index) => {
45
+ const nextIndex = e.key === "ArrowRight" || e.key === "ArrowDown" ? (index + 1) % options.length : e.key === "ArrowLeft" || e.key === "ArrowUp" ? (index - 1 + options.length) % options.length : null;
46
+ if (nextIndex === null) return;
47
+ e.preventDefault();
48
+ const nextOption = options[nextIndex];
49
+ handleSelect(nextOption.value);
50
+ buttonRefs.current[nextIndex]?.focus();
51
+ };
52
+ return /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ ref,
56
+ role: "radiogroup",
57
+ className: cn(
58
+ "relative items-center rounded-full bg-surface-tertiary p-1",
59
+ variant === "fill" ? "flex w-full" : "inline-flex",
60
+ disabled && "cursor-not-allowed opacity-50",
61
+ className
62
+ ),
63
+ ...props,
64
+ children: options.map((option, index) => {
65
+ const isSelected = currentValue === option.value;
66
+ return (
67
+ // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation
68
+ /* @__PURE__ */ jsx(
69
+ "button",
70
+ {
71
+ ref: (el) => {
72
+ buttonRefs.current[index] = el;
73
+ },
74
+ type: "button",
75
+ role: "radio",
76
+ "aria-checked": isSelected,
77
+ tabIndex: isSelected || !anySelected && index === 0 ? 0 : -1,
78
+ disabled,
79
+ onClick: () => handleSelect(option.value),
80
+ onKeyDown: (e) => handleKeyDown(e, index),
81
+ className: cn(
82
+ "relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full",
83
+ "motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
84
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
85
+ variant === "fill" ? "flex-1" : "shrink-0",
86
+ sizeClasses[size],
87
+ isSelected ? "bg-buttons-primary-default text-content-primary-inverted shadow-sm" : "text-content-primary hover:bg-buttons-switch-hover",
88
+ disabled && "pointer-events-none"
89
+ ),
90
+ children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.label })
91
+ },
92
+ option.value
93
+ )
94
+ );
95
+ })
96
+ }
97
+ );
98
+ }
99
+ );
100
+ SegmentedControl.displayName = "SegmentedControl";
101
+ export {
102
+ SegmentedControl
103
+ };
104
+ //# sourceMappingURL=SegmentedControl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SegmentedControl.mjs","sources":["../../../src/components/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Height of the segmented control in pixels. */\nexport type SegmentedControlSize = \"32\" | \"40\" | \"48\";\n\n/**\n * Segment layout.\n * - `\"hug\"`: each segment is sized to its content.\n * - `\"fill\"`: the control spans the full width of its container and each segment grows equally.\n */\nexport type SegmentedControlVariant = \"hug\" | \"fill\";\n\n/** Describes one selectable segment. */\nexport interface SegmentedControlOption {\n /** Display label for the segment. */\n label: string;\n /** Value identifier returned via `onChange`. */\n value: string;\n}\n\nexport interface SegmentedControlProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Height of the control in pixels. @default \"32\" */\n size?: SegmentedControlSize;\n /** Segment layout. @default \"hug\" */\n variant?: SegmentedControlVariant;\n /** The selectable segments. Designed for two or three mutually exclusive options. */\n options: SegmentedControlOption[];\n /** Currently selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). Defaults to the first option. */\n defaultValue?: string;\n /** Callback fired when the selected value changes. */\n onChange?: (value: string) => void;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n}\n\n/** Padding and typography per size. Combined with the container's `p-1` these hit the 32/40/48px heights. */\nconst sizeClasses: Record<SegmentedControlSize, string> = {\n \"32\": \"px-2 py-1 typography-description-12px-semibold\",\n \"40\": \"px-3 py-1.75 typography-body-small-14px-semibold\",\n \"48\": \"px-4 py-2 typography-body-default-16px-semibold\",\n};\n\nfunction warnMissingAccessibleName(ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A compact selector for choosing between two or three mutually exclusive\n * options where the choice affects the content immediately below it. Use\n * instead of tabs when the options are more like settings or filters than\n * navigation, such as toggling a list/grid view or a monthly/annual price.\n *\n * Rendered as a `radiogroup` with roving-tabindex keyboard navigation. Supports\n * both controlled and uncontrolled usage.\n *\n * @example\n * ```tsx\n * <SegmentedControl\n * options={[\n * { label: \"Net\", value: \"net\" },\n * { label: \"Gross\", value: \"gross\" },\n * ]}\n * value={amount}\n * onChange={setAmount}\n * aria-label=\"Amount type\"\n * />\n * ```\n */\nexport const SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n (\n {\n className,\n size = \"32\",\n variant = \"hug\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n warnMissingAccessibleName(props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const anySelected = options.some((o) => o.value === currentValue);\n const buttonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const handleSelect = (optionValue: string) => {\n if (disabled || optionValue === currentValue) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const nextIndex =\n e.key === \"ArrowRight\" || e.key === \"ArrowDown\"\n ? (index + 1) % options.length\n : e.key === \"ArrowLeft\" || e.key === \"ArrowUp\"\n ? (index - 1 + options.length) % options.length\n : null;\n if (nextIndex === null) return;\n e.preventDefault();\n const nextOption = options[nextIndex] as SegmentedControlOption;\n handleSelect(nextOption.value);\n buttonRefs.current[nextIndex]?.focus();\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative items-center rounded-full bg-surface-tertiary p-1\",\n variant === \"fill\" ? \"flex w-full\" : \"inline-flex\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n {options.map((option, index) => {\n const isSelected = currentValue === option.value;\n return (\n // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation\n <button\n key={option.value}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected || (!anySelected && index === 0) ? 0 : -1}\n disabled={disabled}\n onClick={() => handleSelect(option.value)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n className={cn(\n \"relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n variant === \"fill\" ? \"flex-1\" : \"shrink-0\",\n sizeClasses[size],\n isSelected\n ? \"bg-buttons-primary-default text-content-primary-inverted shadow-sm\"\n : \"text-content-primary hover:bg-buttons-switch-hover\",\n disabled && \"pointer-events-none\",\n )}\n >\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </button>\n );\n })}\n </div>\n );\n },\n);\n\nSegmentedControl.displayName = \"SegmentedControl\";\n"],"names":[],"mappings":";;;;AAwCA,MAAM,cAAoD;AAAA,EACxD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,0BAA0B,WAAoB,gBAAyB;AAC9E,MAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,QAAI,CAAC,aAAa,CAAC,gBAAgB;AACjC,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAwBO,MAAM,mBAAmB,MAAM;AAAA,EACpC,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,GAEL,QACG;AACH,8BAA0B,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC;AAGvE,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,gBAAgB,QAAQ,CAAC,GAAG,KAAK;AAC1F,UAAM,eAAe,oBAAoB;AACzC,UAAM,eAAe,eAAe,kBAAkB;AACtD,UAAM,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,YAAY;AAChE,UAAM,aAAa,MAAM,OAAqC,EAAE;AAEhE,UAAM,eAAe,CAAC,gBAAwB;AAC5C,UAAI,YAAY,gBAAgB,aAAc;AAC9C,UAAI,CAAC,cAAc;AACjB,yBAAiB,WAAW;AAAA,MAC9B;AACA,iBAAW,WAAW;AAAA,IACxB;AAEA,UAAM,gBAAgB,CAAC,GAAwB,UAAkB;AAC/D,YAAM,YACJ,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eAC/B,QAAQ,KAAK,QAAQ,SACtB,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAChC,QAAQ,IAAI,QAAQ,UAAU,QAAQ,SACvC;AACR,UAAI,cAAc,KAAM;AACxB,QAAE,eAAA;AACF,YAAM,aAAa,QAAQ,SAAS;AACpC,mBAAa,WAAW,KAAK;AAC7B,iBAAW,QAAQ,SAAS,GAAG,MAAA;AAAA,IACjC;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAW;AAAA,UACT;AAAA,UACA,YAAY,SAAS,gBAAgB;AAAA,UACrC,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA,QAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,gBAAM,aAAa,iBAAiB,OAAO;AAC3C;AAAA;AAAA,YAEE;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,KAAK,CAAC,OAAO;AACX,6BAAW,QAAQ,KAAK,IAAI;AAAA,gBAC9B;AAAA,gBACA,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,gBAAc;AAAA,gBACd,UAAU,cAAe,CAAC,eAAe,UAAU,IAAK,IAAI;AAAA,gBAC5D;AAAA,gBACA,SAAS,MAAM,aAAa,OAAO,KAAK;AAAA,gBACxC,WAAW,CAAC,MAAM,cAAc,GAAG,KAAK;AAAA,gBACxC,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA,YAAY,SAAS,WAAW;AAAA,kBAChC,YAAY,IAAI;AAAA,kBAChB,aACI,uEACA;AAAA,kBACJ,YAAY;AAAA,gBAAA;AAAA,gBAGd,UAAA,oBAAC,QAAA,EAAK,WAAU,oBAAoB,iBAAO,MAAA,CAAM;AAAA,cAAA;AAAA,cAvB5C,OAAO;AAAA,YAAA;AAAA;AAAA,QA0BlB,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,iBAAiB,cAAc;"}
@@ -14,7 +14,7 @@ import { RefAttributes } from 'react';
14
14
  * ```tsx
15
15
  * <DatePicker
16
16
  * mode="range"
17
- * type="double"
17
+ * variant="double"
18
18
  * selected={range}
19
19
  * onSelect={setRange}
20
20
  * onApply={save}
package/dist/index.d.ts CHANGED
@@ -3419,6 +3419,42 @@ export declare const OpenIcon: React_2.ForwardRefExoticComponent<React_2.SVGAttr
3419
3419
  className?: string;
3420
3420
  } & React_2.RefAttributes<SVGSVGElement>>;
3421
3421
 
3422
+ /**
3423
+ * A compact control for stepping through a small, known set of pages one at a
3424
+ * time, showing the position as "current of total" between a back and forward
3425
+ * arrow. Use when the total is small and the exact page number matters less
3426
+ * than moving between them, such as flicking through generated variants or a
3427
+ * short carousel. Reach for `Pagination` instead when consumers need to jump
3428
+ * to a specific numbered page.
3429
+ *
3430
+ * @example
3431
+ * ```tsx
3432
+ * <PageSelector totalPages={3} currentPage={page} onPageChange={setPage} />
3433
+ * ```
3434
+ */
3435
+ export declare const PageSelector: React_2.ForwardRefExoticComponent<PageSelectorProps & React_2.RefAttributes<HTMLElement>>;
3436
+
3437
+ export declare interface PageSelectorProps extends Omit<React_2.HTMLAttributes<HTMLElement>, "onChange"> {
3438
+ /** Total number of pages. */
3439
+ totalPages: number;
3440
+ /** Current active page (1-indexed). */
3441
+ currentPage: number;
3442
+ /** Callback fired when the active page changes. Receives the new 1-indexed page number. */
3443
+ onPageChange?: (page: number) => void;
3444
+ /** Whether reaching either end wraps around to the other. @default false */
3445
+ loop?: boolean;
3446
+ /** Whether the control is disabled. @default false */
3447
+ disabled?: boolean;
3448
+ /** Accessible label for the `<nav>` landmark. @default "Page selector" */
3449
+ ariaLabel?: string;
3450
+ /** Accessible label for the previous-page button. @default "Previous page" */
3451
+ previousLabel?: string;
3452
+ /** Accessible label for the next-page button. @default "Next page" */
3453
+ nextLabel?: string;
3454
+ /** Formats the indicator between the arrows. @default (current, total) => \`${current} of ${total}\` */
3455
+ formatLabel?: (currentPage: number, totalPages: number) => string;
3456
+ }
3457
+
3422
3458
  /**
3423
3459
  * Page navigation control with previous/next buttons and numbered page
3424
3460
  * indicators. Supports a numbered-buttons layout (`"default"`) and a compact
@@ -3930,6 +3966,65 @@ export declare const SearchIcon: React_2.ForwardRefExoticComponent<BaseIconProps
3930
3966
  /** Props for {@link SearchIcon}. See {@link BaseIconProps} for the shared shape. */
3931
3967
  export declare type SearchIconProps = BaseIconProps;
3932
3968
 
3969
+ /**
3970
+ * A compact selector for choosing between two or three mutually exclusive
3971
+ * options where the choice affects the content immediately below it. Use
3972
+ * instead of tabs when the options are more like settings or filters than
3973
+ * navigation, such as toggling a list/grid view or a monthly/annual price.
3974
+ *
3975
+ * Rendered as a `radiogroup` with roving-tabindex keyboard navigation. Supports
3976
+ * both controlled and uncontrolled usage.
3977
+ *
3978
+ * @example
3979
+ * ```tsx
3980
+ * <SegmentedControl
3981
+ * options={[
3982
+ * { label: "Net", value: "net" },
3983
+ * { label: "Gross", value: "gross" },
3984
+ * ]}
3985
+ * value={amount}
3986
+ * onChange={setAmount}
3987
+ * aria-label="Amount type"
3988
+ * />
3989
+ * ```
3990
+ */
3991
+ export declare const SegmentedControl: React_2.ForwardRefExoticComponent<SegmentedControlProps & React_2.RefAttributes<HTMLDivElement>>;
3992
+
3993
+ /** Describes one selectable segment. */
3994
+ export declare interface SegmentedControlOption {
3995
+ /** Display label for the segment. */
3996
+ label: string;
3997
+ /** Value identifier returned via `onChange`. */
3998
+ value: string;
3999
+ }
4000
+
4001
+ export declare interface SegmentedControlProps extends Omit<React_2.HTMLAttributes<HTMLDivElement>, "onChange"> {
4002
+ /** Height of the control in pixels. @default "32" */
4003
+ size?: SegmentedControlSize;
4004
+ /** Segment layout. @default "hug" */
4005
+ variant?: SegmentedControlVariant;
4006
+ /** The selectable segments. Designed for two or three mutually exclusive options. */
4007
+ options: SegmentedControlOption[];
4008
+ /** Currently selected value (controlled). */
4009
+ value?: string;
4010
+ /** Initially selected value (uncontrolled). Defaults to the first option. */
4011
+ defaultValue?: string;
4012
+ /** Callback fired when the selected value changes. */
4013
+ onChange?: (value: string) => void;
4014
+ /** Whether the control is disabled. @default false */
4015
+ disabled?: boolean;
4016
+ }
4017
+
4018
+ /** Height of the segmented control in pixels. */
4019
+ export declare type SegmentedControlSize = "32" | "40" | "48";
4020
+
4021
+ /**
4022
+ * Segment layout.
4023
+ * - `"hug"`: each segment is sized to its content.
4024
+ * - `"fill"`: the control spans the full width of its container and each segment grows equally.
4025
+ */
4026
+ export declare type SegmentedControlVariant = "hug" | "fill";
4027
+
3933
4028
  /**
3934
4029
  * A select field with optional label, helper/error text, and an icon slot,
3935
4030
  * built on Radix UI Select for full accessibility and keyboard navigation.
package/dist/index.mjs CHANGED
@@ -202,6 +202,7 @@ import { Loader } from "./components/Loader/Loader.mjs";
202
202
  import { Logo } from "./components/Logo/Logo.mjs";
203
203
  import { MobileStepper } from "./components/MobileStepper/MobileStepper.mjs";
204
204
  import { OnlineBlinkingIcon } from "./components/OnlineBlinkingIcon/OnlineBlinkingIcon.mjs";
205
+ import { PageSelector } from "./components/PageSelector/PageSelector.mjs";
205
206
  import { Pagination } from "./components/Pagination/Pagination.mjs";
206
207
  import { PasswordField } from "./components/PasswordField/PasswordField.mjs";
207
208
  import { Pill } from "./components/Pill/Pill.mjs";
@@ -213,6 +214,7 @@ import { RadioGroup } from "./components/RadioGroup/RadioGroup.mjs";
213
214
  import { RatingSummary } from "./components/RatingSummary/RatingSummary.mjs";
214
215
  import { ReviewCard } from "./components/ReviewCard/ReviewCard.mjs";
215
216
  import { SearchField } from "./components/SearchField/SearchField.mjs";
217
+ import { SegmentedControl } from "./components/SegmentedControl/SegmentedControl.mjs";
216
218
  import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator } from "./components/Select/Select.mjs";
217
219
  import { Skeleton } from "./components/Skeleton/Skeleton.mjs";
218
220
  import { Slider } from "./components/Slider/Slider.mjs";
@@ -420,6 +422,7 @@ export {
420
422
  NewMessageIcon,
421
423
  OnlineBlinkingIcon,
422
424
  OpenIcon,
425
+ PageSelector,
423
426
  Pagination,
424
427
  PasswordField,
425
428
  PauseIcon,
@@ -446,6 +449,7 @@ export {
446
449
  ReviewCard,
447
450
  SearchField,
448
451
  SearchIcon,
452
+ SegmentedControl,
449
453
  Select,
450
454
  SelectContent,
451
455
  SelectGroup,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "3.10.0",
3
+ "version": "3.12.0",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",