@wistia/ui 1.4.0 → 1.4.1-beta.11a672bb.eaf1b95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  /*
3
- * @license @wistia/ui v1.4.0
3
+ * @license @wistia/ui v1.4.1-beta.11a672bb.eaf1b95
4
4
  *
5
5
  * Copyright (c) 2024-2026, Wistia, Inc. and its affiliates.
6
6
  *
@@ -13,12 +13,6 @@ import { createGlobalStyle, css, keyframes, styled } from "styled-components";
13
13
  import { isArray, isBoolean, isDate, isEmptyString, isFunction, isNil, isNonEmptyArray, isNonEmptyString, isNotNil, isNotUndefined, isNumber, isRecord, isString, isUndefined } from "@wistia/type-guards";
14
14
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
15
  import { Toaster, toast } from "sonner";
16
- import { isPast } from "date-fns/isPast";
17
- import { isValid } from "date-fns/isValid";
18
- import { parse } from "date-fns/parse";
19
- import { startOfDay } from "date-fns/startOfDay";
20
- import { startOfToday } from "date-fns/startOfToday";
21
- import { startOfTomorrow } from "date-fns/startOfTomorrow";
22
16
  import { getValueAndUnit } from "polished";
23
17
  import { useFilePicker as useFilePicker$1, useImperativeFilePicker as useImperativeFilePicker$1 } from "use-file-picker";
24
18
  import { FileAmountLimitValidator, FileSizeValidator, FileTypeValidator, ImageDimensionsValidator, PersistentFileAmountLimitValidator } from "use-file-picker/validators";
@@ -112,6 +106,10 @@ const GlobalStyle = createGlobalStyle`
112
106
  * 1. Without this fonts are too heavy weight in OS X Firefox
113
107
  * 2. Design decision
114
108
  3. See: https://allthingssmitty.com/2020/05/11/css-fix-for-100vh-in-mobile-webkit/
109
+ 4. Avoid typographic orphans and awkward line breaks. 'text-wrap' is inherited,
110
+ so this becomes the default for all text. Guarded by '@supports' because
111
+ 'pretty' is not Baseline yet; browsers without it keep the initial
112
+ 'text-wrap: wrap' behavior, so no polyfill is needed.
115
113
  */
