@db-vincent/components 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/package.json +28 -4
  2. package/src/astro/Accordion.astro +37 -0
  3. package/src/astro/Alert.astro +73 -0
  4. package/src/astro/Avatar.astro +88 -0
  5. package/src/astro/AvatarGroup.astro +63 -0
  6. package/src/astro/Badge.astro +23 -0
  7. package/src/astro/Breadcrumbs.astro +45 -0
  8. package/src/astro/Button.astro +125 -0
  9. package/src/astro/Card.astro +63 -0
  10. package/src/astro/Checkbox.astro +61 -0
  11. package/src/astro/Chip.astro +135 -0
  12. package/src/astro/CodeBlock.astro +17 -0
  13. package/src/astro/Drawer.astro +30 -0
  14. package/src/astro/EmptyState.astro +45 -0
  15. package/src/astro/Example.astro +37 -0
  16. package/src/astro/Grid.astro +56 -0
  17. package/src/astro/GridItem.astro +25 -0
  18. package/src/astro/Icon.astro +52 -0
  19. package/src/astro/Menu.astro +151 -0
  20. package/src/astro/MenuItem.astro +57 -0
  21. package/src/astro/Modal.astro +124 -0
  22. package/src/astro/Navbar.astro +756 -0
  23. package/src/astro/PageHeader.astro +34 -0
  24. package/src/astro/Pagination.astro +212 -0
  25. package/src/astro/Popover.astro +51 -0
  26. package/src/astro/Progress.astro +71 -0
  27. package/src/astro/Radio.astro +58 -0
  28. package/src/astro/RadioGroup.astro +25 -0
  29. package/src/astro/Section.astro +25 -0
  30. package/src/astro/Select.astro +420 -0
  31. package/src/astro/Sidebar.astro +215 -0
  32. package/src/astro/Skeleton.astro +41 -0
  33. package/src/astro/Spinner.astro +20 -0
  34. package/src/astro/Stepper.astro +82 -0
  35. package/src/astro/Switch.astro +78 -0
  36. package/src/astro/Table.astro +268 -0
  37. package/src/astro/Tabs.astro +83 -0
  38. package/src/astro/TextField.astro +305 -0
  39. package/src/astro/ThemeToggle.astro +66 -0
  40. package/src/astro/Toast.astro +181 -0
  41. package/src/astro/Tooltip.astro +54 -0
  42. package/src/astro/TopBar.astro +29 -0
  43. package/src/data/icons.ts +120 -0
  44. package/src/scripts/avatar.ts +33 -0
  45. package/src/scripts/menu.ts +218 -0
  46. package/src/scripts/tabs.ts +127 -0
  47. package/src/scripts/toast.ts +565 -0
  48. package/src/scripts/tooltip.ts +127 -0
  49. package/src/styles/accordion.css +79 -0
  50. package/src/styles/alert.css +108 -0
  51. package/src/styles/avatar.css +131 -0
  52. package/src/styles/badge.css +48 -0
  53. package/src/styles/base.css +205 -0
  54. package/src/styles/breadcrumbs.css +54 -0
  55. package/src/styles/button.css +174 -0
  56. package/src/styles/card.css +112 -0
  57. package/src/styles/chip.css +106 -0
  58. package/src/styles/choice.css +143 -0
  59. package/src/styles/code.css +44 -0
  60. package/src/styles/empty.css +59 -0
  61. package/src/styles/example.css +57 -0
  62. package/src/styles/grid.css +64 -0
  63. package/src/styles/input.css +208 -0
  64. package/src/styles/menu.css +151 -0
  65. package/src/styles/modal.css +285 -0
  66. package/src/styles/motion.css +114 -0
  67. package/src/styles/nav.css +232 -0
  68. package/src/styles/navbar.css +658 -0
  69. package/src/styles/page-header.css +62 -0
  70. package/src/styles/pagination.css +81 -0
  71. package/src/styles/popover.css +134 -0
  72. package/src/styles/progress.css +95 -0
  73. package/src/styles/section.css +38 -0
  74. package/src/styles/select.css +246 -0
  75. package/src/styles/skeleton.css +66 -0
  76. package/src/styles/spinner.css +44 -0
  77. package/src/styles/sr-only.css +14 -0
  78. package/src/styles/stepper.css +169 -0
  79. package/src/styles/switch.css +143 -0
  80. package/src/styles/table.css +177 -0
  81. package/src/styles/tabs.css +120 -0
  82. package/src/styles/theme-toggle.css +31 -0
  83. package/src/styles/toast.css +225 -0
  84. package/src/styles/tooltip.css +63 -0
  85. package/src/styles/topbar.css +32 -0
  86. package/README.md +0 -3
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "@db-vincent/components",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "description": "Minerva design-system components for Astro. Token-driven, dependency-free and accessibility-checked. (Placeholder scope `@ds`; see AGENTS.md.)",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org"
10
+ },
11
+ "files": [
12
+ "src"
13
+ ],
14
+ "sideEffects": [
15
+ "**/*.css",
16
+ "**/*.astro"
17
+ ],
18
+ "exports": {
19
+ "./astro/*.astro": "./src/astro/*.astro",
20
+ "./data/*": "./src/data/*.ts",
21
+ "./scripts/*": "./src/scripts/*.ts",
22
+ "./styles/*.css": "./src/styles/*.css"
23
+ },
24
+ "peerDependencies": {
25
+ "astro": ">=4 <8"
26
+ },
27
+ "dependencies": {
28
+ "@db-vincent/tokens": "0.1.1"
29
+ }
30
+ }
@@ -0,0 +1,37 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+ import "../styles/accordion.css";
4
+
5
+ interface Item {
6
+ title: string;
7
+ content: string;
8
+ open?: boolean;
9
+ }
10
+
11
+ /**
12
+ * Accordion built on <details>/<summary>: native keyboard behaviour, native
13
+ * expansion semantics, no script. Pass `name` for an exclusive accordion in
14
+ * browsers that support it.
15
+ */
16
+ interface Props {
17
+ items: Item[];
18
+ name?: string;
19
+ class?: string;
20
+ }
21
+
22
+ const { items, name, class: className } = Astro.props;
23
+ ---
24
+
25
+ <div class:list={["ds-accordion", className]}>
26
+ {items.map((item) => (
27
+ <details class="ds-accordion__item" open={item.open} name={name}>
28
+ <summary class="ds-accordion__trigger">
29
+ <span>{item.title}</span>
30
+ <Icon name="chevron-down" size={20} class="ds-accordion__icon" />
31
+ </summary>
32
+ <div class="ds-accordion__panel">
33
+ <p>{item.content}</p>
34
+ </div>
35
+ </details>
36
+ ))}
37
+ </div>
@@ -0,0 +1,73 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+ import "../styles/alert.css";
4
+
5
+ /**
6
+ * Inline alert. Not a live region by default: a static page-load message is
7
+ * read in document order. Use Toast for asynchronous feedback. `dismissible`
8
+ * adds a close control when the message can be cleared.
9
+ */
10
+ interface Props {
11
+ tone?: "info" | "success" | "warning" | "danger";
12
+ title: string;
13
+ message?: string;
14
+ dismissible?: boolean;
15
+ class?: string;
16
+ }
17
+
18
+ const {
19
+ tone = "info",
20
+ title,
21
+ message,
22
+ dismissible = false,
23
+ class: className,
24
+ } = Astro.props;
25
+ const icon = {
26
+ info: "info",
27
+ success: "check-circle",
28
+ warning: "alert-triangle",
29
+ danger: "alert-circle",
30
+ }[tone];
31
+ ---
32
+
33
+ <div
34
+ class:list={["ds-alert", tone !== "info" && `ds-alert--${tone}`, className]}
35
+ data-alert
36
+ >
37
+ <span class="ds-alert__icon" aria-hidden="true">
38
+ <Icon name={icon} size={20} />
39
+ </span>
40
+ <div class="ds-alert__content">
41
+ <p class="ds-alert__title">{title}</p>
42
+ {message && <p class="ds-alert__body">{message}</p>}
43
+ <slot />
44
+ </div>
45
+ {dismissible && (
46
+ <button
47
+ class="ds-alert__close"
48
+ type="button"
49
+ aria-label="Dismiss alert"
50
+ data-alert-close
51
+ >
52
+ <Icon name="x" size={16} />
53
+ </button>
54
+ )}
55
+ </div>
56
+
57
+ <script>
58
+ document.addEventListener("click", (event) => {
59
+ const target = event.target;
60
+ if (!(target instanceof Element)) return;
61
+ const close = target.closest("[data-alert-close]");
62
+ if (!close) return;
63
+ const alert = close.closest<HTMLElement>("[data-alert]");
64
+ if (!alert) return;
65
+ // Focus would fall to <body>; hand it to the content the alert sat on.
66
+ const host = alert.parentElement;
67
+ alert.remove();
68
+ if (host) {
69
+ if (!host.hasAttribute("tabindex")) host.tabIndex = -1;
70
+ host.focus({ preventScroll: true });
71
+ }
72
+ });
73
+ </script>
@@ -0,0 +1,88 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+ import { getInitials } from "../scripts/avatar";
4
+ import "../styles/avatar.css";
5
+
6
+ /**
7
+ * Avatar: an image with initials (or an icon) underneath, so a missing or
8
+ * failed image still shows who it is. The root is role="img" named by `name`
9
+ * plus the status, so the status never relies on colour. Image errors are
10
+ * handled by scripts/avatar.ts.
11
+ */
12
+ interface Props {
13
+ /** Full name. Becomes the accessible name and the source of the initials. */
14
+ name: string;
15
+ src?: string;
16
+ size?: "sm" | "md" | "lg";
17
+ shape?: "circle" | "square";
18
+ /** What shows without an image. Defaults to the initials of `name`. */
19
+ fallback?: "initials" | "icon";
20
+ status?: "online" | "away" | "busy" | "offline";
21
+ class?: string;
22
+ }
23
+
24
+ const {
25
+ name,
26
+ src,
27
+ size = "md",
28
+ shape = "circle",
29
+ fallback = "initials",
30
+ status,
31
+ class: className,
32
+ } = Astro.props;
33
+
34
+ const initials = getInitials(name);
35
+ const showIcon = fallback === "icon" || initials === "";
36
+ const label = status ? `${name}, ${status}` : name;
37
+ const iconSize = size === "lg" ? 20 : size === "sm" ? 14 : 16;
38
+ ---
39
+
40
+ <span
41
+ class:list={[
42
+ "ds-avatar",
43
+ size !== "md" && `ds-avatar--${size}`,
44
+ shape === "square" && "ds-avatar--square",
45
+ className,
46
+ ]}
47
+ role="img"
48
+ aria-label={label}
49
+ data-ds-avatar
50
+ >
51
+ <span class="ds-avatar__face">
52
+ <span class="ds-avatar__fallback" aria-hidden="true">
53
+ {showIcon ? <Icon name="user" size={iconSize} /> : initials}
54
+ </span>
55
+ {src && (
56
+ <img
57
+ class="ds-avatar__img"
58
+ src={src}
59
+ alt=""
60
+ loading="lazy"
61
+ decoding="async"
62
+ />
63
+ )}
64
+ </span>
65
+ {status && (
66
+ <span
67
+ class={`ds-avatar__status ds-avatar__status--${status}`}
68
+ aria-hidden="true"
69
+ />
70
+ )}
71
+ </span>
72
+
73
+ <script>
74
+ import { mountAvatar } from "../scripts/avatar";
75
+
76
+ const bind = () => {
77
+ document
78
+ .querySelectorAll<HTMLElement>("[data-ds-avatar]")
79
+ .forEach((root) => {
80
+ if (root.dataset.dsBound) return;
81
+ root.dataset.dsBound = "true";
82
+ mountAvatar(root);
83
+ });
84
+ };
85
+
86
+ bind();
87
+ document.addEventListener("astro:page-load", bind);
88
+ </script>
@@ -0,0 +1,63 @@
1
+ ---
2
+ import Avatar from "./Avatar.astro";
3
+ import "../styles/avatar.css";
4
+
5
+ /**
6
+ * Overlapping avatars with a "+N" count for the rest. The group is named by
7
+ * `label` ("Project members"); the count is named "N more".
8
+ */
9
+ interface Person {
10
+ name: string;
11
+ src?: string;
12
+ }
13
+
14
+ interface Props {
15
+ items: Person[];
16
+ /** Accessible name for the whole group. */
17
+ label: string;
18
+ /** Avatars shown before the "+N" count. */
19
+ max?: number;
20
+ size?: "sm" | "md" | "lg";
21
+ shape?: "circle" | "square";
22
+ class?: string;
23
+ }
24
+
25
+ const {
26
+ items,
27
+ label,
28
+ max = 4,
29
+ size = "md",
30
+ shape = "circle",
31
+ class: className,
32
+ } = Astro.props;
33
+
34
+ const visible = items.slice(0, max);
35
+ const hidden = items.length - visible.length;
36
+ ---
37
+
38
+ <div
39
+ class:list={["ds-avatar-group", className]}
40
+ role="group"
41
+ aria-label={label}
42
+ >
43
+ {visible.map((person) => (
44
+ <Avatar name={person.name} src={person.src} size={size} shape={shape} />
45
+ ))}
46
+ {hidden > 0 && (
47
+ <span
48
+ class:list={[
49
+ "ds-avatar",
50
+ size !== "md" && `ds-avatar--${size}`,
51
+ shape === "square" && "ds-avatar--square",
52
+ ]}
53
+ role="img"
54
+ aria-label={`${hidden} more`}
55
+ >
56
+ <span class="ds-avatar__face">
57
+ <span class="ds-avatar__fallback ds-avatar__count" aria-hidden="true">
58
+ +{hidden}
59
+ </span>
60
+ </span>
61
+ </span>
62
+ )}
63
+ </div>
@@ -0,0 +1,23 @@
1
+ ---
2
+ import "../styles/badge.css";
3
+
4
+ /** Compact status label. Tone never carries meaning alone; the text says it. */
5
+ interface Props {
6
+ tone?: "neutral" | "accent" | "success" | "warning" | "danger";
7
+ dot?: boolean;
8
+ class?: string;
9
+ }
10
+
11
+ const { tone = "neutral", dot = false, class: className } = Astro.props;
12
+ ---
13
+
14
+ <span
15
+ class:list={[
16
+ "ds-badge",
17
+ tone !== "neutral" && `ds-badge--${tone}`,
18
+ className,
19
+ ]}
20
+ >
21
+ {dot && <span class="ds-badge__dot" aria-hidden="true"></span>}
22
+ <slot />
23
+ </span>
@@ -0,0 +1,45 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+ import "../styles/breadcrumbs.css";
4
+
5
+ interface Item {
6
+ label: string;
7
+ href?: string;
8
+ }
9
+
10
+ interface Props {
11
+ items: Item[];
12
+ label?: string;
13
+ class?: string;
14
+ }
15
+
16
+ const { items, label = "Breadcrumb", class: className } = Astro.props;
17
+ ---
18
+
19
+ <nav class:list={["ds-breadcrumbs", className]} aria-label={label}>
20
+ <ol class="ds-breadcrumbs__list">
21
+ {items.map((item, index) => (
22
+ <li class="ds-breadcrumbs__item">
23
+ {item.href && index < items.length - 1 ? (
24
+ <a class="ds-breadcrumbs__link" href={item.href}>
25
+ {item.label}
26
+ </a>
27
+ ) : (
28
+ <span
29
+ class="ds-breadcrumbs__current"
30
+ aria-current={index === items.length - 1 ? "page" : undefined}
31
+ >
32
+ {item.label}
33
+ </span>
34
+ )}
35
+ {index < items.length - 1 && (
36
+ <Icon
37
+ name="chevron-right"
38
+ size={12}
39
+ class="ds-breadcrumbs__separator"
40
+ />
41
+ )}
42
+ </li>
43
+ ))}
44
+ </ol>
45
+ </nav>
@@ -0,0 +1,125 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import Icon from "./Icon.astro";
4
+ import "../styles/button.css";
5
+
6
+ /**
7
+ * Button. One filled primary action per view; secondary and ghost for the rest.
8
+ * Renders <a> when `href` is set, <button> otherwise. Loading sets aria-busy
9
+ * and disables activation while keeping the label in the accessible name.
10
+ */
11
+ interface Props extends Omit<HTMLAttributes<"button">, "type"> {
12
+ variant?: "primary" | "secondary" | "ghost" | "danger";
13
+ size?: "sm" | "md" | "lg";
14
+ href?: string;
15
+ type?: "button" | "submit" | "reset";
16
+ disabled?: boolean;
17
+ loading?: boolean;
18
+ icon?: string;
19
+ iconPosition?: "start" | "end";
20
+ iconOnly?: boolean;
21
+ block?: boolean;
22
+ ariaLabel?: string;
23
+ class?: string;
24
+ }
25
+
26
+ const {
27
+ variant = "secondary",
28
+ size = "md",
29
+ href,
30
+ type = "button",
31
+ disabled = false,
32
+ loading = false,
33
+ icon,
34
+ iconPosition = "start",
35
+ iconOnly = false,
36
+ block = false,
37
+ ariaLabel,
38
+ class: className,
39
+ // Astro assigns named slots at the call site; forwarding the attribute
40
+ // would leak an inert `slot` onto the rendered element.
41
+ slot: _slot,
42
+ ...rest
43
+ } = Astro.props;
44
+
45
+ const Tag = href ? "a" : "button";
46
+ const inactive = disabled || loading;
47
+ const iconSize = size === "lg" ? 20 : 16;
48
+
49
+ const classes = [
50
+ "ds-button",
51
+ `ds-button--${variant}`,
52
+ size !== "md" && `ds-button--${size}`,
53
+ iconOnly && "ds-button--icon",
54
+ block && "ds-button--block",
55
+ className,
56
+ ]
57
+ .filter(Boolean)
58
+ .join(" ");
59
+
60
+ const tagProps = href
61
+ ? {
62
+ href: disabled ? undefined : href,
63
+ role: disabled ? "link" : undefined,
64
+ "aria-disabled": inactive ? "true" : undefined,
65
+ }
66
+ : {
67
+ type,
68
+ // Loading keeps the button focusable (aria-disabled only) so focus is
69
+ // not dropped mid-submit; the click is blocked in CSS and script.
70
+ disabled: disabled,
71
+ "aria-disabled": inactive ? "true" : undefined,
72
+ };
73
+
74
+ const showLeadingIcon = icon && iconPosition === "start";
75
+ const showTrailingIcon = icon && iconPosition === "end";
76
+ ---
77
+
78
+ <Tag
79
+ class={classes}
80
+ aria-label={ariaLabel}
81
+ aria-busy={loading ? "true" : undefined}
82
+ data-loading={loading ? "true" : undefined}
83
+ {...tagProps}
84
+ {...rest}
85
+ >
86
+ {loading && <span class="ds-button__spinner" aria-hidden="true"></span>}
87
+ {showLeadingIcon && !loading && (
88
+ <span class="ds-button__icon" aria-hidden="true">
89
+ <Icon name={icon} size={iconSize} />
90
+ </span>
91
+ )}
92
+ {!iconOnly && (
93
+ <span class="ds-button__label">
94
+ <slot />
95
+ </span>
96
+ )}
97
+ {iconOnly && !icon && (
98
+ <span class="ds-button__icon">
99
+ <slot />
100
+ </span>
101
+ )}
102
+ {showTrailingIcon && !loading && (
103
+ <span class="ds-button__icon" aria-hidden="true">
104
+ <Icon name={icon} size={iconSize} />
105
+ </span>
106
+ )}
107
+ </Tag>
108
+
109
+ <script>
110
+ // A loading button stays focusable (aria-disabled, not disabled), so its
111
+ // activation is blocked here; pointer events are already off in CSS.
112
+ document.addEventListener(
113
+ "click",
114
+ (event) => {
115
+ const button = (event.target as Element | null)?.closest?.(
116
+ '.ds-button[aria-disabled="true"]',
117
+ );
118
+ if (button) {
119
+ event.preventDefault();
120
+ event.stopImmediatePropagation();
121
+ }
122
+ },
123
+ true,
124
+ );
125
+ </script>
@@ -0,0 +1,63 @@
1
+ ---
2
+ import "../styles/card.css";
3
+
4
+ /**
5
+ * Surface container with distinct header, body and footer. `accent` marks the
6
+ * one card that needs attention with a border on every edge; a linked card is
7
+ * one clickable target and exposes its hover/focus states on the root.
8
+ */
9
+ interface Props {
10
+ variant?: "default" | "accent" | "raised" | "flat";
11
+ href?: string;
12
+ eyebrow?: string;
13
+ title?: string;
14
+ meta?: string;
15
+ class?: string;
16
+ }
17
+
18
+ const {
19
+ variant = "default",
20
+ href,
21
+ eyebrow,
22
+ title,
23
+ meta,
24
+ class: className,
25
+ } = Astro.props;
26
+ const Tag = href ? "a" : "div";
27
+ const classes = [
28
+ "ds-card",
29
+ variant !== "default" && `ds-card--${variant}`,
30
+ href && "ds-card--interactive",
31
+ className,
32
+ ]
33
+ .filter(Boolean)
34
+ .join(" ");
35
+
36
+ const hasHeader =
37
+ Boolean(eyebrow || title || meta) || Astro.slots.has("header");
38
+ const hasFooter = Astro.slots.has("footer");
39
+ ---
40
+
41
+ <Tag class={classes} href={href}>
42
+ {hasHeader && (
43
+ <header class="ds-card__header">
44
+ {Astro.slots.has("header") ? (
45
+ <slot name="header" />
46
+ ) : (
47
+ <>
48
+ {eyebrow && <div class="ds-card__eyebrow">{eyebrow}</div>}
49
+ {title && <div class="ds-card__title">{title}</div>}
50
+ {meta && <div class="ds-card__meta">{meta}</div>}
51
+ </>
52
+ )}
53
+ </header>
54
+ )}
55
+ <div class="ds-card__body">
56
+ <slot />
57
+ </div>
58
+ {hasFooter && (
59
+ <footer class="ds-card__footer">
60
+ <slot name="footer" />
61
+ </footer>
62
+ )}
63
+ </Tag>
@@ -0,0 +1,61 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+ import "../styles/choice.css";
4
+
5
+ /** Checkbox with an optional description. Native input, custom visual box. */
6
+ interface Props {
7
+ label: string;
8
+ description?: string;
9
+ id?: string;
10
+ name?: string;
11
+ value?: string;
12
+ checked?: boolean;
13
+ disabled?: boolean;
14
+ required?: boolean;
15
+ class?: string;
16
+ }
17
+
18
+ const {
19
+ label,
20
+ description,
21
+ id,
22
+ name,
23
+ value,
24
+ checked = false,
25
+ disabled = false,
26
+ required = false,
27
+ class: className,
28
+ } = Astro.props;
29
+
30
+ const inputId = id ?? `checkbox-${Math.random().toString(36).slice(2, 9)}`;
31
+ const descriptionId = description ? `${inputId}-description` : undefined;
32
+ // The name comes from the label text only; the description is announced
33
+ // once, as a description, instead of twice (name + description).
34
+ const labelId = `${inputId}-label`;
35
+ ---
36
+
37
+ <label class:list={["ds-choice", className]}>
38
+ <input
39
+ class="ds-choice__input"
40
+ id={inputId}
41
+ type="checkbox"
42
+ name={name}
43
+ value={value}
44
+ checked={checked}
45
+ disabled={disabled}
46
+ required={required}
47
+ aria-labelledby={description ? labelId : undefined}
48
+ aria-describedby={descriptionId}
49
+ />
50
+ <span class="ds-choice__box" aria-hidden="true">
51
+ <Icon name="check" size={14} />
52
+ </span>
53
+ <span class="ds-choice__label">
54
+ <span id={labelId}>{label}</span>
55
+ {description && (
56
+ <span class="ds-choice__description" id={descriptionId}>
57
+ {description}
58
+ </span>
59
+ )}
60
+ </span>
61
+ </label>