@trypattern/sdk 0.1.10 → 0.1.12

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/dist/api.js CHANGED
@@ -3,6 +3,7 @@ import { HttpSessionApi, MemorySessionApi, sessionCalls } from "./auth/sessionAp
3
3
  import { HttpAppInfoApi, MemoryAppInfoApi, appInfoCalls } from "./appInfo.js";
4
4
  import { httpServices, memoryServices } from "./serviceClients.js";
5
5
  import { serviceCallNames } from "./services.js";
6
+ import { ownerIdOf } from "./table.js";
6
7
  import { httpTables, memoryTables, tableCalls } from "./tables.js";
7
8
  export function apiCalls(tables, services = {}) {
8
9
  return [
@@ -47,7 +48,12 @@ export function browserApi(tables, seed, gate, { inMemory, admins, ...setup }) {
47
48
  const options = { latencyMs: 0, gate };
48
49
  return {
49
50
  tables: inMemory
50
- ? memoryTables(tables, seed, () => viewerByEmail(session.rememberedUser(), emails), { latencyMs: 0, gate })
51
+ ? memoryTables(tables, seed, () => {
52
+ const user = session.rememberedUser();
53
+ return viewerByEmail(user === undefined
54
+ ? undefined
55
+ : { ...user, id: ownerIdOf(user.email) }, emails);
56
+ }, { latencyMs: 0, gate })
51
57
  : httpTables(tables, gate, () => session.reportSignedOut()),
52
58
  session,
53
59
  app: new HttpAppInfoApi(gate),
@@ -1,24 +1,2 @@
1
- import type { AppPath } from '../registered.ts';
2
- import type { ReactNode } from 'react';
3
- import type { IconName } from './icons.ts';
4
- import type { Placement } from './Placement.ts';
5
- import type { Size } from './Size.ts';
6
- type ButtonProps = {
7
- children: ReactNode;
8
- variant?: 'primary' | 'secondary' | 'tertiary' | 'transparent';
9
- tone?: 'neutral' | 'brand' | 'danger';
10
- size?: Size;
11
- to?: AppPath;
12
- href?: string;
13
- icon?: IconName;
14
- trailingIcon?: IconName;
15
- shortcut?: readonly string[];
16
- type?: 'button' | 'submit';
17
- disabled?: boolean;
18
- isPending?: boolean;
19
- isFullWidth?: boolean;
20
- onClick?: () => void;
21
- style?: Placement;
22
- };
23
- export declare function Button({ children, variant, tone, size, to, href, icon, trailingIcon, shortcut, type, disabled, isPending, isFullWidth, onClick, style }: ButtonProps): import("react").JSX.Element;
24
- export {};
1
+ import type { ButtonProps } from './ButtonBody.tsx';
2
+ export declare function Button(props: ButtonProps): import("react").JSX.Element;
@@ -1,252 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button as BaseButton } from '@base-ui/react/button';
3
- import { Link as RouterLink } from 'react-router';
4
- import * as stylex from '@stylexjs/stylex';
5
- import { colors, controls, fontWeights, motion, shadows } from "../theme/tokens.stylex.js";
6
- import { controlSizes, focusRing } from "./controlLook.js";
7
- import { Icon } from "./Icon.js";
8
- import { interact, textOf } from "./interaction.js";
9
- import { Kbd } from "./Kbd.js";
10
- import { Spinner } from "./Spinner.js";
11
- export function Button({ children, variant = 'primary', tone = variant === 'primary' ? 'brand' : 'neutral', size = 'md', to, href, icon, trailingIcon, shortcut, type = 'button', disabled = false, isPending = false, isFullWidth = false, onClick, style }) {
12
- const isDisabled = disabled || isPending;
13
- const report = () => {
14
- interact({ primitive: 'Button', event: 'click', label: textOf(children) });
15
- onClick?.();
16
- };
17
- const className = stylex.props(styles.button, controlSizes[size], icon !== undefined && leadingIconSizes[size], trailingIcon !== undefined && trailingIconSizes[size], looks[`${variant}${toneKeys[tone]}`], !isDisabled && hovers[`${variant}${toneKeys[tone]}`], isDisabled && styles.disabled, isDisabled && variant === 'transparent' && styles.disabledTransparent, isFullWidth && styles.fullWidth, focusRing.visible, style).className;
18
- const content = (_jsxs("span", { ...stylex.props(styles.stack), children: [_jsxs("span", { ...stylex.props(styles.content, isPending && styles.hidden), children: [icon === undefined ? null : _jsx(Icon, { name: icon, size: size }), _jsx("span", { ...stylex.props(styles.label, shortcut !== undefined && styles.labelBeforeShortcut), children: children }), trailingIcon === undefined ? null : (_jsx(Icon, { name: trailingIcon, size: size })), shortcut === undefined ? null : _jsx(Kbd, { keys: shortcut })] }), isPending ? (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.pending), children: _jsx(Spinner, { size: size }) })) : null] }));
19
- if (href !== undefined) {
20
- return (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", onClick: report, className: className, children: content }));
21
- }
22
- if (to !== undefined) {
23
- return (_jsx(RouterLink, { to: to, onClick: report, className: className, children: content }));
24
- }
25
- return (_jsx(BaseButton, { type: type, disabled: isDisabled, focusableWhenDisabled: true, "aria-busy": isPending || undefined, onClick: report, className: className, children: content }));
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ButtonBody } from "./ButtonBody.js";
3
+ export function Button(props) {
4
+ return _jsx(ButtonBody, { ...props });
26
5
  }
27
- const toneKeys = {
28
- neutral: 'Neutral',
29
- brand: 'Brand',
30
- danger: 'Danger'
31
- };
32
- const styles = stylex.create({
33
- button: {
34
- position: 'relative',
35
- display: 'flex',
36
- flexShrink: 0,
37
- alignItems: 'center',
38
- justifyContent: 'center',
39
- minWidth: 0,
40
- fontWeight: fontWeights.medium,
41
- whiteSpace: 'nowrap',
42
- textDecoration: 'none',
43
- touchAction: 'manipulation',
44
- outlineOffset: 0,
45
- transitionTimingFunction: motion.easeOut,
46
- transitionDuration: motion.fast,
47
- transitionProperty: 'color, background-color, transform'
48
- },
49
- fullWidth: {
50
- justifyContent: 'flex-start',
51
- width: '100%'
52
- },
53
- stack: {
54
- display: 'grid',
55
- flexGrow: 1,
56
- gridTemplateColumns: 'minmax(0, 1fr)',
57
- gap: 'inherit',
58
- justifyContent: 'inherit',
59
- minWidth: 0,
60
- overflow: 'clip'
61
- },
62
- content: {
63
- display: 'flex',
64
- gridRowStart: 1,
65
- gridColumnStart: 1,
66
- gap: 'inherit',
67
- alignItems: 'center',
68
- justifyContent: 'inherit',
69
- minWidth: 0,
70
- transitionTimingFunction: motion.easeOut,
71
- transitionDuration: motion.base,
72
- transitionProperty: 'opacity, transform'
73
- },
74
- hidden: {
75
- opacity: 0,
76
- transform: 'translateY(24px)'
77
- },
78
- pending: {
79
- display: 'flex',
80
- gridRowStart: 1,
81
- gridColumnStart: 1,
82
- alignItems: 'center',
83
- justifyContent: 'center',
84
- pointerEvents: 'none'
85
- },
86
- label: {
87
- overflow: 'hidden',
88
- textOverflow: 'ellipsis'
89
- },
90
- labelBeforeShortcut: {
91
- flexGrow: 1,
92
- textAlign: 'start'
93
- },
94
- disabled: {
95
- color: colors.disabledForeground,
96
- backgroundColor: colors.disabled,
97
- boxShadow: 'none'
98
- },
99
- disabledTransparent: {
100
- backgroundColor: 'transparent'
101
- }
102
- });
103
- const leadingIconSizes = stylex.create({
104
- xs: { paddingInlineStart: `calc(${controls.paddingXs} - 2px)` },
105
- sm: { paddingInlineStart: `calc(${controls.paddingSm} - 2px)` },
106
- md: { paddingInlineStart: `calc(${controls.paddingMd} - 4px)` },
107
- lg: { paddingInlineStart: `calc(${controls.paddingLg} - 2px)` },
108
- xl: { paddingInlineStart: `calc(${controls.paddingXl} - 4px)` }
109
- });
110
- const trailingIconSizes = stylex.create({
111
- xs: { paddingInlineEnd: `calc(${controls.paddingXs} - 2px)` },
112
- sm: { paddingInlineEnd: `calc(${controls.paddingSm} - 2px)` },
113
- md: { paddingInlineEnd: `calc(${controls.paddingMd} - 4px)` },
114
- lg: { paddingInlineEnd: `calc(${controls.paddingLg} - 2px)` },
115
- xl: { paddingInlineEnd: `calc(${controls.paddingXl} - 4px)` }
116
- });
117
- const looks = stylex.create({
118
- primaryNeutral: {
119
- color: colors.onInvertedForeground,
120
- backgroundColor: colors.invertedSolid
121
- },
122
- primaryBrand: {
123
- color: colors.onBrandSolidForeground,
124
- backgroundColor: colors.brandSolid
125
- },
126
- primaryDanger: {
127
- color: colors.onDangerSolidForeground,
128
- backgroundColor: colors.dangerSolid
129
- },
130
- secondaryNeutral: {
131
- color: colors.primaryForeground,
132
- backgroundColor: colors.componentOnContainerStrong,
133
- boxShadow: shadows.default
134
- },
135
- secondaryBrand: {
136
- color: colors.brandForeground,
137
- backgroundColor: colors.componentOnContainerStrong,
138
- boxShadow: shadows.default
139
- },
140
- secondaryDanger: {
141
- color: colors.dangerForeground,
142
- backgroundColor: colors.componentOnContainerStrong,
143
- boxShadow: shadows.default
144
- },
145
- tertiaryNeutral: {
146
- color: colors.secondaryForeground,
147
- backgroundColor: colors.component
148
- },
149
- tertiaryBrand: {
150
- color: colors.onBrandSubtleForeground,
151
- backgroundColor: colors.brandSubtle
152
- },
153
- tertiaryDanger: {
154
- color: colors.dangerForeground,
155
- backgroundColor: colors.dangerSubtle
156
- },
157
- transparentNeutral: {
158
- color: colors.secondaryForeground,
159
- backgroundColor: 'transparent'
160
- },
161
- transparentBrand: {
162
- color: colors.brandForeground,
163
- backgroundColor: 'transparent'
164
- },
165
- transparentDanger: {
166
- color: colors.dangerForeground,
167
- backgroundColor: 'transparent'
168
- }
169
- });
170
- const hovers = stylex.create({
171
- primaryNeutral: {
172
- backgroundColor: {
173
- default: colors.invertedSolid,
174
- ':hover': colors.invertedSolidHover,
175
- ':active': colors.invertedSolidActive
176
- }
177
- },
178
- primaryBrand: {
179
- backgroundColor: {
180
- default: colors.brandSolid,
181
- ':hover': colors.brandSolidHover,
182
- ':active': colors.brandSolidActive
183
- }
184
- },
185
- primaryDanger: {
186
- backgroundColor: {
187
- default: colors.dangerSolid,
188
- ':hover': colors.dangerSolidHover,
189
- ':active': colors.dangerSolidActive
190
- }
191
- },
192
- secondaryNeutral: {
193
- backgroundColor: {
194
- default: colors.componentOnContainerStrong,
195
- ':hover': colors.componentOnContainerStrongHover
196
- }
197
- },
198
- secondaryBrand: {
199
- backgroundColor: {
200
- default: colors.componentOnContainerStrong,
201
- ':hover': colors.componentOnContainerStrongHover
202
- }
203
- },
204
- secondaryDanger: {
205
- backgroundColor: {
206
- default: colors.componentOnContainerStrong,
207
- ':hover': colors.componentOnContainerStrongHover
208
- }
209
- },
210
- tertiaryNeutral: {
211
- backgroundColor: {
212
- default: colors.component,
213
- ':hover': colors.componentHover,
214
- ':active': colors.componentActive
215
- }
216
- },
217
- tertiaryBrand: {
218
- backgroundColor: {
219
- default: colors.brandSubtle,
220
- ':hover': colors.brandSubtleHover
221
- }
222
- },
223
- tertiaryDanger: {
224
- backgroundColor: {
225
- default: colors.dangerSubtle,
226
- ':hover': colors.dangerSubtleHover,
227
- ':active': colors.dangerSubtleActive
228
- }
229
- },
230
- transparentNeutral: {
231
- color: {
232
- default: colors.secondaryForeground,
233
- ':hover': colors.primaryForeground
234
- },
235
- backgroundColor: {
236
- default: 'transparent',
237
- ':hover': colors.transparentHover
238
- }
239
- },
240
- transparentBrand: {
241
- backgroundColor: {
242
- default: 'transparent',
243
- ':hover': colors.transparentHover
244
- }
245
- },
246
- transparentDanger: {
247
- backgroundColor: {
248
- default: 'transparent',
249
- ':hover': colors.dangerSubtleHover
250
- }
251
- }
252
- });
@@ -0,0 +1,25 @@
1
+ import type { AppPath } from '../registered.ts';
2
+ import type { ReactNode } from 'react';
3
+ import type { IconName } from './icons.ts';
4
+ import type { Placement } from './Placement.ts';
5
+ import type { Size } from './Size.ts';
6
+ export type ButtonProps = {
7
+ children: ReactNode;
8
+ variant?: 'primary' | 'secondary' | 'tertiary' | 'transparent';
9
+ tone?: 'neutral' | 'brand' | 'danger';
10
+ size?: Size;
11
+ to?: AppPath;
12
+ href?: string;
13
+ icon?: IconName;
14
+ trailingIcon?: IconName;
15
+ shortcut?: readonly string[];
16
+ type?: 'button' | 'submit';
17
+ disabled?: boolean;
18
+ isPending?: boolean;
19
+ isFullWidth?: boolean;
20
+ onClick?: () => void;
21
+ style?: Placement;
22
+ };
23
+ export declare function ButtonBody({ children, variant, tone, size, to, href, icon, trailingIcon, shortcut, type, disabled, isPending, isFullWidth, onClick, style, isIconPinned }: ButtonProps & {
24
+ isIconPinned?: boolean;
25
+ }): import("react").JSX.Element;
@@ -0,0 +1,266 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button as BaseButton } from '@base-ui/react/button';
3
+ import { Link as RouterLink } from 'react-router';
4
+ import * as stylex from '@stylexjs/stylex';
5
+ import { colors, controls, fontWeights, iconSizes, motion, shadows } from "../theme/tokens.stylex.js";
6
+ import { controlSizes, focusRing } from "./controlLook.js";
7
+ import { Icon } from "./Icon.js";
8
+ import { interact, textOf } from "./interaction.js";
9
+ import { Kbd } from "./Kbd.js";
10
+ import { Spinner } from "./Spinner.js";
11
+ export function ButtonBody({ children, variant = 'primary', tone = variant === 'primary' ? 'brand' : 'neutral', size = 'md', to, href, icon, trailingIcon, shortcut, type = 'button', disabled = false, isPending = false, isFullWidth = false, onClick, style, isIconPinned = false }) {
12
+ const isDisabled = disabled || isPending;
13
+ const report = () => {
14
+ interact({ primitive: 'Button', event: 'click', label: textOf(children) });
15
+ onClick?.();
16
+ };
17
+ const className = stylex.props(styles.button, controlSizes[size], icon !== undefined && !isIconPinned && leadingIconSizes[size], trailingIcon !== undefined && trailingIconSizes[size], looks[`${variant}${toneKeys[tone]}`], !isDisabled && hovers[`${variant}${toneKeys[tone]}`], isDisabled && styles.disabled, isDisabled && variant === 'transparent' && styles.disabledTransparent, isFullWidth && styles.fullWidth, focusRing.visible, style).className;
18
+ const content = (_jsxs("span", { ...stylex.props(styles.stack), children: [_jsxs("span", { ...stylex.props(styles.content, isIconPinned && styles.pinnedContent, isPending && styles.hidden), children: [icon === undefined ? null : _jsx(Icon, { name: icon, size: size }), _jsx("span", { ...stylex.props(styles.label, shortcut !== undefined && styles.labelBeforeShortcut, isIconPinned && styles.labelBetweenPins), children: children }), isIconPinned ? (_jsx("span", { "aria-hidden": "true", ...stylex.props(pinBalances[size]) })) : null, trailingIcon === undefined ? null : (_jsx(Icon, { name: trailingIcon, size: size })), shortcut === undefined ? null : _jsx(Kbd, { keys: shortcut })] }), isPending ? (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.pending), children: _jsx(Spinner, { size: size }) })) : null] }));
19
+ if (href !== undefined) {
20
+ return (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", onClick: report, className: className, children: content }));
21
+ }
22
+ if (to !== undefined) {
23
+ return (_jsx(RouterLink, { to: to, onClick: report, className: className, children: content }));
24
+ }
25
+ return (_jsx(BaseButton, { type: type, disabled: isDisabled, focusableWhenDisabled: true, "aria-busy": isPending || undefined, onClick: report, className: className, children: content }));
26
+ }
27
+ const toneKeys = {
28
+ neutral: 'Neutral',
29
+ brand: 'Brand',
30
+ danger: 'Danger'
31
+ };
32
+ const styles = stylex.create({
33
+ button: {
34
+ position: 'relative',
35
+ display: 'flex',
36
+ flexShrink: 0,
37
+ alignItems: 'center',
38
+ justifyContent: 'center',
39
+ minWidth: 0,
40
+ fontWeight: fontWeights.medium,
41
+ whiteSpace: 'nowrap',
42
+ textDecoration: 'none',
43
+ touchAction: 'manipulation',
44
+ outlineOffset: 0,
45
+ transitionTimingFunction: motion.easeOut,
46
+ transitionDuration: motion.fast,
47
+ transitionProperty: 'color, background-color, transform'
48
+ },
49
+ fullWidth: {
50
+ justifyContent: 'flex-start',
51
+ width: '100%'
52
+ },
53
+ stack: {
54
+ display: 'grid',
55
+ flexGrow: 1,
56
+ gridTemplateColumns: 'minmax(0, 1fr)',
57
+ gap: 'inherit',
58
+ justifyContent: 'inherit',
59
+ minWidth: 0,
60
+ overflow: 'clip'
61
+ },
62
+ content: {
63
+ display: 'flex',
64
+ gridRowStart: 1,
65
+ gridColumnStart: 1,
66
+ gap: 'inherit',
67
+ alignItems: 'center',
68
+ justifyContent: 'inherit',
69
+ minWidth: 0,
70
+ transitionTimingFunction: motion.easeOut,
71
+ transitionDuration: motion.base,
72
+ transitionProperty: 'opacity, transform'
73
+ },
74
+ hidden: {
75
+ opacity: 0,
76
+ transform: 'translateY(24px)'
77
+ },
78
+ pending: {
79
+ display: 'flex',
80
+ gridRowStart: 1,
81
+ gridColumnStart: 1,
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ pointerEvents: 'none'
85
+ },
86
+ label: {
87
+ overflow: 'hidden',
88
+ textOverflow: 'ellipsis'
89
+ },
90
+ labelBeforeShortcut: {
91
+ flexGrow: 1,
92
+ textAlign: 'start'
93
+ },
94
+ pinnedContent: {
95
+ justifyContent: 'flex-start'
96
+ },
97
+ labelBetweenPins: {
98
+ flexGrow: 1,
99
+ textAlign: 'center'
100
+ },
101
+ disabled: {
102
+ color: colors.disabledForeground,
103
+ backgroundColor: colors.disabled,
104
+ boxShadow: 'none'
105
+ },
106
+ disabledTransparent: {
107
+ backgroundColor: 'transparent'
108
+ }
109
+ });
110
+ const leadingIconSizes = stylex.create({
111
+ xs: { paddingInlineStart: `calc(${controls.paddingXs} - 2px)` },
112
+ sm: { paddingInlineStart: `calc(${controls.paddingSm} - 2px)` },
113
+ md: { paddingInlineStart: `calc(${controls.paddingMd} - 4px)` },
114
+ lg: { paddingInlineStart: `calc(${controls.paddingLg} - 2px)` },
115
+ xl: { paddingInlineStart: `calc(${controls.paddingXl} - 4px)` }
116
+ });
117
+ const trailingIconSizes = stylex.create({
118
+ xs: { paddingInlineEnd: `calc(${controls.paddingXs} - 2px)` },
119
+ sm: { paddingInlineEnd: `calc(${controls.paddingSm} - 2px)` },
120
+ md: { paddingInlineEnd: `calc(${controls.paddingMd} - 4px)` },
121
+ lg: { paddingInlineEnd: `calc(${controls.paddingLg} - 2px)` },
122
+ xl: { paddingInlineEnd: `calc(${controls.paddingXl} - 4px)` }
123
+ });
124
+ const looks = stylex.create({
125
+ primaryNeutral: {
126
+ color: colors.onInvertedForeground,
127
+ backgroundColor: colors.invertedSolid
128
+ },
129
+ primaryBrand: {
130
+ color: colors.onBrandSolidForeground,
131
+ backgroundColor: colors.brandSolid
132
+ },
133
+ primaryDanger: {
134
+ color: colors.onDangerSolidForeground,
135
+ backgroundColor: colors.dangerSolid
136
+ },
137
+ secondaryNeutral: {
138
+ color: colors.primaryForeground,
139
+ backgroundColor: colors.componentOnContainerStrong,
140
+ boxShadow: shadows.default
141
+ },
142
+ secondaryBrand: {
143
+ color: colors.brandForeground,
144
+ backgroundColor: colors.componentOnContainerStrong,
145
+ boxShadow: shadows.default
146
+ },
147
+ secondaryDanger: {
148
+ color: colors.dangerForeground,
149
+ backgroundColor: colors.componentOnContainerStrong,
150
+ boxShadow: shadows.default
151
+ },
152
+ tertiaryNeutral: {
153
+ color: colors.secondaryForeground,
154
+ backgroundColor: colors.component
155
+ },
156
+ tertiaryBrand: {
157
+ color: colors.onBrandSubtleForeground,
158
+ backgroundColor: colors.brandSubtle
159
+ },
160
+ tertiaryDanger: {
161
+ color: colors.dangerForeground,
162
+ backgroundColor: colors.dangerSubtle
163
+ },
164
+ transparentNeutral: {
165
+ color: colors.secondaryForeground,
166
+ backgroundColor: 'transparent'
167
+ },
168
+ transparentBrand: {
169
+ color: colors.brandForeground,
170
+ backgroundColor: 'transparent'
171
+ },
172
+ transparentDanger: {
173
+ color: colors.dangerForeground,
174
+ backgroundColor: 'transparent'
175
+ }
176
+ });
177
+ const hovers = stylex.create({
178
+ primaryNeutral: {
179
+ backgroundColor: {
180
+ default: colors.invertedSolid,
181
+ ':hover': colors.invertedSolidHover,
182
+ ':active': colors.invertedSolidActive
183
+ }
184
+ },
185
+ primaryBrand: {
186
+ backgroundColor: {
187
+ default: colors.brandSolid,
188
+ ':hover': colors.brandSolidHover,
189
+ ':active': colors.brandSolidActive
190
+ }
191
+ },
192
+ primaryDanger: {
193
+ backgroundColor: {
194
+ default: colors.dangerSolid,
195
+ ':hover': colors.dangerSolidHover,
196
+ ':active': colors.dangerSolidActive
197
+ }
198
+ },
199
+ secondaryNeutral: {
200
+ backgroundColor: {
201
+ default: colors.componentOnContainerStrong,
202
+ ':hover': colors.componentOnContainerStrongHover
203
+ }
204
+ },
205
+ secondaryBrand: {
206
+ backgroundColor: {
207
+ default: colors.componentOnContainerStrong,
208
+ ':hover': colors.componentOnContainerStrongHover
209
+ }
210
+ },
211
+ secondaryDanger: {
212
+ backgroundColor: {
213
+ default: colors.componentOnContainerStrong,
214
+ ':hover': colors.componentOnContainerStrongHover
215
+ }
216
+ },
217
+ tertiaryNeutral: {
218
+ backgroundColor: {
219
+ default: colors.component,
220
+ ':hover': colors.componentHover,
221
+ ':active': colors.componentActive
222
+ }
223
+ },
224
+ tertiaryBrand: {
225
+ backgroundColor: {
226
+ default: colors.brandSubtle,
227
+ ':hover': colors.brandSubtleHover
228
+ }
229
+ },
230
+ tertiaryDanger: {
231
+ backgroundColor: {
232
+ default: colors.dangerSubtle,
233
+ ':hover': colors.dangerSubtleHover,
234
+ ':active': colors.dangerSubtleActive
235
+ }
236
+ },
237
+ transparentNeutral: {
238
+ color: {
239
+ default: colors.secondaryForeground,
240
+ ':hover': colors.primaryForeground
241
+ },
242
+ backgroundColor: {
243
+ default: 'transparent',
244
+ ':hover': colors.transparentHover
245
+ }
246
+ },
247
+ transparentBrand: {
248
+ backgroundColor: {
249
+ default: 'transparent',
250
+ ':hover': colors.transparentHover
251
+ }
252
+ },
253
+ transparentDanger: {
254
+ backgroundColor: {
255
+ default: 'transparent',
256
+ ':hover': colors.dangerSubtleHover
257
+ }
258
+ }
259
+ });
260
+ const pinBalances = stylex.create({
261
+ xs: { flexShrink: 0, width: iconSizes.xs },
262
+ sm: { flexShrink: 0, width: iconSizes.sm },
263
+ md: { flexShrink: 0, width: iconSizes.md },
264
+ lg: { flexShrink: 0, width: iconSizes.lg },
265
+ xl: { flexShrink: 0, width: iconSizes.xl }
266
+ });
@@ -0,0 +1,11 @@
1
+ import type { Placement } from './Placement.ts';
2
+ import type { Size } from './Size.ts';
3
+ type ContinueWithGoogleProps = {
4
+ onClick: () => void;
5
+ size?: Size;
6
+ disabled?: boolean;
7
+ isPending?: boolean;
8
+ style?: Placement;
9
+ };
10
+ export declare function ContinueWithGoogle({ onClick, size, disabled, isPending, style }: ContinueWithGoogleProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ButtonBody } from "./ButtonBody.js";
3
+ export function ContinueWithGoogle({ onClick, size = 'md', disabled = false, isPending = false, style }) {
4
+ return (_jsx(ButtonBody, { variant: "secondary", icon: "google", isIconPinned: true, size: size, disabled: disabled, isPending: isPending, onClick: onClick, ...(style === undefined ? {} : { style }), children: "Continue with Google" }));
5
+ }
@@ -5,7 +5,7 @@ import { shapeOf } from "./icons.js";
5
5
  import { tones } from "./tones.js";
