@trypattern/sdk 0.1.9 → 0.1.11

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 (77) 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/ListRow.d.ts +3 -1
  21. package/dist/components/ListRow.js +18 -4
  22. package/dist/components/PlainScreen.d.ts +16 -0
  23. package/dist/components/PlainScreen.js +80 -0
  24. package/dist/components/PlainSidebar.d.ts +15 -0
  25. package/dist/components/PlainSidebar.js +179 -0
  26. package/dist/components/Screen.d.ts +3 -1
  27. package/dist/components/Screen.js +5 -1
  28. package/dist/components/ScrollArea.d.ts +2 -1
  29. package/dist/components/ScrollArea.js +7 -2
  30. package/dist/components/Text.d.ts +1 -1
  31. package/dist/components/Text.js +10 -4
  32. package/dist/components/TextField.d.ts +5 -1
  33. package/dist/components/TextField.js +47 -9
  34. package/dist/components/choiceContext.d.ts +4 -0
  35. package/dist/components/choiceContext.js +7 -0
  36. package/dist/components/frame.d.ts +2 -0
  37. package/dist/components/icons.d.ts +3 -0
  38. package/dist/components/icons.js +4 -1
  39. package/dist/components/interaction.d.ts +1 -1
  40. package/dist/entityStore.d.ts +1 -0
  41. package/dist/entityStore.js +1 -1
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +1 -0
  44. package/dist/media.d.ts +36 -0
  45. package/dist/media.js +74 -0
  46. package/dist/publishWire.d.ts +2 -0
  47. package/dist/publishWire.js +21 -1
  48. package/dist/react.d.ts +5 -0
  49. package/dist/react.js +4 -0
  50. package/dist/rules/checkApiRouting.js +1 -1
  51. package/dist/rules/lintPlugin.js +13 -4
  52. package/dist/rules/messages.d.ts +1 -0
  53. package/dist/rules/messages.js +1 -0
  54. package/dist/schema.d.ts +2 -0
  55. package/dist/schema.js +1 -0
  56. package/dist/server/createServer.d.ts +5 -3
  57. package/dist/server/createServer.js +32 -2
  58. package/dist/server/env.d.ts +3 -1
  59. package/dist/server/imageHeader.d.ts +10 -0
  60. package/dist/server/imageHeader.js +159 -0
  61. package/dist/server/index.d.ts +6 -1
  62. package/dist/server/index.js +3 -0
  63. package/dist/server/jsonLd.d.ts +45 -0
  64. package/dist/server/jsonLd.js +88 -0
  65. package/dist/server/media.d.ts +16 -0
  66. package/dist/server/media.js +135 -0
  67. package/dist/server/pages.d.ts +72 -0
  68. package/dist/server/pages.js +301 -0
  69. package/dist/server/session.js +1 -1
  70. package/dist/server/tables.d.ts +3 -0
  71. package/dist/server/tables.js +23 -8
  72. package/dist/start.js +30 -2
  73. package/dist/table.d.ts +1 -0
  74. package/dist/table.js +8 -1
  75. package/dist/tables.d.ts +2 -1
  76. package/dist/tables.js +10 -1
  77. 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
+ });
@@ -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,
@@ -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({
@@ -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 {};