@common-origin/design-system 2.16.0 → 2.17.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.
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React, { useState, useEffect, useRef, useId, useCallback, useMemo, forwardRef } from 'react';
3
+ import React, { useState, createContext, useContext, useEffect, useRef, useId, useCallback, useMemo, forwardRef } from 'react';
4
4
  import styled, { keyframes, css } from 'styled-components';
5
5
  import { t as tokensJson, i as iconsJson } from './data.esm.js';
6
6
  import { parseISO, format, isToday, isYesterday, isThisWeek } from 'date-fns';
@@ -735,7 +735,11 @@ var Icon = function Icon(_a) {
735
735
  size = _b === void 0 ? 'lg' : _b,
736
736
  _c = _a.iconColor,
737
737
  iconColor = _c === void 0 ? 'default' : _c,
738
+ ariaLabel = _a["aria-label"],
739
+ title = _a.title,
738
740
  dataTestId = _a["data-testid"];
741
+ // Decorative unless it's given a name. A blank name is no name, so the icon stays hidden
742
+ var label = (ariaLabel === null || ariaLabel === void 0 ? void 0 : ariaLabel.trim()) || (title === null || title === void 0 ? void 0 : title.trim()) || undefined;
739
743
  // Get the icon data from the JSON file
740
744
  var iconData = iconsJson[name];
741
745
  if (!iconData) {
@@ -750,16 +754,23 @@ var Icon = function Icon(_a) {
750
754
  "$size": size,
751
755
  "$iconColor": iconColor,
752
756
  "data-testid": dataTestId,
753
- children: jsx("svg", {
757
+ children: jsxs("svg", __assign({
754
758
  viewBox: "0 0 24 24",
755
759
  fill: "currentColor",
756
- xmlns: "http://www.w3.org/2000/svg",
757
- role: "img",
758
- "aria-label": iconData.name,
759
- children: jsx("path", {
760
+ xmlns: "http://www.w3.org/2000/svg"
761
+ }, label ? {
762
+ role: 'img',
763
+ 'aria-label': label
764
+ } : {
765
+ 'aria-hidden': true,
766
+ focusable: false
767
+ }, {
768
+ children: [(title === null || title === void 0 ? void 0 : title.trim()) && jsx("title", {
769
+ children: title
770
+ }), jsx("path", {
760
771
  d: iconData.path
761
- })
762
- })
772
+ })]
773
+ }))
763
774
  });
764
775
  };
765
776
  var templateObject_1$H;
