@roadlittledawn/docs-design-system-react 0.12.1 → 0.12.3

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/USAGE.md CHANGED
@@ -529,10 +529,20 @@ import { List } from "@roadlittledawn/docs-design-system-react";
529
529
 
530
530
  ### List.Item Props
531
531
 
532
- | Prop | Type | Default | Description |
533
- | ----------- | ----------- | ------- | ---------------------- |
534
- | `children` | `ReactNode` | — | List item content |
535
- | `className` | `string` | `""` | Additional CSS classes |
532
+ | Prop | Type | Default | Description |
533
+ | ------------ | ----------- | ------- | --------------------------------------------------------------------------- |
534
+ | `children` | `ReactNode` | — | List item content |
535
+ | `className` | `string` | `""` | Additional CSS classes |
536
+ | `bulletIcon` | `ReactNode` | — | Custom bullet icon for this item. Overrides the parent `List`'s `bulletIcon` |
537
+
538
+ ### CSS classes
539
+
540
+ | Class | Element | Notes |
541
+ | ----- | ------- | ----- |
542
+ | `dds-list` | `<ol>` / `<ul>` | Root list element |
543
+ | `dds-list-item` | `<li>` | Each list item |
544
+ | `dds-list-item-content` | `<div>` | Wraps item children; target for content-level overrides |
545
+ | `dds-list-item-icon` | `<span>` | Wrapper for custom `bulletIcon` SVG/node |
536
546
 
537
547
  ### Examples
538
548
 
@@ -1025,7 +1035,15 @@ A hover/tap-activated floating panel for enriching inline documentation content.
1025
1035
 
1026
1036
  ### Mobile behavior
1027
1037
 
1028
- On screens ≤ 640px, the popover renders as a **bottom sheet** instead of a floating panel. Hover doesn't apply on touch devices; the popover toggles on tap.
1038
+ On screens ≤ 640px, the popover renders as a **bottom sheet** instead of a floating panel. Hover doesn't apply on touch devices; the popover opens on tap.
1039
+
1040
+ ### Closing the popover
1041
+
1042
+ The popover can be closed in three ways:
1043
+
1044
+ - **Close button** — the × button in the upper-right corner of the panel
1045
+ - **Click / tap outside** — native light-dismiss provided by `popover="auto"`
1046
+ - **Escape key** — handled automatically by the Popover API
1029
1047
 
1030
1048
  ### Content modes (choose one)
1031
1049
 
@@ -16,7 +16,7 @@ export interface ListItemProps {
16
16
  children: ReactNode;
17
17
  /** Additional CSS classes */
18
18
  className?: string;
19
- /** Internal: bullet icon injected by List parent for unordered lists */
19
+ /** Custom bullet icon (React node, e.g., SVG) for this item. Overrides the parent List's bulletIcon */
20
20
  bulletIcon?: ReactNode;
21
21
  }
22
22
  export declare function ListItem({ children, className, bulletIcon }: ListItemProps): import("react/jsx-runtime").JSX.Element;
