@stainless-api/ui-primitives 0.1.0-beta.7 → 0.1.0-beta.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @stainless-api/ui-primitives
2
2
 
3
+ ## 0.1.0-beta.8
4
+
5
+ ### Patch Changes
6
+
7
+ - d15a520: fix initialization of dropdown buttons
8
+
3
9
  ## 0.1.0-beta.7
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/ui-primitives",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.0-beta.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -10,6 +10,11 @@
10
10
  "import": "./src/index.ts",
11
11
  "require": "./src/index.ts"
12
12
  },
13
+ "./scripts": {
14
+ "types": "./src/scripts/index.ts",
15
+ "import": "./src/scripts/index.ts",
16
+ "require": "./src/scripts/index.ts"
17
+ },
13
18
  "./styles.css": "./src/styles.css"
14
19
  },
15
20
  "types": "./src/index.ts",
@@ -0,0 +1,109 @@
1
+ import clsx from 'clsx';
2
+ import { ChevronsUpDown, ExternalLink } from 'lucide-react';
3
+
4
+ function PrimaryActionText({ children }: { children?: React.ReactNode }) {
5
+ return <span data-part="primary-action-text">{children}</span>;
6
+ }
7
+
8
+ function PrimaryAction({ children }: { children?: React.ReactNode }) {
9
+ return (
10
+ <button
11
+ type="button"
12
+ data-part="primary-action"
13
+ className="stl-ui-dropdown-button__button stl-ui-dropdown-button--action"
14
+ >
15
+ {children}
16
+ </button>
17
+ );
18
+ }
19
+
20
+ function Trigger() {
21
+ return (
22
+ <button
23
+ className="stl-ui-dropdown-button__button stl-ui-dropdown-button__trigger"
24
+ aria-haspopup="listbox"
25
+ aria-expanded="false"
26
+ data-part="trigger"
27
+ >
28
+ <ChevronsUpDown size={16} />
29
+ </button>
30
+ );
31
+ }
32
+
33
+ function Menu({ children }: { children?: React.ReactNode }) {
34
+ return (
35
+ <div className="stl-ui-dropdown-button__menu" data-state="closed" data-part="menu">
36
+ {children}
37
+ </div>
38
+ );
39
+ }
40
+
41
+ function MenuItemIcon({ children }: { children?: React.ReactNode }) {
42
+ return (
43
+ <div className="stl-ui-dropdown-button__menu-item-icon" data-part="item-icon">
44
+ {children}
45
+ </div>
46
+ );
47
+ }
48
+
49
+ function MenuItemText({ children, subtle }: { children?: React.ReactNode; subtle?: boolean }) {
50
+ return (
51
+ <span
52
+ className={clsx(`stl-ui-dropdown-button__menu-item-text`, {
53
+ 'stl-ui-dropdown-button__menu-item-text--subtle': subtle,
54
+ })}
55
+ data-part="item-text"
56
+ >
57
+ {children}
58
+ </span>
59
+ );
60
+ }
61
+
62
+ function MenuItemTextSubtle({ children }: { children?: React.ReactNode }) {
63
+ return (
64
+ <span className="stl-ui-dropdown-button__menu-item-text-subtle" data-part="item-text-subtle">
65
+ {children}
66
+ </span>
67
+ );
68
+ }
69
+
70
+ function MenuItem({
71
+ children,
72
+ value,
73
+ isExternalLink,
74
+ }: {
75
+ children?: React.ReactNode;
76
+ value: string;
77
+ isExternalLink?: boolean;
78
+ }) {
79
+ return (
80
+ <div className="stl-ui-dropdown-button__menu-item" data-part="item" data-value={value}>
81
+ <div className="stl-ui-dropdown-button__menu-item-content">{children}</div>
82
+ {isExternalLink && (
83
+ <div
84
+ className="stl-ui-dropdown-button__menu-item-external-link-icon"
85
+ data-part="item-external-link-icon"
86
+ >
87
+ <ExternalLink size={16} />
88
+ </div>
89
+ )}
90
+ </div>
91
+ );
92
+ }
93
+
94
+ export function DropdownButton({ id, children }: { id: string; children?: React.ReactNode }) {
95
+ return (
96
+ <div className="stl-ui-dropdown-button stl-ui-not-prose not-content" id={id}>
97
+ {children}
98
+ </div>
99
+ );
100
+ }
101
+
102
+ DropdownButton.Menu = Menu;
103
+ DropdownButton.MenuItem = MenuItem;
104
+ DropdownButton.MenuItemIcon = MenuItemIcon;
105
+ DropdownButton.MenuItemText = MenuItemText;
106
+ DropdownButton.MenuItemTextSubtle = MenuItemTextSubtle;
107
+ DropdownButton.PrimaryAction = PrimaryAction;
108
+ DropdownButton.PrimaryActionText = PrimaryActionText;
109
+ DropdownButton.Trigger = Trigger;
@@ -0,0 +1,177 @@
1
+ /* revert docs-ui resets */
2
+ @layer docs-ui {
3
+ .stldocs-root .stl-ui-dropdown-button {
4
+ all: revert-layer;
5
+
6
+ * {
7
+ all: revert-layer;
8
+ }
9
+ }
10
+ }
11
+
12
+ @layer stl-ui.components {
13
+ .stl-ui-dropdown-button {
14
+ --stl-ui-dropdown-button-color: var(--stl-ui-foreground);
15
+ --stl-ui-dropdown-button-background-color: var(--stl-ui-card-background);
16
+ --stl-ui-dropdown-button-border-color: var(--stl-ui-border);
17
+ --stl-ui-dropdown-button-border-radius: var(--stl-ui-layout-border-radius-sml);
18
+ --stl-ui-dropdown-button-font-size: var(--stl-ui-type-scale-text-sm);
19
+
20
+ --stl-ui-dropdown-button-height: 32px;
21
+ --stl-ui-dropdown-button-padding: 8px 10px;
22
+ --stl-ui-dropdown-button-line-height: 100%;
23
+ --stl-ui-dropdown-button-font-weight: 500;
24
+
25
+ position: relative;
26
+ display: inline-flex;
27
+ align-items: center;
28
+
29
+ background-color: var(--stl-ui-dropdown-button-background-color);
30
+ border: 1px solid var(--stl-ui-dropdown-button-border-color);
31
+ border-radius: var(--stl-ui-dropdown-button-border-radius);
32
+ color: var(--stl-ui-dropdown-button-color);
33
+ gap: 0;
34
+ font-size: var(--stl-ui-dropdown-button-font-size);
35
+
36
+ .stl-ui-dropdown-button__button {
37
+ border: none;
38
+ background: none;
39
+ height: var(--stl-ui-dropdown-button-height);
40
+ padding: var(--stl-ui-dropdown-button-padding);
41
+ line-height: var(--stl-ui-dropdown-button-line-height);
42
+ font-weight: var(--stl-ui-dropdown-button-font-weight);
43
+ cursor: pointer;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+
48
+ &:hover {
49
+ background-color: oklch(from var(--stl-ui-foreground) l c h / 0.05);
50
+ border-color: var(--stl-ui-border-emphasis);
51
+ }
52
+ }
53
+
54
+ .stl-ui-dropdown-button--action {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 8px;
58
+
59
+ &:hover {
60
+ background-color: oklch(from var(--stl-ui-foreground) l c h / 0.05);
61
+ border-color: var(--stl-ui-border-emphasis);
62
+ }
63
+
64
+ &.disabled {
65
+ cursor: not-allowed;
66
+ background-color: oklch(from var(--stl-ui-foreground) l c h / 0.05);
67
+ }
68
+ }
69
+
70
+ .stl-ui-dropdown-button__trigger {
71
+ border-left: 1px solid var(--stl-ui-border);
72
+ border-radius: 0;
73
+
74
+ &:hover {
75
+ background-color: oklch(from var(--stl-ui-foreground) l c h / 0.05);
76
+ border-color: var(--stl-ui-border-emphasis);
77
+ }
78
+ }
79
+
80
+ .stl-ui-dropdown-button__menu {
81
+ --stl-ui-dropdown-button__menu-background-color: var(--stl-ui-card-background);
82
+ --stl-ui-dropdown-button__menu-border-color: var(--stl-ui-border);
83
+ --stl-ui-dropdown-button__menu-box-shadow: var(--stl-ui-shadow-md);
84
+ --stl-ui-dropdown-button__menu-border-radius: var(--stl-ui-layout-border-radius-sml);
85
+
86
+ background-color: var(--stl-ui-dropdown-button__menu-background-color);
87
+ border: 1px solid var(--stl-ui-dropdown-button__menu-border-color);
88
+ box-shadow: var(--stl-ui-dropdown-button__menu-box-shadow);
89
+ border-radius: var(--stl-ui-dropdown-button__menu-border-radius);
90
+
91
+ position: absolute;
92
+ top: 100%;
93
+ right: 0;
94
+ margin-top: 4px;
95
+ z-index: 1000;
96
+ min-width: 100%;
97
+ padding: 4px;
98
+ display: none;
99
+
100
+ &[data-state='open'] {
101
+ display: block;
102
+ }
103
+ }
104
+
105
+ .stl-ui-dropdown-button__menu-item {
106
+ --stl-ui-dropdown-button__menu-item-border-radius: var(--stl-ui-dropdown-button-border-radius);
107
+ --stl-ui-dropdown-button__menu-item-height: var(--stl-ui-dropdown-button-height);
108
+ --stl-ui-dropdown-button__menu-item-line-height: var(--stl-ui-dropdown-button-line-height);
109
+ --stl-ui-dropdown-button__menu-item-hover-background-color: oklch(
110
+ from var(--stl-ui-foreground) l c h / 0.05
111
+ );
112
+
113
+ border-radius: var(--stl-ui-dropdown-button__menu-item-border-radius);
114
+ height: var(--stl-ui-dropdown-button__menu-item-height);
115
+ line-height: var(--stl-ui-dropdown-button__menu-item-line-height);
116
+
117
+ padding: 8px;
118
+ cursor: pointer;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: space-between;
122
+ gap: 16px;
123
+
124
+ &:hover {
125
+ background-color: var(--stl-ui-dropdown-button__menu-item-hover-background-color);
126
+ }
127
+
128
+ .stl-ui-dropdown-button__menu-item-content {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ }
133
+
134
+ .stl-ui-dropdown-button__menu-item-text {
135
+ white-space: nowrap;
136
+ }
137
+
138
+ .stl-ui-dropdown-button__menu-item-text--subtle {
139
+ color: var(--stl-ui-foreground-muted);
140
+ }
141
+
142
+ strong {
143
+ color: var(--stl-ui-foreground);
144
+ font-weight: 500;
145
+ }
146
+
147
+ .stl-ui-dropdown-button__menu-item-icon {
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ }
152
+
153
+ .stl-ui-dropdown-button__menu-item-external-link-icon {
154
+ --stl-ui-dropdown-button__menu-item-external-link-icon-color: oklch(
155
+ from var(--stl-ui-foreground) l c h / 0.25
156
+ );
157
+
158
+ svg {
159
+ color: var(--stl-ui-dropdown-button__menu-item-external-link-icon-color);
160
+ }
161
+ }
162
+ }
163
+
164
+ hr {
165
+ --stl-ui-dropdown-button__divider-height: 1px;
166
+ --stl-ui-dropdown-button__divider-color: var(--stl-ui-border);
167
+
168
+ height: var(--stl-ui-dropdown-button__divider-height);
169
+ background-color: var(--stl-ui-dropdown-button__divider-color);
170
+ border: none;
171
+
172
+ margin: 4px 0;
173
+ width: calc(100% + 8px);
174
+ transform: translateX(-4px);
175
+ }
176
+ }
177
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './components/Button';
2
+ export * from './components/DropdownButton';
2
3
  export * from './components/Callout';
3
4
  export * from './components/DetailsGroup';
@@ -0,0 +1,55 @@
1
+ export function initDropdownButton({
2
+ dropdownId,
3
+ onSelect,
4
+ onPrimaryAction,
5
+ }: {
6
+ dropdownId: string;
7
+ onSelect: (value: string) => void;
8
+ onPrimaryAction: (primaryActionElement: Element) => void;
9
+ }) {
10
+ const dropdown = document.getElementById(dropdownId);
11
+ if (!dropdown) return;
12
+
13
+ const trigger = dropdown.querySelector('[data-part="trigger"]') as HTMLButtonElement | null;
14
+ const menu = dropdown.querySelector('[data-part="menu"]') as HTMLElement | null;
15
+ const primaryAction = dropdown.querySelector('[data-part="primary-action"]');
16
+
17
+ if (!trigger || !menu || !primaryAction) return;
18
+
19
+ let isOpen = false;
20
+
21
+ function toggleDropdown() {
22
+ if (!trigger || !menu) return;
23
+ isOpen = !isOpen;
24
+ menu.dataset.state = isOpen ? 'open' : 'closed';
25
+ trigger.setAttribute('aria-expanded', String(isOpen));
26
+ }
27
+
28
+ trigger.addEventListener('click', toggleDropdown);
29
+
30
+ document.addEventListener('click', (event) => {
31
+ if (!isOpen) return;
32
+ if (!dropdown.contains(event.target as Node)) {
33
+ toggleDropdown();
34
+ }
35
+
36
+ if (primaryAction && primaryAction.contains(event.target as Node)) {
37
+ toggleDropdown();
38
+ }
39
+ });
40
+
41
+ const items = dropdown.querySelectorAll('[data-part="item"]');
42
+ items.forEach((item) => {
43
+ item.addEventListener('click', () => {
44
+ const value = item.getAttribute('data-value');
45
+ if (value) {
46
+ onSelect(value);
47
+ }
48
+ toggleDropdown();
49
+ });
50
+ });
51
+
52
+ primaryAction.addEventListener('click', () => {
53
+ onPrimaryAction(primaryAction);
54
+ });
55
+ }
@@ -0,0 +1 @@
1
+ export * from './dropdown-button';
package/src/styles.css CHANGED
@@ -6,5 +6,6 @@
6
6
  @import './styles/starlight-compat.css';
7
7
 
8
8
  @import './components/button.css';
9
+ @import './components/dropdown-button.css';
9
10
  @import './components/callout.css';
10
11
  @import './components/details.css';