@gtivr4/a1-design-system-react 0.18.0 → 0.19.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.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "React components for the A1 token-driven design system.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -13,6 +13,10 @@ export interface CodeProps extends React.HTMLAttributes<HTMLElement> {
13
13
  editable?: boolean;
14
14
  /** Called with the current string value whenever the editable textarea changes. */
15
15
  onChangeValue?: (value: string) => void;
16
+ /** Cap a long read-only block to `collapsedLines` with a fade + Show more/less toggle (the toggle appears only when the content overflows). Block, non-editable only. Default: false */
17
+ collapsible?: boolean;
18
+ /** Approximate number of lines shown when collapsed. Default: 14 */
19
+ collapsedLines?: number;
16
20
  children?: React.ReactNode;
17
21
  }
18
22
 
@@ -56,12 +56,17 @@ export function Code({
56
56
  copyText,
57
57
  editable = false,
58
58
  onChangeValue,
59
+ collapsible = false,
60
+ collapsedLines = 14,
59
61
  className = "",
60
62
  children,
61
63
  ...props
62
64
  }) {
63
65
  const resolvedVariant = variants.includes(variant) ? variant : "inline";
64
66
  const [copied, setCopied] = useState(false);
67
+ const [expanded, setExpanded] = useState(false);
68
+ const [overflows, setOverflows] = useState(false);
69
+ const preRef = useRef(null);
65
70
  const [editableValue, setEditableValue] = useState(() =>
66
71
  textFromChildren(Children.toArray(children))
67
72
  );
@@ -78,11 +83,15 @@ export function Code({
78
83
  const copyLabel = useLabel("code.copyCode", "Copy code");
79
84
  const copiedLabel = useLabel("code.copied", "Copied");
80
85
  const editLabel = useLabel("code.editCode", "Edit code");
86
+ const showMoreLabel = useLabel("code.showMore", "Show more");
87
+ const showLessLabel = useLabel("code.showLess", "Show less");
81
88
  const textToCopy = useMemo(
82
89
  () => copyText || (editable ? editableValue : textFromChildren(Children.toArray(children))),
83
90
  [children, copyText, editable, editableValue],
84
91
  );
85
92
  const shouldRenderBlock = resolvedVariant === "block" || copyCode || editable;
93
+ // Collapsible only applies to a read-only block (not the editable textarea).
94
+ const collapses = collapsible && !editable && shouldRenderBlock;
86
95
 
87
96
  useEffect(() => {
88
97
  return () => {
@@ -90,6 +99,16 @@ export function Code({
90
99
  };
91
100
  }, []);
92
101
 
102
+ // Detect whether the (collapsed) content actually overflows the cap, so the
103
+ // toggle only appears when it's needed. scrollHeight reports the full content
104
+ // height even while clipped, so this is accurate in either state.
105
+ useEffect(() => {
106
+ if (!collapses) { setOverflows(false); return; }
107
+ if (expanded) return; // measured while collapsed; keep so "Show less" stays
108
+ const el = preRef.current;
109
+ if (el) setOverflows(el.scrollHeight - el.clientHeight > 4);
110
+ }, [collapses, expanded, children, collapsedLines]);
111
+
93
112
  function handleTextareaChange(e) {
94
113
  setEditableValue(e.target.value);
95
114
  onChangeValue?.(e.target.value);
@@ -127,12 +146,15 @@ export function Code({
127
146
  );
128
147
  }
129
148
 
149
+ const collapsed = collapses && overflows && !expanded;
150
+
130
151
  return (
131
152
  <div
132
153
  className={[
133
154
  "a1-code-block",
134
155
  copyCode && "a1-code-block--copyable",
135
156
  editable && "a1-code-block--editable",
157
+ collapsed && "a1-code-block--collapsed",
136
158
  className,
137
159
  ]
138
160
  .filter(Boolean)
@@ -153,7 +175,11 @@ export function Code({
153
175
  {...editableProps}
154
176
  />
155
177
  ) : (
156
- <pre className="a1-code-block__pre">
178
+ <pre
179
+ ref={preRef}
180
+ className="a1-code-block__pre"
181
+ style={collapses ? { "--a1-code-collapsed-max": `${collapsedLines * 1.6}em` } : undefined}
182
+ >
157
183
  <code className={codeClasses} {...props}>
158
184
  {children}
159
185
  </code>
@@ -171,6 +197,18 @@ export function Code({
171
197
  {copied ? copiedLabel : copyLabel}
172
198
  </Button>
173
199
  )}
200
+ {collapses && overflows && (
201
+ <Button
202
+ className="a1-code-block__toggle"
203
+ icon={expanded ? "expand_less" : "expand_more"}
204
+ size="sm"
205
+ variant="tertiary"
206
+ onClick={() => setExpanded((v) => !v)}
207
+ type="button"
208
+ >
209
+ {expanded ? showLessLabel : showMoreLabel}
210
+ </Button>
211
+ )}
174
212
  </div>
175
213
  );
176
214
  }
@@ -87,3 +87,26 @@
87
87
  .a1-code-block__copy {
88
88
  margin: 0;
89
89
  }
90
+
91
+ /* Collapsible block: cap the height with a fade and an Expand/Collapse toggle. */
92
+ .a1-code-block--collapsed .a1-code-block__pre {
93
+ position: relative;
94
+ max-block-size: var(--a1-code-collapsed-max, 22rem);
95
+ overflow-y: hidden;
96
+ }
97
+
98
+ .a1-code-block--collapsed .a1-code-block__pre::after {
99
+ content: "";
100
+ position: absolute;
101
+ inset-inline: 0;
102
+ inset-block-end: 0;
103
+ block-size: var(--base-spacing-48, 3rem);
104
+ background: linear-gradient(to top, var(--semantic-color-surface-panel), transparent);
105
+ pointer-events: none;
106
+ border-end-start-radius: var(--base-radius-md);
107
+ border-end-end-radius: var(--base-radius-md);
108
+ }
109
+
110
+ .a1-code-block__toggle {
111
+ margin: 0;
112
+ }
@@ -20,6 +20,8 @@ export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
20
20
  layout?: "default" | "bento";
21
21
  /** CSS value for `grid-auto-rows` */
22
22
  autoRows?: string;
23
+ /** Cross-axis (vertical) alignment of items within their row. Omit to inherit the grid default ("stretch" = equal-height items filling the row height). */
24
+ alignItems?: "start" | "center" | "end" | "stretch";
23
25
  children?: React.ReactNode;
24
26
  }
25
27
 
@@ -11,6 +11,7 @@ const gapSizes = {
11
11
  };
12
12
  const layouts = ["default", "bento"];
13
13
  const breakpoints = ["xs", "sm", "md", "lg", "xl"];
14
+ const alignments = ["start", "center", "end", "stretch"];
14
15
 
15
16
  function resolveGap(key) {
16
17
  if (key == null) return undefined;
@@ -29,6 +30,7 @@ export function Grid({
29
30
  columnGap,
30
31
  layout = "default",
31
32
  autoRows,
33
+ alignItems,
32
34
  className = "",
33
35
  children,
34
36
  ...props
@@ -40,6 +42,12 @@ export function Grid({
40
42
  classes.push(`a1-grid--${resolvedLayout}`);
41
43
  }
42
44
 
45
+ // Cross-axis (vertical) alignment of items in their row. Omit to inherit the
46
+ // grid default (stretch = equal-height items filling the row).
47
+ if (alignments.includes(alignItems)) {
48
+ classes.push(`a1-grid--align-${alignItems}`);
49
+ }
50
+
43
51
  let inlineCols;
44
52
  if (typeof columns === "number") {
45
53
  inlineCols = columns;
@@ -9,6 +9,12 @@
9
9
  align-items: stretch;
10
10
  }
11
11
 
12
+ /* Cross-axis (vertical) alignment of items within their row. */
13
+ .a1-grid--align-start { align-items: start; }
14
+ .a1-grid--align-center { align-items: center; }
15
+ .a1-grid--align-end { align-items: end; }
16
+ .a1-grid--align-stretch { align-items: stretch; }
17
+
12
18
  .a1-grid--bento > .a1-grid-item {
13
19
  min-height: 0;
14
20
  }