@@ -15,12 +15,16 @@ function ListImpl(_a) {
15
15
  var children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b, _c = _a.ordered, ordered = _c === void 0 ? true : _c, bullet = _a.bullet, bulletIcon = _a.bulletIcon;
16
16
  var listClasses = ["dds-list", className].filter(Boolean).join(" ");
17
17
  var Element = ordered ? "ol" : "ul";
18
- return (_jsx(Element, { className: listClasses, "data-ordered": ordered, "data-has-icon": !!bulletIcon && !ordered, style: bullet && !bulletIcon
18
+ return (_jsx(Element, { className: listClasses, "data-ordered": ordered, style: bullet && !bulletIcon
19
19
  ? { "--dds-list-bullet": JSON.stringify(bullet) }
20
20
  : undefined, children: bulletIcon && !ordered
21
21
  ? React.Children.map(children, function (child) {
22
+ var _a;
22
23
  if (React.isValidElement(child) && child.type === ListItem) {
23
- return React.cloneElement(child, __assign(__assign({}, child.props), { bulletIcon: bulletIcon }));
24
+ var childProps = child.props;
25
+ return React.cloneElement(child, __assign(__assign({}, childProps), {
26
+ // Child's own bulletIcon takes precedence over the parent's
27
+ bulletIcon: (_a = childProps.bulletIcon) !== null && _a !== void 0 ? _a : bulletIcon }));
24
28
  }
25
29
  return child;
26
30
  })
@@ -29,7 +33,7 @@ function ListImpl(_a) {
29
33
  export function ListItem(_a) {
30
34
  var children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b, bulletIcon = _a.bulletIcon;
31
35
  var itemClasses = ["dds-list-item", className].filter(Boolean).join(" ");
32
- return (_jsxs("li", { className: itemClasses, children: [bulletIcon && (_jsx("span", { className: "dds-list-item-icon", "aria-hidden": "true", children: bulletIcon })), children] }));
36
+ return (_jsxs("li", { className: itemClasses, "data-has-icon": bulletIcon ? "true" : undefined, children: [bulletIcon && (_jsx("span", { className: "dds-list-item-icon", "aria-hidden": "true", children: bulletIcon })), _jsx("div", { className: "dds-list-item-content", children: children })] }));
33
37
  }
34
38
  export var List = Object.assign(ListImpl, {
35
39
  Item: ListItem,
@@ -80,6 +80,14 @@ export function Popover(_a) {
80
80
  var popoverRef = useRef(null);
81
81
  var showTimerRef = useRef(null);
82
82
  var hideTimerRef = useRef(null);
83
+ // Tracks when the popover was last shown automatically (hover/focus).
84
+ // Used to prevent the click handler from immediately closing a popover that
85
+ // was just opened by the hover timer (fixes the first-tap flicker on touch).
86
+ // On touch devices the synthetic `click` arrives ~300 ms after touch-start;
87
+ // the guard window must exceed showDelay (200 ms default) + that browser
88
+ // delay, so 400 ms is a safe minimum.
89
+ var FLICKER_GUARD_MS = 400;
90
+ var lastAutoShowTimeRef = useRef(0);
83
91
  var clearTimers = useCallback(function () {
84
92
  if (showTimerRef.current)
85
93
  clearTimeout(showTimerRef.current);
@@ -131,6 +139,8 @@ export function Popover(_a) {
131
139
  }
132
140
  positionPopover();
133
141
  popover.style.visibility = "";
142
+ // Record the time so the click handler knows the popover was auto-shown
143
+ lastAutoShowTimeRef.current = Date.now();
134
144
  }, showDelay);
135
145
  }, [clearTimers, showDelay, positionPopover]);
136
146
  var hidePopover = useCallback(function () {
@@ -188,6 +198,15 @@ export function Popover(_a) {
188
198
  var popover = popoverRef.current;
189
199
  if (!popover)
190
200
  return;
201
+ var isOpen = popover.matches(":popover-open") ||
202
+ popover.style.display === "block";
203
+ // Flicker guard: if the popover was just shown automatically by the
204
+ // hover/focus timer within the last 400 ms, a tap's synthetic click
205
+ // would arrive and toggle it closed. Instead, keep it open so the
206
+ // first tap reliably shows the popover.
207
+ if (isOpen && Date.now() - lastAutoShowTimeRef.current < FLICKER_GUARD_MS) {
208
+ return;
209
+ }
191
210
  try {
192
211
  popover.togglePopover();
193
212
  if (popover.matches(":popover-open"))
@@ -199,5 +218,17 @@ export function Popover(_a) {
199
218
  if (!isVisible)
200
219
  positionPopover();
201
220
  }
202
- }, "aria-describedby": popoverId, tabIndex: 0, children: children }), _jsx("div", __assign({ ref: popoverRef, id: popoverId }, { popover: "auto" }, { className: popoverClasses, onMouseEnter: clearTimers, onMouseLeave: hidePopover, children: popoverContent }))] }));
221
+ }, "aria-describedby": popoverId, tabIndex: 0, children: children }), _jsxs("div", __assign({ ref: popoverRef, id: popoverId }, { popover: "auto" }, { className: popoverClasses, onMouseEnter: clearTimers, onMouseLeave: hidePopover, children: [_jsx("button", { className: "dds-popover-close", "aria-label": "Close", onClick: function (e) {
222
+ e.stopPropagation();
223
+ clearTimers();
224
+ var popover = popoverRef.current;
225
+ if (!popover)
226
+ return;
227
+ try {
228
+ popover.hidePopover();
229
+ }
230
+ catch (_a) {
231
+ popover.style.display = "none";
232
+ }
233
+ }, children: "\u00D7" }), popoverContent] }))] }));
203
234
  }
