@stainless-api/ui-primitives 0.1.0-beta.2 → 0.1.0-beta.21

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 (51) hide show
  1. package/.turbo/turbo-build.log +35 -0
  2. package/CHANGELOG.md +118 -0
  3. package/dist/Accordion-CJL9SWwS.d.ts +26 -0
  4. package/dist/Accordion-DLQE3Td6.js +31 -0
  5. package/dist/Button-DBhd6kU7.js +51 -0
  6. package/dist/Button-DwndlytB.d.ts +38 -0
  7. package/dist/Callout-CMz3Yl_5.d.ts +18 -0
  8. package/dist/Callout-UZQRuCQ5.js +28 -0
  9. package/dist/DropdownButton-DoYDi8tB.js +82 -0
  10. package/dist/DropdownButton-zcvep_xH.d.ts +50 -0
  11. package/dist/components/Accordion.d.ts +2 -0
  12. package/dist/components/Accordion.js +3 -0
  13. package/dist/components/Button.d.ts +2 -0
  14. package/dist/components/Button.js +3 -0
  15. package/dist/components/Callout.d.ts +2 -0
  16. package/dist/components/Callout.js +3 -0
  17. package/dist/components/DropdownButton.d.ts +2 -0
  18. package/dist/components/DropdownButton.js +3 -0
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.js +6 -0
  21. package/dist/scripts/index.d.ts +12 -0
  22. package/dist/scripts/index.js +33 -0
  23. package/dist/styles.css +1181 -0
  24. package/dist/styles.js +1 -0
  25. package/package.json +9 -9
  26. package/src/components/Accordion.tsx +41 -0
  27. package/src/components/Button.tsx +1 -0
  28. package/src/components/accordion.css +145 -0
  29. package/src/components/button.css +165 -135
  30. package/src/components/callout.css +78 -57
  31. package/src/components/dropdown/Dropdown.tsx +51 -0
  32. package/src/components/dropdown/DropdownButton.tsx +54 -0
  33. package/src/components/dropdown/DropdownMenu.tsx +113 -0
  34. package/src/components/dropdown/dropdown.css +232 -0
  35. package/src/index.ts +3 -2
  36. package/src/scripts/dropdown-button.ts +20 -36
  37. package/src/scripts/dropdown.ts +193 -0
  38. package/src/scripts/index.ts +1 -0
  39. package/src/styles/layout.css +3 -1
  40. package/src/styles/scales.css +1 -1
  41. package/src/styles/starlight-compat.css +138 -107
  42. package/src/styles/swatches.css +2 -2
  43. package/src/styles/theme.css +2 -2
  44. package/src/styles/typography.css +117 -147
  45. package/src/styles.css +2 -2
  46. package/tsconfig.json +3 -7
  47. package/.env +0 -1
  48. package/src/components/DetailsGroup.tsx +0 -17
  49. package/src/components/DropdownButton.tsx +0 -98
  50. package/src/components/details.css +0 -126
  51. package/src/components/dropdown-button.css +0 -162