@@ -1091,8 +1102,7 @@ var CategoryLabel = function CategoryLabel(_a) {
1091
1102
  children: [icon && jsx(Icon, {
1092
1103
  name: icon,
1093
1104
  size: iconSize,
1094
- iconColor: variant === 'filled' ? 'inverse' : 'inherit',
1095
- "aria-hidden": "true"
1105
+ iconColor: variant === 'filled' ? 'inverse' : 'inherit'
1096
1106
  }), children]
1097
1107
  }), accessibleLabel && jsx(HiddenLabel, {
1098
1108
  children: accessibleLabel
@@ -1537,12 +1547,9 @@ var DateFormatter = function DateFormatter(_a) {
1537
1547
  var date = parseISO(dateString);
1538
1548
  var displayText;
1539
1549
  switch (mode) {
1540
- case 'relative':
1541
- // Always use relative labels (Today/Yesterday) or fall back to day name
1542
- displayText = formatDateSmart(date, formatString);
1543
- break;
1550
+ case 'relative': // Deprecated alias for 'smart', removed in 3.0 (#82)
1544
1551
  case 'smart':
1545
- // Smart mode: relative for recent, absolute for older
1552
+ // Today, Yesterday or the day name for recent dates; formatted for older ones
1546
1553
  displayText = formatDateSmart(date, formatString);
1547
1554
  break;
1548
1555
  case 'absolute':
@@ -1784,14 +1791,18 @@ var MoneyDisplay = function MoneyDisplay(_a) {
1784
1791
  "$weight": weight,
1785
1792
  "aria-label": ariaLabel,
1786
1793
  "data-testid": dataTestId,
1787
- children: [showPositiveIcon && !showNegativeIcon && jsx(Icon, {
1794
+ children: [showPositiveIcon && !showNegativeIcon &&
1795
+ // The amount is shown without a sign, so the icon is the sign and needs a name (#85)
1796
+ jsx(Icon, {
1788
1797
  name: "addRing",
1789
1798
  size: iconSize,
1790
- iconColor: iconColor
1799
+ iconColor: iconColor,
1800
+ "aria-label": "Plus"
1791
1801
  }), showNegativeIcon && jsx(Icon, {
1792
1802
  name: "remove",
1793
1803
  size: iconSize,
1794
- iconColor: iconColor
1804
+ iconColor: iconColor,
1805
+ "aria-label": "Minus"
1795
1806
  }), jsx(Typography, {
1796
1807
  variant: typographyVariant,
1797
1808
  color: amountColor,
@@ -2193,8 +2204,7 @@ var StatusLabelBase = function StatusLabelBase(_a) {
2193
2204
  children: [showIcon && jsx(Icon, {
2194
2205
  name: statusConfig.icon,
2195
2206
  size: sizeStyles.iconSize,
2196
- iconColor: "inherit",
2197
- "aria-hidden": "true"
2207
+ iconColor: "inherit"
2198
2208
  }), displayLabel, jsxs(ScreenReaderOnly, {
2199
2209
  children: [status, " status"]
2200
2210
  })]
@@ -2521,6 +2531,12 @@ var AccountCard = function AccountCard(_a) {
2521
2531
  };
2522
2532
  var templateObject_1$t, templateObject_2$o, templateObject_3$m, templateObject_4$i, templateObject_5$f;
2523
2533
 
2534
+ /**
2535
+ * The spacing a List gives its items (#77). A ListItem without its own `spacing` uses it; outside
2536
+ * a List it's `comfortable`. Internal: not exported from the package.
2537
+ */
2538
+ var ListSpacingContext = /*#__PURE__*/createContext('comfortable');
2539
+
2524
2540
  var semantic$b = tokensJson.semantic;
2525
2541
  var _a$5 = semantic$b.motion,
2526
2542
  duration$2 = _a$5.duration,
@@ -2627,8 +2643,7 @@ var ListItem = function ListItem(_a) {
2627
2643
  selected = _f === void 0 ? false : _f,
2628
2644
  _g = _a.destructive,
2629
2645
  destructive = _g === void 0 ? false : _g,
2630
- _h = _a.spacing,
2631
- spacing = _h === void 0 ? 'comfortable' : _h,
2646
+ spacingProp = _a.spacing,
2632
2647
  children = _a.children,
2633
2648
  className = _a.className,
2634
2649
  dataTestId = _a["data-testid"],
@@ -2638,6 +2653,8 @@ var ListItem = function ListItem(_a) {
2638
2653
  customTabIndex = _a.tabIndex,
2639
2654
  customOnKeyDown = _a.onKeyDown,
2640
2655
  props = __rest(_a, ["primary", "secondary", "badge", "icon", "expandable", "expanded", "onToggle", "interactive", "onClick", "disabled", "selected", "destructive", "spacing", "children", "className", 'data-testid', "role", 'aria-selected', "id", "tabIndex", "onKeyDown"]);
2656
+ var listSpacing = useContext(ListSpacingContext);
2657
+ var spacing = spacingProp !== null && spacingProp !== void 0 ? spacingProp : listSpacing;
2641
2658
  var isInteractive = interactive || expandable || customRole === 'option';
2642
2659
  var contentRole = customRole === 'option' ? undefined : isInteractive ? 'button' : undefined;
2643
2660
  var ariaExpanded = expandable ? expanded : undefined;
@@ -2662,61 +2679,69 @@ var ListItem = function ListItem(_a) {
2662
2679
  handleClick();
2663
2680
  }
2664
2681
  };
2665
- return jsxs(StyledListItem, __assign({
2666
- "$interactive": isInteractive,
2667
- "$disabled": disabled,
2668
- "$selected": selected,
2669
- "$destructive": destructive,
2670
- "$spacing": spacing,
2671
- className: className,
2672
- "data-testid": dataTestId,
2673
- role: customRole || 'listitem',
2674
- id: id,
2675
- "aria-selected": ariaSelected
2676
- }, props, {
2677
- children: [jsxs(StyledItemContent, {
2678
- "$interactive": isInteractive,
2679
- "$disabled": disabled,
2680
- "$selected": selected,
2681
- "$destructive": destructive,
2682
- "$spacing": spacing,
2683
- role: contentRole,
2684
- "aria-expanded": ariaExpanded,
2685
- "aria-disabled": ariaDisabled,
2686
- "aria-current": ariaCurrent,
2687
- tabIndex: customTabIndex !== undefined ? customTabIndex : isInteractive && !disabled ? 0 : undefined,
2688
- onClick: handleClick,
2689
- onKeyDown: handleKeyDown,
2690
- children: [icon && jsx(StyledIconContainer$1, {
2691
- "aria-hidden": "true",
2692
- children: icon
2693
- }), jsxs(StyledTextContent, {
2694
- children: [jsx(Typography, {
2695
- variant: "body",
2696
- color: destructive ? 'error' : 'default',
2697
- children: primary
2698
- }), secondary && jsx(Typography, {
2699
- variant: "small",
2700
- color: "subdued",
2701
- children: secondary
2702
- })]
2703
- }), jsxs(StyledRightContent, {
2704
- children: [badge, expandable && jsx(StyledChevronIcon, {
2682
+ return (
2683
+ // The List's spacing is for its own items: reset it for this item's content, so components
2684
+ // built on ListItem (ActionSheet, even through its portal) and expanded content keep their own
2685
+ // density. A nested List provides its own spacing (#77)
2686
+ jsx(ListSpacingContext.Provider, {
2687
+ value: "comfortable",
2688
+ children: jsxs(StyledListItem, __assign({
2689
+ "$interactive": isInteractive,
2690
+ "$disabled": disabled,
2691
+ "$selected": selected,
2692
+ "$destructive": destructive,
2693
+ "$spacing": spacing,
2694
+ className: className,
2695
+ "data-testid": dataTestId,
2696
+ role: customRole || 'listitem',
2697
+ id: id,
2698
+ "aria-selected": ariaSelected
2699
+ }, props, {
2700
+ children: [jsxs(StyledItemContent, {
2701
+ "$interactive": isInteractive,
2702
+ "$disabled": disabled,
2703
+ "$selected": selected,
2704
+ "$destructive": destructive,
2705
+ "$spacing": spacing,
2706
+ role: contentRole,
2707
+ "aria-expanded": ariaExpanded,
2708
+ "aria-disabled": ariaDisabled,
2709
+ "aria-current": ariaCurrent,
2710
+ tabIndex: customTabIndex !== undefined ? customTabIndex : isInteractive && !disabled ? 0 : undefined,
2711
+ onClick: handleClick,
2712
+ onKeyDown: handleKeyDown,
2713
+ children: [icon && jsx(StyledIconContainer$1, {
2714
+ "aria-hidden": "true",
2715
+ children: icon
2716
+ }), jsxs(StyledTextContent, {
2717
+ children: [jsx(Typography, {
2718
+ variant: "body",
2719
+ color: destructive ? 'error' : 'default',
2720
+ children: primary
2721
+ }), secondary && jsx(Typography, {
2722
+ variant: "small",
2723
+ color: "subdued",
2724
+ children: secondary
2725
+ })]
2726
+ }), jsxs(StyledRightContent, {
2727
+ children: [badge, expandable && jsx(StyledChevronIcon, {
2728
+ "$expanded": expanded,
2729
+ "aria-hidden": "true",
2730
+ children: jsx(Icon, {
2731
+ name: "caretDown",
2732
+ size: "sm"
2733
+ })
2734
+ })]
2735
+ })]
2736
+ }), expandable && children && jsx(StyledExpandedContent, {
2737
+ "$spacing": spacing,
2705
2738
  "$expanded": expanded,
2706
- "aria-hidden": "true",
2707
- children: jsx(Icon, {
2708
- name: "caretDown",
2709
- size: "sm"
2710
- })
2739
+ "aria-hidden": !expanded,
2740
+ children: children
2711
2741
  })]
2712
- })]
2713
- }), expandable && children && jsx(StyledExpandedContent, {
2714
- "$spacing": spacing,
2715
- "$expanded": expanded,
2716
- "aria-hidden": !expanded,
2717
- children: children
2718
- })]
2719
- }));
2742
+ }))
2743
+ })
2744
+ );
2720
2745
  };
2721
2746
  ListItem.displayName = 'ListItem';
2722
2747
  var templateObject_1$s, templateObject_2$n, templateObject_3$l, templateObject_4$h, templateObject_5$e, templateObject_6$b, templateObject_7$9;
@@ -4463,12 +4488,12 @@ var StyledCheckbox = styled.span.withConfig({
4463
4488
  return props.$checked || props.$indeterminate ? tokensJson.semantic.color.background.inverse : tokensJson.component.input["default"].backgroundColor;
4464
4489
  }, function (props) {
4465
4490
  if (props.$state === 'error') return tokensJson.component.input.error.borderColor;
4466
- if (props.$checked || props.$indeterminate) return tokensJson.semantic.border.strong;
4491
+ if (props.$checked || props.$indeterminate) return tokensJson.semantic.color.border.strong;
4467
4492
  return tokensJson.component.input["default"].borderColor;
4468
4493
  }, function (props) {
4469
4494
  return props.$state === 'disabled' ? 'not-allowed' : 'pointer';
4470
4495
  }, tokensJson.semantic.motion.duration.fast, tokensJson.semantic.motion.easing.easeOut, HiddenCheckboxInput, function (props) {
4471
- return props.$state === 'error' ? tokensJson.component.input.error.hover.borderColor : props.$checked || props.$indeterminate ? tokensJson.semantic.border.strong : tokensJson.component.input.hover.borderColor;
4496
+ return props.$state === 'error' ? tokensJson.component.input.error.hover.borderColor : props.$checked || props.$indeterminate ? tokensJson.semantic.color.border.strong : tokensJson.component.input.hover.borderColor;
4472
4497
  }, HiddenCheckboxInput, tokensJson.component.input.focus.outline, tokensJson.semantic.border.focusOffset, function (props) {
4473
4498
  return props.$state === 'error' ? tokensJson.component.input.error.focus.borderColor : tokensJson.component.input.focus.borderColor;
4474
4499
  }, function (props) {
@@ -5459,15 +5484,17 @@ var List = function List(_a) {
5459
5484
  className = _a.className,
5460
5485
  dataTestId = _a["data-testid"],
5461
5486
  props = __rest(_a, ["children", "dividers", "spacing", "className", 'data-testid']);
5462
- return jsx(StyledList, __assign({
5463
- "$dividers": dividers,
5464
- "$spacing": spacing,
5465
- className: className,
5466
- "data-testid": dataTestId,
5467
- role: "list"
5468
- }, props, {
5469
- children: children
5470
- }));
5487
+ return jsx(ListSpacingContext.Provider, {
5488
+ value: spacing,
5489
+ children: jsx(StyledList, __assign({
5490
+ "$dividers": dividers,
5491
+ className: className,
5492
+ "data-testid": dataTestId,
5493
+ role: "list"
5494
+ }, props, {
5495
+ children: children
5496
+ }))
5497
+ });
5471
5498
  };
5472
5499
  List.displayName = 'List';
5473
5500
  var templateObject_1$c;
@@ -6456,8 +6483,7 @@ var SearchField = /*#__PURE__*/forwardRef(function (_a, ref) {
6456
6483
  children: [jsx(Icon, {
6457
6484
  name: "search",
6458
6485
  size: "sm",
6459
- iconColor: "subdued",
6460
- "aria-hidden": "true"
6486
+ iconColor: "subdued"
6461
6487
  }), jsx(StyledInput, __assign({
6462
6488
  ref: inputRef,
6463
6489
  type: "text",
@@ -6523,8 +6549,7 @@ var SearchField = /*#__PURE__*/forwardRef(function (_a, ref) {
6523
6549
  secondary: description,
6524
6550
  icon: item.type === 'recent' ? jsx(Icon, {
6525
6551
  name: "refresh",
6526
- size: "xs",
6527
- "aria-hidden": "true"
6552
+ size: "xs"
6528
6553
  }) : undefined,
6529
6554
  interactive: true,
6530
6555
  selected: index === highlightedIndex,
@@ -7187,7 +7212,7 @@ var StyledTab = styled.button.withConfig({
7187
7212
  },
7188
7213
  displayName: "TabBar__StyledTab",
7189
7214
  componentId: "sc-1g9ib0y-1"
7190
- })(templateObject_2$3 || (templateObject_2$3 = __makeTemplateObject(["\n display: inline-flex;\n align-items: center;\n gap: ", ";\n padding: ", " ", ";\n font: ", ";\n white-space: nowrap;\n cursor: ", ";\n border: none;\n background: transparent;\n transition: ", ";\n position: relative;\n \n /* Selected tab: the light-blue selected treatment plus a blue underline (decisions 0016, 0020).\n The text darkens with the fill on hover and press so every state stays at least 4.5:1. */\n border-radius: ", " ", " 0 0;\n background-color: ", ";\n color: ", ";\n\n &::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: ", ";\n background-color: ", ";\n transition: ", ";\n }\n\n &:hover:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n &:active:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n /* Disabled state */\n opacity: ", ";\n \n /* Focus state */\n &:focus-visible {\n outline: ", " solid ", ";\n outline-offset: ", ";\n z-index: 1;\n }\n \n /* Pressed nudge */\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n"], ["\n display: inline-flex;\n align-items: center;\n gap: ", ";\n padding: ", " ", ";\n font: ", ";\n white-space: nowrap;\n cursor: ", ";\n border: none;\n background: transparent;\n transition: ", ";\n position: relative;\n \n /* Selected tab: the light-blue selected treatment plus a blue underline (decisions 0016, 0020).\n The text darkens with the fill on hover and press so every state stays at least 4.5:1. */\n border-radius: ", " ", " 0 0;\n background-color: ", ";\n color: ", ";\n\n &::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: ", ";\n background-color: ", ";\n transition: ", ";\n }\n\n &:hover:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n &:active:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n /* Disabled state */\n opacity: ", ";\n \n /* Focus state */\n &:focus-visible {\n outline: ", " solid ", ";\n outline-offset: ", ";\n z-index: 1;\n }\n \n /* Pressed nudge */\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n"])), semantic.spacing.layout.sm, semantic.spacing.layout.md, semantic.spacing.layout.lg, semantic.typography.button2, function (props) {
7215
+ })(templateObject_2$3 || (templateObject_2$3 = __makeTemplateObject(["\n display: inline-flex;\n align-items: center;\n gap: ", ";\n padding: ", " ", ";\n font: ", ";\n white-space: nowrap;\n cursor: ", ";\n border: none;\n background: transparent;\n transition: ", ";\n position: relative;\n \n /* Selected tab: the light-blue selected treatment plus a blue underline (decisions 0016, 0020).\n The text darkens with the fill on hover and press so every state stays at least 4.5:1. */\n border-radius: ", " ", " 0 0;\n background-color: ", ";\n color: ", ";\n\n &::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: ", ";\n background-color: ", ";\n transition: ", ";\n }\n\n &:hover:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n &:active:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n /* Disabled state */\n opacity: ", ";\n \n /* Focus state */\n &:focus-visible {\n outline: ", " solid ", ";\n outline-offset: ", ";\n z-index: 1;\n }\n\n"], ["\n display: inline-flex;\n align-items: center;\n gap: ", ";\n padding: ", " ", ";\n font: ", ";\n white-space: nowrap;\n cursor: ", ";\n border: none;\n background: transparent;\n transition: ", ";\n position: relative;\n \n /* Selected tab: the light-blue selected treatment plus a blue underline (decisions 0016, 0020).\n The text darkens with the fill on hover and press so every state stays at least 4.5:1. */\n border-radius: ", " ", " 0 0;\n background-color: ", ";\n color: ", ";\n\n &::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 0;\n right: 0;\n height: ", ";\n background-color: ", ";\n transition: ", ";\n }\n\n &:hover:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n &:active:not(:disabled) {\n background-color: ", ";\n color: ", ";\n }\n\n /* Disabled state */\n opacity: ", ";\n \n /* Focus state */\n &:focus-visible {\n outline: ", " solid ", ";\n outline-offset: ", ";\n z-index: 1;\n }\n\n"])), semantic.spacing.layout.sm, semantic.spacing.layout.md, semantic.spacing.layout.lg, semantic.typography.button2, function (props) {
7191
7216
  return props.$disabled ? 'not-allowed' : 'pointer';
7192
7217
  }, semantic.motion.hover, semantic.border.radius.sm, semantic.border.radius.sm, function (props) {
7193
7218
  return props.$isActive ? semantic.color.background['interactive-subtle'] : 'transparent';
@@ -7671,20 +7696,14 @@ var TransactionListItem = function TransactionListItem(_a) {
7671
7696
  })
7672
7697
  })]
7673
7698
  }), (hasReceipt || hasNote) && jsxs(StyledIconIndicator, {
7674
- children: [hasReceipt && jsx("span", {
7675
- "aria-label": "Has receipt",
7676
- role: "img",
7677
- children: jsx(Icon, {
7678
- name: "fileDocSearch",
7679
- size: "xs"
7680
- })
7681
- }), hasNote && jsx("span", {
7682
- "aria-label": "Has note",
7683
- role: "img",
7684
- children: jsx(Icon, {
7685
- name: "paper",
7686
- size: "xs"
7687
- })
7699
+ children: [hasReceipt && jsx(Icon, {
7700
+ name: "fileDocSearch",
7701
+ size: "xs",
7702
+ "aria-label": "Has receipt"
7703
+ }), hasNote && jsx(Icon, {
7704
+ name: "paper",
7705
+ size: "xs",
7706
+ "aria-label": "Has note"
7688
7707
  })]
7689
7708
  })]
7690
7709
  })]