6
6
  export function Icon({ name, size = 'md', tone = 'inherit', weight = 'regular' }) {
7
7
  const shape = shapeOf(name);
8
- return (_jsx("svg", { viewBox: "0 0 24 24", fill: shape.isFilled ? 'currentColor' : 'none', fillRule: shape.rule, stroke: shape.isFilled ? 'none' : 'currentColor', strokeWidth: weight === 'light' ? '1.5' : '2', strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...stylex.props(styles.icon, sizes[size], tones[tone]), children: shape.paths.map((path) => (_jsx("path", { d: path }, path))) }));
8
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: shape.isFilled ? 'currentColor' : 'none', fillRule: shape.rule, stroke: shape.isFilled ? 'none' : 'currentColor', strokeWidth: weight === 'light' ? '1.5' : '2', strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...stylex.props(styles.icon, sizes[size], tones[tone]), children: shape.paths.map((path, index) => (_jsx("path", { d: path, fill: shape.colors?.[index] }, path))) }));
9
9
  }
10
10
  const styles = stylex.create({
11
11
  icon: {
@@ -2,12 +2,14 @@ import type { ReactNode } from 'react';
2
2
  import type { AppPath } from '../registered.ts';
3
3
  type ListRowProps = {
4
4
  to?: AppPath;
5
+ href?: string;
5
6
  onClick?: () => void;
6
7
  isMuted?: boolean;
7
8
  isCurrent?: boolean;
9
+ size?: 'sm' | 'md' | 'lg';
8
10
  leading?: ReactNode;
9
11
  trailing?: ReactNode;
10
12
  children: ReactNode;
11
13
  };
12
- export declare function ListRow({ to, onClick, isMuted, isCurrent, leading, trailing, children }: ListRowProps): import("react").JSX.Element;
14
+ export declare function ListRow({ to, href, onClick, isMuted, isCurrent, size, leading, trailing, children }: ListRowProps): import("react").JSX.Element;
13
15
  export {};
@@ -5,20 +5,24 @@ import { Link as RouterLink } from 'react-router';
5
5
  import { colors, controls, radii, space } from "../theme/tokens.stylex.js";
6
6
  import { focusRing } from "./controlLook.js";
7
7
  import { interact, textOf } from "./interaction.js";
8
- export function ListRow({ to, onClick, isMuted = false, isCurrent = false, leading, trailing, children }) {
8
+ export function ListRow({ to, href, onClick, isMuted = false, isCurrent = false, size = 'md', leading, trailing, children }) {
9
9
  const content = (_jsxs(_Fragment, { children: [leading, _jsx("span", { ...stylex.props(styles.main), children: children }), trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: trailing }))] }));
10
- const look = stylex.props(styles.row, (to !== undefined || onClick !== undefined) && styles.interactive, isMuted && styles.muted, isCurrent && styles.current, focusRing.visible, focusRing.inside);
10
+ const look = stylex.props(styles.row, (to !== undefined || href !== undefined || onClick !== undefined) &&
11
+ styles.interactive, isMuted && styles.muted, isCurrent && styles.current, size === 'sm' && styles.small, size === 'lg' && styles.large, focusRing.visible, focusRing.inside);
11
12
  const report = () => {
12
13
  interact({
13
14
  primitive: 'ListRow',
14
- event: to === undefined ? 'click' : 'navigate',
15
- label: textOf(children) || (to ?? '')
15
+ event: to === undefined && href === undefined ? 'click' : 'navigate',
16
+ label: textOf(children) || (to ?? href ?? '')
16
17
  });
17
18
  onClick?.();
18
19
  };
19
20
  if (to !== undefined) {
20
21
  return (_jsx(RouterLink, { to: to, onClick: report, "aria-current": isCurrent ? 'page' : undefined, ...look, children: content }));
21
22
  }
23
+ if (href !== undefined) {
24
+ return (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", onClick: report, ...look, children: content }));
25
+ }
22
26
  if (onClick !== undefined) {
23
27
  return (_jsx(BaseButton, { onClick: report, "aria-pressed": isCurrent, className: look.className, children: content }));
24
28
  }
@@ -49,6 +53,16 @@ const styles = stylex.create({
49
53
  current: {
50
54
  backgroundColor: colors.transparentHover
51
55
  },
56
+ large: {
57
+ height: 'auto',
58
+ minHeight: controls.heightXl,
59
+ paddingBlock: space.md
60
+ },
61
+ small: {
62
+ gap: space.sm,
63
+ height: controls.heightMd,
64
+ paddingInline: space.sm
65
+ },
52
66
  main: {
53
67
  display: 'flex',
54
68
  flexGrow: 1,
@@ -10,6 +10,7 @@ type ScrollAreaProps = {
10
10
  paddingBlock?: number | string;
11
11
  paddingInline?: number | string;
12
12
  }>;
13
+ axis?: 'vertical' | 'horizontal';
13
14
  };
14
- export declare function ScrollArea({ children, style }: ScrollAreaProps): import("react").JSX.Element;
15
+ export declare function ScrollArea({ children, style, axis }: ScrollAreaProps): import("react").JSX.Element;
15
16
  export {};
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as stylex from '@stylexjs/stylex';
3
- export function ScrollArea({ children, style }) {
4
- return _jsx("div", { ...stylex.props(styles.scroll, style), children: children });
3
+ export function ScrollArea({ children, style, axis = 'vertical' }) {
4
+ return (_jsx("div", { ...stylex.props(styles.scroll, axis === 'horizontal' && styles.horizontal, style), children: children }));
5
5
  }
6
6
  const styles = stylex.create({
7
7
  scroll: {
@@ -9,5 +9,10 @@ const styles = stylex.create({
9
9
  minHeight: 0,
10
10
  overflowY: 'auto',
11
11
  overscrollBehavior: 'contain'
12
+ },
13
+ horizontal: {
14
+ height: 'auto',
15
+ overflowX: 'auto',
16
+ overflowY: 'hidden'
12
17
  }
13
18
  });
@@ -3,7 +3,7 @@ import type { Placement } from './Placement.ts';
3
3
  import type { Tone } from './tones.ts';
4
4
  type TextProps = {
5
5
  children: ReactNode;
6
- variant?: 'heading' | 'subheading' | 'title' | 'label' | 'body' | 'caption' | 'code';
6
+ variant?: 'heading' | 'subheading' | 'title' | 'lead' | 'label' | 'body' | 'caption' | 'code';
7
7
  tone?: Tone;
8
8
  align?: 'start' | 'center' | 'end';
9
9
  as?: 'span' | 'p' | 'h1' | 'h2' | 'h3';
@@ -23,17 +23,23 @@ const styles = stylex.create({
23
23
  const variants = stylex.create({
24
24
  heading: {
25
25
  fontSize: fontSizes.heading5,
26
- fontWeight: fontWeights.semibold,
27
- lineHeight: lineHeights.heading5
26
+ fontWeight: fontWeights.medium,
27
+ lineHeight: lineHeights.heading5,
28
+ letterSpacing: '-0.01em'
28
29
  },
29
30
  subheading: {
30
31
  fontSize: fontSizes.heading6,
31
- fontWeight: fontWeights.semibold,
32
+ fontWeight: fontWeights.medium,
32
33
  lineHeight: lineHeights.heading6
33
34
  },
34
35
  title: {
35
36
  fontSize: fontSizes.bodyBase,
36
- fontWeight: fontWeights.semibold,
37
+ fontWeight: fontWeights.medium,
38
+ lineHeight: lineHeights.body
39
+ },
40
+ lead: {
41
+ fontSize: fontSizes.bodyBase,
42
+ fontWeight: fontWeights.normal,
37
43
  lineHeight: lineHeights.body
38
44
  },
39
45
  label: {
@@ -1,3 +1,4 @@
1
+ import type { IconName } from './icons.ts';
1
2
  import type { Placement } from './Placement.ts';
2
3
  import type { Size } from './Size.ts';
3
4
  type TextFieldProps = {
@@ -21,7 +22,8 @@ type TextFieldProps = {
21
22
  multiline?: boolean;
22
23
  rows?: number;
23
24
  size?: Size;
25
+ icon?: IconName;
24
26
  style?: Placement;
25
27
  };
26
- export declare function TextField({ label, isLabelHidden, description, error, onChange, multiline, rows, size, style, ...input }: TextFieldProps): import("react").JSX.Element;
28
+ export declare function TextField({ label, isLabelHidden, description, error, onChange, multiline, rows, size, icon, style, ...input }: TextFieldProps): import("react").JSX.Element;
27
29
  export {};
@@ -3,18 +3,19 @@ import { Field } from '@base-ui/react/field';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { useRef } from 'react';
5
5
  import { borders, colors, controls, fontSizes, fontWeights, lineHeights, motion, shadows, space } from "../theme/tokens.stylex.js";
6
+ import { Icon } from "./Icon.js";
6
7
  import { controlSizes } from "./controlLook.js";
7
8
  import { interact } from "./interaction.js";
8
- export function TextField({ label, isLabelHidden = false, description, error, onChange, multiline = false, rows = 3, size = 'md', style, ...input }) {
9
+ export function TextField({ label, isLabelHidden = false, description, error, onChange, multiline = false, rows = 3, size = 'md', icon, style, ...input }) {
9
10
  const isInvalid = error !== undefined;
10
11
  const valueOnFocus = useRef('');
11
- return (_jsxs(Field.Root, { invalid: isInvalid, disabled: input.disabled ?? false, className: stylex.props(styles.field, style).className, children: [isLabelHidden ? null : (_jsx(Field.Label, { className: stylex.props(styles.label).className, children: label })), _jsx(Field.Control, { ...input, ...(multiline ? { render: _jsx("textarea", { rows: rows }) } : {}), "aria-label": isLabelHidden ? label : undefined, onChange: (event) => onChange?.(event.currentTarget.value), onFocus: (event) => {
12
- valueOnFocus.current = event.currentTarget.value;
13
- }, onBlur: (event) => {
14
- if (event.currentTarget.value !== valueOnFocus.current) {
15
- interact({ primitive: 'TextField', event: 'change', label });
16
- }
17
- }, className: stylex.props(styles.input, controlSizes[size], multiline && styles.multiline, multiline && multilinePaddings[size], isInvalid && styles.invalid, input.disabled === true && styles.disabled).className }), description === undefined ? null : (_jsx(Field.Description, { className: stylex.props(styles.note).className, children: description })), error === undefined ? null : (_jsx(Field.Error, { match: true, className: stylex.props(styles.error).className, children: error }))] }));
12
+ return (_jsxs(Field.Root, { invalid: isInvalid, disabled: input.disabled ?? false, className: stylex.props(styles.field, style).className, children: [isLabelHidden ? null : (_jsx(Field.Label, { className: stylex.props(styles.label).className, children: label })), _jsxs("div", { ...stylex.props(styles.control), children: [icon === undefined ? null : (_jsx("span", { ...stylex.props(styles.icon), children: _jsx(Icon, { name: icon, tone: "tertiary" }) })), _jsx(Field.Control, { ...input, ...(multiline ? { render: _jsx("textarea", { rows: rows }) } : {}), "aria-label": isLabelHidden ? label : undefined, onChange: (event) => onChange?.(event.currentTarget.value), onFocus: (event) => {
13
+ valueOnFocus.current = event.currentTarget.value;
14
+ }, onBlur: (event) => {
15
+ if (event.currentTarget.value !== valueOnFocus.current) {
16
+ interact({ primitive: 'TextField', event: 'change', label });
17
+ }
18
+ }, className: stylex.props(styles.input, controlSizes[size], multiline && styles.multiline, multiline && multilinePaddings[size], icon !== undefined && styles.withIcon, isInvalid && styles.invalid, input.disabled === true && styles.disabled).className })] }), description === undefined ? null : (_jsx(Field.Description, { className: stylex.props(styles.note).className, children: description })), error === undefined ? null : (_jsx(Field.Error, { match: true, className: stylex.props(styles.error).className, children: error }))] }));
18
19
  }
19
20
  const focusRing = `inset 0 0 0 ${borders.hairline} ${colors.brandBorderFocus}, 0 0 0 ${borders.thin} ${colors.brandBorderFocus}, ${shadows.default}`;
20
21
  const invalidRing = `inset 0 0 0 ${borders.hairline} ${colors.dangerBorderDefault}, 0 0 0 ${borders.thin} ${colors.dangerBorderDefault}, ${shadows.default}`;
@@ -25,6 +26,22 @@ const styles = stylex.create({
25
26
  gap: space.xs,
26
27
  minWidth: 0
27
28
  },
29
+ control: {
30
+ position: 'relative',
31
+ display: 'flex',
32
+ flexDirection: 'column'
33
+ },
34
+ icon: {
35
+ position: 'absolute',
36
+ insetBlock: 0,
37
+ insetInlineStart: space.sm,
38
+ display: 'flex',
39
+ alignItems: 'center',
40
+ pointerEvents: 'none'
41
+ },
42
+ withIcon: {
43
+ paddingInlineStart: `calc(${space.xl} + ${space.xs})`
44
+ },
28
45
  label: {
29
46
  fontWeight: fontWeights.medium,
30
47
  color: colors.primaryForeground
@@ -2,6 +2,7 @@ import type { AppIconName } from '../registered.ts';
2
2
  type FilledDrawing = {
3
3
  readonly fill: string | readonly string[];
4
4
  readonly rule?: 'evenodd';
5
+ readonly colors?: readonly string[];
5
6
  };
6
7
  export type IconDrawing = string | readonly string[] | FilledDrawing;
7
8
  export type IconName = keyof typeof builtInIcons | AppIconName;
@@ -12,6 +13,10 @@ declare const builtInIcons: {
12
13
  sun: {
13
14
  fill: string[];
14
15
  };
16
+ google: {
17
+ fill: string[];
18
+ colors: string[];
19
+ };
15
20
  moon: {
16
21
  fill: string;
17
22
  };
@@ -54,9 +59,11 @@ export declare function shapeOf(name: IconName): {
54
59
  paths: readonly string[];
55
60
  isFilled: boolean;
56
61
  rule?: never;
62
+ colors?: never;
57
63
  } | {
58
64
  paths: string[];
59
65
  isFilled: boolean;
60
66
  rule: "evenodd" | undefined;
67
+ colors: readonly string[] | undefined;
61
68
  };
62
69
  export {};
@@ -23,6 +23,15 @@ const builtInIcons = {
23
23
  'M7.75736 7.75736C10.1005 5.41421 13.8995 5.41421 16.2426 7.75736C18.5858 10.1005 18.5858 13.8995 16.2426 16.2426C13.8995 18.5858 10.1005 18.5858 7.75736 16.2426C5.41421 13.8995 5.41421 10.1005 7.75736 7.75736Z'
24
24
  ]
25
25
  },
26
+ google: {
27
+ fill: [
28
+ 'M22.5 12.3c0-.8-.1-1.5-.2-2.2H12v4.2h5.9a5 5 0 0 1-2.2 3.3v2.7h3.5c2.1-1.9 3.3-4.7 3.3-8z',
29
+ 'M12 23c3 0 5.5-1 7.3-2.7l-3.5-2.7c-1 .7-2.3 1.1-3.8 1.1-2.9 0-5.4-2-6.3-4.7H2.1v2.8A11 11 0 0 0 12 23z',
30
+ 'M5.7 14c-.2-.7-.4-1.4-.4-2s.1-1.4.4-2V7.2H2.1a11 11 0 0 0 0 9.7L5.7 14z',
31
+ 'M12 5.4c1.6 0 3.1.6 4.2 1.7l3.1-3.1A11 11 0 0 0 2.1 7.2L5.7 10c.9-2.7 3.4-4.6 6.3-4.6z'
32
+ ],
33
+ colors: ['#4285F4', '#34A853', '#FBBC05', '#EA4335']
34
+ },
26
35
  moon: {
27
36
  fill: 'M12.0517 3.59971C12.2712 3.28123 12.2873 2.86472 12.0931 2.53021C11.8989 2.19569 11.5292 2.00315 11.1438 2.03581C6.0214 2.46985 2 6.76372 2 11.9979C2 17.5197 6.47632 21.996 11.9981 21.996C17.2324 21.996 21.5264 17.9745 21.9602 12.8519C21.9929 12.4664 21.8003 12.0968 21.4658 11.9026C21.1313 11.7084 20.7148 11.7246 20.3963 11.9441C19.4302 12.61 18.2602 12.9998 16.9961 12.9998C13.6824 12.9998 10.9961 10.3135 10.9961 6.99976C10.9961 5.73577 11.3858 4.56582 12.0517 3.59971Z'
28
37
  },
@@ -107,7 +116,12 @@ export function shapeOf(name) {
107
116
  return { paths: [drawing], isFilled: false };
108
117
  if (isPathList(drawing))
109
118
  return { paths: drawing, isFilled: false };
110
- return { paths: [drawing.fill].flat(), isFilled: true, rule: drawing.rule };
119
+ return {
120
+ paths: [drawing.fill].flat(),
121
+ isFilled: true,
122
+ rule: drawing.rule,
123
+ colors: drawing.colors
124
+ };
111
125
  }
112
126
  function isPathList(drawing) {
113
127
  return Array.isArray(drawing);
package/dist/react.d.ts CHANGED
@@ -14,6 +14,7 @@ export { CommandPalette } from './components/CommandPalette.tsx';
14
14
  export type { CommandMenuItem } from './components/CommandMenu.tsx';
15
15
  export { Content } from './components/Content.tsx';
16
16
  export { ContextMenu } from './components/ContextMenu.tsx';
17
+ export { ContinueWithGoogle } from './components/ContinueWithGoogle.tsx';
17
18
  export { CopyButton } from './components/CopyButton.tsx';
18
19
  export { Dialog } from './components/Dialog.tsx';
19
20
  export { Divider } from './components/Divider.tsx';
package/dist/react.js CHANGED
@@ -13,6 +13,7 @@ export { CommandMenu } from "./components/CommandMenu.js";
13
13
  export { CommandPalette } from "./components/CommandPalette.js";
14
14
  export { Content } from "./components/Content.js";
15
15
  export { ContextMenu } from "./components/ContextMenu.js";
16
+ export { ContinueWithGoogle } from "./components/ContinueWithGoogle.js";
16
17
  export { CopyButton } from "./components/CopyButton.js";
17
18
  export { Dialog } from "./components/Dialog.js";
18
19
  export { Divider } from "./components/Divider.js";
@@ -43,7 +43,7 @@ export async function sessionCookieFor(env, user) {
43
43
  email: user.email,
44
44
  recheckAt: nowInSeconds() + RECHECK_SECONDS
45
45
  };
46
- const cookie = await serializeSigned(SESSION_COOKIE, JSON.stringify(pass), env.SESSION_SECRET);
46
+ const cookie = await serializeSigned(SESSION_COOKIE, JSON.stringify(pass), env.SESSION_SECRET, { path: '/', secure: true });
47
47
  return { cookie: cookie.split(';')[0] ?? cookie, sessionId };
48
48
  }
49
49
  export async function readPass(c) {
package/dist/start.js CHANGED
@@ -165,6 +165,9 @@ const base = stylex.create({
165
165
  [breakpoints.compact]: lineHeights.body
166
166
  },
167
167
  color: colors.primaryForeground,
168
- backgroundColor: colors.shell
168
+ backgroundColor: colors.shell,
169
+ WebkitFontSmoothing: 'antialiased',
170
+ MozOsxFontSmoothing: 'grayscale',
171
+ fontSynthesis: 'none'
169
172
  }
170
173
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",