@heliosgraphics/fx 0.0.1-alpha.0

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 (67) hide show
  1. package/components/LiveComponents/LiveComponents.tsx +116 -0
  2. package/components/LiveComponents/LiveComponents.types.ts +9 -0
  3. package/components/LiveComponents/LiveComponents.utils.ts +1 -0
  4. package/components/LiveComponents/_components/GridBackground/GridBackground.module.css +14 -0
  5. package/components/LiveComponents/_components/GridBackground/GridBackground.tsx +18 -0
  6. package/components/LiveComponents/_components/GridBackground/GridBackground.types.ts +5 -0
  7. package/components/LiveComponents/_components/GridBackground/index.ts +1 -0
  8. package/components/LiveComponents/_components/LiveComponent/LiveComponent.module.css +45 -0
  9. package/components/LiveComponents/_components/LiveComponent/LiveComponent.tsx +63 -0
  10. package/components/LiveComponents/_components/LiveComponent/LiveComponent.types.ts +4 -0
  11. package/components/LiveComponents/_components/LiveComponent/index.ts +1 -0
  12. package/components/LiveComponents/_components/PermutationsGrid/PermutationsGrid.module.css +28 -0
  13. package/components/LiveComponents/_components/PermutationsGrid/PermutationsGrid.tsx +117 -0
  14. package/components/LiveComponents/_components/PermutationsGrid/PermutationsGrid.types.ts +8 -0
  15. package/components/LiveComponents/_components/PermutationsGrid/PermutationsGrid.utils.ts +24 -0
  16. package/components/LiveComponents/_components/PermutationsGrid/index.ts +2 -0
  17. package/components/LiveComponents/index.ts +2 -0
  18. package/components/Page/Page.tsx +52 -0
  19. package/components/Page/Page.types.ts +13 -0
  20. package/components/Page/index.ts +2 -0
  21. package/components/PageBlock/PageBlock.tsx +16 -0
  22. package/components/PageBlock/PageBlock.types.ts +6 -0
  23. package/components/PageBlock/index.ts +2 -0
  24. package/components/PropsTable/PropsTable.tsx +84 -0
  25. package/components/PropsTable/PropsTable.types.ts +7 -0
  26. package/components/PropsTable/PropsTable.utils.tsx +81 -0
  27. package/components/PropsTable/_components/BooleanEditor/BooleanEditor.tsx +23 -0
  28. package/components/PropsTable/_components/BooleanEditor/BooleanEditor.types.ts +6 -0
  29. package/components/PropsTable/_components/BooleanEditor/index.ts +2 -0
  30. package/components/PropsTable/_components/EnumEditor/EnumEditor.tsx +64 -0
  31. package/components/PropsTable/_components/EnumEditor/EnumEditor.types.ts +9 -0
  32. package/components/PropsTable/_components/EnumEditor/index.ts +2 -0
  33. package/components/PropsTable/_components/NumberEditor/NumberEditor.tsx +26 -0
  34. package/components/PropsTable/_components/NumberEditor/NumberEditor.types.ts +9 -0
  35. package/components/PropsTable/_components/NumberEditor/index.ts +2 -0
  36. package/components/PropsTable/_components/StringEditor/StringEditor.tsx +22 -0
  37. package/components/PropsTable/_components/StringEditor/StringEditor.types.ts +6 -0
  38. package/components/PropsTable/_components/StringEditor/index.ts +2 -0
  39. package/components/PropsTable/index.ts +2 -0
  40. package/components/WorkshopAside/WorkshopAside.tsx +103 -0
  41. package/components/WorkshopAside/WorkshopAside.types.ts +6 -0
  42. package/components/WorkshopAside/index.ts +2 -0
  43. package/components/WorkshopNavigation/WorkshopNavigation.tsx +36 -0
  44. package/components/WorkshopNavigation/WorkshopNavigation.types.ts +1 -0
  45. package/components/WorkshopNavigation/index.ts +2 -0
  46. package/components/index.ts +17 -0
  47. package/config/index.ts +24 -0
  48. package/contexts/LayoutProvider/LayoutProvider.tsx +28 -0
  49. package/contexts/LayoutProvider/LayoutProvider.types.ts +8 -0
  50. package/contexts/LayoutProvider/index.ts +2 -0
  51. package/contexts/WorkshopProvider/WorkshopProvider.tsx +57 -0
  52. package/contexts/WorkshopProvider/WorkshopProvider.types.ts +11 -0
  53. package/contexts/WorkshopProvider/index.ts +2 -0
  54. package/contexts/index.ts +5 -0
  55. package/index.ts +17 -0
  56. package/package.json +41 -0
  57. package/pages/ComponentPage/ComponentPage.tsx +26 -0
  58. package/pages/ComponentPage/ComponentPage.types.ts +10 -0
  59. package/pages/ComponentPage/index.ts +2 -0
  60. package/pages/DashboardPage/DashboardPage.tsx +88 -0
  61. package/pages/DashboardPage/DashboardPage.types.ts +9 -0
  62. package/pages/DashboardPage/DashboardPage.utils.ts +33 -0
  63. package/pages/DashboardPage/index.ts +2 -0
  64. package/pages/index.ts +5 -0
  65. package/types/config.ts +18 -0
  66. package/types/knobs.ts +13 -0
  67. package/types/meta.ts +28 -0