@@ -64,7 +64,7 @@ var meta = {
64
64
  layout: "centered",
65
65
  docs: {
66
66
  description: {
67
- component: "\nA hover/tap-activated popover for enriching inline content in documentation.\nBuilt on the native [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) for reliable top-layer rendering \u2014 no z-index wars, no overflow clipping.\n\nCommon use cases include glossary term definitions and Wikipedia-style content previews.\n\n## Content modes\n\nThe `Popover` supports three mutually exclusive content modes, checked in this order:\n\n1. **`content`** \u2014 arbitrary `ReactNode`; you control everything\n2. **`glossary`** \u2014 structured `{ term, title, definition }` template\n3. **`preview`** \u2014 structured `{ title, excerpt, imageUrl, href }` template\n\n## When to Use\n\n- Inline term definitions that would interrupt reading flow if expanded in-place\n- Link previews that let users get context without navigating away\n- Any contextual content that benefits from being on-demand rather than always visible\n\n## When Not to Use\n\n- For critical information users must read \u2014 use a `Callout` instead\n- As a primary navigation mechanism\n- For content that needs persistent visibility\n\n## Mobile behavior\n\nOn screens \u2264 640 px the popover renders as a bottom sheet instead of a floating panel.\nHover events don't apply; the popover toggles on tap.\n\n## Accessibility\n\n- Trigger has `tabIndex={0}` and shows the popover on focus (keyboard accessible)\n- Popover panel has `role=\"tooltip\"` and is linked via `aria-describedby`\n- The native Popover API handles Escape-key dismissal automatically\n- Light-dismiss (click outside) is provided by `popover=\"auto\"`\n ",
67
+ component: "\nA hover/tap-activated popover for enriching inline content in documentation.\nBuilt on the native [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) for reliable top-layer rendering \u2014 no z-index wars, no overflow clipping.\n\nCommon use cases include glossary term definitions and Wikipedia-style content previews.\n\n## Content modes\n\nThe `Popover` supports three mutually exclusive content modes, checked in this order:\n\n1. **`content`** \u2014 arbitrary `ReactNode`; you control everything\n2. **`glossary`** \u2014 structured `{ term, title, definition }` template\n3. **`preview`** \u2014 structured `{ title, excerpt, imageUrl, href }` template\n\n## When to Use\n\n- Inline term definitions that would interrupt reading flow if expanded in-place\n- Link previews that let users get context without navigating away\n- Any contextual content that benefits from being on-demand rather than always visible\n\n## When Not to Use\n\n- For critical information users must read \u2014 use a `Callout` instead\n- As a primary navigation mechanism\n- For content that needs persistent visibility\n\n## Mobile behavior\n\nOn screens \u2264 640 px the popover renders as a bottom sheet instead of a floating panel.\nHover events don't apply; the popover opens on tap.\n\n## Closing the popover\n\nThe popover can be closed in three ways:\n- **Close button** \u2014 the \u00D7 button in the upper-right corner of the panel\n- **Click / tap outside** \u2014 native light-dismiss provided by `popover=\"auto\"`\n- **Escape key** \u2014 handled automatically by the Popover API\n\n## Accessibility\n\n- Trigger has `tabIndex={0}` and shows the popover on focus (keyboard accessible)\n- Popover panel has `role=\"tooltip\"` and is linked via `aria-describedby`\n- The native Popover API handles Escape-key dismissal automatically\n- Light-dismiss (click outside) is provided by `popover=\"auto\"`\n ",
68
68
  },
69
69
  },
70
70
  },
