@gtivr4/a1-design-system-react 0.5.0 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtivr4/a1-design-system-react",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "React components for the A1 token-driven design system.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import type { HeadingProps } from "../heading/Heading";
3
+
4
+ export type DefinitionListDirection = "row" | "column";
5
+ export type DefinitionListSize = "sm" | "md" | "lg";
6
+ export type DefinitionListLabelWidth = "auto" | "fixed";
7
+
8
+ export interface DefinitionListItem {
9
+ /** Stable key for this label/value pair. Falls back to label or index. */
10
+ id?: React.Key;
11
+ /** Label rendered in the `<dt>`. */
12
+ label: React.ReactNode;
13
+ /** Value rendered in the `<dd>`. */
14
+ value?: React.ReactNode;
15
+ /** Alternate value content for JSX object-literal ergonomics. */
16
+ children?: React.ReactNode;
17
+ /** Enables or disables the copy button for this item. Defaults to the list-level copyValue prop. */
18
+ copyValue?: boolean;
19
+ /** Exact text copied to the clipboard. Defaults to the rendered text value when it can be inferred. */
20
+ copyText?: string;
21
+ /** Accessible label for this item's copy button. Defaults to the list-level copyLabel. */
22
+ copyLabel?: string;
23
+ /** Accessible label shown after this item's value is copied. Defaults to the list-level copiedLabel. */
24
+ copiedLabel?: string;
25
+ /** Heading props for this item's value. Overrides the list-level valueHeadingProps. */
26
+ valueHeadingProps?: Omit<HeadingProps, "children" | "className">;
27
+ }
28
+
29
+ export interface DefinitionListProps extends React.HTMLAttributes<HTMLDListElement> {
30
+ /** Label/value pairs to render as `<dt>` and `<dd>` groups. */
31
+ items?: DefinitionListItem[];
32
+ /** Pair layout direction. Default: "row" */
33
+ direction?: DefinitionListDirection;
34
+ /** Spacing and body text size. Default: "md" */
35
+ size?: DefinitionListSize;
36
+ /**
37
+ * Row label sizing. "auto" lets each label hug content; "fixed" aligns values on a responsive label column.
38
+ * Only applies when direction="row". Default: "auto"
39
+ */
40
+ labelWidth?: DefinitionListLabelWidth;
41
+ /** Show copy buttons for copyable text values. Can be overridden per item. Default: false */
42
+ copyValue?: boolean;
43
+ /** Accessible label for copy buttons. Default: "Copy value" */
44
+ copyLabel?: string;
45
+ /** Accessible label used after a copy succeeds. Default: "Copied" */
46
+ copiedLabel?: string;
47
+ /** Render values with Heading, including Heading type and size support. Can be overridden per item. */
48
+ valueHeadingProps?: Omit<HeadingProps, "children" | "className">;
49
+ }
50
+
51
+ export declare function DefinitionList(props: DefinitionListProps): React.ReactElement;
@@ -0,0 +1,154 @@
1
+ import { Children, isValidElement, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Heading } from "../heading/Heading.jsx";
3
+ import { IconButton } from "../icon-button/IconButton.jsx";
4
+ import "./definition-list.css";
5
+
6
+ const directions = ["row", "column"];
7
+ const sizes = ["sm", "md", "lg"];
8
+ const labelWidths = ["auto", "fixed"];
9
+
10
+ function textFromNode(node) {
11
+ if (node == null || typeof node === "boolean") return "";
12
+ if (typeof node === "string" || typeof node === "number") return String(node);
13
+ if (Array.isArray(node)) return node.map(textFromNode).join("");
14
+ if (isValidElement(node)) return textFromNode(node.props.children);
15
+ return "";
16
+ }
17
+
18
+ function writeClipboard(text) {
19
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
20
+ return navigator.clipboard.writeText(text).catch(() => writeClipboardFallback(text));
21
+ }
22
+
23
+ return writeClipboardFallback(text);
24
+ }
25
+
26
+ function writeClipboardFallback(text) {
27
+ if (typeof document === "undefined") return Promise.reject(new Error("Clipboard unavailable"));
28
+
29
+ const textarea = document.createElement("textarea");
30
+ textarea.value = text;
31
+ textarea.setAttribute("readonly", "");
32
+ textarea.style.position = "fixed";
33
+ textarea.style.insetBlockStart = "0";
34
+ textarea.style.insetInlineStart = "0";
35
+ textarea.style.inlineSize = "1px";
36
+ textarea.style.blockSize = "1px";
37
+ textarea.style.opacity = "0";
38
+ document.body.appendChild(textarea);
39
+ textarea.focus();
40
+ textarea.select();
41
+
42
+ try {
43
+ textarea.setSelectionRange(0, textarea.value.length);
44
+ const copied = document.execCommand("copy");
45
+ if (!copied) throw new Error("Copy command failed");
46
+ return Promise.resolve();
47
+ } catch (error) {
48
+ return Promise.reject(error);
49
+ } finally {
50
+ document.body.removeChild(textarea);
51
+ }
52
+ }
53
+
54
+ function DefinitionValue({ item, inheritedHeadingProps }) {
55
+ const headingProps = item.valueHeadingProps ?? inheritedHeadingProps;
56
+ const value = item.value ?? item.children;
57
+
58
+ if (!headingProps) return value;
59
+
60
+ return (
61
+ <Heading as="p" type="heading" size="md" {...headingProps}>
62
+ {value}
63
+ </Heading>
64
+ );
65
+ }
66
+
67
+ function DefinitionCopyButton({ text, label = "Copy value", copiedLabel = "Copied" }) {
68
+ const [copied, setCopied] = useState(false);
69
+ const resetTimer = useRef(null);
70
+
71
+ useEffect(() => {
72
+ return () => {
73
+ if (resetTimer.current) window.clearTimeout(resetTimer.current);
74
+ };
75
+ }, []);
76
+
77
+ async function handleCopy() {
78
+ await writeClipboard(text);
79
+ setCopied(true);
80
+
81
+ if (resetTimer.current) window.clearTimeout(resetTimer.current);
82
+ resetTimer.current = window.setTimeout(() => {
83
+ setCopied(false);
84
+ resetTimer.current = null;
85
+ }, 2000);
86
+ }
87
+
88
+ return (
89
+ <IconButton
90
+ className="a1-definition-list__copy"
91
+ icon={copied ? "check" : "content_copy"}
92
+ label={copied ? copiedLabel : label}
93
+ onClick={handleCopy}
94
+ variant="tertiary"
95
+ />
96
+ );
97
+ }
98
+
99
+ export function DefinitionList({
100
+ items = [],
101
+ direction = "row",
102
+ size = "md",
103
+ labelWidth = "auto",
104
+ copyValue = false,
105
+ copyLabel = "Copy value",
106
+ copiedLabel = "Copied",
107
+ valueHeadingProps,
108
+ className = "",
109
+ ...props
110
+ }) {
111
+ const resolvedDirection = directions.includes(direction) ? direction : "row";
112
+ const resolvedSize = sizes.includes(size) ? size : "md";
113
+ const resolvedLabelWidth = labelWidths.includes(labelWidth) ? labelWidth : "auto";
114
+ const normalizedItems = useMemo(() => items.filter(Boolean), [items]);
115
+
116
+ const classes = [
117
+ "a1-definition-list",
118
+ `a1-definition-list--${resolvedDirection}`,
119
+ `a1-definition-list--${resolvedSize}`,
120
+ resolvedDirection === "row" && `a1-definition-list--label-${resolvedLabelWidth}`,
121
+ className,
122
+ ].filter(Boolean).join(" ");
123
+
124
+ return (
125
+ <dl className={classes} {...props}>
126
+ {normalizedItems.map((item, index) => {
127
+ const value = item.value ?? item.children;
128
+ const textToCopy = item.copyText ?? textFromNode(Children.toArray(value));
129
+ const shouldCopy = item.copyValue ?? copyValue;
130
+ const itemKey = item.id ?? (
131
+ typeof item.label === "string" || typeof item.label === "number" ? item.label : index
132
+ );
133
+
134
+ return (
135
+ <div className="a1-definition-list__item" key={itemKey}>
136
+ <dt className="a1-definition-list__label">{item.label}</dt>
137
+ <dd className="a1-definition-list__value">
138
+ <span className="a1-definition-list__value-content">
139
+ <DefinitionValue item={item} inheritedHeadingProps={valueHeadingProps} />
140
+ </span>
141
+ {shouldCopy && textToCopy && (
142
+ <DefinitionCopyButton
143
+ text={textToCopy}
144
+ label={item.copyLabel ?? copyLabel}
145
+ copiedLabel={item.copiedLabel ?? copiedLabel}
146
+ />
147
+ )}
148
+ </dd>
149
+ </div>
150
+ );
151
+ })}
152
+ </dl>
153
+ );
154
+ }
@@ -0,0 +1,111 @@
1
+ .a1-definition-list {
2
+ container-type: inline-size;
3
+ margin: 0;
4
+ font-family: var(--component-paragraph-font-family);
5
+ color: var(--semantic-color-text-default);
6
+ }
7
+
8
+ .a1-definition-list--sm {
9
+ --a1-definition-list-gap: var(--component-definition-list-gap-sm);
10
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-sm);
11
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-sm);
12
+ --a1-definition-list-font-size: var(--semantic-font-size-body-sm);
13
+ }
14
+
15
+ .a1-definition-list--md {
16
+ --a1-definition-list-gap: var(--component-definition-list-gap-md);
17
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-md);
18
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-md);
19
+ --a1-definition-list-font-size: var(--semantic-font-size-body-md);
20
+ }
21
+
22
+ .a1-definition-list--lg {
23
+ --a1-definition-list-gap: var(--component-definition-list-gap-lg);
24
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-lg);
25
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-lg);
26
+ --a1-definition-list-font-size: var(--semantic-font-size-body-lg);
27
+ }
28
+
29
+ .a1-definition-list__item {
30
+ min-inline-size: 0;
31
+ }
32
+
33
+ .a1-definition-list__label,
34
+ .a1-definition-list__value {
35
+ min-inline-size: 0;
36
+ margin: 0;
37
+ font-size: var(--a1-definition-list-font-size);
38
+ line-height: var(--semantic-font-line-height-body);
39
+ }
40
+
41
+ .a1-definition-list__label {
42
+ font-weight: var(--component-definition-list-label-font-weight);
43
+ color: var(--semantic-color-text-muted);
44
+ }
45
+
46
+ .a1-definition-list__value {
47
+ color: var(--semantic-color-text-default);
48
+ }
49
+
50
+ .a1-definition-list__value-content {
51
+ min-inline-size: 0;
52
+ }
53
+
54
+ .a1-definition-list__copy {
55
+ margin-block-start: calc((1lh - var(--component-icon-button-size)) / 2);
56
+ }
57
+
58
+ .a1-definition-list--column {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: var(--a1-definition-list-gap);
62
+ }
63
+
64
+ .a1-definition-list--column .a1-definition-list__item {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: var(--a1-definition-list-row-gap);
68
+ }
69
+
70
+ .a1-definition-list--row {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: var(--a1-definition-list-row-gap);
74
+ }
75
+
76
+ .a1-definition-list--row .a1-definition-list__item {
77
+ display: grid;
78
+ grid-template-columns: var(--a1-definition-list-label-column) minmax(0, 1fr);
79
+ column-gap: var(--a1-definition-list-column-gap);
80
+ align-items: start;
81
+ }
82
+
83
+ .a1-definition-list--row.a1-definition-list--label-auto {
84
+ --a1-definition-list-label-column: max-content;
85
+ }
86
+
87
+ .a1-definition-list--row.a1-definition-list--label-fixed {
88
+ --a1-definition-list-label-column: clamp(
89
+ var(--component-definition-list-label-width-min),
90
+ var(--component-definition-list-label-width-preferred),
91
+ var(--component-definition-list-label-width-max)
92
+ );
93
+ }
94
+
95
+ .a1-definition-list__value {
96
+ display: flex;
97
+ align-items: flex-start;
98
+ gap: var(--component-definition-list-copy-gap);
99
+ }
100
+
101
+ .a1-definition-list__value-content {
102
+ flex: 1 1 auto;
103
+ }
104
+
105
+ @container (max-width: 360px) {
106
+ .a1-definition-list--row .a1-definition-list__item {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: var(--a1-definition-list-row-gap);
110
+ }
111
+ }
@@ -3,12 +3,19 @@ import * as React from "react";
3
3
  export interface DialogProps extends React.DialogHTMLAttributes<HTMLDialogElement> {
4
4
  /** Whether the dialog is visible. Default: false */
5
5
  open?: boolean;
6
- /** Called when the user closes the dialog (Escape, close button, or backdrop click) */
6
+ /** Called when the user closes the dialog (Escape, close button, or backdrop click). Omit to hide the close button. */
7
7
  onClose?: () => void;
8
8
  /** Dialog title shown in the header */
9
9
  title?: string;
10
10
  /** Footer content — wrapped in a right-aligned `ButtonContainer` */
11
11
  footer?: React.ReactNode;
12
+ /**
13
+ * Status variant — renders a full-bleed colored hero area at the top with a status icon.
14
+ * "success" | "error" | "warn" | "info" | "neutral"
15
+ */
16
+ status?: "success" | "error" | "warn" | "info" | "neutral";
17
+ /** Override the icon shown in the hero area. Defaults to the status icon when `status` is set. */
18
+ icon?: string;
12
19
  children?: React.ReactNode;
13
20
  }