116
114
  body {
117
115
  -moz-osx-font-smoothing: grayscale; /* 1 */
@@ -119,6 +117,10 @@ const GlobalStyle = createGlobalStyle`
119
117
  line-height: 1.5; /* 2 */
120
118
  min-height: 100vh; /* 3 */
121
119
  min-height: -webkit-fill-available; /* 3 */
120
+
121
+ @supports (text-wrap: pretty) {
122
+ text-wrap: pretty; /* 4 */
123
+ }
122
124
  }
123
125
 
124
126
  /* Remove default margin in favour of better control in authored CSS */
@@ -736,17 +738,69 @@ const MONTH_NAMES = [
736
738
  "November",
737
739
  "December"
738
740
  ];
739
- const DATE_FORMATS = [
740
- "MMM d, yyyy",
741
- "MMM d",
742
- "MMMM d, yyyy",
743
- "MMMM d",
744
- "M/d/yyyy",
745
- "M/d",
746
- "M-d-yyyy",
747
- "M-d",
748
- "yyyy-MM-dd"
741
+ const MONTH_ABBREVIATIONS = MONTH_NAMES.map((name) => name.slice(0, 3));
742
+ const startOfDay = (date) => {
743
+ const result = new Date(date);
744
+ result.setHours(0, 0, 0, 0);
745
+ return result;
746
+ };
747
+ const startOfToday = () => startOfDay(/* @__PURE__ */ new Date());
748
+ const startOfTomorrow = () => {
749
+ const tomorrow = startOfToday();
750
+ tomorrow.setDate(tomorrow.getDate() + 1);
751
+ return tomorrow;
752
+ };
753
+ const monthIndexIn = (names, value) => names.findIndex((name) => name.toLowerCase() === value.toLowerCase());
754
+ const toNumberOrUndefined = (value) => value === void 0 ? void 0 : Number(value);
755
+ const DATE_PATTERNS = [
756
+ {
757
+ regex: new RegExp(`^(${MONTH_ABBREVIATIONS.join("|")}) (\\d{1,2})(?:, (\\d{1,4}))?$`, "i"),
758
+ toParts: ([, month, day, year]) => ({
759
+ monthIndex: monthIndexIn(MONTH_ABBREVIATIONS, month ?? ""),
760
+ day: Number(day),
761
+ year: toNumberOrUndefined(year)
762
+ })
763
+ },
764
+ {
765
+ regex: new RegExp(`^(${MONTH_NAMES.join("|")}) (\\d{1,2})(?:, (\\d{1,4}))?$`, "i"),
766
+ toParts: ([, month, day, year]) => ({
767
+ monthIndex: monthIndexIn(MONTH_NAMES, month ?? ""),
768
+ day: Number(day),
769
+ year: toNumberOrUndefined(year)
770
+ })
771
+ },
772
+ {
773
+ regex: /^(\d{1,2})\/(\d{1,2})(?:\/(\d{1,4}))?$/,
774
+ toParts: ([, month, day, year]) => ({
775
+ monthIndex: Number(month) - 1,
776
+ day: Number(day),
777
+ year: toNumberOrUndefined(year)
778
+ })
779
+ },
780
+ {
781
+ regex: /^(\d{1,2})-(\d{1,2})(?:-(\d{1,4}))?$/,
782
+ toParts: ([, month, day, year]) => ({
783
+ monthIndex: Number(month) - 1,
784
+ day: Number(day),
785
+ year: toNumberOrUndefined(year)
786
+ })
787
+ },
788
+ {
789
+ regex: /^(\d{1,4})-(\d{1,2})-(\d{1,2})$/,
790
+ toParts: ([, year, month, day]) => ({
791
+ monthIndex: Number(month) - 1,
792
+ day: Number(day),
793
+ year: Number(year)
794
+ })
795
+ }
749
796
  ];
797
+ const toValidDate = ({ monthIndex, day, year }, referenceDate) => {
798
+ if (year === 0) return null;
799
+ const resolvedYear = year ?? referenceDate.getFullYear();
800
+ const date = new Date(referenceDate.getFullYear(), 0, 1);
801
+ date.setFullYear(resolvedYear, monthIndex, day);
802
+ return date.getFullYear() === resolvedYear && date.getMonth() === monthIndex && date.getDate() === day ? date : null;
803
+ };
750
804
  const parseDateString = (str) => {
751
805
  const trimmed = str.trim();
752
806
  if (!trimmed) return null;
@@ -757,14 +811,19 @@ const parseDateString = (str) => {
757
811
  if (monthIndex !== -1) {
758
812
  const today = startOfToday();
759
813
  let date = new Date(today.getFullYear(), monthIndex, 1);
760
- if (isPast(date)) date = new Date(today.getFullYear() + 1, monthIndex, 1);
761
- return startOfDay(date);
814
+ if (date.getTime() < Date.now()) date = new Date(today.getFullYear() + 1, monthIndex, 1);
815
+ return date;
762
816
  }
763
817
  const referenceDate = /* @__PURE__ */ new Date();
764
- const parsedDate = DATE_FORMATS.map((fmt) => parse(trimmed, fmt, referenceDate)).find((date) => isValid(date));
765
- if (parsedDate !== void 0) return startOfDay(parsedDate);
818
+ for (const { regex, toParts } of DATE_PATTERNS) {
819
+ const match = trimmed.match(regex);
820
+ if (match) {
821
+ const parsed = toValidDate(toParts(match), referenceDate);
822
+ if (parsed !== null) return parsed;
823
+ }
824
+ }
766
825
  const native = new Date(trimmed);
767
- if (isValid(native)) return startOfDay(native);
826
+ if (!Number.isNaN(native.getTime())) return startOfDay(native);
768
827
  return null;
769
828
  };
770
829
  //#endregion
@@ -811,7 +870,7 @@ const getLocalDateParts = (date) => {
811
870
  //#region src/helpers/dateTime/differenceInCalendarDays.ts
812
871
  /**
813
872
  * Returns the difference in calendar days between two dates in local time,
814
- * ignoring the time of day. Essentially replicates date-fns/differenceInCalendarDays.
873
+ * ignoring the time of day.
815
874
  *
816
875
  * @param {Date|number} dateLeft - The later date (or timestamp).
817
876
  * @param {Date|number} dateRight - The earlier date (or timestamp).
@@ -1426,7 +1485,7 @@ const useFocusTrap = (active = true, options = {}) => {
1426
1485
  setTimeout(() => {
1427
1486
  if (node.ownerDocument) processNode(node);
1428
1487
  if (!node.ownerDocument) console.warn("[useFocusTrap]: The focus trap is not part of the DOM yet, so it is unable to correctly set focus. Make sure to render the ref node.", node);
1429
- });
1488
+ }, 0);
1430
1489
  ref.current = node;
1431
1490
  } else ref.current = null;
1432
1491
  }, [
@@ -5347,6 +5406,9 @@ const IconComponent = ({ useInlineStyles = true, invertColor, colorScheme = "inh
5347
5406
  });
5348
5407
  };
5349
5408
  IconComponent.displayName = "Icon_UI";
5409
+ /**
5410
+ * An `Icon` displays a visual symbol to represent an action, concept, or entity, rendering the SVG registered for its `type` at a fixed or responsive `size`.
5411
+ */
5350
5412
  const Icon = IconComponent;
5351
5413
  //#endregion
5352
5414
  //#region src/components/Link/Link.tsx
@@ -5928,6 +5990,9 @@ const StyledImage = styled.img`
5928
5990
  object-fit: ${({ $objFit }) => $objFit};
5929
5991
  object-position: ${({ $objPosition }) => $objPosition};
5930
5992
  `;
5993
+ /**
5994
+ * An `Image` displays a photo or graphic inline, with controls for border radius, object fit, and how it fills its container. It lazy-loads by default via the native `loading` attribute.
5995
+ */
5931
5996
  const Image = ({ src, alt, borderRadius, fill: fillContainer = false, fit: objFit, loading = "lazy", position: objPosition, onLoad, onError, ...props }) => /* @__PURE__ */ jsx(StyledImage, {
5932
5997
  $borderRadius: borderRadius,
5933
5998
  $fillContainer: fillContainer,
@@ -7285,6 +7350,9 @@ const StyledHiddenCheckboxInput = styled.input`
7285
7350
  ${visuallyHiddenStyle}
7286
7351
  position: relative;
7287
7352
  `;
7353
+ /**
7354
+ * A `Checkbox` lets users select one or more options from a set, or toggle a single option on or off. It supports both standalone controlled usage and grouped usage via `CheckboxGroup`.
7355
+ */
7288
7356
  const Checkbox = ({ checked, disabled = false, id, label, description, name, onChange, size = "md", value, required = false, hideLabel = false, ref, ...props }) => {
7289
7357
  const generatedId = useId();
7290
7358
  const computedId = isNonEmptyString(id) ? id : `wistia-ui-checkbox-${generatedId}`;
@@ -8550,6 +8618,9 @@ const StyledSwitchThumb = styled.div`
8550
8618
  const StyledHiddenSwitchInput = styled.input`
8551
8619
  ${visuallyHiddenStyle}
8552
8620
  `;
8621
+ /**
8622
+ * A `Switch` allows users to toggle between two mutually exclusive states, such as on and off.
8623
+ */
8553
8624
  const Switch = ({ align = "left", checked, disabled = false, id, label, description, name, onChange, size = "md", value, required = false, hideLabel = false, ref, ...props }) => {
8554
8625
  const generatedId = useId();
8555
8626
  const computedId = isNonEmptyString(id) ? id : `wistia-ui-switch-${generatedId}`;
@@ -9766,6 +9837,9 @@ const defaultDisplayValues = (values, onRemove) => {
9766
9837
  onClickRemoveLabel: `Remove ${selectedValue}`
9767
9838
  }, selectedValue));
