@ultimat3/ui 29.3.2 → 30.0.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.
Files changed (168) hide show
  1. package/CATALOG.md +491 -24
  2. package/CLAUDE.md +23 -16
  3. package/README.md +338 -33
  4. package/package.json +16 -5
  5. package/src/components/AppShell.module.scss +23 -7
  6. package/src/components/AppShell.tsx +9 -0
  7. package/src/components/BarChart.module.scss +26 -0
  8. package/src/components/BarChart.tsx +242 -93
  9. package/src/components/Board.module.scss +30 -0
  10. package/src/components/Board.tsx +47 -0
  11. package/src/components/BottomNav.module.scss +49 -0
  12. package/src/components/BottomNav.tsx +57 -0
  13. package/src/components/Calendar.module.scss +118 -0
  14. package/src/components/Calendar.tsx +246 -0
  15. package/src/components/ChartFrame.module.scss +40 -3
  16. package/src/components/ChartFrame.tsx +84 -28
  17. package/src/components/CodeBlock.module.scss +101 -0
  18. package/src/components/CodeBlock.tsx +76 -0
  19. package/src/components/CommandPalette.module.scss +25 -0
  20. package/src/components/CommandPalette.tsx +50 -22
  21. package/src/components/ConfirmDialog.module.scss +36 -0
  22. package/src/components/ConfirmDialog.tsx +188 -0
  23. package/src/components/DataTable.tsx +92 -33
  24. package/src/components/DatePicker.module.scss +37 -0
  25. package/src/components/DatePicker.tsx +134 -0
  26. package/src/components/DateRangePicker.module.scss +54 -0
  27. package/src/components/DateRangePicker.tsx +156 -0
  28. package/src/components/DescriptionList.module.scss +46 -0
  29. package/src/components/DescriptionList.tsx +38 -0
  30. package/src/components/DiffView.module.scss +124 -0
  31. package/src/components/DiffView.tsx +169 -0
  32. package/src/components/DonutChart.tsx +13 -4
  33. package/src/components/Drawer.module.scss +40 -0
  34. package/src/components/Drawer.tsx +56 -0
  35. package/src/components/Gauge.module.scss +8 -0
  36. package/src/components/Gauge.tsx +40 -13
  37. package/src/components/HeatStrip.module.scss +133 -0
  38. package/src/components/HeatStrip.tsx +64 -0
  39. package/src/components/JsonView.module.scss +64 -0
  40. package/src/components/JsonView.tsx +68 -0
  41. package/src/components/LineChart.tsx +45 -18
  42. package/src/components/LiveIndicator.module.scss +44 -0
  43. package/src/components/LiveIndicator.tsx +45 -0
  44. package/src/components/LogViewer.module.scss +102 -0
  45. package/src/components/LogViewer.tsx +123 -0
  46. package/src/components/Markdown.module.scss +87 -0
  47. package/src/components/Markdown.tsx +84 -0
  48. package/src/components/Meter.module.scss +7 -0
  49. package/src/components/Meter.tsx +16 -0
  50. package/src/components/NavList.module.scss +66 -0
  51. package/src/components/NavList.tsx +95 -0
  52. package/src/components/Pagination.module.scss +6 -2
  53. package/src/components/Pagination.tsx +57 -13
  54. package/src/components/Popover.tsx +12 -2
  55. package/src/components/PullToRefresh.module.scss +41 -0
  56. package/src/components/PullToRefresh.tsx +108 -0
  57. package/src/components/Radio.module.scss +51 -0
  58. package/src/components/Radio.tsx +12 -1
  59. package/src/components/SearchableSelect.module.scss +216 -0
  60. package/src/components/SearchableSelect.tsx +331 -0
  61. package/src/components/SearchableSelectChips.tsx +41 -0
  62. package/src/components/SearchableSelectNative.tsx +89 -0
  63. package/src/components/ShortcutSheet.module.scss +33 -0
  64. package/src/components/ShortcutSheet.tsx +67 -0
  65. package/src/components/Slider.module.scss +50 -0
  66. package/src/components/Slider.tsx +68 -0
  67. package/src/components/SortableList.module.scss +72 -0
  68. package/src/components/SortableList.tsx +42 -0
  69. package/src/components/Sparkline.module.scss +10 -2
  70. package/src/components/Sparkline.tsx +27 -10
  71. package/src/components/SplitPane.module.scss +39 -0
  72. package/src/components/SplitPane.tsx +96 -0
  73. package/src/components/StatTile.module.scss +29 -3
  74. package/src/components/StatTile.tsx +75 -16
  75. package/src/components/Stepper.module.scss +78 -0
  76. package/src/components/Stepper.tsx +73 -0
  77. package/src/components/Tabs.module.scss +13 -1
  78. package/src/components/Tabs.tsx +77 -1
  79. package/src/components/TerminalView.module.scss +68 -0
  80. package/src/components/TerminalView.tsx +97 -0
  81. package/src/components/TimeRangePicker.module.scss +85 -0
  82. package/src/components/TimeRangePicker.tsx +91 -0
  83. package/src/components/Timeline.module.scss +74 -0
  84. package/src/components/Timeline.tsx +60 -0
  85. package/src/components/Tree.module.scss +98 -0
  86. package/src/components/Tree.tsx +185 -0
  87. package/src/components/VirtualList.module.scss +60 -0
  88. package/src/components/VirtualList.tsx +251 -0
  89. package/src/components/app-shell-view.ts +4 -1
  90. package/src/components/bar-chart-view.ts +121 -39
  91. package/src/components/board-view.ts +25 -0
  92. package/src/components/bottom-nav-view.ts +18 -0
  93. package/src/components/calendar-view.ts +226 -0
  94. package/src/components/chart-checks.ts +59 -0
  95. package/src/components/chart-frame-view.ts +33 -2
  96. package/src/components/chart-reference-view.ts +41 -0
  97. package/src/components/command-palette-view.ts +47 -6
  98. package/src/components/confirm-dialog-view.ts +47 -0
  99. package/src/components/data-table-rows.ts +93 -0
  100. package/src/components/date-picker-view.ts +38 -0
  101. package/src/components/date-range-view.ts +46 -0
  102. package/src/components/diff-view-view.ts +85 -0
  103. package/src/components/drawer-gesture-view.ts +33 -0
  104. package/src/components/gauge-view.ts +29 -0
  105. package/src/components/heat-strip-view.ts +68 -0
  106. package/src/components/json-view-view.ts +54 -0
  107. package/src/components/line-chart-view.ts +87 -25
  108. package/src/components/log-viewer-view.ts +51 -0
  109. package/src/components/markdown-nodes.tsx +150 -0
  110. package/src/components/nav-list-view.ts +37 -0
  111. package/src/components/pull-refresh-view.ts +93 -0
  112. package/src/components/rank-options.ts +61 -0
  113. package/src/components/searchable-select-keys.ts +118 -0
  114. package/src/components/searchable-select-view.ts +187 -0
  115. package/src/components/slider-view.ts +47 -0
  116. package/src/components/sort-pointer.ts +141 -0
  117. package/src/components/sort-surface.tsx +231 -0
  118. package/src/components/sparkline-view.ts +74 -14
  119. package/src/components/split-pane-view.ts +69 -0
  120. package/src/components/stat-delta.ts +54 -11
  121. package/src/components/stepper-view.ts +38 -0
  122. package/src/components/thunk.ts +9 -0
  123. package/src/components/timeline-view.ts +29 -0
  124. package/src/components/tree-node.tsx +133 -0
  125. package/src/components/tree-view.ts +185 -0
  126. package/src/components/use-command-palette.ts +153 -0
  127. package/src/components/virtual-list-anchor.ts +35 -0
  128. package/src/components/virtual-list-observer.ts +58 -0
  129. package/src/components/virtual-list-view.ts +145 -0
  130. package/src/content/ansi.ts +144 -0
  131. package/src/content/heading-slug.ts +32 -0
  132. package/src/content/highlight.ts +129 -0
  133. package/src/content/languages/all.ts +21 -0
  134. package/src/content/languages/diff.ts +14 -0
  135. package/src/content/languages/html.ts +16 -0
  136. package/src/content/languages/json.ts +15 -0
  137. package/src/content/languages/shell.ts +20 -0
  138. package/src/content/languages/sql.ts +27 -0
  139. package/src/content/languages/ts.ts +26 -0
  140. package/src/content/languages/yaml.ts +18 -0
  141. package/src/content/line-diff.ts +191 -0
  142. package/src/content/markdown-inline.ts +368 -0
  143. package/src/content/markdown-parse.ts +267 -0
  144. package/src/content/markdown-tree.ts +58 -0
  145. package/src/content/unified-patch.ts +53 -0
  146. package/src/drag/pointer-drag.ts +185 -0
  147. package/src/drag/sort-controller.ts +196 -0
  148. package/src/drag/sort-view.ts +201 -0
  149. package/src/errors.ts +1 -1
  150. package/src/form/field-path.ts +7 -33
  151. package/src/form/form-binding.ts +5 -8
  152. package/src/form/form-issue.ts +30 -36
  153. package/src/form/form-values.ts +3 -2
  154. package/src/hotkeys-view.ts +150 -0
  155. package/src/i18n-keys.ts +7 -50
  156. package/src/index.ts +214 -15
  157. package/src/list-url.ts +123 -0
  158. package/src/live-window.ts +23 -0
  159. package/src/roving.ts +2 -0
  160. package/src/theme/inert-runtime.ts +4 -0
  161. package/src/theme/runtime-slot.ts +2 -2
  162. package/src/theme/solid-adapter.ts +11 -5
  163. package/src/time-range-url.ts +200 -0
  164. package/src/tokens/_mixins.scss +1 -1
  165. package/src/tokens/_touch.scss +30 -1
  166. package/src/tokens/contrast-pairs.ts +12 -0
  167. package/src/ui-keys.ts +126 -0
  168. package/src/use-hotkeys.ts +145 -0