package/dist/styles.css CHANGED
@@ -1745,7 +1745,9 @@ a.no-text-decoration {
1745
1745
  .dds-list-item {
1746
1746
  counter-increment: list-counter;
1747
1747
  position: relative;
1748
- padding-inline-start: calc(var(--dds-list-badge-size) + 1rem);
1748
+ display: flex;
1749
+ align-items: flex-start;
1750
+ gap: 1rem;
1749
1751
  margin-bottom: var(--dds-list-item-margin-bottom, 1.5rem);
1750
1752
  min-height: var(--dds-list-badge-size);
1751
1753
  }
@@ -1767,13 +1769,10 @@ a.no-text-decoration {
1767
1769
  /* Ordered list badges */
1768
1770
  .dds-list[data-ordered="true"] .dds-list-item::before {
1769
1771
  content: counter(list-counter);
1770
- position: absolute;
1771
- left: 0;
1772
- top: 0;
1772
+ flex-shrink: 0;
1773
1773
  width: var(--dds-list-badge-size, 2.5rem);
1774
1774
  height: var(--dds-list-badge-size, 2.5rem);
1775
1775
  line-height: var(--dds-line-height-relaxed);
1776
- inset-inline-start: 0;
1777
1776
  display: flex;
1778
1777
  align-items: center;
1779
1778
  justify-content: center;
@@ -1787,27 +1786,24 @@ a.no-text-decoration {
1787
1786
  /* Unordered list bullets */
1788
1787
  .dds-list[data-ordered="false"] .dds-list-item::before {
1789
1788
  content: var(--dds-list-bullet, "•");
1790
- position: absolute;
1791
- left: 0;
1792
- top: 0;
1789
+ flex-shrink: 0;
1793
1790
  width: var(--dds-list-badge-size, 2.5rem);
1794
1791
  height: var(--dds-list-badge-size, 2.5rem);
1795
- inset-inline-start: 0;
1796
1792
  display: flex;
1793
+ transform: translateY(var(--dds-list-bullet-offset, -0.1em));
1797
1794
  align-items: center;
1798
1795
  justify-content: center;
1799
- font-size: var(--dds-list-bullet-size, 1.5rem);
1796
+ font-size: var(--dds-list-bullet-size, 1.25rem);
1800
1797
  color: var(--dds-list-badge-text);
1801
1798
  }
1802
- /* Hide ::before when using custom icon */
1803
- .dds-list[data-has-icon="true"] .dds-list-item::before {
1799
+ /* Hide ::before when item has a custom icon.
1800
+ Specificity matches the show rules above (0-3-1) and comes after, so it wins. */
1801
+ .dds-list .dds-list-item[data-has-icon="true"]::before {
1804
1802
  display: none;
1805
1803
  }
1806
1804
  /* Custom icon bullet */
1807
1805
  .dds-list-item-icon {
1808
- position: absolute;
1809
- left: 0;
1810
- top: 0;
1806
+ flex-shrink: 0;
1811
1807
  width: var(--dds-list-badge-size, 2.5rem);
1812
1808
  height: var(--dds-list-badge-size, 2.5rem);
1813
1809
  display: flex;
@@ -1819,6 +1815,27 @@ a.no-text-decoration {
1819
1815
  width: 1.25rem;
1820
1816
  height: 1.25rem;
1821
1817
  }
1818
+ /* Content wrapper — flex child that fills remaining space */
1819
+ .dds-list-item-content {
1820
+ flex: 1;
1821
+ min-width: 0;
1822
+ }
1823
+ /* Neutralize consuming-site typography margins that would misalign the badge.
1824
+ The list item's own margin/padding controls spacing — not the children's. */
1825
+ .dds-list-item-content > :first-child {
1826
+ margin-top: 0;
1827
+ }
1828
+ .dds-list-item-content > :last-child {
1829
+ margin-bottom: 0;
1830
+ }
1831
+ /* For unordered lists, vertically center the content within the li so the first
1832
+ text line's center aligns with the bullet/icon center.
1833
+ margin-block: auto distributes remaining space evenly above and below the
1834
+ content span, so its midpoint always lands at badge-size/2 — the same as the
1835
+ bullet center — regardless of font-size or line-height. */
1836
+ .dds-list[data-ordered="false"] .dds-list-item-content {
1837
+ margin-block: auto;
1838
+ }
1822
1839
  /* ==========================================================================
1823
1840
  Popover Component
1824
1841
  Uses the native Popover API for top-layer rendering.
@@ -1827,7 +1844,7 @@ a.no-text-decoration {
1827
1844
  /* Trigger */
1828
1845
  .dds-popover-trigger {
1829
1846
  display: inline;
1830
- cursor: default;
1847
+ cursor: help;
1831
1848
  text-decoration-line: underline;
1832
1849
  text-decoration-style: dotted;
1833
1850
  text-decoration-color: currentColor;
@@ -1923,6 +1940,38 @@ a.no-text-decoration {
1923
1940
  .dds-popover-lg {
1924
1941
  width: var(--dds-popover-width-lg);
1925
1942
  }
1943
+ /* ==========================================================================
1944
+ Close button — sits in the upper-right corner of the popover panel
1945
+ ========================================================================== */
1946
+ .dds-popover-close {
1947
+ position: absolute;
1948
+ top: 0.5rem;
1949
+ right: 0.5rem;
1950
+ display: flex;
1951
+ align-items: center;
1952
+ justify-content: center;
1953
+ width: 1.5rem;
1954
+ height: 1.5rem;
1955
+ padding: 0;
1956
+ background: transparent;
1957
+ border: none;
1958
+ border-radius: 0.25rem;
1959
+ cursor: pointer;
1960
+ color: var(--dds-popover-eyebrow-color);
1961
+ font-size: 1.125rem;
1962
+ line-height: 1;
1963
+ transition:
1964
+ background 120ms,
1965
+ color 120ms;
1966
+ }
1967
+ .dds-popover-close:hover {
1968
+ background: var(--dds-popover-border);
1969
+ color: var(--dds-popover-text);
1970
+ }
1971
+ .dds-popover-close:focus-visible {
1972
+ outline: 2px solid var(--dds-link-color);
1973
+ outline-offset: 2px;
1974
+ }
1926
1975
  /* ==========================================================================
1927
1976
  Shared inner elements
1928
1977
  ========================================================================== */
@@ -1934,6 +1983,7 @@ a.no-text-decoration {
1934
1983
  text-transform: uppercase;
1935
1984
  color: var(--dds-popover-eyebrow-color);
1936
1985
  margin-bottom: 0.25rem;
1986
+ padding-right: 1.75rem; /* leave room for the close button */
1937
1987
  }
1938
1988
  .dds-popover-title {
1939
1989
  margin: 0 0 0.5rem;
@@ -1941,6 +1991,7 @@ a.no-text-decoration {
1941
1991
  font-weight: var(--dds-font-semibold);
1942
1992
  color: var(--dds-popover-title-color);
1943
1993
  line-height: var(--dds-line-height-tight);
1994
+ padding-right: 1.75rem; /* leave room for the close button */
1944
1995
  }
1945
1996
  .dds-popover-title dfn {
1946
1997
  font-style: normal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roadlittledawn/docs-design-system-react",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "license": "MIT",
5
5
  "description": "React components for documentation design system",
6
6
  "repository": {