9768
9839
  };
9840
+ /**
9841
+ * A `Combobox` is a searchable single- or multi-select input. Users can filter options by typing, and selected values in multi-select mode are displayed as removable `Tag` components.
9842
+ */
9769
9843
  const Combobox = ({ placeholder, value, onChange, searchValue, onSearchValueChange, displayValues = defaultDisplayValues, children, flipPopover = true, fullWidth = true }) => {
9770
9844
  const [isOpen, setIsOpen] = useState(false);
9771
9845
  const [isPending, startTransition] = useTransition();
@@ -9991,13 +10065,13 @@ const ContextMenuPopup = styled(Menu$1.Popup)`
9991
10065
  ${menuContentCss}
9992
10066
  outline: none;
9993
10067
  `;
10068
+ const NOOP$4 = () => null;
9994
10069
  /**
9995
10070
  * The ContextMenu is an extended implementation of the [Menu](/components/Menu) component that allows for right-click context menus.
9996
10071
  * It can be used in two ways:
9997
10072
  * 1. By providing a `triggerRef`, which will render the menu when the referenced element is right-clicked.
9998
10073
  * 2. By providing a `position` prop, which will render the menu at the specified coordinates.
9999
10074
  */
10000
- const NOOP$4 = () => null;
10001
10075
  const ContextMenu = ({ position, triggerRef, children, side = "bottom", onRequestClose = NOOP$4, compact = false }) => {
10002
10076
  const [isRightClicked, setIsRightClicked] = useState(false);
10003
10077
  const [menuPosition, setMenuPosition] = useState(position ?? {
@@ -12506,6 +12580,9 @@ const GridComponent = ({ ref, children, columns = "auto", minChildWidth = "auto"
12506
12580
  children
12507
12581
  });
12508
12582
  };
12583
+ /**
12584
+ * A `Grid` arranges its children in a CSS grid layout with configurable `columns`, `gap`, and responsive breakpoint support.
12585
+ */
12509
12586
  const Grid = makePolymorphic(GridComponent);
12510
12587
  //#endregion
12511
12588
  //#region src/components/PreviewCard/PreviewCard.tsx
@@ -13514,6 +13591,9 @@ const keyToString = (key) => {
13514
13591
  default: return key.toUpperCase();
13515
13592
  }
13516
13593
  };
13594
+ /**
13595
+ * A `KeyboardShortcut` displays one or more keyboard keys with an optional label, rendering each key in a styled `kbd` element. Platform-specific keys like `Cmd`/`Meta` are rendered with the appropriate symbol for the detected operating system.
13596
+ */
13517
13597
  const KeyboardShortcut = ({ label, keyboardKeys, fullWidth = false, ...otherProps }) => /* @__PURE__ */ jsxs(StyledKeyboardShortcut, {
13518
13598
  $fullWidth: fullWidth,
13519
13599
  ...otherProps,
@@ -13634,6 +13714,9 @@ const renderListFromArray = (listItems, variant, otherProps) => {
13634
13714
  return /* @__PURE__ */ jsx(ListItem, { children: item }, key);
13635
13715
  }), variant, otherProps);
13636
13716
  };
13717
+ /**
13718
+ * A `List` groups related items in bulleted, ordered, or separator-based layouts, composed with `ListItem` children or a flat/nested `items` array.
13719
+ */
13637
13720
  const List = ({ children, items, variant, ...otherProps }) => {
13638
13721
  const listVariant = variant ?? "bullets";
13639
13722
  if (isNotNil(children)) {
@@ -14749,6 +14832,9 @@ const StyledRadioInput = styled.div`
14749
14832
  const StyledHiddenRadioInput = styled.input`
14750
14833
  ${visuallyHiddenStyle}
14751
14834
  `;
14835
+ /**
14836
+ * A `Radio` lets users select exactly one option from a visible set of choices.
14837
+ */
14752
14838
  const Radio = ({ checked, disabled = false, id, label, description, name, onChange, size = "md", value = "false", required = false, hideLabel = false, ref, ...props }) => {
14753
14839
  const generatedId = useId();
14754
14840
  const computedId = isNonEmptyString(id) ? id : `wistia-ui-radio-${generatedId}`;
@@ -15032,6 +15118,9 @@ const RadioCardDefaultLayout = ({ icon, label, description, showIndicator = true
15032
15118
  RadioCardDefaultLayout.displayName = "RadioCardDefaultLayout_UI";
15033
15119
  //#endregion
15034
15120
  //#region src/components/RadioCard/RadioCard.tsx
15121
+ /**
15122
+ * A `RadioCard` lets users select exactly one option from a set of visually rich, card-based choices.
15123
+ */
15035
15124
  const RadioCard = ({ icon, label, description, showIndicator, isGated, children, ref, ...rootProps }) => {
15036
15125
  return /* @__PURE__ */ jsx(RadioCardRoot, {
15037
15126
  ref,
@@ -15652,6 +15741,9 @@ const StyledSidebar = styled.nav`
15652
15741
  overflow: hidden;
15653
15742
  border-right: 1px solid var(--wui-color-border);
15654
15743
  `;
15744
+ /**
15745
+ * The `Sidebar` provides vertical navigation with a composable header, scrollable content area, and footer.
15746
+ */
15655
15747
  const Sidebar = ({ children, ref, ...props }) => {
15656
15748
  return /* @__PURE__ */ jsx(StyledSidebar, {
15657
15749
  ref,
@@ -15895,6 +15987,9 @@ const StyledSliderThumb = styled(Slider$1.Thumb)`
15895
15987
  }
15896
15988
  `;
15897
15989
  const DEFAULT_INITIAL_VALUE = [0];
15990
+ /**
15991
+ * A `Slider` lets users select a value or range from a continuous scale via a draggable thumb.
15992
+ */
15898
15993
  const Slider = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, disabled = false, initialValue = DEFAULT_INITIAL_VALUE, max = 100, min = 0, onChange, onValueCommitted, step = 1, value, "data-testid": dataTestId = "ui-slider", ...otherProps }) => {
15899
15994
  if (!(isNonEmptyString(ariaLabel) || isNonEmptyString(ariaLabelledby))) throw new Error("UI Slider: Sliders should have an accessible name. Add a label using the aria-label or aria-labelledby prop.");
15900
15995
  const values = value ?? initialValue;
@@ -16122,6 +16217,9 @@ const useTabsValue = () => {
16122
16217
  };
16123
16218
  //#endregion
16124
16219
  //#region src/components/Tabs/Tabs.tsx
16220
+ /**
16221
+ * The `Tabs` component lets users switch between panels of content. It is composed from the `TabsList`, `TabsTrigger`, and `TabsContent` subcomponents.
16222
+ */
16125
16223
  const Tabs = ({ children, value: valueProp, onValueChange: onValueChangeProp, defaultValue, ...props }) => {
16126
16224
  const [internalValue, setInternalValue] = useState(valueProp ?? defaultValue);
16127
16225
  const isControlled = isNotNil(valueProp);