@@ -0,0 +1,193 @@
1
+ export function initDropdown({
2
+ root,
3
+ onSelect,
4
+ initialValue,
5
+ }: {
6
+ root?: Element | null;
7
+ onSelect?: (value: string) => void;
8
+ initialValue?: string;
9
+ }) {
10
+ if (!root) {
11
+ console.error('Dropdown root element not found');
12
+ return;
13
+ }
14
+ const trigger = root.querySelector<HTMLElement>('[data-part="trigger"]');
15
+ const menu = root.querySelector<HTMLElement>('[data-part="menu"]');
16
+
17
+ if (!trigger) {
18
+ console.error('Dropdown trigger not found');
19
+ return;
20
+ }
21
+
22
+ if (!menu) {
23
+ console.error('Dropdown menu not found');
24
+ return;
25
+ }
26
+
27
+ const selectedSlot = trigger.querySelector('[data-part="trigger-selected"]');
28
+
29
+ const items = Array.from(menu.querySelectorAll<HTMLElement>('[data-part="item"]'));
30
+
31
+ function open() {
32
+ if (!trigger || !menu || !root) return;
33
+ root.setAttribute('aria-expanded', 'true');
34
+ menu.dataset.state = 'open';
35
+ menu.removeAttribute('aria-hidden');
36
+
37
+ // Determine if menu should open above or below
38
+ const triggerRect = trigger.getBoundingClientRect();
39
+ const menuHeight = menu.offsetHeight;
40
+ const viewportHeight = window.innerHeight;
41
+ const spaceBelow = viewportHeight - triggerRect.bottom;
42
+ const spaceAbove = triggerRect.top;
43
+ menu.classList.remove('stl-ui-dropdown-menu--above', 'stl-ui-dropdown-menu--below');
44
+
45
+ if (spaceBelow < menuHeight && spaceAbove > spaceBelow) {
46
+ menu.classList.add('stl-ui-dropdown-menu--above');
47
+ } else {
48
+ menu.classList.add('stl-ui-dropdown-menu--below');
49
+ }
50
+ }
51
+
52
+ function close() {
53
+ if (!trigger || !menu || !root) return;
54
+ root.setAttribute('aria-expanded', 'false');
55
+ menu.dataset.state = 'closed';
56
+ menu.setAttribute('aria-hidden', 'true');
57
+ }
58
+
59
+ function renderSelectedFromItem(item: Element) {
60
+ // If there is no selected slot, do nothing. This allows for dropdowns without a selected display.
61
+ if (!selectedSlot) {
62
+ return;
63
+ }
64
+ const tmpl = item.querySelector<HTMLTemplateElement>('template[data-part="selected-template"]');
65
+
66
+ if (!tmpl) {
67
+ console.error('Dropdown item template not found');
68
+ return;
69
+ }
70
+
71
+ selectedSlot.innerHTML = '';
72
+ selectedSlot.appendChild(tmpl.content.cloneNode(true));
73
+ selectedSlot.removeAttribute('data-placeholder');
74
+ }
75
+
76
+ function selectItem(item: Element) {
77
+ if (!trigger) {
78
+ console.error('Dropdown trigger not found');
79
+ return;
80
+ }
81
+ items.forEach((i) => i.setAttribute('aria-selected', String(i === item)));
82
+ trigger.dataset.value = item.getAttribute('data-value') || '';
83
+ renderSelectedFromItem(item);
84
+ }
85
+
86
+ function handleItemSelection(e: MouseEvent | KeyboardEvent) {
87
+ const item = (e.target as Element).closest('[data-part="item"]');
88
+ if (!item) {
89
+ console.error('Dropdown item not found');
90
+ return;
91
+ }
92
+ if (!trigger) {
93
+ console.error('Dropdown trigger not found');
94
+ return;
95
+ }
96
+
97
+ selectItem(item);
98
+ onSelect?.(item.getAttribute('data-value') || '');
99
+ close();
100
+ trigger.focus();
101
+ }
102
+
103
+ function handleTriggerClick() {
104
+ if (!root) return false;
105
+ const isOpen = root.getAttribute('aria-expanded') === 'true';
106
+ if (isOpen) {
107
+ close();
108
+ return false;
109
+ } else {
110
+ open();
111
+ return true;
112
+ }
113
+ }
114
+
115
+ // Initialize selected item
116
+ const initial =
117
+ (initialValue
118
+ ? items.find((i) => i.getAttribute('data-value') === initialValue)
119
+ : items.find((i) => i.getAttribute('aria-selected') === 'true')) ?? items[0];
120
+
121
+ if (initial) selectItem(initial);
122
+
123
+ // add event listeners
124
+ trigger.addEventListener('click', () => {
125
+ handleTriggerClick();
126
+ });
127
+
128
+ // When using the keyboard to open the dropdown, we focus the first item
129
+ trigger.addEventListener('keydown', (e) => {
130
+ if (e.key !== 'Enter' && e.key !== ' ') return;
131
+ e.preventDefault();
132
+ const didOpen = handleTriggerClick();
133
+ if (didOpen) {
134
+ items[0]?.focus();
135
+ }
136
+ });
137
+
138
+ menu.addEventListener('click', (e) => {
139
+ handleItemSelection(e);
140
+ });
141
+
142
+ document.addEventListener('keydown', (e) => {
143
+ const isOpen = root.getAttribute('aria-expanded') === 'true';
144
+ if (!isOpen) return;
145
+
146
+ if (e.key === 'Escape') {
147
+ close();
148
+ trigger.focus();
149
+ return;
150
+ }
151
+
152
+ if (e.key === 'ArrowDown') {
153
+ e.preventDefault();
154
+ const activeElement = document.activeElement;
155
+
156
+ // if the active item is the button itself, focus the first item
157
+ if (activeElement === trigger) {
158
+ items[0]?.focus();
159
+ return;
160
+ }
161
+ let nextSibling = activeElement?.nextElementSibling;
162
+ while (nextSibling && nextSibling.tagName.toLowerCase() === 'hr') {
163
+ nextSibling = nextSibling.nextElementSibling;
164
+ }
165
+ if (nextSibling instanceof HTMLElement && items.includes(nextSibling)) {
166
+ nextSibling.focus();
167
+ }
168
+ return;
169
+ }
170
+
171
+ if (e.key === 'ArrowUp') {
172
+ e.preventDefault();
173
+ const activeElement = document.activeElement;
174
+ // if the active item is the button itself, focus the last item
175
+ if (activeElement === trigger) {
176
+ items[items.length - 1]?.focus();
177
+ return;
178
+ }
179
+ let prevSibling = activeElement?.previousElementSibling;
180
+ while (prevSibling && prevSibling.tagName.toLowerCase() === 'hr') {
181
+ prevSibling = prevSibling.previousElementSibling;
182
+ }
183
+ if (prevSibling instanceof HTMLElement && items.includes(prevSibling)) {
184
+ prevSibling.focus();
185
+ }
186
+ return;
187
+ }
188
+ });
189
+
190
+ document.addEventListener('click', (e) => {
191
+ if (!root.contains(e.target as Element)) close();
192
+ });
193
+ }
@@ -1 +1,2 @@
1
1
  export * from './dropdown-button';
