@trypattern/sdk 0.1.11 → 0.1.13

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 (87) hide show
  1. package/README.md +73 -12
  2. package/dist/access.d.ts +0 -4
  3. package/dist/access.js +0 -5
  4. package/dist/api.d.ts +3 -1
  5. package/dist/api.js +5 -17
  6. package/dist/app.d.ts +20 -5
  7. package/dist/app.js +32 -2
  8. package/dist/blocks.d.ts +18 -0
  9. package/dist/blocks.js +36 -0
  10. package/dist/bookings.d.ts +400 -0
  11. package/dist/bookings.js +300 -0
  12. package/dist/bookingsMemory.d.ts +9 -0
  13. package/dist/bookingsMemory.js +79 -0
  14. package/dist/bookingsStore.d.ts +698 -0
  15. package/dist/bookingsStore.js +129 -0
  16. package/dist/cli/app.js +143 -59
  17. package/dist/cli/publishing.d.ts +3 -1
  18. package/dist/cli/publishing.js +33 -5
  19. package/dist/cli/starter.d.ts +17 -0
  20. package/dist/cli/starter.js +140 -0
  21. package/dist/components/Button.d.ts +2 -24
  22. package/dist/components/Button.js +4 -251
  23. package/dist/components/ButtonBody.d.ts +25 -0
  24. package/dist/components/ButtonBody.js +266 -0
  25. package/dist/components/Calendar.d.ts +20 -0
  26. package/dist/components/Calendar.js +195 -0
  27. package/dist/components/Card.d.ts +1 -1
  28. package/dist/components/Card.js +7 -29
  29. package/dist/components/Choice.js +27 -13
  30. package/dist/components/ChoiceGroup.d.ts +3 -1
  31. package/dist/components/ChoiceGroup.js +3 -4
  32. package/dist/components/ContinueWithGoogle.d.ts +11 -0
  33. package/dist/components/ContinueWithGoogle.js +5 -0
  34. package/dist/components/Dialog.d.ts +1 -1
  35. package/dist/components/Dialog.js +3 -1
  36. package/dist/components/Icon.js +1 -1
  37. package/dist/components/ListRow.js +2 -1
  38. package/dist/components/RadioGroup.d.ts +2 -1
  39. package/dist/components/RadioGroup.js +10 -2
  40. package/dist/components/Text.d.ts +1 -1
  41. package/dist/components/Text.js +43 -2
  42. package/dist/components/calendarDates.d.ts +14 -0
  43. package/dist/components/calendarDates.js +84 -0
  44. package/dist/components/controlLook.js +11 -11
  45. package/dist/components/icons.d.ts +9 -0
  46. package/dist/components/icons.js +17 -1
  47. package/dist/components/interaction.d.ts +1 -1
  48. package/dist/flows/browser.d.ts +7 -1
  49. package/dist/flows/browser.js +25 -7
  50. package/dist/flows/flowBrowser.d.ts +2 -2
  51. package/dist/flows/flowBrowser.js +2 -2
  52. package/dist/flows/flows.js +8 -2
  53. package/dist/index.d.ts +4 -1
  54. package/dist/index.js +1 -0
  55. package/dist/media.d.ts +11 -1
  56. package/dist/patternAccess.d.ts +11 -2
  57. package/dist/patternAccess.js +8 -4
  58. package/dist/publishWire.d.ts +24 -2
  59. package/dist/publishWire.js +22 -2
  60. package/dist/react.d.ts +3 -0
  61. package/dist/react.js +2 -0
  62. package/dist/rules/checkAllFiles.js +16 -2
  63. package/dist/rules/lintPlugin.js +1 -0
  64. package/dist/schema.d.ts +3 -1
  65. package/dist/schema.js +1 -0
  66. package/dist/server/bookings.d.ts +12 -0
  67. package/dist/server/bookings.js +142 -0
  68. package/dist/server/createServer.d.ts +3 -2
  69. package/dist/server/createServer.js +13 -4
  70. package/dist/server/env.d.ts +8 -0
  71. package/dist/server/index.d.ts +3 -1
  72. package/dist/server/index.js +1 -0
  73. package/dist/server/media.d.ts +16 -3
  74. package/dist/server/media.js +72 -23
  75. package/dist/server/patternViewer.js +16 -10
  76. package/dist/server/tables.d.ts +2 -0
  77. package/dist/server/tables.js +17 -1
  78. package/dist/start.js +12 -0
  79. package/dist/table.js +9 -3
  80. package/dist/theme/scales.d.ts +1 -1
  81. package/dist/theme/scales.js +1 -0
  82. package/dist/theme/tokens.stylex.d.ts +18 -0
  83. package/dist/theme/tokens.stylex.js +19 -1
  84. package/dist/vite/localWorker.d.ts +2 -0
  85. package/dist/vite/localWorker.js +48 -3
  86. package/dist/vite/vite.js +5 -1
  87. package/package.json +1 -1
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { Children, isValidElement } from 'react';
5
- import { space } from "../theme/tokens.stylex.js";
6
5
  import { ChoiceContext } from "./choiceContext.js";
