@trypattern/sdk 0.1.8 → 0.1.10
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/README.md +37 -8
- package/dist/app.d.ts +1 -0
- package/dist/app.js +4 -3
- package/dist/cli/app.js +120 -7
- package/dist/cli/publishing.js +3 -1
- package/dist/components/AppFrame.d.ts +5 -1
- package/dist/components/AppFrame.js +25 -7
- package/dist/components/Appear.d.ts +4 -1
- package/dist/components/Appear.js +82 -3
- package/dist/components/Card.d.ts +7 -2
- package/dist/components/Card.js +59 -3
- package/dist/components/Checkbox.d.ts +10 -0
- package/dist/components/Checkbox.js +59 -0
- package/dist/components/Choice.d.ts +15 -0
- package/dist/components/Choice.js +80 -0
- package/dist/components/ChoiceGroup.d.ts +11 -0
- package/dist/components/ChoiceGroup.js +44 -0
- package/dist/components/Icon.d.ts +2 -1
- package/dist/components/Icon.js +2 -2
- package/dist/components/Image.d.ts +11 -0
- package/dist/components/Image.js +76 -0
- package/dist/components/Link.js +8 -3
- package/dist/components/ListRow.d.ts +2 -1
- package/dist/components/ListRow.js +7 -4
- package/dist/components/PlainScreen.d.ts +16 -0
- package/dist/components/PlainScreen.js +80 -0
- package/dist/components/PlainSidebar.d.ts +15 -0
- package/dist/components/PlainSidebar.js +179 -0
- package/dist/components/Screen.d.ts +3 -1
- package/dist/components/Screen.js +5 -1
- package/dist/components/Sidebar.js +2 -1
- package/dist/components/TextField.d.ts +3 -1
- package/dist/components/TextField.js +25 -4
- package/dist/components/choiceContext.d.ts +4 -0
- package/dist/components/choiceContext.js +7 -0
- package/dist/components/frame.d.ts +2 -0
- package/dist/components/icons.d.ts +3 -0
- package/dist/components/icons.js +4 -1
- package/dist/components/interaction.d.ts +1 -1
- package/dist/entityStore.d.ts +1 -0
- package/dist/entityStore.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/media.d.ts +36 -0
- package/dist/media.js +74 -0
- package/dist/patternAccess.d.ts +21 -0
- package/dist/patternAccess.js +110 -0
- package/dist/publishWire.d.ts +318 -0
- package/dist/publishWire.js +128 -1
- package/dist/react.d.ts +5 -0
- package/dist/react.js +4 -0
- package/dist/rules/checkApiRouting.js +1 -1
- package/dist/rules/lintPlugin.js +13 -4
- package/dist/rules/messages.d.ts +1 -0
- package/dist/rules/messages.js +1 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +1 -0
- package/dist/server/createServer.d.ts +92 -6
- package/dist/server/createServer.js +38 -2
- package/dist/server/database.d.ts +74 -0
- package/dist/server/database.js +93 -0
- package/dist/server/env.d.ts +3 -1
- package/dist/server/imageHeader.d.ts +10 -0
- package/dist/server/imageHeader.js +159 -0
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.js +3 -0
- package/dist/server/jsonLd.d.ts +45 -0
- package/dist/server/jsonLd.js +88 -0
- package/dist/server/keyset.d.ts +13 -0
- package/dist/server/keyset.js +52 -0
- package/dist/server/media.d.ts +16 -0
- package/dist/server/media.js +135 -0
- package/dist/server/pages.d.ts +72 -0
- package/dist/server/pages.js +301 -0
- package/dist/server/patternKeys.d.ts +1 -0
- package/dist/server/patternKeys.js +3 -0
- package/dist/server/patternViewer.d.ts +6 -0
- package/dist/server/patternViewer.js +27 -0
- package/dist/server/services.js +1 -1
- package/dist/server/tables.d.ts +19 -3
- package/dist/server/tables.js +38 -14
- package/dist/serviceClients.js +3 -5
- package/dist/start.js +26 -1
- package/dist/table.d.ts +1 -0
- package/dist/table.js +8 -1
- package/dist/tables.d.ts +2 -1
- package/dist/tables.js +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Menu as BaseMenu } from '@base-ui/react/menu';
|
|
3
|
+
import * as stylex from '@stylexjs/stylex';
|
|
4
|
+
import { NavLink } from 'react-router';
|
|
5
|
+
import { borders, breakpoints, colors, controls, fontSizes, fontWeights, layers, lineHeights, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
|
|
6
|
+
import { Avatar } from "./Avatar.js";
|
|
7
|
+
import { Badge } from "./Badge.js";
|
|
8
|
+
import { focusRing, transparentLook } from "./controlLook.js";
|
|
9
|
+
import { Button } from "./Button.js";
|
|
10
|
+
import { Icon } from "./Icon.js";
|
|
11
|
+
import { interact } from "./interaction.js";
|
|
12
|
+
import { MenuItems } from "./MenuItems.js";
|
|
13
|
+
import { Popup } from "./Popup.js";
|
|
14
|
+
import { ScrollArea } from "./ScrollArea.js";
|
|
15
|
+
import { Text } from "./Text.js";
|
|
16
|
+
export function PlainSidebar({ title, user, accountMenu, links, isToggled, isAnimating, onFollowLink, onSearch }) {
|
|
17
|
+
const followLink = (link) => {
|
|
18
|
+
interact({ primitive: 'AppFrame', event: 'navigate', label: link.label });
|
|
19
|
+
onFollowLink();
|
|
20
|
+
};
|
|
21
|
+
return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx("span", { ...stylex.props(styles.title), children: title }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsx("div", { ...stylex.props(styles.group), children: links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/' ||
|
|
22
|
+
links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon, weight: "light" }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to))) }) }) }), user === null ? null : (_jsxs(BaseMenu.Root, { children: [_jsxs(BaseMenu.Trigger, { className: (state) => stylex.props(styles.account, transparentLook.rest, transparentLook.hoverable, state.open && transparentLook.open, styles.accountText, focusRing.visible).className, children: [_jsx(Avatar, { name: user.name, imageUrl: user.avatarUrl }), _jsxs("span", { ...stylex.props(styles.who), children: [_jsx("span", { ...stylex.props(styles.name), children: user.name }), _jsx("span", { ...stylex.props(styles.email), children: user.email })] }), _jsx(Icon, { name: "chevronUpDown", tone: "secondary" })] }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { side: "top", align: "start", sideOffset: 4, className: stylex.props(styles.positioner).className, children: _jsx(BaseMenu.Popup, { render: (props, state) => (_jsx(Popup, { ...props, kind: "menu", isOpen: state.open, transitionStatus: state.transitionStatus })), children: _jsx(MenuItems, { items: accountMenu, primitive: "Menu" }) }) }) })] }))] }));
|
|
23
|
+
}
|
|
24
|
+
const styles = stylex.create({
|
|
25
|
+
sidebar: {
|
|
26
|
+
position: {
|
|
27
|
+
default: 'relative',
|
|
28
|
+
[breakpoints.compact]: 'fixed'
|
|
29
|
+
},
|
|
30
|
+
insetBlock: {
|
|
31
|
+
default: null,
|
|
32
|
+
[breakpoints.compact]: 0
|
|
33
|
+
},
|
|
34
|
+
insetInlineStart: {
|
|
35
|
+
default: null,
|
|
36
|
+
[breakpoints.compact]: 0
|
|
37
|
+
},
|
|
38
|
+
zIndex: {
|
|
39
|
+
default: null,
|
|
40
|
+
[breakpoints.compact]: layers.sidebar
|
|
41
|
+
},
|
|
42
|
+
display: 'flex',
|
|
43
|
+
visibility: {
|
|
44
|
+
default: null,
|
|
45
|
+
[breakpoints.compact]: 'hidden'
|
|
46
|
+
},
|
|
47
|
+
flexShrink: 0,
|
|
48
|
+
flexDirection: 'column',
|
|
49
|
+
gap: space.lg,
|
|
50
|
+
width: widths.sidebar,
|
|
51
|
+
height: '100%',
|
|
52
|
+
paddingBlock: space.lg,
|
|
53
|
+
paddingInline: space.md,
|
|
54
|
+
backgroundColor: colors.page,
|
|
55
|
+
borderInlineEndColor: colors.borderDefault,
|
|
56
|
+
borderInlineEndStyle: {
|
|
57
|
+
default: 'solid',
|
|
58
|
+
[breakpoints.compact]: 'none'
|
|
59
|
+
},
|
|
60
|
+
borderInlineEndWidth: borders.thin,
|
|
61
|
+
boxShadow: {
|
|
62
|
+
default: null,
|
|
63
|
+
[breakpoints.compact]: shadows.extraLarge
|
|
64
|
+
},
|
|
65
|
+
transform: {
|
|
66
|
+
default: null,
|
|
67
|
+
[breakpoints.compact]: 'translateX(-100%)'
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
sidebarAnimating: {
|
|
71
|
+
transitionTimingFunction: motion.slideEasing,
|
|
72
|
+
transitionDuration: motion.slide,
|
|
73
|
+
transitionProperty: 'transform, visibility'
|
|
74
|
+
},
|
|
75
|
+
sidebarToggled: {
|
|
76
|
+
visibility: {
|
|
77
|
+
default: null,
|
|
78
|
+
[breakpoints.compact]: 'visible'
|
|
79
|
+
},
|
|
80
|
+
transform: {
|
|
81
|
+
default: null,
|
|
82
|
+
[breakpoints.compact]: 'translateX(0)'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
header: {
|
|
86
|
+
display: 'flex',
|
|
87
|
+
flexShrink: 0,
|
|
88
|
+
alignItems: 'center',
|
|
89
|
+
height: controls.heightMd,
|
|
90
|
+
paddingInline: controls.paddingSm
|
|
91
|
+
},
|
|
92
|
+
title: {
|
|
93
|
+
overflow: 'hidden',
|
|
94
|
+
textOverflow: 'ellipsis',
|
|
95
|
+
fontSize: fontSizes.bodySm,
|
|
96
|
+
fontWeight: fontWeights.semibold,
|
|
97
|
+
lineHeight: lineHeights.bodySm,
|
|
98
|
+
color: colors.primaryForeground,
|
|
99
|
+
whiteSpace: 'nowrap'
|
|
100
|
+
},
|
|
101
|
+
body: {
|
|
102
|
+
flexGrow: 1,
|
|
103
|
+
minHeight: 0
|
|
104
|
+
},
|
|
105
|
+
group: {
|
|
106
|
+
display: 'flex',
|
|
107
|
+
flexDirection: 'column',
|
|
108
|
+
gap: borders.thin
|
|
109
|
+
},
|
|
110
|
+
item: {
|
|
111
|
+
display: 'flex',
|
|
112
|
+
gap: space.sm,
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
height: {
|
|
115
|
+
default: controls.heightMd,
|
|
116
|
+
[breakpoints.compact]: controls.heightXl
|
|
117
|
+
},
|
|
118
|
+
paddingInline: controls.paddingSm,
|
|
119
|
+
color: {
|
|
120
|
+
default: colors.secondaryForeground,
|
|
121
|
+
':hover': colors.primaryForeground
|
|
122
|
+
},
|
|
123
|
+
textDecoration: 'none',
|
|
124
|
+
backgroundColor: {
|
|
125
|
+
default: 'transparent',
|
|
126
|
+
':hover': colors.transparentHover
|
|
127
|
+
},
|
|
128
|
+
borderRadius: radii.md
|
|
129
|
+
},
|
|
130
|
+
itemCurrent: {
|
|
131
|
+
color: colors.primaryForeground,
|
|
132
|
+
backgroundColor: colors.transparentHover
|
|
133
|
+
},
|
|
134
|
+
itemLabel: {
|
|
135
|
+
flexGrow: 1
|
|
136
|
+
},
|
|
137
|
+
account: {
|
|
138
|
+
display: 'flex',
|
|
139
|
+
flexShrink: 0,
|
|
140
|
+
gap: controls.gapMd,
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
width: '100%',
|
|
143
|
+
height: controls.heightXl,
|
|
144
|
+
paddingInline: controls.paddingXs,
|
|
145
|
+
textAlign: 'start',
|
|
146
|
+
outlineOffset: 0,
|
|
147
|
+
borderRadius: radii.lg
|
|
148
|
+
},
|
|
149
|
+
accountText: {
|
|
150
|
+
color: colors.primaryForeground
|
|
151
|
+
},
|
|
152
|
+
who: {
|
|
153
|
+
display: 'flex',
|
|
154
|
+
flexGrow: 1,
|
|
155
|
+
flexDirection: 'column',
|
|
156
|
+
minWidth: 0
|
|
157
|
+
},
|
|
158
|
+
name: {
|
|
159
|
+
overflow: 'hidden',
|
|
160
|
+
textOverflow: 'ellipsis',
|
|
161
|
+
fontSize: fontSizes.bodySm,
|
|
162
|
+
fontWeight: fontWeights.medium,
|
|
163
|
+
lineHeight: lineHeights.caption,
|
|
164
|
+
color: colors.primaryForeground,
|
|
165
|
+
whiteSpace: 'nowrap'
|
|
166
|
+
},
|
|
167
|
+
email: {
|
|
168
|
+
overflow: 'hidden',
|
|
169
|
+
textOverflow: 'ellipsis',
|
|
170
|
+
fontSize: fontSizes.captionLg,
|
|
171
|
+
lineHeight: lineHeights.caption,
|
|
172
|
+
color: colors.secondaryForeground,
|
|
173
|
+
whiteSpace: 'nowrap'
|
|
174
|
+
},
|
|
175
|
+
positioner: {
|
|
176
|
+
zIndex: layers.menu,
|
|
177
|
+
outline: 'none'
|
|
178
|
+
}
|
|
179
|
+
});
|
|
@@ -2,12 +2,14 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { AppPath } from '../registered.ts';
|
|
3
3
|
type ScreenProps = {
|
|
4
4
|
title: string;
|
|
5
|
+
eyebrow?: string;
|
|
5
6
|
parent?: {
|
|
6
7
|
label: string;
|
|
7
8
|
to: AppPath;
|
|
8
9
|
};
|
|
9
10
|
actions?: ReactNode;
|
|
11
|
+
width?: 'xs' | 'sm' | 'md' | 'lg' | 'full';
|
|
10
12
|
children: ReactNode;
|
|
11
13
|
};
|
|
12
|
-
export declare function Screen({ title, parent, actions, children }: ScreenProps): import("react").JSX.Element;
|
|
14
|
+
export declare function Screen({ title, eyebrow, parent, actions, width, children }: ScreenProps): import("react").JSX.Element;
|
|
13
15
|
export {};
|
|
@@ -3,13 +3,17 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
3
3
|
import { use } from 'react';
|
|
4
4
|
import { borders, colors, controls, space } from "../theme/tokens.stylex.js";
|
|
5
5
|
import { FrameContext } from "./frame.js";
|
|
6
|
+
import { PlainScreen } from "./PlainScreen.js";
|
|
6
7
|
import { Icon } from "./Icon.js";
|
|
7
8
|
import { IconButton } from "./IconButton.js";
|
|
8
9
|
import { Link } from "./Link.js";
|
|
9
10
|
import { ScrollArea } from "./ScrollArea.js";
|
|
10
11
|
import { Text } from "./Text.js";
|
|
11
|
-
export function Screen({ title, parent, actions, children }) {
|
|
12
|
+
export function Screen({ title, eyebrow, parent, actions, width = 'md', children }) {
|
|
12
13
|
const frame = use(FrameContext);
|
|
14
|
+
if (frame?.look === 'plain') {
|
|
15
|
+
return (_jsx(PlainScreen, { title: title, eyebrow: eyebrow, parent: parent, actions: actions, width: width, onToggleSidebar: frame.onToggleSidebar, children: children }));
|
|
16
|
+
}
|
|
13
17
|
return (_jsxs("div", { ...stylex.props(styles.screen), children: [_jsxs("header", { ...stylex.props(styles.header), children: [_jsxs("div", { ...stylex.props(styles.leading), children: [frame === null ? null : (_jsx(IconButton, { icon: "panelLeft", label: "Toggle sidebar", onClick: frame.onToggleSidebar })), _jsxs("div", { ...stylex.props(styles.breadcrumb), children: [parent === undefined ? null : (_jsxs(_Fragment, { children: [_jsx(Link, { to: parent.to, tone: "secondary", children: parent.label }), _jsx(Icon, { name: "chevronRight", tone: "tertiary" })] })), _jsx(Text, { as: "h1", variant: "label", tone: "primary", isTruncated: true, children: title })] })] }), _jsx("div", { ...stylex.props(styles.actions), children: actions })] }), _jsx(ScrollArea, { style: styles.body, children: children })] }));
|
|
14
18
|
}
|
|
15
19
|
const styles = stylex.create({
|
|
@@ -15,7 +15,8 @@ export function Sidebar({ workspace, workspaceMenu, links, isToggled, isAnimatin
|
|
|
15
15
|
interact({ primitive: 'AppFrame', event: 'navigate', label: link.label });
|
|
16
16
|
onFollowLink();
|
|
17
17
|
};
|
|
18
|
-
return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/'
|
|
18
|
+
return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/' ||
|
|
19
|
+
links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
|
|
19
20
|
}
|
|
20
21
|
const styles = stylex.create({
|
|
21
22
|
sidebar: {
|
|
@@ -18,8 +18,10 @@ type TextFieldProps = {
|
|
|
18
18
|
inputMode?: 'text' | 'email' | 'numeric' | 'decimal' | 'tel' | 'url' | 'search';
|
|
19
19
|
maxLength?: number;
|
|
20
20
|
pattern?: string;
|
|
21
|
+
multiline?: boolean;
|
|
22
|
+
rows?: number;
|
|
21
23
|
size?: Size;
|
|
22
24
|
style?: Placement;
|
|
23
25
|
};
|
|
24
|
-
export declare function TextField({ label, isLabelHidden, description, error, onChange, size, style, ...input }: TextFieldProps): import("react").JSX.Element;
|
|
26
|
+
export declare function TextField({ label, isLabelHidden, description, error, onChange, multiline, rows, size, style, ...input }: TextFieldProps): import("react").JSX.Element;
|
|
25
27
|
export {};
|
|
@@ -2,19 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Field } from '@base-ui/react/field';
|
|
3
3
|
import * as stylex from '@stylexjs/stylex';
|
|
4
4
|
import { useRef } from 'react';
|
|
5
|
-
import { borders, colors, fontSizes, fontWeights, lineHeights, motion, shadows, space } from "../theme/tokens.stylex.js";
|
|
5
|
+
import { borders, colors, controls, fontSizes, fontWeights, lineHeights, motion, shadows, space } from "../theme/tokens.stylex.js";
|
|
6
6
|
import { controlSizes } from "./controlLook.js";
|
|
7
7
|
import { interact } from "./interaction.js";
|
|
8
|
-
export function TextField({ label, isLabelHidden = false, description, error, onChange, size = 'md', style, ...input }) {
|
|
8
|
+
export function TextField({ label, isLabelHidden = false, description, error, onChange, multiline = false, rows = 3, size = 'md', style, ...input }) {
|
|
9
9
|
const isInvalid = error !== undefined;
|
|
10
10
|
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, "aria-label": isLabelHidden ? label : undefined, onChange: (event) => onChange?.(event.currentTarget.value), onFocus: (event) => {
|
|
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
12
|
valueOnFocus.current = event.currentTarget.value;
|
|
13
13
|
}, onBlur: (event) => {
|
|
14
14
|
if (event.currentTarget.value !== valueOnFocus.current) {
|
|
15
15
|
interact({ primitive: 'TextField', event: 'change', label });
|
|
16
16
|
}
|
|
17
|
-
}, className: stylex.props(styles.input, controlSizes[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 }))] }));
|
|
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 }))] }));
|
|
18
18
|
}
|
|
19
19
|
const focusRing = `inset 0 0 0 ${borders.hairline} ${colors.brandBorderFocus}, 0 0 0 ${borders.thin} ${colors.brandBorderFocus}, ${shadows.default}`;
|
|
20
20
|
const invalidRing = `inset 0 0 0 ${borders.hairline} ${colors.dangerBorderDefault}, 0 0 0 ${borders.thin} ${colors.dangerBorderDefault}, ${shadows.default}`;
|
|
@@ -49,6 +49,10 @@ const styles = stylex.create({
|
|
|
49
49
|
color: colors.tertiaryForeground
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
multiline: {
|
|
53
|
+
height: 'auto',
|
|
54
|
+
resize: 'vertical'
|
|
55
|
+
},
|
|
52
56
|
invalid: {
|
|
53
57
|
boxShadow: {
|
|
54
58
|
default: invalidRing,
|
|
@@ -75,3 +79,20 @@ const styles = stylex.create({
|
|
|
75
79
|
color: colors.dangerText
|
|
76
80
|
}
|
|
77
81
|
});
|
|
82
|
+
const multilinePaddings = stylex.create({
|
|
83
|
+
xs: {
|
|
84
|
+
paddingBlock: `calc((${controls.heightXs} - ${lineHeights.caption}) / 2)`
|
|
85
|
+
},
|
|
86
|
+
sm: {
|
|
87
|
+
paddingBlock: `calc((${controls.heightSm} - ${lineHeights.bodySm}) / 2)`
|
|
88
|
+
},
|
|
89
|
+
md: {
|
|
90
|
+
paddingBlock: `calc((${controls.heightMd} - ${lineHeights.bodySm}) / 2)`
|
|
91
|
+
},
|
|
92
|
+
lg: {
|
|
93
|
+
paddingBlock: `calc((${controls.heightLg} - ${lineHeights.bodySm}) / 2)`
|
|
94
|
+
},
|
|
95
|
+
xl: {
|
|
96
|
+
paddingBlock: `calc((${controls.heightXl} - ${lineHeights.bodySm}) / 2)`
|
|
97
|
+
}
|
|
98
|
+
});
|
|
@@ -43,6 +43,9 @@ declare const builtInIcons: {
|
|
|
43
43
|
copy: string[];
|
|
44
44
|
arrowDown: string;
|
|
45
45
|
logOut: string[];
|
|
46
|
+
today: string;
|
|
47
|
+
checklist: string;
|
|
48
|
+
chart: string;
|
|
46
49
|
};
|
|
47
50
|
export declare const builtInIconNames: string[];
|
|
48
51
|
export declare function defineIcons<const Icons extends Record<string, IconDrawing>>(icons: Icons): Icons;
|
package/dist/components/icons.js
CHANGED
|
@@ -87,7 +87,10 @@ const builtInIcons = {
|
|
|
87
87
|
logOut: [
|
|
88
88
|
'M14 4.5H7.5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2H14',
|
|
89
89
|
'M19.5 12H10M16 8.5l3.5 3.5-3.5 3.5'
|
|
90
|
-
]
|
|
90
|
+
],
|
|
91
|
+
today: 'M11.998 3.291V1.768M5.84 18.159L4.763 19.236M11.998 22.233V20.709M19.233 4.765L18.156 5.842M20.707 12H22.23M18.156 18.159L19.233 19.236M1.766 12H3.289M4.763 4.765L5.84 5.842M15.71 8.288C17.761 10.338 17.761 13.662 15.71 15.712C13.66 17.763 10.336 17.763 8.286 15.712C6.235 13.662 6.235 10.338 8.286 8.288C10.336 6.238 13.66 6.238 15.71 8.288Z',
|
|
92
|
+
checklist: 'M11.75 16.25H20.25M11.75 7.75H20.25M3.75 8.625L5.417 9.75L8.75 5.25M3.75 17.625L5.417 18.75L8.75 14.25',
|
|
93
|
+
chart: 'M2.75 4.75V17.25C2.75 18.355 3.645 19.25 4.75 19.25H21.25M6.75 15.25L10 12C10.552 11.448 11.448 11.448 12 12C12.552 12.552 13.448 12.552 14 12L18.75 7.25M14.75 6.75H19.25V11.25'
|
|
91
94
|
};
|
|
92
95
|
const builtIns = new Map(Object.entries(builtInIcons));
|
|
93
96
|
let appIcons = new Map();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
export type Interaction = {
|
|
3
|
-
primitive: 'AppFrame' | 'Button' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup';
|
|
3
|
+
primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup';
|
|
4
4
|
event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
|
|
5
5
|
label: string;
|
|
6
6
|
page: string | null;
|
package/dist/entityStore.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type EntityDeps<T extends Entity> = {
|
|
|
30
30
|
api: EntityApi<T>;
|
|
31
31
|
cache: Cached<readonly T[]>;
|
|
32
32
|
writes: Writes;
|
|
33
|
+
initial?: readonly T[] | undefined;
|
|
33
34
|
};
|
|
34
35
|
export declare class WriteRefused extends Error {
|
|
35
36
|
readonly outcome: Exclude<WriteOutcome, 'saved' | 'failed'>;
|
package/dist/entityStore.js
CHANGED
|
@@ -54,7 +54,7 @@ export class EntityStore extends StoreBase {
|
|
|
54
54
|
#generation = 0;
|
|
55
55
|
#loading;
|
|
56
56
|
initialState() {
|
|
57
|
-
const cached = this.deps.cache.read();
|
|
57
|
+
const cached = this.deps.initial ?? this.deps.cache.read();
|
|
58
58
|
return {
|
|
59
59
|
status: cached === undefined ? 'idle' : 'ready',
|
|
60
60
|
items: cached ?? [],
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type { MemoryApiOptions } from './memoryApi.ts';
|
|
|
35
35
|
export { StoreBase, keepEqualParts, snapshotsOf } from './store.ts';
|
|
36
36
|
export type { Snapshots, StoreClass, StoreConfig } from './store.ts';
|
|
37
37
|
export type { Json, Persisted, StateOf, StateSpec } from './state.ts';
|
|
38
|
+
export { imageSources, media, mediaPath } from './media.ts';
|
|
39
|
+
export type { ImageSources, Media, MediaImage } from './media.ts';
|
|
38
40
|
export { defineTables } from './table.ts';
|
|
39
41
|
export type { Access, Columns, Fields, Level, PublicRow, Row, Seed, Table, TableDefinition, TableOf, Tables, Value, Writes } from './table.ts';
|
|
40
42
|
export { HttpTableApi, MemoryTableApi, PublicTableStore, TableStore, httpTables, memoryTables, publicStoreName, tableCalls } from './tables.ts';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { EntityStore, WriteRefused, entityCalls } from "./entityStore.js";
|
|
|
16
16
|
export { Gate } from "./gate.js";
|
|
17
17
|
export { MemoryEntityApi, MemoryServer } from "./memoryApi.js";
|
|
18
18
|
export { StoreBase, keepEqualParts, snapshotsOf } from "./store.js";
|
|
19
|
+
export { imageSources, media, mediaPath } from "./media.js";
|
|
19
20
|
export { defineTables } from "./table.js";
|
|
20
21
|
export { HttpTableApi, MemoryTableApi, PublicTableStore, TableStore, httpTables, memoryTables, publicStoreName, tableCalls } from "./tables.js";
|
|
21
22
|
export { ModelBase, command, commandsOf, createModels, modelSnapshotsOf, parseMessage, sendMessage, splitType } from "./model.js";
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Row } from './table.ts';
|
|
3
|
+
export declare const media: import("./table.ts").TableOf<"media", {
|
|
4
|
+
readonly columns: {
|
|
5
|
+
readonly key: z.ZodString;
|
|
6
|
+
readonly width: z.ZodNumber;
|
|
7
|
+
readonly height: z.ZodNumber;
|
|
8
|
+
readonly placeholder: z.ZodNullable<z.ZodString>;
|
|
9
|
+
readonly alt: z.ZodString;
|
|
10
|
+
readonly type: z.ZodString;
|
|
11
|
+
readonly bytes: z.ZodNumber;
|
|
12
|
+
};
|
|
13
|
+
readonly access: {
|
|
14
|
+
readonly read: "anyone";
|
|
15
|
+
readonly create: "server";
|
|
16
|
+
readonly update: "admin";
|
|
17
|
+
readonly delete: "admin";
|
|
18
|
+
};
|
|
19
|
+
readonly writes: "confirmed";
|
|
20
|
+
}>;
|
|
21
|
+
export type Media = Row<typeof media>;
|
|
22
|
+
export type MediaImage = Pick<Media, 'key' | 'width' | 'height' | 'placeholder' | 'alt'>;
|
|
23
|
+
export declare const MEDIA_LIMIT_BYTES: number;
|
|
24
|
+
export declare const IMAGE_WIDTHS: readonly [320, 640, 960, 1280, 1920];
|
|
25
|
+
export declare function mediaPath(key: string): string;
|
|
26
|
+
export declare function transformsOn(hostname: string): boolean;
|
|
27
|
+
export type ImageSources = {
|
|
28
|
+
src: string;
|
|
29
|
+
srcSet: string | undefined;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function imageSources(image: MediaImage, { aspect, transforms }: {
|
|
34
|
+
aspect?: number | undefined;
|
|
35
|
+
transforms: boolean;
|
|
36
|
+
}): ImageSources;
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { patternTable } from "./table.js";
|
|
3
|
+
export const media = patternTable('media', {
|
|
4
|
+
columns: {
|
|
5
|
+
key: z.string(),
|
|
6
|
+
width: z.number().int(),
|
|
7
|
+
height: z.number().int(),
|
|
8
|
+
placeholder: z.string().nullable(),
|
|
9
|
+
alt: z.string().min(1),
|
|
10
|
+
type: z.string(),
|
|
11
|
+
bytes: z.number().int()
|
|
12
|
+
},
|
|
13
|
+
access: {
|
|
14
|
+
read: 'anyone',
|
|
15
|
+
create: 'server',
|
|
16
|
+
update: 'admin',
|
|
17
|
+
delete: 'admin'
|
|
18
|
+
},
|
|
19
|
+
writes: 'confirmed'
|
|
20
|
+
});
|
|
21
|
+
export const MEDIA_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
22
|
+
export const IMAGE_WIDTHS = [320, 640, 960, 1280, 1920];
|
|
23
|
+
const QUALITY = 82;
|
|
24
|
+
const FALLBACK_WIDTH = 960;
|
|
25
|
+
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
|
|
26
|
+
export function mediaPath(key) {
|
|
27
|
+
return `/media/${key}`;
|
|
28
|
+
}
|
|
29
|
+
export function transformsOn(hostname) {
|
|
30
|
+
return !LOCAL_HOSTS.has(hostname) && !hostname.endsWith('.localhost');
|
|
31
|
+
}
|
|
32
|
+
function largestWidth(image, aspect) {
|
|
33
|
+
return Math.max(1, Math.floor(aspect === undefined
|
|
34
|
+
? image.width
|
|
35
|
+
: Math.min(image.width, image.height * aspect)));
|
|
36
|
+
}
|
|
37
|
+
function transformed(key, width, aspect) {
|
|
38
|
+
const options = [
|
|
39
|
+
`width=${width}`,
|
|
40
|
+
...(aspect === undefined
|
|
41
|
+
? []
|
|
42
|
+
: [`height=${Math.max(1, Math.round(width / aspect))}`]),
|
|
43
|
+
`quality=${QUALITY}`,
|
|
44
|
+
'format=auto',
|
|
45
|
+
`fit=${aspect === undefined ? 'scale-down' : 'cover'}`
|
|
46
|
+
];
|
|
47
|
+
return `/cdn-cgi/image/${options.join(',')}${mediaPath(key)}`;
|
|
48
|
+
}
|
|
49
|
+
export function imageSources(image, { aspect, transforms }) {
|
|
50
|
+
const largest = largestWidth(image, aspect);
|
|
51
|
+
const height = aspect === undefined
|
|
52
|
+
? image.height
|
|
53
|
+
: Math.max(1, Math.round(largest / aspect));
|
|
54
|
+
if (!transforms) {
|
|
55
|
+
return {
|
|
56
|
+
src: mediaPath(image.key),
|
|
57
|
+
srcSet: undefined,
|
|
58
|
+
width: largest,
|
|
59
|
+
height
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const widths = [
|
|
63
|
+
...IMAGE_WIDTHS.filter((width) => width < largest),
|
|
64
|
+
largest
|
|
65
|
+
].filter((width, index, all) => all.indexOf(width) === index);
|
|
66
|
+
return {
|
|
67
|
+
src: transformed(image.key, Math.min(FALLBACK_WIDTH, largest), aspect),
|
|
68
|
+
srcSet: widths
|
|
69
|
+
.map((width) => `${transformed(image.key, width, aspect)} ${width}w`)
|
|
70
|
+
.join(', '),
|
|
71
|
+
width: largest,
|
|
72
|
+
height
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const TABLES_READ = "tables:read";
|
|
3
|
+
export declare const ASSERTION_SECONDS = 60;
|
|
4
|
+
export declare const NEXT_PAGE_HEADER = "x-pattern-next";
|
|
5
|
+
export declare const assertionKey: z.ZodObject<{
|
|
6
|
+
kid: z.ZodString;
|
|
7
|
+
kty: z.ZodLiteral<"OKP">;
|
|
8
|
+
crv: z.ZodLiteral<"Ed25519">;
|
|
9
|
+
x: z.ZodString;
|
|
10
|
+
d: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type AssertionKey = z.output<typeof assertionKey>;
|
|
13
|
+
export declare function signTablesAssertion(key: AssertionKey, { sub, aud }: {
|
|
14
|
+
sub: string;
|
|
15
|
+
aud: string;
|
|
16
|
+
}, now: number): Promise<string>;
|
|
17
|
+
export declare function verifiedTablesAssertion(token: string, { hostname, keys, now }: {
|
|
18
|
+
hostname: string;
|
|
19
|
+
keys: Readonly<Record<string, string>>;
|
|
20
|
+
now: number;
|
|
21
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const TABLES_READ = 'tables:read';
|
|
3
|
+
export const ASSERTION_SECONDS = 60;
|
|
4
|
+
export const NEXT_PAGE_HEADER = 'x-pattern-next';
|
|
5
|
+
const CLOCK_SKEW_SECONDS = 30;
|
|
6
|
+
const ED25519 = { name: 'Ed25519' };
|
|
7
|
+
export const assertionKey = z.object({
|
|
8
|
+
kid: z.string().min(1),
|
|
9
|
+
kty: z.literal('OKP'),
|
|
10
|
+
crv: z.literal('Ed25519'),
|
|
11
|
+
x: z.string(),
|
|
12
|
+
d: z.string()
|
|
13
|
+
});
|
|
14
|
+
const assertionHeader = z.object({
|
|
15
|
+
alg: z.literal('EdDSA'),
|
|
16
|
+
typ: z.literal('JWT'),
|
|
17
|
+
kid: z.string()
|
|
18
|
+
});
|
|
19
|
+
const assertionClaims = z.object({
|
|
20
|
+
sub: z.email(),
|
|
21
|
+
aud: z.string(),
|
|
22
|
+
scope: z.literal(TABLES_READ),
|
|
23
|
+
iat: z.int(),
|
|
24
|
+
exp: z.int()
|
|
25
|
+
});
|
|
26
|
+
const encoder = new TextEncoder();
|
|
27
|
+
function base64url(bytes) {
|
|
28
|
+
return btoa(String.fromCharCode(...bytes))
|
|
29
|
+
.replaceAll('+', '-')
|
|
30
|
+
.replaceAll('/', '_')
|
|
31
|
+
.replaceAll('=', '');
|
|
32
|
+
}
|
|
33
|
+
function bytesOf(text) {
|
|
34
|
+
try {
|
|
35
|
+
const binary = atob(text.replaceAll('-', '+').replaceAll('_', '/'));
|
|
36
|
+
return Uint8Array.from(binary, (letter) => letter.charCodeAt(0));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function jsonOf(text) {
|
|
43
|
+
const bytes = bytesOf(text);
|
|
44
|
+
if (bytes === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function encoded(value) {
|
|
54
|
+
return base64url(encoder.encode(JSON.stringify(value)));
|
|
55
|
+
}
|
|
56
|
+
export async function signTablesAssertion(key, { sub, aud }, now) {
|
|
57
|
+
const privateKey = await crypto.subtle.importKey('jwk', { kty: key.kty, crv: key.crv, x: key.x, d: key.d }, ED25519, false, ['sign']);
|
|
58
|
+
const signed = `${encoded({ alg: 'EdDSA', typ: 'JWT', kid: key.kid })}.${encoded({
|
|
59
|
+
sub,
|
|
60
|
+
aud,
|
|
61
|
+
scope: TABLES_READ,
|
|
62
|
+
iat: now,
|
|
63
|
+
exp: now + ASSERTION_SECONDS
|
|
64
|
+
})}`;
|
|
65
|
+
const signature = await crypto.subtle.sign(ED25519, privateKey, encoder.encode(signed));
|
|
66
|
+
return `${signed}.${base64url(new Uint8Array(signature))}`;
|
|
67
|
+
}
|
|
68
|
+
async function isSignedBy(publicKey, signed, signature) {
|
|
69
|
+
const keyBytes = bytesOf(publicKey);
|
|
70
|
+
const signatureBytes = bytesOf(signature);
|
|
71
|
+
if (keyBytes === undefined || signatureBytes === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
try {
|
|
74
|
+
const key = await crypto.subtle.importKey('raw', keyBytes, ED25519, false, [
|
|
75
|
+
'verify'
|
|
76
|
+
]);
|
|
77
|
+
return await crypto.subtle.verify(ED25519, key, signatureBytes, encoder.encode(signed));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function verifiedTablesAssertion(token, { hostname, keys, now }) {
|
|
84
|
+
const [header, claims, signature, ...rest] = token.split('.');
|
|
85
|
+
if (header === undefined ||
|
|
86
|
+
claims === undefined ||
|
|
87
|
+
signature === undefined ||
|
|
88
|
+
rest.length > 0) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const parsedHeader = assertionHeader.safeParse(jsonOf(header));
|
|
92
|
+
if (!parsedHeader.success)
|
|
93
|
+
return undefined;
|
|
94
|
+
const { kid } = parsedHeader.data;
|
|
95
|
+
const publicKey = Object.hasOwn(keys, kid) ? keys[kid] : undefined;
|
|
96
|
+
if (publicKey === undefined)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (!(await isSignedBy(publicKey, `${header}.${claims}`, signature))) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const parsed = assertionClaims.safeParse(jsonOf(claims));
|
|
102
|
+
if (!parsed.success)
|
|
103
|
+
return undefined;
|
|
104
|
+
const { sub, aud, iat, exp } = parsed.data;
|
|
105
|
+
const isFresh = exp > iat &&
|
|
106
|
+
exp - iat <= ASSERTION_SECONDS &&
|
|
107
|
+
iat <= now + CLOCK_SKEW_SECONDS &&
|
|
108
|
+
now < exp;
|
|
109
|
+
return aud === hostname && isFresh ? sub.toLowerCase() : undefined;
|
|
110
|
+
}
|