@@ -0,0 +1,116 @@
1
+ "use client"
2
+
3
+ import { Segments, Flex, Separator, Toggle } from "@heliosgraphics/ui"
4
+ import { LiveComponent } from "./_components/LiveComponent"
5
+ import { GridBackground } from "./_components/GridBackground"
6
+ import { PermutationsGrid } from "./_components/PermutationsGrid"
7
+ import { useContext, useEffect, useMemo } from "react"
8
+ import { LayoutContext, DEFAULT_DEMO_PATTERN } from "../../contexts/LayoutProvider"
9
+ import { WorkshopContext } from "../../contexts/WorkshopProvider"
10
+ import type { FC, ReactNode } from "react"
11
+ import type { LiveComponentsProps } from "./LiveComponents.types"
12
+ import { toScreamingCase } from "./LiveComponents.utils"
13
+
14
+ export const LiveComponents: FC<LiveComponentsProps> = ({ demoComponent, demoMeta, knobs }) => {
15
+ const { activeDemoPattern, setActiveDemoPattern, isPermutationsMode, setIsPermutationsMode } =
16
+ useContext(LayoutContext)
17
+ const { knobValues, knobSetters, componentScope } = useContext(WorkshopContext)
18
+
19
+ const isDefaultPattern: boolean = activeDemoPattern === DEFAULT_DEMO_PATTERN
20
+ const hasWorkshopBackground = knobValues["hasWorkshopBackground"] === true
21
+ const canShowPermutations: boolean = !isDefaultPattern
22
+
23
+ const onBackgroundToggle = (): void => {
24
+ const setter = knobSetters["hasWorkshopBackground"]
25
+
26
+ if (setter) setter(!hasWorkshopBackground)
27
+ }
28
+
29
+ const onTogglePermutations = (): void => {
30
+ if (isDefaultPattern) return
31
+
32
+ setIsPermutationsMode((prev) => !prev)
33
+ }
34
+
35
+ const onSelectDemo = (): void => {
36
+ setActiveDemoPattern(DEFAULT_DEMO_PATTERN)
37
+ setIsPermutationsMode(false)
38
+ }
39
+
40
+ const hasDemoComponent: boolean = !!demoComponent
41
+ const firstPatternContent: string | undefined = demoMeta._patterns[0]?.content
42
+
43
+ useEffect(() => {
44
+ if (!hasDemoComponent && firstPatternContent) {
45
+ setActiveDemoPattern(firstPatternContent)
46
+ }
47
+
48
+ return (): void => {
49
+ setActiveDemoPattern(DEFAULT_DEMO_PATTERN)
50
+ setIsPermutationsMode(false)
51
+ }
52
+ }, [hasDemoComponent, firstPatternContent, setActiveDemoPattern, setIsPermutationsMode])
53
+
54
+ const fullScope = useMemo<Record<string, unknown>>(() => {
55
+ const scope: Record<string, unknown> = { ...componentScope }
56
+
57
+ for (const [key, value] of Object.entries(knobValues)) {
58
+ if (knobs[key]?.isInternal) continue
59
+
60
+ scope[toScreamingCase(key)] = value
61
+ }
62
+
63
+ return scope
64
+ }, [componentScope, knobValues, knobs])
65
+
66
+ const renderContent = (): ReactNode => {
67
+ if (isDefaultPattern) return demoComponent
68
+
69
+ if (isPermutationsMode) {
70
+ return <PermutationsGrid code={activeDemoPattern} scope={fullScope} knobs={knobs} knobValues={knobValues} />
71
+ }
72
+
73
+ return <LiveComponent code={activeDemoPattern} scope={fullScope} />
74
+ }
75
+
76
+ return (
77
+ <Flex isColumn={true}>
78
+ <Flex gap={8} padding={8} isYCentered={true} isBetween={true}>
79
+ <Flex gap={8} isYCentered={true}>
80
+ <Segments isSmall={true}>
81
+ {demoComponent && (
82
+ <Segments.Button
83
+ key="demoComponent"
84
+ value="demo"
85
+ iconLeft="arrow-4way"
86
+ isActive={isDefaultPattern && !isPermutationsMode}
87
+ onClick={onSelectDemo}
88
+ />
89
+ )}
90
+ {demoMeta._patterns.map((p, key) => {
91
+ const onSelectPattern = (): void => setActiveDemoPattern(p.content)
92
+ const isActive: boolean = !isDefaultPattern && !isPermutationsMode && activeDemoPattern === p.content
93
+
94
+ return <Segments.Button key={key} value={p.description} isActive={isActive} onClick={onSelectPattern} />
95
+ })}
96
+ </Segments>
97
+ {canShowPermutations && (
98
+ <Segments isSmall={true}>
99
+ <Segments.Button
100
+ value="Permutations"
101
+ iconLeft="layout-bottom"
102
+ isActive={isPermutationsMode}
103
+ onClick={onTogglePermutations}
104
+ />
105
+ </Segments>
106
+ )}
107
+ </Flex>
108
+ {knobSetters["hasWorkshopBackground"] && (
109
+ <Toggle label="Show grid" isChecked={hasWorkshopBackground} isSmall={true} onChange={onBackgroundToggle} />
110
+ )}
111
+ </Flex>
112
+ <Separator emphasis="secondary" />
113
+ <GridBackground>{renderContent()}</GridBackground>
114
+ </Flex>
115
+ )
116
+ }
@@ -0,0 +1,9 @@
1
+ import type { WorkshopAttributeMeta } from "../../types/meta"
2
+ import type { KnobRegistry } from "../../types/knobs"
3
+ import type { ReactNode } from "react"
4
+
5
+ export interface LiveComponentsProps {
6
+ demoComponent?: ReactNode
7
+ demoMeta: WorkshopAttributeMeta<unknown>
8
+ knobs: KnobRegistry
9
+ }
@@ -0,0 +1 @@
1
+ export const toScreamingCase = (str: string): string => str.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()
@@ -0,0 +1,14 @@
1
+ .gridBackground {
2
+ margin-top: -1px;
3
+ margin-left: -1px;
4
+
5
+ background-color: var(--ui-bg);
6
+ box-shadow: inset 0 0 0 8px var(--ui-bg);
7
+ }
8
+
9
+ .gridBackgroundAlt {
10
+ background-image:
11
+ linear-gradient(90deg, var(--ui-border-secondary) 0, transparent 1px),
12
+ linear-gradient(180deg, var(--ui-border-secondary) 0, transparent 1px);
13
+ background-size: 8px 8px;
14
+ }
@@ -0,0 +1,18 @@
1
+ "use client"
2
+
3
+ import { getClasses } from "@heliosgraphics/utils"
4
+ import styles from "./GridBackground.module.css"
5
+ import { useContext, type FC } from "react"
6
+ import type { GridBackgroundProps } from "./GridBackground.types"
7
+ import { WorkshopContext } from "../../../../contexts/WorkshopProvider"
8
+
9
+ export const GridBackground: FC<GridBackgroundProps> = ({ children }) => {
10
+ const { knobValues } = useContext(WorkshopContext)
11
+ const hasWorkshopBackground = knobValues["hasWorkshopBackground"] === true
12
+
13
+ const gridBackgroundClasses: string = getClasses(styles.gridBackground, {
14
+ [styles.gridBackgroundAlt]: hasWorkshopBackground,
15
+ })
16
+
17
+ return <div className={gridBackgroundClasses}>{children}</div>
18
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ export interface GridBackgroundProps {
4
+ children: ReactNode
5
+ }
@@ -0,0 +1 @@
1
+ export { GridBackground } from "./GridBackground"
@@ -0,0 +1,45 @@
1
+ .liveEditor {
2
+ background-color: var(--ui-bg-primary);
3
+ }
4
+
5
+ .liveEditor pre {
6
+ position: relative;
7
+
8
+ padding: 24px 24px 24px 0 !important;
9
+
10
+ border-top: 1px solid var(--ui-border-secondary);
11
+
12
+ counter-reset: token-line;
13
+ }
14
+
15
+ .liveEditor pre:before {
16
+ position: absolute;
17
+ top: 0;
18
+ left: 0;
19
+ z-index: 0;
20
+
21
+ height: 100%;
22
+ padding-block: 16px;
23
+ width: 40px;
24
+
25
+ background-color: var(--ui-bg-secondary);
26
+ border-right: 1px solid var(--ui-border-secondary);
27
+ content: " ";
28
+ }
29
+
30
+ .liveEditor pre > span::before {
31
+ position: relative;
32
+ z-index: 1;
33
+
34
+ justify-content: flex-end;
35
+ display: inline-flex;
36
+ margin-right: 16px;
37
+ min-width: 40px;
38
+ padding: 0 8px;
39
+ width: 40px;
40
+
41
+ color: var(--ui-text-tertiary);
42
+
43
+ counter-increment: token-line;
44
+ content: counter(token-line);
45
+ }
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import styles from "./LiveComponent.module.css"
4
+ import type { LiveComponentProps } from "./LiveComponent.types"
5
+ import type { PrismTheme } from "prism-react-renderer"
6
+ import { LiveProvider, LiveEditor, LiveError, LivePreview } from "recat"
7
+ import { Flex } from "@heliosgraphics/ui"
8
+ import { getClasses } from "@heliosgraphics/utils"
9
+ import type { FC } from "react"
10
+
11
+ const PRISM_THEME: PrismTheme = {
12
+ plain: {
13
+ backgroundColor: "var(--ui-bg)",
14
+ color: "var(--ui-text-secondary)",
15
+ },
16
+ styles: [
17
+ {
18
+ types: ["comment", "prolog", "doctype", "cdata", "punctuation"],
19
+ style: { color: "var(--ui-text-secondary)" },
20
+ },
21
+ {
22
+ types: [
23
+ "boolean",
24
+ "string",
25
+ "entity",
26
+ "url",
27
+ "attr-value",
28
+ "keyword",
29
+ "control",
30
+ "directive",
31
+ "unit",
32
+ "statement",
33
+ "regex",
34
+ "at-rule",
35
+ "placeholder",
36
+ "variable",
37
+ "script",
38
+ ],
39
+ style: {
40
+ color: "hsl(var(--orange-hue), var(--orange-saturation), 50%)",
41
+ fontWeight: "700",
42
+ },
43
+ },
44
+ ],
45
+ }
46
+
47
+ export const LiveComponent: FC<LiveComponentProps> = ({ code, scope }) => {
48
+ const liveEditorClasses: string = getClasses(styles.liveEditor, "mono tiny", {})
49
+
50
+ if (!code) return null
51
+
52
+ return (
53
+ <Flex isColumn={true}>
54
+ <LiveProvider code={code} scope={scope} theme={PRISM_THEME}>
55
+ <Flex isColumn={true} gap={12} padding={16}>
56
+ <LiveError />
57
+ <LivePreview />
58
+ </Flex>
59
+ <LiveEditor className={liveEditorClasses} disabled />
60
+ </LiveProvider>
61
+ </Flex>
62
+ )
63
+ }
@@ -0,0 +1,4 @@
1
+ export interface LiveComponentProps {
2
+ code?: string
3
+ scope: Record<string, unknown>
4
+ }
@@ -0,0 +1 @@
1
+ export { LiveComponent } from "./LiveComponent"
@@ -0,0 +1,28 @@
1
+ .permutationsGrid {
2
+ display: grid;
3
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
4
+ gap: 1px;
5
+ /*background-color: var(--ui-border-secondary);*/
6
+ }
7
+
8
+ .permutationsCell {
9
+ display: flex;
10
+ flex-direction: column;
11
+ background-color: var(--ui-bg);
12
+ overflow: hidden;
13
+ }
14
+
15
+ .permutationsCellPreview {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ padding: 16px;
20
+ flex: 1;
21
+ min-height: 80px;
22
+ }
23
+
24
+ .permutationsCellLabel {
25
+ padding: 4px 8px;
26
+ background-color: var(--ui-bg-secondary);
27
+ border-top: 1px solid var(--ui-border-secondary);
28
+ }
@@ -0,0 +1,117 @@
1
+ "use client"
2
+
3
+ import styles from "./PermutationsGrid.module.css"
4
+ import { LiveProvider, LivePreview, LiveError } from "recat"
5
+ import { Checkbox, Flex, Text } from "@heliosgraphics/ui"
6
+ import { useState, useEffect, useMemo, useCallback } from "react"
7
+ import type { FC, ChangeEvent } from "react"
8
+ import type { PermutationsGridProps } from "./PermutationsGrid.types"
9
+ import type { Combination } from "./PermutationsGrid.utils"
10
+ import { DEFAULT_AXIS_COUNT, getPatternKnobNames, getCartesianProduct } from "./PermutationsGrid.utils"
11
+ import { toScreamingCase } from "../../LiveComponents.utils"
12
+
13
+ export const PermutationsGrid: FC<PermutationsGridProps> = ({ code, scope, knobs }) => {
14
+ const relevantKnobNames: Array<string> = useMemo(
15
+ () =>
16
+ getPatternKnobNames(
17
+ code,
18
+ Object.keys(knobs).filter((k) => !knobs[k]?.isInternal),
19
+ ),
20
+ [code, knobs],
21
+ )
22
+
23
+ const [activeAxes, setActiveAxes] = useState<Array<string>>(() => relevantKnobNames.slice(0, DEFAULT_AXIS_COUNT))
24
+
25
+ useEffect(() => {
26
+ const validAxes: Array<string> = activeAxes.filter((axis) => relevantKnobNames.includes(axis))
27
+
28
+ if (validAxes.length !== activeAxes.length) {
29
+ const nextAxes: Array<string> = validAxes.length ? validAxes : relevantKnobNames.slice(0, DEFAULT_AXIS_COUNT)
30
+ setActiveAxes(nextAxes)
31
+ }
32
+ }, [relevantKnobNames, activeAxes])
33
+
34
+ const onToggleAxis = useCallback(
35
+ (name: string) =>
36
+ (_event: ChangeEvent<HTMLInputElement>): void => {
37
+ setActiveAxes((prev) => {
38
+ if (prev.includes(name)) {
39
+ if (prev.length <= 1) return prev
40
+ return prev.filter((axis) => axis !== name)
41
+ }
42
+
43
+ return [...prev, name]
44
+ })
45
+ },
46
+ [],
47
+ )
48
+
49
+ const combinations: Array<Combination> = useMemo(() => {
50
+ const axes = activeAxes.map((name) => ({ name, values: knobs[name]?.values ?? [] }))
51
+ return getCartesianProduct(axes)
52
+ }, [activeAxes, knobs])
53
+
54
+ const buildCellScope = useCallback(
55
+ (combination: Combination): Record<string, unknown> => {
56
+ const cellScope: Record<string, unknown> = { ...scope }
57
+
58
+ for (const [key, value] of Object.entries(combination)) {
59
+ cellScope[toScreamingCase(key)] = value
60
+ }
61
+
62
+ return cellScope
63
+ },
64
+ [scope],
65
+ )
66
+
67
+ const getCombinationLabel = useCallback(
68
+ (combination: Combination): string => activeAxes.map((axis) => String(combination[axis])).join(" · "),
69
+ [activeAxes],
70
+ )
71
+
72
+ if (!relevantKnobNames.length) {
73
+ return (
74
+ <Flex padding={16}>
75
+ <Text emphasis="secondary" type="small">
76
+ No permutable knobs detected in this pattern.
77
+ </Text>
78
+ </Flex>
79
+ )
80
+ }
81
+
82
+ return (
83
+ <Flex isColumn={true}>
84
+ <Flex padding={8} gap={8} isYCentered={true} isWrapped={true}>
85
+ <Text type="tiny" emphasis="secondary">
86
+ Axes:
87
+ </Text>
88
+ {relevantKnobNames.map((name) => (
89
+ <Checkbox
90
+ key={name}
91
+ label={knobs[name]?.label ?? name}
92
+ isChecked={activeAxes.includes(name)}
93
+ onChange={onToggleAxis(name)}
94
+ isSmall={true}
95
+ />
96
+ ))}
97
+ </Flex>
98
+ <div className={styles.permutationsGrid}>
99
+ {combinations.map((combination, index) => (
100
+ <div key={index} className={styles.permutationsCell}>
101
+ <div className={styles.permutationsCellPreview}>
102
+ <LiveProvider code={code} scope={buildCellScope(combination)}>
103
+ <LiveError />
104
+ <LivePreview />
105
+ </LiveProvider>
106
+ </div>
107
+ <div className={styles.permutationsCellLabel}>
108
+ <Text type="tiny" emphasis="secondary">
109
+ {getCombinationLabel(combination)}
110
+ </Text>
111
+ </div>
112
+ </div>
113
+ ))}
114
+ </div>
115
+ </Flex>
116
+ )
117
+ }
@@ -0,0 +1,8 @@
1
+ import type { KnobRegistry, KnobValues } from "../../../../types/knobs"
2
+
3
+ export interface PermutationsGridProps {
4
+ code: string
5
+ scope: Record<string, unknown>
6
+ knobs: KnobRegistry
7
+ knobValues: KnobValues
8
+ }
@@ -0,0 +1,24 @@
1
+ import { toScreamingCase } from "../../LiveComponents.utils"
2
+
3
+ type KnobValue = string | number | boolean
4
+
5
+ export type Combination = Record<string, KnobValue>
6
+
7
+ export const DEFAULT_AXIS_COUNT: number = 2
8
+
9
+ export const getPatternKnobNames = (code: string, knobKeys: Array<string>): Array<string> => {
10
+ return knobKeys.filter((key) => {
11
+ const screamingKey: string = toScreamingCase(key)
12
+ return code.includes(`{${screamingKey}}`) || code.includes(`=${screamingKey}`)
13
+ })
14
+ }
15
+
16
+ export const getCartesianProduct = (axes: Array<{ name: string; values: Array<KnobValue> }>): Array<Combination> => {
17
+ if (!axes.length) return []
18
+
19
+ return axes.reduce<Array<Combination>>(
20
+ (combinations, axis) =>
21
+ combinations.flatMap((combo) => axis.values.map((value) => ({ ...combo, [axis.name]: value }))),
22
+ [{}],
23
+ )
24
+ }
@@ -0,0 +1,2 @@
1
+ export { PermutationsGrid } from "./PermutationsGrid"
2
+ export type { PermutationsGridProps } from "./PermutationsGrid.types"
@@ -0,0 +1,2 @@
1
+ export { LiveComponents } from "./LiveComponents"
2
+ export type { LiveComponentsProps } from "./LiveComponents.types"
@@ -0,0 +1,52 @@
1
+ "use client"
2
+
3
+ import { Flex, Text, Heading, Separator } from "@heliosgraphics/ui"
4
+ import type { HeliosScaleType } from "@heliosgraphics/ui"
5
+ import type { FC } from "react"
6
+ import type { PageProps } from "./Page.types"
7
+
8
+ export const Page: FC<PageProps> = ({
9
+ children,
10
+ description,
11
+ breadcrumb,
12
+ disabledPadding,
13
+ footer,
14
+ header,
15
+ contentFlexProps,
16
+ title,
17
+ }) => {
18
+ const commonFlexProps = {
19
+ isColumn: true as const,
20
+ gap: disabledPadding ? (0 as const) : (16 as const),
21
+ padding: disabledPadding ? (0 as const) : ([8, 16, 24] as [HeliosScaleType, HeliosScaleType, HeliosScaleType]),
22
+ withBackground: "primary" as const,
23
+ ...contentFlexProps,
24
+ }
25
+
26
+ return (
27
+ <>
28
+ {header}
29
+ <Separator emphasis="tertiary" />
30
+ <Flex {...commonFlexProps}>
31
+ {title && (
32
+ <Flex isColumn={true} padding={disabledPadding ? 16 : 0}>
33
+ <Flex isYCentered={true} gap={4} isBetween={true}>
34
+ {breadcrumb}
35
+ </Flex>
36
+ <Heading level={0}>{title}</Heading>
37
+ </Flex>
38
+ )}
39
+ {description && <Text type="paragraph">{description}</Text>}
40
+ </Flex>
41
+ <Separator emphasis="tertiary" />
42
+ <Flex {...commonFlexProps}>{children}</Flex>
43
+ {footer ?? (
44
+ <Flex withBackground="primary" padding={16} gap={4} isBetween={true} isYCentered={true}>
45
+ <Text type="tiny" emphasis="tertiary">
46
+ &copy; {new Date().getFullYear()}
47
+ </Text>
48
+ </Flex>
49
+ )}
50
+ </>
51
+ )
52
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react"
2
+ import type { FlexProps } from "@heliosgraphics/ui"
3
+
4
+ export interface PageProps {
5
+ breadcrumb?: ReactNode | undefined
6
+ children?: ReactNode | undefined
7
+ description?: string | undefined
8
+ disabledPadding?: boolean | undefined
9
+ footer?: ReactNode | undefined
10
+ header?: ReactNode | undefined
11
+ contentFlexProps?: Partial<FlexProps> | undefined
12
+ title?: string | undefined
13
+ }
@@ -0,0 +1,2 @@
1
+ export { Page } from "./Page"
2
+ export type { PageProps } from "./Page.types"
@@ -0,0 +1,16 @@
1
+ import { Flex, Heading } from "@heliosgraphics/ui"
2
+ import type { FC } from "react"
3
+ import type { PageBlockProps } from "./PageBlock.types"
4
+
5
+ export const PageBlock: FC<PageBlockProps> = ({ children, title }) => {
6
+ return (
7
+ <Flex isColumn={true} gap={8}>
8
+ {title && (
9
+ <Heading level={2} fontWeight="semibold">
10
+ {title}
11
+ </Heading>
12
+ )}
13
+ {children}
14
+ </Flex>
15
+ )
16
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ export interface PageBlockProps {
4
+ children: ReactNode
5
+ title?: string
6
+ }
@@ -0,0 +1,2 @@
1
+ export { PageBlock } from "./PageBlock"
2
+ export type { PageBlockProps } from "./PageBlock.types"
@@ -0,0 +1,84 @@
1
+ "use client"
2
+
3
+ import { type ReactNode, useContext, type FC } from "react"
4
+ import { Text, Flex, Table, Pill, Separator } from "@heliosgraphics/ui"
5
+ import { WorkshopContext } from "../../contexts/WorkshopProvider"
6
+ import { LayoutContext, DEFAULT_DEMO_PATTERN } from "../../contexts/LayoutProvider"
7
+ import { getEditorComponent } from "./PropsTable.utils"
8
+ import type { PropsTableProps } from "./PropsTable.types"
9
+
10
+ export const PropsTable: FC<PropsTableProps> = ({ meta, knobs }) => {
11
+ const context = useContext(WorkshopContext)
12
+ const { activeDemoPattern } = useContext(LayoutContext)
13
+
14
+ if (!meta) return null
15
+
16
+ const renderRow = ([name, value]: [string, unknown], key: number): ReactNode => {
17
+ const row = value as Record<string, unknown>
18
+ const type: string = (row?.["type"] as string) ?? row?.toString?.() ?? "Unknown"
19
+ const isExtends: boolean = name === "_extends"
20
+ const isRequired: boolean = !isExtends && !row?.["isOptional"]
21
+
22
+ if ((name.startsWith("_") && !isExtends) || row?.["alias"] != null) return null
23
+
24
+ const isDisabled: boolean =
25
+ activeDemoPattern !== DEFAULT_DEMO_PATTERN && !activeDemoPattern.toLowerCase().includes(`${name}=`.toLowerCase())
26
+
27
+ const editorComponent = getEditorComponent(
28
+ name,
29
+ type,
30
+ row?.["knob"] as string | undefined,
31
+ knobs,
32
+ context,
33
+ isDisabled,
34
+ )
35
+
36
+ return (
37
+ <tr key={key} className={isExtends ? "ui-bg-secondary" : ""}>
38
+ <td>
39
+ <Flex isYCentered={true} gap={4}>
40
+ {isExtends ? <em>extends</em> : name}
41
+ {isRequired && (
42
+ <Flex>
43
+ <Pill color="orange" label="Required" size="tiny" isMono={true} />
44
+ </Flex>
45
+ )}
46
+ </Flex>
47
+ </td>
48
+ <td>
49
+ <Flex gap={8} isYCentered={true} isBetween={true}>
50
+ <Pill label={type} color="gray" size="tiny" isMono={true} />
51
+ {editorComponent}
52
+ </Flex>
53
+ </td>
54
+ <td>
55
+ <Text type="tiny" emphasis="secondary">
56
+ {(row?.["default"] as string) || "-"}
57
+ </Text>
58
+ </td>
59
+ <td>
60
+ <Text type="tiny" emphasis="secondary">
61
+ {(row?.["description"] as string) || "-"}
62
+ </Text>
63
+ </td>
64
+ </tr>
65
+ )
66
+ }
67
+
68
+ return (
69
+ <>
70
+ <Table isMonoHeader={true}>
71
+ <thead>
72
+ <tr>
73
+ <th scope="col">Name</th>
74
+ <th scope="col">Type</th>
75
+ <th scope="col">Default</th>
76
+ <th scope="col">Description</th>
77
+ </tr>
78
+ </thead>
79
+ <tbody>{Object.entries(meta).map(renderRow)}</tbody>
80
+ </Table>
81
+ <Separator emphasis="secondary" />
82
+ </>
83
+ )
84
+ }
@@ -0,0 +1,7 @@
1
+ import type { WorkshopAttributeMeta } from "../../types/meta"
2
+ import type { KnobRegistry } from "../../types/knobs"
3
+
4
+ export interface PropsTableProps {
5
+ meta: WorkshopAttributeMeta<unknown>
6
+ knobs: KnobRegistry
7
+ }