7
6
  import { interact, textOf } from "./interaction.js";
8
- export function ChoiceGroup({ label, value, onChange, children, style }) {
7
+ import { gaps } from "./spacing.js";
8
+ export function ChoiceGroup({ label, value, onChange, children, gap = 'sm', style }) {
9
9
  return (_jsx(ChoiceContext, { value: { value }, children: _jsx(BaseRadioGroup, { "aria-label": label, value: value, onValueChange: (next) => {
10
10
  if (typeof next !== 'string')
11
11
  return;
@@ -15,7 +15,7 @@ export function ChoiceGroup({ label, value, onChange, children, style }) {
15
15
  label: labelOf(children, next)
16
16
  });
17
17
  onChange(next);
18
- }, className: stylex.props(styles.group, style).className, children: children }) }));
18
+ }, className: stylex.props(styles.group, gaps[gap], style).className, children: children }) }));
19
19
  }
20
20
  function labelOf(children, value) {
21
21
  for (const child of Children.toArray(children)) {
@@ -37,7 +37,6 @@ const styles = stylex.create({
37
37
  group: {
38
38
  display: 'flex',
39
39
  flexWrap: 'wrap',
40
- gap: space.sm,
41
40
  alignItems: 'stretch',
42
41
  minWidth: 0
43
42
  }
@@ -0,0 +1,11 @@
1
+ import type { Placement } from './Placement.ts';
2
+ import type { Size } from './Size.ts';
3
+ type ContinueWithGoogleProps = {
4
+ onClick: () => void;
5
+ size?: Size;
6
+ disabled?: boolean;
7
+ isPending?: boolean;
8
+ style?: Placement;
9
+ };
10
+ export declare function ContinueWithGoogle({ onClick, size, disabled, isPending, style }: ContinueWithGoogleProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ButtonBody } from "./ButtonBody.js";
3
+ export function ContinueWithGoogle({ onClick, size = 'md', disabled = false, isPending = false, style }) {
4
+ return (_jsx(ButtonBody, { variant: "secondary", icon: "google", isIconPinned: true, size: size, disabled: disabled, isPending: isPending, onClick: onClick, ...(style === undefined ? {} : { style }), children: "Continue with Google" }));
5
+ }
@@ -4,7 +4,7 @@ type DialogProps = {
4
4
  isOpen: boolean;
5
5
  onOpenChange: (isOpen: boolean) => void;
6
6
  placement?: 'center' | 'top';
7
- size?: 'sm' | 'md' | 'lg';
7
+ size?: 'sm' | 'md' | 'lg' | 'xl';
8
8
  children: ReactNode;
9
9
  };
10
10
  export declare function Dialog({ label, isOpen, onOpenChange, placement, size, children }: DialogProps): import("react").JSX.Element;
@@ -45,6 +45,7 @@ const styles = stylex.create({
45
45
  frame: {
46
46
  display: 'flex',
47
47
  flexDirection: 'column',
48
+ maxHeight: 'calc(100dvh - 2rem)',
48
49
  overflow: 'hidden',
49
50
  color: colors.primaryForeground,
50
51
  backgroundColor: colors.componentOnSunken,
@@ -77,5 +78,6 @@ const hiddenPlacements = stylex.create({
77
78
  const widths = stylex.create({
78
79
  sm: { maxWidth: 'min(400px, calc(100% - 2rem))' },
79
80
  md: { maxWidth: 'min(560px, calc(100% - 2rem))' },
80
- lg: { maxWidth: 'min(720px, calc(100% - 2rem))' }
81
+ lg: { maxWidth: 'min(720px, calc(100% - 2rem))' },
82
+ xl: { maxWidth: 'min(800px, calc(100% - 2rem))' }
81
83
  });
@@ -5,7 +5,7 @@ import { shapeOf } from "./icons.js";
5
5
  import { tones } from "./tones.js";
6
6
  export function Icon({ name, size = 'md', tone = 'inherit', weight = 'regular' }) {
7
7
  const shape = shapeOf(name);
8
- return (_jsx("svg", { viewBox: "0 0 24 24", fill: shape.isFilled ? 'currentColor' : 'none', fillRule: shape.rule, stroke: shape.isFilled ? 'none' : 'currentColor', strokeWidth: weight === 'light' ? '1.5' : '2', strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...stylex.props(styles.icon, sizes[size], tones[tone]), children: shape.paths.map((path) => (_jsx("path", { d: path }, path))) }));
8
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: shape.isFilled ? 'currentColor' : 'none', fillRule: shape.rule, stroke: shape.isFilled ? 'none' : 'currentColor', strokeWidth: weight === 'light' ? '1.5' : '2', strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...stylex.props(styles.icon, sizes[size], tones[tone]), children: shape.paths.map((path, index) => (_jsx("path", { d: path, fill: shape.colors?.[index] }, path))) }));
9
9
  }
10
10
  const styles = stylex.create({
11
11
  icon: {
@@ -7,7 +7,8 @@ import { focusRing } from "./controlLook.js";
7
7
  import { interact, textOf } from "./interaction.js";
8
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 || href !== undefined || onClick !== undefined) &&
10
+ const look = stylex.props(styles.row, size === 'sm' &&
11
+ (to !== undefined || href !== undefined || onClick !== undefined) &&
11
12
  styles.interactive, isMuted && styles.muted, isCurrent && styles.current, size === 'sm' && styles.small, size === 'lg' && styles.large, focusRing.visible, focusRing.inside);
12
13
  const report = () => {
13
14
  interact({
@@ -12,7 +12,8 @@ type RadioGroupProps<Value extends string> = {
12
12
  value: Value | null;
13
13
  options: readonly RadioOption<Value>[];
14
14
  onChange: (value: Value) => void;
15
+ size?: 'md' | 'lg';
15
16
  style?: Placement;
16
17
  };
17
- export declare function RadioGroup<Value extends string>({ label, value, options, onChange, style }: RadioGroupProps<Value>): import("react").JSX.Element;
18
+ export declare function RadioGroup<Value extends string>({ label, value, options, onChange, size, style }: RadioGroupProps<Value>): import("react").JSX.Element;
18
19
  export {};
@@ -6,7 +6,7 @@ import { borders, colors, iconSizes, motion, radii, shadows, space } from "../th
6
6
  import { focusRing } from "./controlLook.js";
7
7
  import { interact } from "./interaction.js";
8
8
  import { Text } from "./Text.js";
9
- export function RadioGroup({ label, value, options, onChange, style }) {
9
+ export function RadioGroup({ label, value, options, onChange, size = 'md', style }) {
10
10
  return (_jsx(BaseRadioGroup, { "aria-label": label, value: value, onValueChange: (next) => {
11
11
  const chosen = options.find((option) => option.value === next);
12
12
  if (chosen === undefined)
@@ -17,7 +17,8 @@ export function RadioGroup({ label, value, options, onChange, style }) {
17
17
  label: chosen.label
18
18
  });
19
19
  onChange(chosen.value);
20
- }, className: stylex.props(styles.group, style).className, children: options.map((option) => (_jsxs("label", { ...stylex.props(styles.row, option.isDisabled === true && styles.rowDisabled), children: [_jsx(BaseRadio.Root, { value: option.value, disabled: option.isDisabled === true, "aria-label": option.label, className: (state) => stylex.props(styles.dot, state.checked ? styles.dotOn : styles.dotOff, focusRing.visible).className, children: _jsx(BaseRadio.Indicator, { className: stylex.props(styles.mark).className }) }), _jsx("span", { ...stylex.props(styles.content), children: option.content ?? (_jsx(Text, { tone: option.isDisabled === true ? 'tertiary' : 'primary', isTruncated: true, children: option.label })) }), option.trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: option.trailing }))] }, option.value))) }));
20
+ }, className: stylex.props(styles.group, size === 'lg' && styles.groupLarge, style)
21
+ .className, children: options.map((option) => (_jsxs("label", { ...stylex.props(styles.row, size === 'lg' && styles.rowLarge, option.isDisabled === true && styles.rowDisabled), children: [_jsx(BaseRadio.Root, { value: option.value, disabled: option.isDisabled === true, "aria-label": option.label, className: (state) => stylex.props(styles.dot, state.checked ? styles.dotOn : styles.dotOff, focusRing.visible).className, children: _jsx(BaseRadio.Indicator, { className: stylex.props(styles.mark).className }) }), _jsx("span", { ...stylex.props(styles.content), children: option.content ?? (_jsx(Text, { variant: size === 'lg' ? 'lead' : 'body', tone: option.isDisabled === true ? 'tertiary' : 'primary', isTruncated: true, children: option.label })) }), option.trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: option.trailing }))] }, option.value))) }));
21
22
  }
22
23
  const styles = stylex.create({
23
24
  group: {
@@ -42,6 +43,13 @@ const styles = stylex.create({
42
43
  },
43
44
  borderTopWidth: borders.hairline
44
45
  },
46
+ groupLarge: {
47
+ borderRadius: radii.xxl
48
+ },
49
+ rowLarge: {
50
+ paddingBlock: space.lg,
51
+ paddingInline: `calc(${space.md} + ${space.xxs})`
52
+ },
45
53
  rowDisabled: {
46
54
  color: colors.tertiaryForeground
47
55
  },
@@ -3,7 +3,7 @@ import type { Placement } from './Placement.ts';
3
3
  import type { Tone } from './tones.ts';
4
4
  type TextProps = {
5
5
  children: ReactNode;
6
- variant?: 'heading' | 'subheading' | 'title' | 'lead' | 'label' | 'body' | 'caption' | 'code';
6
+ variant?: 'hero' | 'display' | 'section' | 'heading' | 'subheading' | 'title' | 'lead' | 'intro' | 'label' | 'body' | 'caption' | 'code';
7
7
  tone?: Tone;
8
8
  align?: 'start' | 'center' | 'end';
9
9
  as?: 'span' | 'p' | 'h1' | 'h2' | 'h3';
@@ -1,6 +1,6 @@
1
1
  import * as stylex from '@stylexjs/stylex';
2
2
  import { createElement } from 'react';
3
- import { breakpoints, fontSizes, fontWeights, fonts, lineHeights } from "../theme/tokens.stylex.js";
3
+ import { breakpoints, fontSizes, fontWeights, fonts, lineHeights, trackings } from "../theme/tokens.stylex.js";
4
4
  import { tones } from "./tones.js";
5
5
  export function Text({ children, variant = 'body', tone = 'inherit', align, as = 'span', isTruncated = false, isSelectable = false, style }) {
6
6
  return createElement(as, stylex.props(as !== 'span' && styles.block, variants[variant], tones[tone], align !== undefined && alignments[align], isTruncated && styles.truncated, isSelectable && styles.selectable, style), children);
@@ -21,11 +21,47 @@ const styles = stylex.create({
21
21
  }
22
22
  });
23
23
  const variants = stylex.create({
24
+ hero: {
25
+ fontSize: {
26
+ default: fontSizes.heading1,
27
+ [breakpoints.compact]: fontSizes.heading2
28
+ },
29
+ fontWeight: fontWeights.medium,
30
+ lineHeight: {
31
+ default: lineHeights.heading1,
32
+ [breakpoints.compact]: lineHeights.heading2
33
+ },
34
+ letterSpacing: trackings.hero
35
+ },
36
+ display: {
37
+ fontSize: {
38
+ default: fontSizes.heading2,
39
+ [breakpoints.compact]: fontSizes.heading3
40
+ },
41
+ fontWeight: fontWeights.medium,
42
+ lineHeight: {
43
+ default: lineHeights.heading2,
44
+ [breakpoints.compact]: lineHeights.heading3
45
+ },
46
+ letterSpacing: trackings.display
47
+ },
48
+ section: {
49
+ fontSize: {
50
+ default: fontSizes.heading3,
51
+ [breakpoints.compact]: fontSizes.heading5
52
+ },
53
+ fontWeight: fontWeights.medium,
54
+ lineHeight: {
55
+ default: lineHeights.heading3,
56
+ [breakpoints.compact]: lineHeights.heading5
57
+ },
58
+ letterSpacing: trackings.section
59
+ },
24
60
  heading: {
25
61
  fontSize: fontSizes.heading5,
26
62
  fontWeight: fontWeights.medium,
27
63
  lineHeight: lineHeights.heading5,
28
- letterSpacing: '-0.01em'
64
+ letterSpacing: trackings.heading
29
65
  },
30
66
  subheading: {
31
67
  fontSize: fontSizes.heading6,
@@ -42,6 +78,11 @@ const variants = stylex.create({
42
78
  fontWeight: fontWeights.normal,
43
79
  lineHeight: lineHeights.body
44
80
  },
81
+ intro: {
82
+ fontSize: fontSizes.bodyLg,
83
+ fontWeight: fontWeights.normal,
84
+ lineHeight: lineHeights.bodyLg
85
+ },
45
86
  label: {
46
87
  fontSize: {
47
88
  default: fontSizes.bodySm,
@@ -0,0 +1,14 @@
1
+ export declare function monthOf(date: string): string;
2
+ export declare function addDays(date: string, days: number): string;
3
+ export declare function addMonths(date: string, months: number): string;
4
+ export declare function daysIn(month: string): number;
5
+ export declare function weekdayIndex(date: string): number;
6
+ export declare function weeksOf(month: string): readonly (readonly (string | null)[])[];
7
+ export declare function localToday(): string;
8
+ export declare function monthTitle(month: string, locale: string | undefined): string;
9
+ export declare function dayLabel(date: string, locale: string | undefined): string;
10
+ export declare function dayNumber(date: string, locale: string | undefined): string;
11
+ export declare function weekdayNames(locale: string | undefined): readonly {
12
+ short: string;
13
+ long: string;
14
+ }[];
@@ -0,0 +1,84 @@
1
+ const DAY_MS = 24 * 60 * 60 * 1000;
2
+ function utc(date) {
3
+ return new Date(`${date}T00:00:00Z`);
4
+ }
5
+ function isoDate(time) {
6
+ return new Date(time).toISOString().slice(0, 10);
7
+ }
8
+ export function monthOf(date) {
9
+ return date.slice(0, 7);
10
+ }
11
+ export function addDays(date, days) {
12
+ return isoDate(utc(date).getTime() + days * DAY_MS);
13
+ }
14
+ export function addMonths(date, months) {
15
+ const start = utc(date);
16
+ const year = start.getUTCFullYear();
17
+ const month = start.getUTCMonth() + months;
18
+ const last = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
19
+ return isoDate(Date.UTC(year, month, Math.min(start.getUTCDate(), last)));
20
+ }
21
+ export function daysIn(month) {
22
+ const start = utc(`${month}-01`);
23
+ return new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 0)).getUTCDate();
24
+ }
25
+ export function weekdayIndex(date) {
26
+ return (utc(date).getUTCDay() + 6) % 7;
27
+ }
28
+ export function weeksOf(month) {
29
+ const first = `${month}-01`;
30
+ const cells = [
31
+ ...Array.from({ length: weekdayIndex(first) }, () => null),
32
+ ...Array.from({ length: daysIn(month) }, (_, index) => addDays(first, index))
33
+ ];
34
+ const weeks = [];
35
+ for (let index = 0; index < cells.length; index += 7) {
36
+ weeks.push(cells.slice(index, index + 7));
37
+ }
38
+ return weeks;
39
+ }
40
+ export function localToday() {
41
+ const now = new Date();
42
+ return [
43
+ String(now.getFullYear()).padStart(4, '0'),
44
+ String(now.getMonth() + 1).padStart(2, '0'),
45
+ String(now.getDate()).padStart(2, '0')
46
+ ].join('-');
47
+ }
48
+ export function monthTitle(month, locale) {
49
+ return new Intl.DateTimeFormat(locale, {
50
+ month: 'long',
51
+ year: 'numeric',
52
+ timeZone: 'UTC'
53
+ }).format(utc(`${month}-01`));
54
+ }
55
+ export function dayLabel(date, locale) {
56
+ return new Intl.DateTimeFormat(locale, {
57
+ dateStyle: 'full',
58
+ timeZone: 'UTC'
59
+ }).format(utc(date));
60
+ }
61
+ export function dayNumber(date, locale) {
62
+ return new Intl.DateTimeFormat(locale, {
63
+ day: 'numeric',
64
+ timeZone: 'UTC'
65
+ }).format(utc(date));
66
+ }
67
+ export function weekdayNames(locale) {
68
+ const monday = '2024-01-01';
69
+ const short = new Intl.DateTimeFormat(locale, {
70
+ weekday: 'short',
71
+ timeZone: 'UTC'
72
+ });
73
+ const long = new Intl.DateTimeFormat(locale, {
74
+ weekday: 'long',
75
+ timeZone: 'UTC'
76
+ });
77
+ return Array.from({ length: 7 }, (_, index) => {
78
+ const day = utc(addDays(monday, index));
79
+ return {
80
+ short: [...short.format(day)].slice(0, 2).join(''),
81
+ long: long.format(day)
82
+ };
83
+ });
84
+ }
@@ -1,44 +1,44 @@
1
1
  import * as stylex from '@stylexjs/stylex';
2
- import { borders, colors, controls, fontSizes, lineHeights, motion, radii } from "../theme/tokens.stylex.js";
2
+ import { borders, colors, controls, motion, radii } from "../theme/tokens.stylex.js";
3
3
  export const controlSizes = stylex.create({
4
4
  xs: {
5
5
  gap: controls.gapXs,
6
6
  height: controls.heightXs,
7
7
  paddingInline: controls.paddingXs,
8
- fontSize: fontSizes.captionLg,
9
- lineHeight: lineHeights.caption,
8
+ fontSize: controls.fontSizeXs,
9
+ lineHeight: controls.lineHeightXs,
10
10
  borderRadius: radii.sm
11
11
  },
12
12
  sm: {
13
13
  gap: controls.gapSm,
14
14
  height: controls.heightSm,
15
15
  paddingInline: controls.paddingSm,
16
- fontSize: fontSizes.bodySm,
17
- lineHeight: lineHeights.bodySm,
16
+ fontSize: controls.fontSizeSm,
17
+ lineHeight: controls.lineHeightSm,
18
18
  borderRadius: radii.md
19
19
  },
20
20
  md: {
21
21
  gap: controls.gapMd,
22
22
  height: controls.heightMd,
23
23
  paddingInline: controls.paddingMd,
24
- fontSize: fontSizes.bodySm,
25
- lineHeight: lineHeights.bodySm,
24
+ fontSize: controls.fontSizeMd,
25
+ lineHeight: controls.lineHeightMd,
26
26
  borderRadius: radii.md
27
27
  },
28
28
  lg: {
29
29
  gap: controls.gapLg,
30
30
  height: controls.heightLg,
31
31
  paddingInline: controls.paddingLg,
32
- fontSize: fontSizes.bodySm,
33
- lineHeight: lineHeights.bodySm,
32
+ fontSize: controls.fontSizeLg,
33
+ lineHeight: controls.lineHeightLg,
34
34
  borderRadius: radii.lg
35
35
  },
36
36
  xl: {
37
37
  gap: controls.gapXl,
38
38
  height: controls.heightXl,
39
39
  paddingInline: controls.paddingXl,
40
- fontSize: fontSizes.bodySm,
41
- lineHeight: lineHeights.bodySm,
40
+ fontSize: controls.fontSizeXl,
41
+ lineHeight: controls.lineHeightXl,
42
42
  borderRadius: radii.lg
43
43
  }
44
44
  });
@@ -2,6 +2,7 @@ import type { AppIconName } from '../registered.ts';
2
2
  type FilledDrawing = {
3
3
  readonly fill: string | readonly string[];
4
4
  readonly rule?: 'evenodd';
5
+ readonly colors?: readonly string[];
5
6
  };
6
7
  export type IconDrawing = string | readonly string[] | FilledDrawing;
7
8
  export type IconName = keyof typeof builtInIcons | AppIconName;
@@ -12,6 +13,10 @@ declare const builtInIcons: {
12
13
  sun: {
13
14
  fill: string[];
14
15
  };
16
+ google: {
17
+ fill: string[];
18
+ colors: string[];
19
+ };
15
20
  moon: {
16
21
  fill: string;
17
22
  };
@@ -28,6 +33,8 @@ declare const builtInIcons: {
28
33
  alertCircle: string[];
29
34
  chevronDown: string;
30
35
  chevronRight: string;
36
+ previous: string;
37
+ next: string;
31
38
  search: string[];
32
39
  panelLeft: string[];
33
40
  more: {
@@ -54,9 +61,11 @@ export declare function shapeOf(name: IconName): {
54
61
  paths: readonly string[];
55
62
  isFilled: boolean;
56
63
  rule?: never;
64
+ colors?: never;
57
65
  } | {
58
66
  paths: string[];
59
67
  isFilled: boolean;
60
68
  rule: "evenodd" | undefined;
69
+ colors: readonly string[] | undefined;
61
70
  };
62
71
  export {};
@@ -23,6 +23,15 @@ const builtInIcons = {
23
23
  'M7.75736 7.75736C10.1005 5.41421 13.8995 5.41421 16.2426 7.75736C18.5858 10.1005 18.5858 13.8995 16.2426 16.2426C13.8995 18.5858 10.1005 18.5858 7.75736 16.2426C5.41421 13.8995 5.41421 10.1005 7.75736 7.75736Z'
24
24
  ]
25
25
  },
26
+ google: {
27
+ fill: [
28
+ 'M22.5 12.3c0-.8-.1-1.5-.2-2.2H12v4.2h5.9a5 5 0 0 1-2.2 3.3v2.7h3.5c2.1-1.9 3.3-4.7 3.3-8z',
29
+ 'M12 23c3 0 5.5-1 7.3-2.7l-3.5-2.7c-1 .7-2.3 1.1-3.8 1.1-2.9 0-5.4-2-6.3-4.7H2.1v2.8A11 11 0 0 0 12 23z',
30
+ 'M5.7 14c-.2-.7-.4-1.4-.4-2s.1-1.4.4-2V7.2H2.1a11 11 0 0 0 0 9.7L5.7 14z',
31
+ 'M12 5.4c1.6 0 3.1.6 4.2 1.7l3.1-3.1A11 11 0 0 0 2.1 7.2L5.7 10c.9-2.7 3.4-4.6 6.3-4.6z'
32
+ ],
33
+ colors: ['#4285F4', '#34A853', '#FBBC05', '#EA4335']
34
+ },
26
35
  moon: {
27
36
  fill: 'M12.0517 3.59971C12.2712 3.28123 12.2873 2.86472 12.0931 2.53021C11.8989 2.19569 11.5292 2.00315 11.1438 2.03581C6.0214 2.46985 2 6.76372 2 11.9979C2 17.5197 6.47632 21.996 11.9981 21.996C17.2324 21.996 21.5264 17.9745 21.9602 12.8519C21.9929 12.4664 21.8003 12.0968 21.4658 11.9026C21.1313 11.7084 20.7148 11.7246 20.3963 11.9441C19.4302 12.61 18.2602 12.9998 16.9961 12.9998C13.6824 12.9998 10.9961 10.3135 10.9961 6.99976C10.9961 5.73577 11.3858 4.56582 12.0517 3.59971Z'
28
37
  },
@@ -53,6 +62,8 @@ const builtInIcons = {
53
62
  ],
54
63
  chevronDown: 'M7 10l5 5 5-5',
55
64
  chevronRight: 'M10 7l5 5-5 5',
65
+ previous: 'M15 18l-6-6 6-6',
66
+ next: 'M9 18l6-6-6-6',
56
67
  search: ['M4.5 11a6.5 6.5 0 1 0 13 0a6.5 6.5 0 1 0 -13 0', 'M16 16l4 4'],
57
68
  panelLeft: [
58
69
  'M6.5 4.5h11a3 3 0 0 1 3 3v9a3 3 0 0 1 -3 3h-11a3 3 0 0 1 -3 -3v-9a3 3 0 0 1 3 -3Z',
@@ -107,7 +118,12 @@ export function shapeOf(name) {
107
118
  return { paths: [drawing], isFilled: false };
108
119
  if (isPathList(drawing))
109
120
  return { paths: drawing, isFilled: false };
110
- return { paths: [drawing.fill].flat(), isFilled: true, rule: drawing.rule };
121
+ return {
122
+ paths: [drawing.fill].flat(),
123
+ isFilled: true,
124
+ rule: drawing.rule,
125
+ colors: drawing.colors
126
+ };
111
127
  }
112
128
  function isPathList(drawing) {
113
129
  return Array.isArray(drawing);
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type Interaction = {
3
- primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup';
3
+ primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar';
4
4
  event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
5
5
  label: string;
6
6
  page: string | null;
@@ -31,4 +31,10 @@ export type AppPage = {
31
31
  captureAt: (viewport: ViewportName) => Promise<string>;
32
32
  close: () => Promise<void>;
33
33
  };
34
- export declare function openApp(browser: Browser, baseUrl: string, path: string, viewport: ViewportName, signedInAs: string | null, seed?: object, admins?: readonly string[], world?: unknown): Promise<AppPage>;
34
+ export type MemoryRun = {
35
+ seed: object;
36
+ admins: readonly string[];
37
+ title: string;
38
+ world: unknown;
39
+ };
40
+ export declare function openApp(browser: Browser, baseUrl: string, path: string, viewport: ViewportName, signedInAs: string | null, memory?: MemoryRun): Promise<AppPage>;
@@ -59,15 +59,27 @@ function watchNetwork(page) {
59
59
  }
60
60
  };
61
61
  }
62
+ function isMissingPage(response, page) {
63
+ const request = response.request();
64
+ return (request.isNavigationRequest() &&
65
+ request.frame() === page.mainFrame() &&
66
+ response.status() >= 400 &&
67
+ response.status() < 500);
68
+ }
62
69
  function recordProblems(page, problems) {
70
+ const missingPages = new Set();
63
71
  const record = (problem) => {
64
72
  if (!problems.includes(problem))
65
73
  problems.push(problem);
66
74
  };
67
75
  page.on('console', (message) => {
68
- if (message.type() === 'error') {
69
- record(`console error: ${message.text()}`);
76
+ if (message.type() !== 'error')
77
+ return;
78
+ if (missingPages.has(message.location().url) &&
79
+ message.text().startsWith('Failed to load resource')) {
80
+ return;
70
81
  }
82
+ record(`console error: ${message.text()}`);
71
83
  });
72
84
  page.on('pageerror', (error) => {
73
85
  record(`uncaught page error: ${error.message}`);
@@ -76,12 +88,16 @@ function recordProblems(page, problems) {
76
88
  record(`request failed: ${request.method()} ${request.url()} (${request.failure()?.errorText ?? 'unknown'})`);
77
89
  });
78
90
  page.on('response', (response) => {
91
+ if (isMissingPage(response, page)) {
92
+ missingPages.add(response.url());
93
+ return;
94
+ }
79
95
  if (response.status() >= 400) {
80
96
  record(`request failed: ${response.request().method()} ${response.url()} (HTTP ${response.status()})`);
81
97
  }
82
98
  });
83
99
  }
84
- export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed, admins = [], world = {}) {
100
+ export async function openApp(browser, baseUrl, path, viewport, signedInAs, memory) {
85
101
  const { width, height, deviceScaleFactor } = viewports[viewport];
86
102
  const context = await browser.newContext({
87
103
  viewport: { width, height },
@@ -89,20 +105,22 @@ export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed
89
105
  reducedMotion: 'reduce',
90
106
  colorScheme: 'light'
91
107
  });
92
- if (signedInAs !== null) {
108
+ if (memory === undefined && signedInAs !== null) {
93
109
  const signIn = await context.request.post(new URL('/api/auth/dev-sign-in', baseUrl).href, { data: { email: signedInAs } });
94
110
  if (!signIn.ok()) {
95
111
  throw new Error(`Dev sign-in as ${signedInAs} failed (HTTP ${signIn.status()}): ${await signIn.text()}`);
96
112
  }
97
113
  }
98
- if (seed !== undefined) {
114
+ if (memory !== undefined) {
99
115
  await context.addInitScript((value) => {
100
116
  Object.assign(globalThis, {
101
117
  patternSeed: value.seed,
102
118
  patternAdmins: value.admins,
103
- patternWorld: value.world
119
+ patternTitle: value.title,
120
+ patternWorld: value.world,
121
+ patternSignedInAs: value.signedInAs
104
122
  });
105
- }, { seed, admins, world });
123
+ }, { ...memory, signedInAs });
106
124
  }
107
125
  const page = await context.newPage();
108
126
  const problems = [];
@@ -1,4 +1,4 @@
1
- import type { ViewportName } from './browser.ts';
1
+ import type { MemoryRun, ViewportName } from './browser.ts';
2
2
  export type VisualFlowSettings = {
3
3
  url: string;
4
4
  wsEndpoint: string;
@@ -11,7 +11,7 @@ declare module 'vitest' {
11
11
  }
12
12
  }
13
13
  export declare function closeFlowBrowser(): Promise<void>;
14
- export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null, seed: object, admins: readonly string[], world: unknown): Promise<{
14
+ export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null, memory: MemoryRun): Promise<{
15
15
  send: (command: unknown) => Promise<void>;
16
16
  snapshot: () => Promise<unknown>;
17
17
  hold: (call: string) => Promise<void>;
@@ -5,9 +5,9 @@ export async function closeFlowBrowser() {
5
5
  browser = undefined;
6
6
  await (await connected)?.close();
7
7
  }
8
- export async function openFlowPage(settings, viewport, startAt, signedInAs, seed, admins, world) {
8
+ export async function openFlowPage(settings, viewport, startAt, signedInAs, memory) {
9
9
  browser ??= connectChrome(settings.wsEndpoint);
10
- const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs, seed, admins, world);
10
+ const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs, memory);
11
11
  return {
12
12
  send: app.dispatch,
13
13
  snapshot: app.snapshot,
@@ -59,6 +59,12 @@ async function runSteps(name, steps, run) {
59
59
  }
60
60
  }
61
61
  }
62
+ function leadingHolds(steps) {
63
+ const firstOther = steps.findIndex((step) => !('hold' in step));
64
+ return steps
65
+ .slice(0, firstOther === -1 ? steps.length : firstOther)
66
+ .flatMap((step) => ('hold' in step ? [step.hold] : []));
67
+ }
62
68
  function withTestParams(path, failing, held) {
63
69
  const params = new URLSearchParams([
64
70
  ['memory', ''],
@@ -133,8 +139,8 @@ export function defineFlow({ App }) {
133
139
  for (const viewport of visual.viewports) {
134
140
  test(`${name} [${viewport}]`, async () => {
135
141
  const { openFlowPage } = await import("./flowBrowser.js");
136
- const held = new Set();
137
- const page = await openFlowPage(visual, viewport, withTestParams(startAt, failing, held), signedInAs, seed, admins, startingWorld);
142
+ const held = new Set(leadingHolds(steps));
143
+ const page = await openFlowPage(visual, viewport, withTestParams(startAt, failing, held), signedInAs, { seed, admins, title, world: startingWorld });
138
144
  let stepFailure;
139
145
  try {
140
146
  await runSteps(name, steps, {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { defineApp } from './app.ts';
2
+ export { Bookings, bookings } from './bookingsStore.ts';
3
+ export type { BookingsWorld } from './bookingsMemory.ts';
4
+ export type { AppBlock } from './blocks.ts';
2
5
  export { ActionsStore, bindActions, rankActions } from './actions.ts';
3
6
  export type { Action, ActionsBuilder } from './actions.ts';
4
7
  export { startApp } from './start.ts';
@@ -16,7 +19,7 @@ export { HttpSessionApi, MemorySessionApi } from './auth/sessionApi.ts';
16
19
  export type { EmailCodeOutcome, SessionApi, SessionCall } from './auth/sessionApi.ts';
17
20
  export { userSchema } from './auth/user.ts';
18
21
  export type { User } from './auth/user.ts';
19
- export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
22
+ export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, BlockStores, CheckedBlocks, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
20
23
  export { defineRoutes } from './routes.ts';
21
24
  export { ServiceRefused, refused } from './services.ts';
22
25
  export type { ServiceClient, ServiceMemory } from './services.ts';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { defineApp } from "./app.js";
2
+ export { Bookings, bookings } from "./bookingsStore.js";
2
3
  export { ActionsStore, bindActions, rankActions } from "./actions.js";
3
4
  export { startApp } from "./start.js";
4
5
  export { appearance } from "./appearance.js";