14
21
 
@@ -1,8 +1,25 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import "./dialog.css";
3
+ import { Icon } from "../icon/Icon.jsx";
3
4
  import { IconButton } from "../icon-button/IconButton.jsx";
4
5
  import { ButtonContainer } from "../button-container/ButtonContainer.jsx";
5
6
 
7
+ const HERO_COLORS = {
8
+ success: "var(--semantic-color-status-success-background)",
9
+ error: "var(--semantic-color-status-error-background)",
10
+ warn: "var(--semantic-color-status-warn-background)",
11
+ info: "var(--semantic-color-status-info-background)",
12
+ neutral: "var(--semantic-color-surface-inverse)",
13
+ };
14
+
15
+ const STATUS_ICONS = {
16
+ success: "check_circle",
17
+ error: "error",
18
+ warn: "warning",
19
+ info: "info",
20
+ neutral: "info",
21
+ };
22
+
6
23
  const FOCUSABLE_SELECTORS = [
7
24
  "button:not([disabled])",
8
25
  "[href]",
@@ -12,7 +29,17 @@ const FOCUSABLE_SELECTORS = [
12
29
  '[tabindex]:not([tabindex="-1"])',
13
30
  ].join(", ");
14
31
 
15
- export function Dialog({ open = false, onClose, title, footer, children, ...props }) {
32
+ export function Dialog({
33
+ open = false,
34
+ onClose,
35
+ title,
36
+ footer,
37
+ status,
38
+ icon,
39
+ children,
40
+ className = "",
41
+ ...props
42
+ }) {
16
43
  const ref = useRef(null);
17
44
  const triggerRef = useRef(null);
18
45
 
@@ -65,17 +92,36 @@ export function Dialog({ open = false, onClose, title, footer, children, ...prop
65
92
  return () => el.removeEventListener("keydown", handleKeyDown);
66
93
  }, [open]);
67
94
 
95
+ const heroBg = status ? (HERO_COLORS[status] ?? HERO_COLORS.neutral) : null;
96
+ const resolvedIcon = icon ?? (status ? STATUS_ICONS[status] : null);
97
+ const hasHeader = title || onClose;
98
+
99
+ const classes = [
100
+ "a1-dialog",
101
+ status && "a1-dialog--has-hero",
102
+ className,
103
+ ].filter(Boolean).join(" ");
104
+
68
105
  return (
69
- <dialog ref={ref} className="a1-dialog" {...props}>
70
- <div className="a1-dialog__header">
71
- {title && <p className="a1-dialog__title">{title}</p>}
72
- <IconButton
73
- icon="close"
74
- label="Close dialog"
75
- onClick={onClose}
76
- className="a1-dialog__close"
77
- />
78
- </div>
106
+ <dialog ref={ref} className={classes} {...props}>
107
+ {status && (
108
+ <div className="a1-dialog__hero" style={{ "--a1-dialog-hero-bg": heroBg }}>
109
+ {resolvedIcon && <Icon name={resolvedIcon} aria-hidden="true" />}
110
+ </div>
111
+ )}
112
+ {hasHeader && (
113
+ <div className="a1-dialog__header">
114
+ {title && <p className="a1-dialog__title">{title}</p>}
115
+ {onClose && (
116
+ <IconButton
117
+ icon="close"
118
+ label="Close dialog"
119
+ onClick={onClose}
120
+ className="a1-dialog__close"
121
+ />
122
+ )}
123
+ </div>
124
+ )}
79
125
  <div className="a1-dialog__body" tabIndex={0}>{children}</div>
80
126
  {footer && (
81
127
  <div className="a1-dialog__footer">
@@ -1,4 +1,5 @@
1
1
  .a1-dialog {
2
+ --a1-dialog-padding-inline: var(--component-dialog-padding);
2
3
  box-sizing: border-box;
3
4
  border: var(--component-dialog-border-width) solid var(--semantic-color-border-strong);
4
5
  border-radius: var(--component-dialog-border-radius);
@@ -58,8 +59,36 @@
58
59
  border-top: var(--component-dialog-footer-border-width) solid var(--semantic-color-border-subtle);
59
60
  }
60
61
 
62
+ /* ─── Hero icon (status variant) ─────────────────────────────────────────── */
63
+
64
+ .a1-dialog--has-hero {
65
+ overflow: hidden;
66
+ }
67
+
68
+ .a1-dialog--has-hero[open] {
69
+ /* Body is the 3rd child when a hero is present, so needs an extra auto row */
70
+ grid-template-rows: auto auto minmax(0, 1fr);
71
+ }
72
+
73
+ .a1-dialog__hero {
74
+ margin-top: calc(-1 * var(--component-dialog-padding));
75
+ margin-inline: calc(-1 * var(--a1-dialog-padding-inline, var(--component-dialog-padding)));
76
+ padding: var(--base-spacing-32) 0;
77
+ background: var(--a1-dialog-hero-bg, var(--semantic-color-action-background));
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ --a1-icon-opsz: 48;
82
+ }
83
+
84
+ .a1-dialog__hero .a1-icon {
85
+ font-size: var(--base-spacing-64);
86
+ color: var(--semantic-color-text-inverse);
87
+ }
88
+
61
89
  @media (--bp-sm-down) {
62
90
  .a1-dialog {
91
+ --a1-dialog-padding-inline: var(--base-spacing-20);
63
92
  padding-inline: var(--base-spacing-20);
64
93
  }
65
94
  }
@@ -3,6 +3,16 @@ import * as React from "react";
3
3
  export interface IconProps extends React.HTMLAttributes<HTMLSpanElement> {
4
4
  /** Material Symbols icon name (e.g. "check_circle", "home", "arrow_back") */
5
5
  name: string;
6
+ /**
7
+ * Icon size. "md" (default) inherits font-size from the parent.
8
+ * "xs"=16px, "sm"=20px, "md"=inherit, "lg"=32px, "xl"=40px, "jumbo"=64px, "xJumbo"=96px
9
+ */
10
+ size?: "xs" | "sm" | "md" | "lg" | "xl" | "jumbo" | "xJumbo";
11
+ /**
12
+ * Icon color. Omit to inherit the current text color.
13
+ * Status values map to semantic status background tokens.
14
+ */
15
+ color?: "muted" | "accent" | "inverse" | "success" | "error" | "warn" | "info";
6
16
  /**
7
17
  * Variable font weight axis (100–700).
8
18
  * Default is set via CSS token `--a1-icon-weight`.
@@ -1,7 +1,28 @@
1
1
  import "./icon.css";
2
2
 
3
+ const SIZE_MAP = {
4
+ xs: "a1-icon-xs",
5
+ sm: "a1-icon-sm",
6
+ lg: "a1-icon-lg",
7
+ xl: "a1-icon-xl",
8
+ jumbo: "a1-icon-jumbo",
9
+ xJumbo: "a1-icon-xjumbo",
10
+ };
11
+
12
+ const COLOR_MAP = {
13
+ muted: "a1-icon-muted",
14
+ accent: "a1-icon-accent",
15
+ inverse: "a1-icon-inverse",
16
+ success: "a1-icon-success",
17
+ error: "a1-icon-error",
18
+ warn: "a1-icon-warn",
19
+ info: "a1-icon-info",
20
+ };
21
+
3
22
  export function Icon({
4
23
  name,
24
+ size,
25
+ color,
5
26
  weight,
6
27
  grade,
7
28
  opticalSize,
@@ -17,11 +38,19 @@ export function Icon({
17
38
  ...(fill != null && { "--a1-icon-fill": fill ? 1 : 0 }),
18
39
  };
19
40
 
41
+ const classes = [
42
+ "a1-icon",
43
+ "material-symbols-outlined",
44
+ SIZE_MAP[size],
45
+ COLOR_MAP[color],
46
+ className,
47
+ ]
48
+ .filter(Boolean)
49
+ .join(" ");
50
+
20
51
  return (
21
52
  <span
22
- className={["a1-icon", "material-symbols-outlined", className]
23
- .filter(Boolean)
24
- .join(" ")}
53
+ className={classes}
25
54
  style={{ ...vars, ...style }}
26
55
  aria-hidden="true"
27
56
  {...props}
@@ -1,5 +1,5 @@
1
1
  .a1-icon {
2
- font-size: inherit;
2
+ font-size: var(--a1-icon-size, inherit);
3
3
  line-height: 1;
4
4
  user-select: none;
5
5
  font-family: var(--a1-icon-font-family, 'Material Symbols Outlined');
@@ -9,3 +9,23 @@
9
9
  "GRAD" var(--a1-icon-grade, 0),
10
10
  "opsz" var(--a1-icon-opsz, var(--base-icon-optical-size));
11
11
  }
12
+
13
+ /* ─── Sizes ───────────────────────────────────────────────────────────────── */
14
+
15
+ .a1-icon-xs { --a1-icon-size: var(--base-spacing-16); --a1-icon-opsz: 20; }
16
+ .a1-icon-sm { --a1-icon-size: var(--base-spacing-20); --a1-icon-opsz: 20; }
17
+ /* md = default (inherits from parent, ~24px) */
18
+ .a1-icon-lg { --a1-icon-size: var(--base-spacing-32); --a1-icon-opsz: 40; }
19
+ .a1-icon-xl { --a1-icon-size: var(--base-spacing-40); --a1-icon-opsz: 48; }
20
+ .a1-icon-jumbo { --a1-icon-size: var(--base-spacing-64); --a1-icon-opsz: 48; }
21
+ .a1-icon-xjumbo { --a1-icon-size: var(--base-spacing-96); --a1-icon-opsz: 48; }
22
+
23
+ /* ─── Colors ──────────────────────────────────────────────────────────────── */
24
+
25
+ .a1-icon-muted { color: var(--semantic-color-text-muted); }
26
+ .a1-icon-accent { color: var(--semantic-color-text-accent); }
27
+ .a1-icon-inverse { color: var(--semantic-color-text-inverse); }
28
+ .a1-icon-success { color: var(--semantic-color-status-success-background); }
29
+ .a1-icon-error { color: var(--semantic-color-status-error-background); }
30
+ .a1-icon-warn { color: var(--semantic-color-status-warn-background); }
31
+ .a1-icon-info { color: var(--semantic-color-status-info-background); }