@trypattern/sdk 0.1.9 → 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.
Files changed (70) hide show
  1. package/README.md +37 -8
  2. package/dist/app.d.ts +1 -0
  3. package/dist/app.js +4 -3
  4. package/dist/cli/app.js +119 -6
  5. package/dist/cli/publishing.js +2 -0
  6. package/dist/components/AppFrame.d.ts +4 -1
  7. package/dist/components/AppFrame.js +25 -7
  8. package/dist/components/Card.d.ts +7 -2
  9. package/dist/components/Card.js +59 -3
  10. package/dist/components/Checkbox.d.ts +10 -0
  11. package/dist/components/Checkbox.js +59 -0
  12. package/dist/components/Choice.d.ts +15 -0
  13. package/dist/components/Choice.js +80 -0
  14. package/dist/components/ChoiceGroup.d.ts +11 -0
  15. package/dist/components/ChoiceGroup.js +44 -0
  16. package/dist/components/Icon.d.ts +2 -1
  17. package/dist/components/Icon.js +2 -2
  18. package/dist/components/Image.d.ts +11 -0
  19. package/dist/components/Image.js +76 -0
  20. package/dist/components/PlainScreen.d.ts +16 -0
  21. package/dist/components/PlainScreen.js +80 -0
  22. package/dist/components/PlainSidebar.d.ts +15 -0
  23. package/dist/components/PlainSidebar.js +179 -0
  24. package/dist/components/Screen.d.ts +3 -1
  25. package/dist/components/Screen.js +5 -1
  26. package/dist/components/TextField.d.ts +3 -1
  27. package/dist/components/TextField.js +25 -4
  28. package/dist/components/choiceContext.d.ts +4 -0
  29. package/dist/components/choiceContext.js +7 -0
  30. package/dist/components/frame.d.ts +2 -0
  31. package/dist/components/icons.d.ts +3 -0
  32. package/dist/components/icons.js +4 -1
  33. package/dist/components/interaction.d.ts +1 -1
  34. package/dist/entityStore.d.ts +1 -0
  35. package/dist/entityStore.js +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +1 -0
  38. package/dist/media.d.ts +36 -0
  39. package/dist/media.js +74 -0
  40. package/dist/publishWire.d.ts +2 -0
  41. package/dist/publishWire.js +21 -1
  42. package/dist/react.d.ts +5 -0
  43. package/dist/react.js +4 -0
  44. package/dist/rules/checkApiRouting.js +1 -1
  45. package/dist/rules/lintPlugin.js +13 -4
  46. package/dist/rules/messages.d.ts +1 -0
  47. package/dist/rules/messages.js +1 -0
  48. package/dist/schema.d.ts +2 -0
  49. package/dist/schema.js +1 -0
  50. package/dist/server/createServer.d.ts +5 -3
  51. package/dist/server/createServer.js +32 -2
  52. package/dist/server/env.d.ts +3 -1
  53. package/dist/server/imageHeader.d.ts +10 -0
  54. package/dist/server/imageHeader.js +159 -0
  55. package/dist/server/index.d.ts +6 -1
  56. package/dist/server/index.js +3 -0
  57. package/dist/server/jsonLd.d.ts +45 -0
  58. package/dist/server/jsonLd.js +88 -0
  59. package/dist/server/media.d.ts +16 -0
  60. package/dist/server/media.js +135 -0
  61. package/dist/server/pages.d.ts +72 -0
  62. package/dist/server/pages.js +301 -0
  63. package/dist/server/tables.d.ts +3 -0
  64. package/dist/server/tables.js +23 -8
  65. package/dist/start.js +26 -1
  66. package/dist/table.d.ts +1 -0
  67. package/dist/table.js +8 -1
  68. package/dist/tables.d.ts +2 -1
  69. package/dist/tables.js +10 -1
  70. package/package.json +1 -1
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Radio as BaseRadio } from '@base-ui/react/radio';
3
+ import * as stylex from '@stylexjs/stylex';
4
+ import { borders, colors, fontSizes, fontWeights, lineHeights, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
5
+ import { useChosen } from "./choiceContext.js";
6
+ import { focusRing } from "./controlLook.js";
7
+ export function Choice({ value, title, label, subline, isDisabled = false, children }) {
8
+ const chosen = useChosen();
9
+ const isFaded = chosen !== null && chosen !== value;
10
+ const hasWords = title !== undefined || subline !== undefined;
11
+ return (_jsxs(BaseRadio.Root, { value: value, disabled: isDisabled, "aria-label": label, className: (state) => stylex.props(styles.choice, children !== undefined && !hasWords && styles.media, !state.disabled && styles.enabled, state.checked && styles.checked, isFaded && !state.checked && !state.disabled && styles.faded, state.disabled && styles.disabled, focusRing.visible).className, children: [children === undefined ? null : (_jsx("span", { ...stylex.props(styles.content), children: children })), title === undefined ? null : (_jsx("span", { ...stylex.props(styles.title), children: title })), subline === undefined ? null : (_jsx("span", { ...stylex.props(styles.subline, isDisabled && styles.sublineDisabled), children: subline }))] }));
12
+ }
13
+ const checkedRing = `inset 0 0 0 ${borders.thin} ${colors.borderInverted}, 0 0 0 ${borders.thin} ${colors.borderInverted}`;
14
+ const styles = stylex.create({
15
+ choice: {
16
+ display: 'flex',
17
+ flexDirection: 'column',
18
+ gap: space.xxs,
19
+ alignItems: 'flex-start',
20
+ minWidth: 0,
21
+ paddingBlock: space.sm,
22
+ paddingInline: space.md,
23
+ fontSize: fontSizes.bodySm,
24
+ fontWeight: fontWeights.medium,
25
+ lineHeight: lineHeights.bodySm,
26
+ color: colors.primaryForeground,
27
+ textAlign: 'start',
28
+ outlineOffset: 0,
29
+ backgroundColor: colors.componentOnContainerStrong,
30
+ borderRadius: radii.lg,
31
+ boxShadow: shadows.default,
32
+ transitionTimingFunction: motion.easeOut,
33
+ transitionDuration: motion.fast,
34
+ transitionProperty: 'background-color, box-shadow, opacity'
35
+ },
36
+ media: {
37
+ flexShrink: 0,
38
+ width: `calc(2 * ${space.xxxl})`,
39
+ padding: space.xs
40
+ },
41
+ enabled: {
42
+ backgroundColor: {
43
+ default: colors.componentOnContainerStrong,
44
+ ':hover': colors.componentOnContainerStrongHover
45
+ }
46
+ },
47
+ checked: {
48
+ boxShadow: checkedRing
49
+ },
50
+ faded: {
51
+ opacity: {
52
+ default: 0.56,
53
+ ':hover': 1
54
+ }
55
+ },
56
+ disabled: {
57
+ color: colors.disabledForeground,
58
+ backgroundColor: colors.disabled,
59
+ boxShadow: 'none'
60
+ },
61
+ content: {
62
+ display: 'block',
63
+ alignSelf: 'stretch',
64
+ minWidth: 0,
65
+ overflow: 'hidden',
66
+ borderRadius: radii.md
67
+ },
68
+ title: {
69
+ minWidth: 0
70
+ },
71
+ subline: {
72
+ fontSize: fontSizes.captionLg,
73
+ fontWeight: fontWeights.normal,
74
+ lineHeight: lineHeights.caption,
75
+ color: colors.secondaryForeground
76
+ },
77
+ sublineDisabled: {
78
+ color: colors.disabledForeground
79
+ }
80
+ });
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Placement } from './Placement.ts';
3
+ type ChoiceGroupProps<Value extends string> = {
4
+ label: string;
5
+ value: Value | null;
6
+ onChange: (value: Value) => void;
7
+ children: ReactNode;
8
+ style?: Placement;
9
+ };
10
+ export declare function ChoiceGroup<Value extends string>({ label, value, onChange, children, style }: ChoiceGroupProps<Value>): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
3
+ import * as stylex from '@stylexjs/stylex';
4
+ import { Children, isValidElement } from 'react';
5
+ import { space } from "../theme/tokens.stylex.js";
6
+ import { ChoiceContext } from "./choiceContext.js";
7
+ import { interact, textOf } from "./interaction.js";
8
+ export function ChoiceGroup({ label, value, onChange, children, style }) {
9
+ return (_jsx(ChoiceContext, { value: { value }, children: _jsx(BaseRadioGroup, { "aria-label": label, value: value, onValueChange: (next) => {
10
+ if (typeof next !== 'string')
11
+ return;
12
+ interact({
13
+ primitive: 'ChoiceGroup',
14
+ event: 'change',
15
+ label: labelOf(children, next)
16
+ });
17
+ onChange(next);
18
+ }, className: stylex.props(styles.group, style).className, children: children }) }));
19
+ }
20
+ function labelOf(children, value) {
21
+ for (const child of Children.toArray(children)) {
22
+ if (!isValidElement(child))
23
+ continue;
24
+ const { props } = child;
25
+ if (props['value'] !== value)
26
+ continue;
27
+ for (const name of ['title', 'label']) {
28
+ const text = props[name];
29
+ if (typeof text === 'string' && text !== '')
30
+ return text;
31
+ }
32
+ return textOf(props['children']) || value;
33
+ }
34
+ return value;
35
+ }
36
+ const styles = stylex.create({
37
+ group: {
38
+ display: 'flex',
39
+ flexWrap: 'wrap',
40
+ gap: space.sm,
41
+ alignItems: 'stretch',
42
+ minWidth: 0
43
+ }
44
+ });
@@ -5,6 +5,7 @@ type IconProps = {
5
5
  name: IconName;
6
6
  size?: Size;
7
7
  tone?: Tone;
8
+ weight?: 'regular' | 'light';
8
9
  };