2
+ export * from './dropdown';
@@ -1,4 +1,4 @@
1
- @layer stl-ui {
1
+ @layer stl-ui.tokens {
2
2
  /* Layout - Stainless */
3
3
  :root {
4
4
  --stl-ui-layout-border-radius-xs: 4px;
@@ -7,5 +7,7 @@
7
7
  --stl-ui-layout-border-radius-med: 16px;
8
8
  --stl-ui-layout-border-radius-max: 9999px;
9
9
  --stl-ui-left-content-indent: 24px;
10
+
11
+ --stl-ui-page-padding-inline: 20px;
10
12
  }
11
13
  }
@@ -1,4 +1,4 @@
1
- @layer stl-ui {
1
+ @layer stl-ui.tokens {
2
2
  /* Scales - Default */
3
3
  :root {
4
4
  --stl-ui-gray-50: rgba(250, 250, 250, 1);
@@ -1,125 +1,156 @@
1
- @layer stl-ui {
2
- :root {
3
- --stl-ui-button-border-radius: 8px;
4
- --stl-ui-text-body: 14px;
5
- --stl-ui-font-family: 'Geist', system-ui, sans-serif;
6
- --stl-ui-button-size: 2rem;
7
-
8
- /* Brand colors */
9
- --stl-ui-color-text-brand: var(--stl-ui-color-text-invert);
10
- --stl-ui-color-brand: var(--stl-ui-color-accent);
11
- }
1
+ :root {
2
+ /* Layout and Typography */
3
+ --sl-font: var(--stl-ui-typography-font);
4
+ --sl-font-mono: var(--stl-ui-typography-font-mono);
5
+ --sl-line-height: var(--stl-ui-typography-line-height);
6
+
7
+ /* Headings */
8
+ --sl-text-h1: var(--stl-ui-typography-text-h1);
9
+ --sl-text-h2: var(--stl-ui-typography-text-h2);
10
+ --sl-text-h3: var(--stl-ui-typography-text-h3);
11
+ --sl-text-h4: var(--stl-ui-typography-text-h4);
12
+ --sl-text-h5: var(--stl-ui-typography-text-h5);
13
+
14
+ /* Colors */
15
+ --sl-color-bg: var(--stl-ui-background);
16
+ --sl-color-bg-sidebar: var(--stl-ui-background);
17
+ --sl-color-bg-ui: var(--stl-ui-card-background);
18
+ --sl-color-bg-nav: var(--stl-ui-background);
19
+ --sl-color-bg-inline-code: var(--stl-ui-muted-background);
20
+ --sl-color-bg-accent: var(--stl-ui-accent-background);
21
+
22
+ --sl-color-text: var(--stl-ui-foreground);
23
+ --sl-color-text-secondary: var(--stl-ui-foreground-secondary);
24
+ --sl-color-text-tertiary: var(--stl-ui-foreground-muted);
25
+ --sl-color-text-accent: var(--stl-ui-foreground-accent);
26
+ --sl-color-hairline: var(--stl-ui-border);
27
+ --sl-color-hairline-light: var(--stl-ui-border-muted);
28
+ --sl-color-hairline-shade: var(--stl-ui-border-emphasis);
29
+ --sl-color-text-invert: var(--stl-ui-inverse-foreground);
30
+
31
+ /* Primary colors */
32
+ --sl-color-red-low: var(--stl-ui-swatch-red-faint);
33
+ --sl-color-red: var(--stl-ui-swatch-red-base);
34
+ --sl-color-red-high: var(--stl-ui-swatch-red-base);
35
+
36
+ --sl-color-green-low: var(--stl-ui-swatch-green-faint);
37
+ --sl-color-green: var(--stl-ui-swatch-green-base);
38
+ --sl-color-green-high: var(--stl-ui-swatch-green-base);
39
+
40
+ --sl-color-blue-low: var(--stl-ui-swatch-blue-faint);
41
+ --sl-color-blue: var(--stl-ui-swatch-blue-base);
42
+ --sl-color-blue-high: var(--stl-ui-swatch-blue-base);
43
+
44
+ --sl-color-orange-low: var(--stl-ui-swatch-orange-faint);
45
+ --sl-color-orange: var(--stl-ui-swatch-orange-base);
46
+ --sl-color-orange-high: var(--stl-ui-swatch-orange-base);
47
+
48
+ --sl-color-purple-low: var(--stl-ui-swatch-purple-faint);
49
+ --sl-color-purple: var(--stl-ui-swatch-purple-base);
50
+ --sl-color-purple-high: var(--stl-ui-swatch-purple-base);
51
+
52
+ --sl-color-teal-low: var(--stl-ui-swatch-teal-faint);
53
+ --sl-color-teal: var(--stl-ui-swatch-teal-base);
54
+ --sl-color-teal-high: var(--stl-ui-swatch-teal-base);
55
+
56
+ --sl-color-magenta-low: var(--stl-ui-swatch-pink-faint);
57
+ --sl-color-magenta: var(--stl-ui-swatch-pink-base);
58
+ --sl-color-magenta-high: var(--stl-ui-swatch-pink-base);
59
+
60
+ --sl-color-yellow-low: var(--stl-ui-swatch-yellow-faint);
61
+ --sl-color-yellow: var(--stl-ui-swatch-yellow-base);
62
+ --sl-color-yellow-high: var(--stl-ui-swatch-yellow-base);
63
+ }
12
64
 
13
- :root {
14
- /* Layout and Typography */
15
- --sl-text-h1: var(--stl-ui-typography-text-h1);
16
- --sl-text-h2: var(--stl-ui-typography-text-h2);
17
- --sl-text-h3: var(--stl-ui-typography-text-h3);
18
- --sl-text-h4: var(--stl-ui-typography-text-h4);
19
- --sl-text-h4: var(--stl-ui-typography-text-h5);
20
-
21
- /* Colors */
22
- --sl-color-bg: var(--stl-ui-background);
23
- --sl-color-bg-sidebar: var(--stl-ui-background);
24
- --sl-color-bg-ui: var(--stl-ui-card-background);
25
- --sl-color-bg-nav: var(--stl-ui-background);
26
- --sl-color-bg-inline-code: var(--stl-ui-muted-background);
27
- --sl-color-bg-accent: var(--stl-ui-accent-background);
28
-
29
- --sl-color-text: var(--stl-ui-foreground);
30
- --sl-color-text-secondary: var(--stl-ui-foreground-secondary);
31
- --sl-color-text-tertiary: var(--stl-ui-foreground-muted);
32
- --sl-color-text-accent: var(--stl-ui-foreground-accent);
33
- --sl-color-hairline: var(--stl-ui-border);
34
- --sl-color-hairline-light: var(--stl-ui-border-muted);
35
- --sl-color-hairline-shade: var(--stl-ui-border-emphasis);
36
- --sl-color-text-invert: var(--stl-ui-inverse-foreground);
37
-
38
- /* Primary colors */
39
- --sl-color-red-low: var(--stl-ui-swatch-red-faint);
40
- --sl-color-red: var(--stl-ui-swatch-red-base);
41
- --sl-color-red-high: var(--stl-ui-swatch-red-base);
42
-
43
- --sl-color-green-low: var(--stl-ui-swatch-green-faint);
44
- --sl-color-green: var(--stl-ui-swatch-green-base);
45
- --sl-color-green-high: var(--stl-ui-swatch-green-base);
46
-
47
- --sl-color-blue-low: var(--stl-ui-swatch-blue-faint);
48
- --sl-color-blue: var(--stl-ui-swatch-blue-base);
49
- --sl-color-blue-high: var(--stl-ui-swatch-blue-base);
50
-
51
- --sl-color-orange-low: var(--stl-ui-swatch-orange-faint);
52
- --sl-color-orange: var(--stl-ui-swatch-orange-base);
53
- --sl-color-orange-high: var(--stl-ui-swatch-orange-base);
54
-
55
- --sl-color-purple-low: var(--stl-ui-swatch-purple-faint);
56
- --sl-color-purple: var(--stl-ui-swatch-purple-base);
57
- --sl-color-purple-high: var(--stl-ui-swatch-purple-base);
58
-
59
- --sl-color-teal-low: var(--stl-ui-swatch-teal-faint);
60
- --sl-color-teal: var(--stl-ui-swatch-teal-base);
61
- --sl-color-teal-high: var(--stl-ui-swatch-teal-base);
62
-
63
- --sl-color-magenta-low: var(--stl-ui-swatch-pink-faint);
64
- --sl-color-magenta: var(--stl-ui-swatch-pink-base);
65
- --sl-color-magenta-high: var(--stl-ui-swatch-pink-base);
66
-
67
- --sl-color-yellow-low: var(--stl-ui-swatch-yellow-faint);
68
- --sl-color-yellow: var(--stl-ui-swatch-yellow-base);
69
- --sl-color-yellow-high: var(--stl-ui-swatch-yellow-base);
65
+ /* Starlight Compatibility */
66
+ .stl-ui-prose {
67
+ /* TODO: Disable starlight headingLinks and replace with our own */
68
+ /* Duplicate styles from h1–5 in typography; TODO: move to mixins after adopting preprocessor */
69
+ .sl-heading-wrapper.level-h1 {
70
+ font-size: var(--stl-ui-typography-text-h1);
71
+ letter-spacing: -0.03em;
72
+ margin-top: 64px;
73
+ line-height: var(--stl-ui-typography-line-height-headings);
74
+ }
75
+ .sl-heading-wrapper.level-h2 {
76
+ font-size: var(--stl-ui-typography-text-h2);
77
+ letter-spacing: -0.03em;
78
+ margin-top: 48px;
79
+ line-height: var(--stl-ui-typography-line-height-headings);
70
80
  }
71
81
 
72
- /* Starlight Compatibility */
73
- .stl-ui-prose {
74
- .sl-anchor-link svg {
75
- margin-top: 4px;
76
- }
77
-
78
- h2 code {
79
- font-size: var(--stl-ui-typography-text-h4);
80
- line-height: 120%;
81
- }
82
-
83
- h3 code {
84
- font-size: var(--stl-ui-typography-text-h5);
85
- line-height: 120%;
86
- }
87
-
88
- /* Tab component */
89
- starlight-tabs {
90
- a {
91
- text-decoration: none;
92
- line-height: unset;
93
- color: var(--stl-ui-foreground);
82
+ .sl-heading-wrapper.level-h3 {
83
+ font-size: var(--stl-ui-typography-text-h3);
84
+ letter-spacing: -0.02em;
85
+ margin-top: 40px;
86
+ line-height: var(--stl-ui-typography-line-height-headings);
87
+ }
88
+ .sl-heading-wrapper.level-h4 {
89
+ font-size: var(--stl-ui-typography-text-h4);
90
+ letter-spacing: -0.02em;
91
+ margin-top: 32px;
92
+ line-height: var(--stl-ui-typography-line-height-headings);
93
+ }
94
+ .sl-heading-wrapper.level-h5 {
95
+ font-size: var(--stl-ui-typography-text-h5);
96
+ letter-spacing: -0.02em;
97
+ margin-top: 24px;
98
+ line-height: var(--stl-ui-typography-line-height-headings);
99
+ }
100
+ .sl-heading-wrapper {
101
+ --sl-anchor-icon-size: 0.8em;
102
+ }
103
+ /* TODO: replace with an icon that matches Stainless branding */
104
+ .sl-anchor-link svg {
105
+ margin-top: 0;
106
+ }
107
+ }
94
108
 
95
- &[aria-selected='true'] {
96
- color: var(--stl-ui-foreground-accent);
97
- }
109
+ /* TODO: remove these */
110
+ .stl-ui-prose {
111
+ /* Tab component */
112
+ starlight-tabs {
113
+ a {
114
+ text-decoration: none;
115
+ line-height: unset;
116
+ color: var(--stl-ui-foreground);
117
+
118
+ &[aria-selected='true'] {
119
+ color: var(--stl-ui-foreground-accent);
120
+ font-weight: normal;
98
121
  }
122
+ }
99
123
 
100
- ol,
101
- ul {
124
+ ol,
125
+ ul {
126
+ &:not(.stl-ui-not-prose *) {
102
127
  padding-left: 0;
103
128
  }
129
+ }
104
130
 
105
- li {
131
+ li:not(.stl-ui-not-prose *) {
132
+ margin-bottom: -2px;
133
+ &:not(:last-child) {
106
134
  margin-bottom: -2px;
107
- &:not(:last-child) {
108
- margin-bottom: -2px;
109
- }
135
+ }
110
136
 
111
- a:first-child {
112
- display: flex;
113
- }
137
+ a:first-child {
138
+ display: flex;
114
139
  }
115
140
  }
141
+ }
116
142
 
117
- /* Pagination Links */
143
+ /* Pagination Links */
118
144
 
119
- .pagination-links {
120
- a {
121
- color: var(--stl-ui-foreground);
122
- }
145
+ .pagination-links {
146
+ a {
147
+ color: var(--stl-ui-foreground);
148
+ }
149
+ }
150
+
151
+ .starlight-aside {
152
+ svg {
153
+ margin-top: 0;
123
154
  }
124
155
  }
125
156
  }
@@ -1,6 +1,6 @@
1
- @layer stl-ui {
1
+ @layer stl-ui.tokens {
2
2
  /* --stl-ui-swatch-es - Light */
3
- :root[data-theme='light'] {
3
+ :root {
4
4
  --stl-ui-swatch-gray-gray-1: var(--stl-ui-gray-800);
5
5
  --stl-ui-swatch-gray-gray-2: var(--stl-ui-gray-700);
6
6
  --stl-ui-swatch-gray-gray-3: var(--stl-ui-gray-600);
@@ -1,6 +1,6 @@
1
- @layer stl-ui {
1
+ @layer stl-ui.tokens {
2
2
  /* Swatches - Light */
3
- :root[data-theme='light'] {
3
+ :root {
4
4
  --stl-ui-background: var(--stl-ui-swatch-gray-white);
5
5
  --stl-ui-card-background: var(--stl-ui-swatch-gray-white);
6
6
  --stl-ui-muted-background: var(--stl-ui-swatch-gray-gray-8);