@@ -0,0 +1,41 @@
1
+ @use '../tokens' as t;
2
+
3
+ .root {
4
+ position: relative;
5
+ // The pull is ours at the top edge: no browser bounce or native refresh layered on top.
6
+ overscroll-behavior-y: contain;
7
+ }
8
+
9
+ .indicator {
10
+ position: absolute;
11
+ inset-block-start: 0;
12
+ inset-inline: 0;
13
+ display: flex;
14
+ justify-content: center;
15
+ padding-block: t.space(2);
16
+ color: t.role('fg-muted');
17
+ font-size: t.text(sm);
18
+ opacity: 0;
19
+ pointer-events: none;
20
+ }
21
+
22
+ .root[data-state='pulling'] .indicator,
23
+ .root[data-state='refreshing'] .indicator {
24
+ opacity: 1;
25
+ }
26
+
27
+ .content {
28
+ translate: 0 var(--pull-distance, 0);
29
+ }
30
+
31
+ .root[data-state='idle'] .content,
32
+ .root[data-state='refreshing'] .content {
33
+ @include t.transition(translate);
34
+ }
35
+
36
+ // The content follows the finger only where motion is welcome; the hint still shows.
37
+ @media (prefers-reduced-motion: reduce) {
38
+ .content {
39
+ translate: none;
40
+ }
41
+ }
@@ -0,0 +1,108 @@
1
+ // Pull down at the top of a list to refresh it — the gesture an installed app has no browser to
2
+ // give it. No refresh of its own: `onRefresh` is the caller's (`refresh()` from the page's router,
3
+ // usually), so `ui` never imports `render`.
4
+ //
5
+ // Touch only, and only at the top: a pull mid-list is the page's scroll, and a fine pointer gets
6
+ // nothing — the caller's toolbar button is the way there, and the way for every keyboard. Inert
7
+ // with scripting off; the scroll position stays.
8
+
9
+ import type { JSX } from 'solid-js';
10
+ import { announce } from '../a11y';
11
+ import { cx } from '../cx';
12
+ import { UI_KEYS } from '../i18n-keys';
13
+ import { useUi } from '../theme/context';
14
+ import { solid } from '../theme/solid-adapter';
15
+ import styles from './PullToRefresh.module.scss';
16
+ import { PULL_IDLE, type PullState, pullController } from './pull-refresh-view';
17
+
18
+ export interface PullToRefreshProps {
19
+ /** Re-reads the content. Its settling — resolved or rejected — ends the busy state. */
20
+ onRefresh: () => Promise<void>;
21
+ /** A rejected refresh, after the busy state ended. Required: a failed refresh is never silent. */
22
+ onError: (error: unknown) => void;
23
+ children: JSX.Element;
24
+ class?: string | undefined;
25
+ }
26
+
27
+ const finePointer = (): boolean =>
28
+ typeof matchMedia === 'function' && matchMedia('(pointer: fine)').matches;
29
+
30
+ const scrolledToTop = (el: HTMLElement): boolean =>
31
+ el.scrollTop <= 0 && (document.scrollingElement?.scrollTop ?? 0) <= 0;
32
+
33
+ export function PullToRefresh(props: PullToRefreshProps): JSX.Element {
34
+ const ui = useUi();
35
+ const rt = solid();
36
+ const [state, setState] = rt.createSignal<PullState>(PULL_IDLE);
37
+ let root: HTMLDivElement | undefined;
38
+
39
+ const pull = pullController(
40
+ () => props.onRefresh(),
41
+ (next) => {
42
+ setState(next);
43
+ if (next.kind === 'refreshing') announce(ui.t(UI_KEYS.refreshBusy));
44
+ },
45
+ (error) => props.onError(error),
46
+ );
47
+
48
+ rt.createEffect(() => {
49
+ const el = root;
50
+ if (el === undefined) return;
51
+ let startY = 0;
52
+ const onStart = (event: TouchEvent): void => {
53
+ const touch = event.touches[0];
54
+ if (touch === undefined || event.touches.length !== 1) return;
55
+ startY = touch.clientY;
56
+ pull.send({ type: 'start', atTop: scrolledToTop(el), touch: !finePointer() });
57
+ };
58
+ const onMove = (event: TouchEvent): void => {
59
+ const touch = event.touches[0];
60
+ if (touch === undefined || pull.state().kind !== 'pulling') return;
61
+ const dy = touch.clientY - startY;
62
+ // An upward move is the list's scroll, handed back to the browser.
63
+ if (dy <= 0) return;
64
+ event.preventDefault();
65
+ pull.send({ type: 'move', dy });
66
+ };
67
+ const onEnd = (): void => pull.send({ type: 'end' });
68
+ el.addEventListener('touchstart', onStart, { passive: true });
69
+ el.addEventListener('touchmove', onMove, { passive: false });
70
+ el.addEventListener('touchend', onEnd);
71
+ el.addEventListener('touchcancel', onEnd);
72
+ rt.onCleanup(() => {
73
+ el.removeEventListener('touchstart', onStart);
74
+ el.removeEventListener('touchmove', onMove);
75
+ el.removeEventListener('touchend', onEnd);
76
+ el.removeEventListener('touchcancel', onEnd);
77
+ });
78
+ });
79
+
80
+ const hint = (): string => {
81
+ const now = state();
82
+ if (now.kind === 'refreshing') return ui.t(UI_KEYS.refreshBusy);
83
+ if (now.kind === 'pulling' && now.armed) return ui.t(UI_KEYS.refreshRelease);
84
+ return ui.t(UI_KEYS.refreshPull);
85
+ };
86
+ const distance = (): number => {
87
+ const now = state();
88
+ return now.kind === 'pulling' ? now.distance : 0;
89
+ };
90
+
91
+ return (
92
+ <div
93
+ ref={(el: HTMLDivElement) => {
94
+ root = el;
95
+ }}
96
+ class={cx(styles['root'], props.class)}
97
+ data-state={state().kind}
98
+ aria-busy={state().kind === 'refreshing' ? 'true' : undefined}
99
+ style={{ '--pull-distance': `${distance()}px` }}
100
+ >
101
+ {/* Decorative: the announcement and `aria-busy` carry the state to assistive tech. */}
102
+ <div class={styles['indicator']} aria-hidden="true">
103
+ {hint()}
104
+ </div>
105
+ <div class={styles['content']}>{props.children}</div>
106
+ </div>
107
+ );
108
+ }
@@ -85,3 +85,54 @@
85
85
  color: t.role('fg-muted');