9
- export declare function Icon({ name, size, tone }: IconProps): import("react").JSX.Element;
10
+ export declare function Icon({ name, size, tone, weight }: IconProps): import("react").JSX.Element;
10
11
  export {};
@@ -3,9 +3,9 @@ import * as stylex from '@stylexjs/stylex';
3
3
  import { iconSizes } from "../theme/tokens.stylex.js";
4
4
  import { shapeOf } from "./icons.js";
5
5
  import { tones } from "./tones.js";
6
- export function Icon({ name, size = 'md', tone = 'inherit' }) {
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: "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) => (_jsx("path", { d: path }, path))) }));
9
9
  }
10
10
  const styles = stylex.create({
11
11
  icon: {
@@ -0,0 +1,11 @@
1
+ import type { MediaImage } from '../media.ts';
2
+ import type { Placement } from './Placement.ts';
3
+ type ImageProps = {
4
+ image: MediaImage | null | undefined;
5
+ sizes: string;
6
+ priority?: boolean;
7
+ aspect?: number;
8
+ style?: Placement;
9
+ };
10
+ export declare function Image({ image, sizes, priority, aspect, style }: ImageProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { useState } from 'react';
4
+ import { imageSources, transformsOn } from "../media.js";
5
+ import { colors, motion } from "../theme/tokens.stylex.js";
6
+ export function Image({ image, sizes, priority = false, aspect, style }) {
7
+ const key = image?.key ?? null;
8
+ const [seen, setSeen] = useState({
9
+ key,
10
+ status: 'loading'
11
+ });
12
+ const status = seen.key === key ? seen.status : 'loading';
13
+ if (image === null || image === undefined) {
14
+ return (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.frame, styles.ratio(aspect ?? MISSING_ASPECT), style) }));
15
+ }
16
+ const ratio = aspect ?? image.width / image.height;
17
+ if (status === 'failed') {
18
+ return (_jsx("span", { role: "img", "aria-label": image.alt, ...stylex.props(styles.frame, styles.ratio(ratio), style) }));
19
+ }
20
+ const sources = imageSources(image, {
21
+ aspect,
22
+ transforms: servedWithTransforms()
23
+ });
24
+ return (_jsxs("span", { ...stylex.props(styles.frame, styles.ratio(ratio), style), children: [image.placeholder === null ? null : (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.placeholder(image.placeholder)) })), _jsx("img", { ref: (element) => {
25
+ if (status === 'loading' &&
26
+ element !== null &&
27
+ element.complete &&
28
+ element.naturalWidth > 0) {
29
+ setSeen({ key, status: 'shown' });
30
+ }
31
+ }, alt: image.alt, width: sources.width, height: sources.height, loading: priority ? 'eager' : 'lazy', decoding: priority ? 'auto' : 'async', fetchPriority: priority ? 'high' : 'auto', sizes: sizes, srcSet: sources.srcSet, src: sources.src, onLoad: () => setSeen((current) => current.key === key && current.status === 'shown'
32
+ ? current
33
+ : { key, status: 'fading' }), onError: () => setSeen({ key, status: 'failed' }), ...stylex.props(styles.image, status === 'fading' && styles.fading, status === 'shown' && styles.shown) })] }));
34
+ }
35
+ const MISSING_ASPECT = 4 / 3;
36
+ function servedWithTransforms() {
37
+ return typeof location !== 'undefined' && transformsOn(location.hostname);
38
+ }
39
+ const styles = stylex.create({
40
+ frame: {
41
+ position: 'relative',
42
+ display: 'block',
43
+ width: '100%',
44
+ overflow: 'hidden',
45
+ backgroundColor: colors.component
46
+ },
47
+ ratio: (ratio) => ({
48
+ aspectRatio: String(ratio)
49
+ }),
50
+ placeholder: (url) => ({
51
+ position: 'absolute',
52
+ inset: 0,
53
+ backgroundImage: `url("${url}")`,
54
+ backgroundPosition: 'center',
55
+ backgroundSize: 'cover',
56
+ filter: 'blur(16px)',
57
+ transform: 'scale(1.1)'
58
+ }),
59
+ image: {
60
+ position: 'relative',
61
+ display: 'block',
62
+ width: '100%',
63
+ height: '100%',
64
+ objectFit: 'cover',
65
+ opacity: 0
66
+ },
67
+ fading: {
68
+ opacity: 1,
69
+ transitionTimingFunction: motion.easeOut,
70
+ transitionDuration: motion.base,
71
+ transitionProperty: 'opacity'
72
+ },
73
+ shown: {
74
+ opacity: 1
75
+ }
76
+ });
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AppPath } from '../registered.ts';
3
+ type PlainScreenProps = {
4
+ title: string;
5
+ eyebrow: string | undefined;
6
+ parent: {
7
+ label: string;
8
+ to: AppPath;
9
+ } | undefined;
10
+ actions: ReactNode;
11
+ width: 'xs' | 'sm' | 'md' | 'lg' | 'full';
12
+ onToggleSidebar: () => void;
13
+ children: ReactNode;
14
+ };
15
+ export declare function PlainScreen({ title, eyebrow, parent, actions, width, onToggleSidebar, children }: PlainScreenProps): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { breakpoints, controls, space, widths } from "../theme/tokens.stylex.js";
4
+ import { Icon } from "./Icon.js";
5
+ import { IconButton } from "./IconButton.js";
6
+ import { Link } from "./Link.js";
7
+ import { ScrollArea } from "./ScrollArea.js";
8
+ import { Text } from "./Text.js";
9
+ export function PlainScreen({ title, eyebrow, parent, actions, width, onToggleSidebar, children }) {
10
+ return (_jsxs("div", { ...stylex.props(styles.screen), children: [_jsx("div", { ...stylex.props(styles.bar), children: _jsx(IconButton, { icon: "panelLeft", label: "Toggle sidebar", onClick: onToggleSidebar }) }), _jsx(ScrollArea, { style: styles.body, children: _jsx("div", { ...stylex.props(styles.page), children: _jsxs("div", { ...stylex.props(styles.column, columnWidths[width]), children: [_jsxs("header", { ...stylex.props(styles.header), children: [_jsxs("div", { ...stylex.props(styles.heading), children: [parent === undefined ? null : (_jsxs("span", { ...stylex.props(styles.parent), children: [_jsx(Link, { to: parent.to, tone: "secondary", children: parent.label }), _jsx(Icon, { name: "chevronRight", tone: "tertiary" })] })), parent !== undefined || eyebrow === undefined ? null : (_jsx(Text, { tone: "secondary", children: eyebrow })), _jsx(Text, { as: "h1", variant: "heading", tone: "primary", isSelectable: true, children: title })] }), actions === undefined || actions === null ? null : (_jsx("div", { ...stylex.props(styles.actions), children: actions }))] }), children] }) }) })] }));
11
+ }
12
+ const styles = stylex.create({
13
+ screen: {
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ height: '100%'
17
+ },
18
+ bar: {
19
+ display: {
20
+ default: 'none',
21
+ [breakpoints.compact]: 'flex'
22
+ },
23
+ flexShrink: 0,
24
+ alignItems: 'center',
25
+ height: `calc(${controls.heightXl} + ${space.sm})`,
26
+ paddingInline: space.sm
27
+ },
28
+ body: {
29
+ flexGrow: 1,
30
+ height: 'auto'
31
+ },
32
+ page: {
33
+ paddingInline: {
34
+ default: space.xxxl,
35
+ [breakpoints.compact]: space.lg
36
+ },
37
+ paddingTop: {
38
+ default: `calc(${space.xxxl} + ${space.sm})`,
39
+ [breakpoints.compact]: space.sm
40
+ },
41
+ paddingBottom: `calc(${space.xxxl} * 2)`
42
+ },
43
+ column: {
44
+ display: 'flex',
45
+ flexDirection: 'column',
46
+ gap: space.xl,
47
+ width: '100%',
48
+ marginInline: 'auto'
49
+ },
50
+ header: {
51
+ display: 'flex',
52
+ gap: space.md,
53
+ alignItems: 'flex-end',
54
+ justifyContent: 'space-between'
55
+ },
56
+ heading: {
57
+ display: 'flex',
58
+ flexDirection: 'column',
59
+ gap: space.xxs,
60
+ minWidth: 0
61
+ },
62
+ parent: {
63
+ display: 'flex',
64
+ gap: space.xs,
65
+ alignItems: 'center'
66
+ },
67
+ actions: {
68
+ display: 'flex',
69
+ flexShrink: 0,
70
+ gap: space.sm,
71
+ alignItems: 'center'
72
+ }
73
+ });
74
+ const columnWidths = stylex.create({
75
+ xs: { maxWidth: widths.contentXs },
76
+ sm: { maxWidth: widths.contentSm },
77
+ md: { maxWidth: widths.contentMd },
78
+ lg: { maxWidth: widths.contentLg },
79
+ full: { maxWidth: 'none' }
80
+ });
@@ -0,0 +1,15 @@
1
+ import type { User } from '../auth/user.ts';
2
+ import type { MenuEntry } from './MenuItems.tsx';
3
+ import type { SidebarLink } from './Sidebar.tsx';
4
+ type PlainSidebarProps = {
5
+ title: string;
6
+ user: User | null;
7
+ accountMenu: readonly MenuEntry[];
8
+ links: readonly SidebarLink[];
9
+ isToggled: boolean;
10
+ isAnimating: boolean;
11
+ onFollowLink: () => void;
12
+ onSearch: (() => void) | undefined;
13
+ };
14
+ export declare function PlainSidebar({ title, user, accountMenu, links, isToggled, isAnimating, onFollowLink, onSearch }: PlainSidebarProps): import("react").JSX.Element;
15
+ export {};
@@ -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({
@@ -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
+ });
@@ -0,0 +1,4 @@
1
+ export declare const ChoiceContext: import("react").Context<{
2
+ value: string | null;
3
+ }>;
4
+ export declare function useChosen(): string | null;