86
86
  font-size: t.text(xs);
87
87
  }
88
+
89
+ // Segmented: the same native radios as one joined strip. The dot goes; the checked segment fills.
90
+ .appearance-segmented {
91
+ flex-flow: row wrap;
92
+ gap: 0;
93
+
94
+ .legend {
95
+ margin-block-end: t.space(2);
96
+ }
97
+
98
+ .row {
99
+ @include t.padding-inline(t.space(3));
100
+ align-items: center;
101
+ min-block-size: t.touch(target);
102
+ border: 1px solid t.role('line');
103
+ margin-inline-start: -1px;
104
+ background: t.role('surface-raised');
105
+
106
+ &:first-of-type {
107
+ margin-inline-start: 0;
108
+ border-start-start-radius: t.radius(md);
109
+ border-end-start-radius: t.radius(md);
110
+ }
111
+
112
+ &:last-of-type {
113
+ border-start-end-radius: t.radius(md);
114
+ border-end-end-radius: t.radius(md);
115
+ }
116
+
117
+ &:has(.input:checked) {
118
+ position: relative;
119
+ border-color: t.role('accent');
120
+ color: t.role('accent-fg');
121
+ background: t.role('accent');
122
+ }
123
+
124
+ &:has(.input:focus-visible) {
125
+ position: relative;
126
+ outline: t.stroke(thick) solid t.role('accent');
127
+ outline-offset: t.stroke(thick);
128
+ }
129
+ }
130
+
131
+ .dot {
132
+ display: none;
133
+ }
134
+
135
+ .description {
136
+ color: inherit;
137
+ }
138
+ }
@@ -23,6 +23,11 @@ export interface RadioProps {
23
23
  disabled?: boolean | undefined;
24
24
  required?: boolean | undefined;
25
25
  direction?: 'row' | 'column' | undefined;
26
+ /**
27
+ * `segmented` draws the options as one joined strip of buttons — still native radios, so arrows,
28
+ * a form post and scripting off are the platform's. One prop, not a second component.
29
+ */
30
+ appearance?: 'default' | 'segmented' | undefined;
26
31
  class?: string | undefined;
27
32
  'aria-describedby'?: string | undefined;
28
33
  onChange?: JSX.EventHandlerUnion<HTMLInputElement, Event> | undefined;
@@ -32,7 +37,13 @@ export function Radio(props: RadioProps): JSX.Element {
32
37
  const group = useId('radio');
33
38
  return (
34
39
  <fieldset
35
- class={cx(styles['group'], styles[`direction-${props.direction ?? 'column'}`], props.class)}
40
+ class={cx(
41
+ styles['group'],
42
+ props.appearance === 'segmented'
43
+ ? styles['appearance-segmented']
44
+ : styles[`direction-${props.direction ?? 'column'}`],
45
+ props.class,
46
+ )}
36
47
  disabled={props.disabled === true}
37
48
  aria-describedby={props['aria-describedby']}
38
49
  >
@@ -0,0 +1,216 @@
1
+ @use '../tokens' as t;
2
+
3
+ .root {
4
+ position: relative;
5
+ display: block;
6
+ inline-size: 100%;
7
+ }
8
+
9
+ // The no-script control: `Select`'s look, so the swap moves nothing on screen.
10
+ .native {
11
+ @include t.control;
12
+ display: inline-flex;
13
+ align-items: center;
14
+
15
+ &:focus-within {
16
+ border-color: t.role('accent');
17
+ }
18
+ }
19
+
20
+ .select {
21
+ flex: 1;
22
+ min-inline-size: 0;
23
+ border: 0;
24
+ background: none;
25
+ color: inherit;
26
+ font: inherit;
27
+ outline: none;
28
+ }
29
+
30
+ .enhanced {
31
+ @include t.column(t.space(1));
32
+ }
33
+
34
+ .control {
35
+ @include t.control;
36
+ display: flex;
37
+ align-items: center;
38
+
39
+ &:focus-within {
40
+ border-color: t.role('accent');
41
+ }
42
+ }
43
+
44
+ // `md` is the 16px floor: below it a phone zooms the page on focus (`wiki/Interface-Rules.md`).
45
+ .input {
46
+ flex: 1;
47
+ min-inline-size: 0;
48
+ border: 0;
49
+ background: none;
50
+ color: inherit;
51
+ font: inherit;
52
+ font-size: t.text(md);
53
+ outline: none;
54
+ }
55
+
56
+ .clear {
57
+ @include t.focus-ring;
58
+ @include t.touch-target(true);
59
+ flex: none;
60
+ padding: 0 t.space(1);
61
+ border: 0;
62
+ background: none;
63
+ color: t.role('fg-muted');
64
+ font: inherit;
65
+ font-size: t.text(sm);
66
+ cursor: pointer;
67
+
68
+ &:hover {
69
+ color: t.role('fg');
70
+ }
71
+ }
72
+
73
+ .listbox {
74
+ @include t.surface('surface-raised', 'md');
75
+ @include t.custom-scrollbar;
76
+ position: absolute;
77
+ z-index: t.z(dropdown);
78
+ inset-block-start: 100%;
79
+ inset-inline: 0;
80
+ max-block-size: min(t.rem(320px), 50vh);
81
+ margin-block-start: t.space(1);
82
+ padding-block: t.space(1);
83
+ overflow-y: auto;
84
+ border: 1px solid t.role('line');
85
+ border-radius: t.radius(md);
86
+
87
+ &[hidden] {
88
+ display: none;
89
+ }
90
+ }
91
+
92
+ .group {
93
+ @include t.label-caps;
94
+ padding: t.space(2) t.space(3) t.space(1);
95
+ color: t.role('fg-muted');
96
+ }
97
+
98
+ .option {
99
+ @include t.touch-target;
100
+ display: flex;
101
+ flex-direction: column;
102
+ justify-content: center;
103
+ padding-inline: t.space(3);
104
+ cursor: pointer;
105
+
106
+ &[data-active='true'] {
107
+ background: t.role('accent-soft');
108
+ outline: t.stroke(thick) solid t.role('accent');
109
+ outline-offset: calc(-1 * #{t.stroke(thick)});
110
+ }
111
+
112
+ &[aria-selected='true'] .optionLabel {
113
+ font-weight: t.weight(medium);
114
+ }
115
+
116
+ &[aria-disabled='true'] {
117
+ @include t.disabled;
118
+ cursor: default;
119
+ }
120
+ }
121
+
122
+ .optionLabel {
123
+ color: t.role('fg');
124
+ }
125
+
126
+ .description {
127
+ color: t.role('fg-muted');
128
+ font-size: t.text(sm);
129
+ }
130
+
131
+ .chips {
132
+ display: flex;
133
+ flex-wrap: wrap;
134
+ gap: t.space(1);
135
+ margin: 0;
136
+ padding: 0;
137
+ list-style: none;
138
+ }
139
+
140
+ .chip {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ gap: t.space(1);
144
+ padding-inline-start: t.space(2);
145
+ border: 1px solid t.role('line');
146
+ border-radius: t.radius(pill);
147
+ background: t.role('surface-raised');
148
+ font-size: t.text(sm);
149
+ }
150
+
151
+ .chipLabel {
152
+ @include t.truncate;
153
+ }
154
+
155
+ .chipRemove {
156
+ @include t.focus-ring;
157
+ @include t.touch-target(true);
158
+ display: inline-grid;
159
+ place-items: center;
160
+ inline-size: t.space(5);
161
+ block-size: t.space(5);
162
+ padding: 0;
163
+ border: 0;
164
+ border-radius: t.radius(pill);
165
+ background: none;
166
+ color: t.role('fg-muted');
167
+ cursor: pointer;
168
+
169
+ &:hover {
170
+ color: t.role('fg');
171
+ }
172
+ }
173
+
174
+ // Two strokes: a cross drawn from tokens, never a glyph from a font.
175
+ .cross {
176
+ position: relative;
177
+ inline-size: t.space(3);
178
+ block-size: t.space(3);
179
+
180
+ &::before,
181
+ &::after {
182
+ content: '';
183
+ position: absolute;
184
+ inset-block-start: 50%;
185
+ inset-inline: 0;
186
+ border-block-start: t.stroke(thick) solid currentcolor;
187
+ transform: rotate(45deg);
188
+ }
189
+
190
+ &::after {
191
+ transform: rotate(-45deg);
192
+ }
193
+ }
194
+
195
+ .status {
196
+ margin: 0;
197
+ color: t.role('fg-muted');
198
+ font-size: t.text(sm);
199
+ }
200
+
201
+ .quiet {
202
+ @include t.visually-hidden;
203
+ }
204
+
205
+ .size-sm {
206
+ padding-block: t.space(1);
207
+ }
208
+
209
+ .size-md {
210
+ padding-block: t.space(2);
211
+ }
212
+
213
+ .size-lg {
214
+ padding-block: t.space(3);
215
+ font-size: t.text(lg);
216
+ }