@themeable/layout 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Stack.css ADDED
@@ -0,0 +1,86 @@
1
+ .h-stack {
2
+ display: flex;
3
+ flex-direction: row;
4
+ flex-shrink: 0;
5
+ }
6
+
7
+ .v-stack {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: flex-start;
11
+ flex-shrink: 0;
12
+ }
13
+
14
+ .v-stack.left {
15
+ align-items: flex-start;
16
+ }
17
+
18
+ .v-stack.center {
19
+ align-items: center;
20
+ }
21
+
22
+ .v-stack.right {
23
+ align-items: flex-end;
24
+ }
25
+
26
+ .v-stack.top {
27
+ justify-content: flex-start;
28
+ }
29
+
30
+ .v-stack.middle {
31
+ justify-content: center;
32
+ }
33
+
34
+ .v-stack.bottom {
35
+ justify-content: flex-end;
36
+ }
37
+
38
+ .h-stack.left {
39
+ justify-content: flex-start;
40
+ }
41
+
42
+ .h-stack.center {
43
+ justify-content: center;
44
+ }
45
+
46
+ .h-stack.right {
47
+ justify-content: flex-end;
48
+ }
49
+
50
+ .h-stack.top {
51
+ align-items: flex-start;
52
+ }
53
+
54
+ .h-stack.middle {
55
+ align-items: center;
56
+ }
57
+
58
+ .h-stack.bottom {
59
+ align-items: flex-end;
60
+ }
61
+
62
+ /* Self controls */
63
+ .v-stack>.width-fill,
64
+ .h-stack>.height-fill {
65
+ align-self: stretch;
66
+ }
67
+
68
+ .v-stack>.height-fill,
69
+ .h-stack>.width-fill {
70
+ flex: 1 0 0;
71
+ }
72
+
73
+
74
+ .scroll-vertical {
75
+ overflow-y: auto;
76
+ flex: 1;
77
+ }
78
+
79
+ .scroll-horizontal {
80
+ overflow-x: auto;
81
+ flex: 1;
82
+ }
83
+
84
+ .h-stack.wrap {
85
+ flex-wrap: wrap;
86
+ }
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const EnviroSniffer: () => React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { useThemeable } from "./Provider";
3
+ import { useTokens } from "./Provider/Tokens";
4
+ export var EnviroSniffer = function () {
5
+ var viewSize = useTokens().viewSize;
6
+ var browserViewport = useThemeable().browserViewport;
7
+ return (React.createElement(React.Fragment, null,
8
+ React.createElement("p", null,
9
+ "Browser size: ",
10
+ browserViewport),
11
+ React.createElement("p", null,
12
+ "View size: ",
13
+ viewSize)));
14
+ };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ type ViewSize = 'compact' | 'medium' | 'expanded';
3
+ type ViewSizeLimit = 'compact' | 'medium';
4
+ type SpacingContextValue = {
5
+ spacingMode?: 'normal' | 'grouped';
6
+ setMaxViewSize?: ViewSizeLimit;
7
+ viewSize?: ViewSize;
8
+ };
9
+ export declare const Modifier: ({ setMaxViewSize: localSetMaxViewSize, children, }: {
10
+ setMaxViewSize?: ViewSizeLimit;
11
+ children: React.ReactNode;
12
+ }) => React.JSX.Element;
13
+ export declare const useTokens: () => SpacingContextValue;
14
+ export {};
@@ -0,0 +1,35 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import React, { createContext, useContext } from 'react';
13
+ import { useThemeable } from './index';
14
+ var clampViewSize = function (actual, limit) {
15
+ if (!limit)
16
+ return actual;
17
+ if (limit === 'compact')
18
+ return 'compact';
19
+ return actual === 'compact' ? 'compact' : 'medium';
20
+ };
21
+ // @ts-ignore
22
+ var TokenContext = createContext({});
23
+ export var Modifier = function (_a) {
24
+ var localSetMaxViewSize = _a.setMaxViewSize, children = _a.children;
25
+ var parent = useContext(TokenContext);
26
+ var browserViewport = useThemeable().browserViewport;
27
+ // Inherit from parent if not provided locally
28
+ var inheritedSetMaxViewSize = localSetMaxViewSize !== null && localSetMaxViewSize !== void 0 ? localSetMaxViewSize : parent.setMaxViewSize;
29
+ var maxViewSize = clampViewSize(browserViewport || 'expanded', inheritedSetMaxViewSize);
30
+ var value = __assign(__assign({}, parent), { setMaxViewSize: inheritedSetMaxViewSize, viewSize: maxViewSize });
31
+ return (React.createElement(TokenContext.Provider, { value: value }, children));
32
+ };
33
+ export var useTokens = function () {
34
+ return useContext(TokenContext);
35
+ };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { Theme } from "./types";
3
+ export declare const ThemeableContext: React.Context<Theme | undefined>;
4
+ interface ThemeConfigProps {
5
+ children: React.ReactNode;
6
+ }
7
+ export declare const LayoutProvider: ({ children, }: ThemeConfigProps) => React.JSX.Element;
8
+ export declare const useThemeable: () => Theme;
9
+ export {};
@@ -0,0 +1,37 @@
1
+ import React, { createContext, useContext, useState, useEffect } from 'react';
2
+ export var ThemeableContext = createContext(undefined);
3
+ export var LayoutProvider = function (_a) {
4
+ var children = _a.children;
5
+ var _b = useState('compact'), browserViewport = _b[0], setBrowserViewport = _b[1];
6
+ var prefResponsive = {
7
+ medium: 600,
8
+ expanded: 1200
9
+ };
10
+ useEffect(function () {
11
+ var updateViewportSize = function () {
12
+ var width = window.innerWidth;
13
+ if (width > prefResponsive.expanded) {
14
+ setBrowserViewport('expanded');
15
+ }
16
+ else if (width > prefResponsive.medium) {
17
+ setBrowserViewport('medium');
18
+ }
19
+ else {
20
+ setBrowserViewport('compact');
21
+ }
22
+ };
23
+ updateViewportSize();
24
+ window.addEventListener('resize', updateViewportSize);
25
+ return function () { return window.removeEventListener('resize', updateViewportSize); };
26
+ }, [prefResponsive]);
27
+ return (React.createElement(ThemeableContext.Provider, { value: {
28
+ browserViewport: browserViewport
29
+ } }, children));
30
+ };
31
+ export var useThemeable = function () {
32
+ var context = useContext(ThemeableContext);
33
+ if (!context) {
34
+ throw new Error('useThemeable must be used within a ThemeableProvider');
35
+ }
36
+ return context;
37
+ };
@@ -0,0 +1,193 @@
1
+ export interface ColorScheme {
2
+ textSecondaryAlpha: string;
3
+ textPrimary: string;
4
+ iconAlpha: string;
5
+ accentAlpha: string;
6
+ notice: string;
7
+ onPrimary: string;
8
+ fillContainer: string;
9
+ surfaceHoverAlpha: string;
10
+ negative: string;
11
+ primaryAlpha: string;
12
+ divider: string;
13
+ backgroundElevated: string;
14
+ dividerAlpha: string;
15
+ surfaceDown: string;
16
+ fillControl: string;
17
+ icon: string;
18
+ informative: string;
19
+ backgroundFlat: string;
20
+ border: string;
21
+ surfaceDownAlpha: string;
22
+ readOnly: string;
23
+ readOnlyAlpha: string;
24
+ surfaceHover: string;
25
+ fillContainerAlpha: string;
26
+ borderAlpha: string;
27
+ fillControlAlpha: string;
28
+ textSecondary: string;
29
+ textPrimaryAlpha: string;
30
+ positive: string;
31
+ onAccent: string;
32
+ accent: string;
33
+ primary: string;
34
+ }
35
+ export interface Radius {
36
+ smallControl: number;
37
+ largeControl: number;
38
+ container: number;
39
+ page: number;
40
+ }
41
+ export interface Size {
42
+ large: number;
43
+ small: number;
44
+ base: number;
45
+ }
46
+ export interface ResponsiveSize {
47
+ expanded: number;
48
+ medium: number;
49
+ maxPageBleed: number;
50
+ }
51
+ export interface Space {
52
+ hExtra: number;
53
+ hSmall: number;
54
+ content: number;
55
+ vExtra: number;
56
+ hSection: number;
57
+ hUnify: number;
58
+ vSection: number;
59
+ vSmall: number;
60
+ vUnify: number;
61
+ }
62
+ export interface Text {
63
+ title3LetterSpacing: number;
64
+ headlineLetterSpacing: number;
65
+ title2Size: number;
66
+ title1LineHeight: number;
67
+ subheadLineHeight: number;
68
+ bodyLineHeight: number;
69
+ title3LineHeight: number;
70
+ subheadSize: number;
71
+ caption1LetterSpacing: number;
72
+ caption2LineHeight: number;
73
+ caption1Size: number;
74
+ footnoteSize: number;
75
+ caption2LetterSpacing: number;
76
+ bodyLetterSpacing: number;
77
+ title2LineHeight: number;
78
+ headlineLineHeight: number;
79
+ title2LetterSpacing: number;
80
+ caption1LineHeight: number;
81
+ title3Size: number;
82
+ footnoteLetterSpacing: number;
83
+ title1Size: number;
84
+ footnoteLineHeight: number;
85
+ subheadLetterSpacing: number;
86
+ caption2Size: number;
87
+ bodySize: number;
88
+ headlineSize: number;
89
+ title1LetterSpacing: number;
90
+ }
91
+ export interface TextWeight {
92
+ footnoteBold: number;
93
+ bodyLight: number;
94
+ footnoteLight: number;
95
+ footnote: number;
96
+ caption2Bold: number;
97
+ bodyBold: number;
98
+ subhead: number;
99
+ title2Regular: number;
100
+ title2: number;
101
+ subheadLight: number;
102
+ title2Light: number;
103
+ caption2: number;
104
+ headlineBold: number;
105
+ footnoteRegular: number;
106
+ body: number;
107
+ title1: number;
108
+ title1Light: number;
109
+ title3Light: number;
110
+ subheadRegular: number;
111
+ subheadBold: number;
112
+ caption1Bold: number;
113
+ title3Bold: number;
114
+ caption1Light: number;
115
+ headline: number;
116
+ title3Regular: number;
117
+ headlineRegular: number;
118
+ caption1: number;
119
+ title2Bold: number;
120
+ title1Bold: number;
121
+ headlineLight: number;
122
+ bodyRegular: number;
123
+ caption2Light: number;
124
+ title1Regular: number;
125
+ caption2Regular: number;
126
+ caption1Regular: number;
127
+ title3: number;
128
+ }
129
+ export interface TextFamily {
130
+ name: string;
131
+ titleFallback: string;
132
+ fallback: string;
133
+ src: string;
134
+ titleName: string;
135
+ titleSrc: string;
136
+ }
137
+ export interface Foundations {
138
+ radius: {
139
+ compact: Radius;
140
+ medium: Radius;
141
+ expanded: Radius;
142
+ };
143
+ size: {
144
+ responsive: ResponsiveSize;
145
+ compact: Size;
146
+ medium: Size;
147
+ expanded: Size;
148
+ };
149
+ space: {
150
+ compact: Space;
151
+ medium: Space;
152
+ expanded: Space;
153
+ };
154
+ color: {
155
+ light: ColorScheme;
156
+ dark: ColorScheme;
157
+ };
158
+ text: {
159
+ compact: Text;
160
+ medium: Text;
161
+ expanded: Text;
162
+ weight: TextWeight;
163
+ family: TextFamily;
164
+ };
165
+ }
166
+ export interface ThemeData {
167
+ id: string;
168
+ owner: string;
169
+ createdOn: {
170
+ _seconds: number;
171
+ _nanoseconds: number;
172
+ };
173
+ name: string;
174
+ foundations: Foundations;
175
+ css: string;
176
+ lastModified: {
177
+ _seconds: number;
178
+ _nanoseconds: number;
179
+ };
180
+ }
181
+ export interface Theme {
182
+ color?: ColorScheme;
183
+ radius?: Radius;
184
+ textStyle?: Text;
185
+ textWeight?: TextWeight;
186
+ textFamily?: TextFamily;
187
+ space?: Space;
188
+ size?: Size;
189
+ viewportSize?: 'compact' | 'medium' | 'expanded';
190
+ svgPath?: string;
191
+ browserViewport?: 'compact' | 'medium' | 'expanded';
192
+ spacingMode?: 'normal' | 'grouped';
193
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { HStackProps } from "./types";
3
+ import '../../Stack.css';
4
+ export declare const HStack: React.FC<HStackProps>;
@@ -0,0 +1,76 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import React, { useMemo } from "react";
13
+ import { generateGridTemplateColumns, showOnSplit } from "./utils";
14
+ import '../../Stack.css';
15
+ import { Modifier, useTokens } from "../Provider/Tokens";
16
+ export var HStack = function (_a) {
17
+ var style = _a.style, _b = _a.maxWidth, maxWidth = _b === void 0 ? undefined : _b, children = _a.children, className = _a.className, align = _a.align, showOn = _a.showOn, gridExpanded = _a.gridExpanded, gridMedium = _a.gridMedium, gridCompact = _a.gridCompact, ref = _a.ref, _c = _a.width, width = _c === void 0 ? 'fill' : _c, _d = _a.height, height = _d === void 0 ? 'hug' : _d, wrap = _a.wrap, _e = _a.sectionSpacing, sectionSpacing = _e === void 0 ? false : _e, _f = _a.group, group = _f === void 0 ? false : _f, setMaxViewSize = _a.setMaxViewSize;
18
+ var _g = useTokens(), spacingMode = _g.spacingMode, viewSize = _g.viewSize;
19
+ var grid = {
20
+ compact: gridCompact,
21
+ medium: gridMedium,
22
+ expanded: gridExpanded
23
+ };
24
+ var hstackStyles = useMemo(function () {
25
+ var display = (function () {
26
+ if (gridExpanded && gridMedium && gridCompact)
27
+ return 'grid';
28
+ if (viewSize && showOn)
29
+ return showOnSplit(showOn, viewSize);
30
+ return undefined;
31
+ })();
32
+ return __assign({ maxWidth: maxWidth, transition: '.2s', display: display,
33
+ // paddingTop: group ? `${space.vExtra}px` : undefined,
34
+ // paddingBottom: group ? `${space.vExtra}px` : undefined,
35
+ // paddingRight: group ? `${space.hExtra}px` : undefined,
36
+ // paddingLeft: group ? `${space.hExtra}px` : undefined,
37
+ gridTemplateColumns: gridCompact && gridMedium && gridExpanded && viewSize
38
+ ? generateGridTemplateColumns(grid[viewSize])
39
+ : undefined,
40
+ // gap: sectionSpacing ? `${space.vSection}px ${space.hSection}px` : undefined,
41
+ gap: sectionSpacing ? "".concat(24, "px ").concat(24, "px") : undefined }, style);
42
+ }, [
43
+ maxWidth,
44
+ style,
45
+ gridExpanded,
46
+ gridMedium,
47
+ gridCompact,
48
+ viewSize,
49
+ showOn,
50
+ sectionSpacing,
51
+ // space.vExtra,
52
+ // space.hExtra,
53
+ // space.vSection,
54
+ // space.hSection,
55
+ grid,
56
+ ]);
57
+ var classes = [
58
+ 'h-stack',
59
+ className,
60
+ group && 'is-group',
61
+ "width-".concat(width),
62
+ "height-".concat(height),
63
+ // showOn ? showOnSplit(showOn, viewSize || 'expanded') : '',
64
+ align,
65
+ wrap && 'wrap',
66
+ // group && 'group'
67
+ ]
68
+ .filter(Boolean)
69
+ .join(' ');
70
+ var content = (React.createElement("div", { ref: ref, style: hstackStyles, className: classes }, children));
71
+ if (setMaxViewSize === 'medium' || setMaxViewSize === 'compact') {
72
+ return (React.createElement(Modifier, { setMaxViewSize: setMaxViewSize }, content));
73
+ }
74
+ return content;
75
+ };
76
+ HStack.displayName = 'HStack';
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { VStackProps } from "./types";
3
+ import '../../Stack.css';
4
+ export declare const VStack: React.ForwardRefExoticComponent<Omit<VStackProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,39 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import React, { forwardRef, useRef, useImperativeHandle } from "react";
13
+ // import { useViewportSize, ViewportSizeProvider } from "../ProviderResponsive";
14
+ import { showOnSplit } from "./utils";
15
+ import '../../Stack.css';
16
+ import { Modifier, useTokens } from "../Provider/Tokens";
17
+ export var VStack = forwardRef(function (_a, parentRef) {
18
+ var style = _a.style, maxWidth = _a.maxWidth, children = _a.children, className = _a.className, align = _a.align, showOn = _a.showOn, _b = _a.height, height = _b === void 0 ? 'hug' : _b, _c = _a.width, width = _c === void 0 ? 'fill' : _c, scroll = _a.scroll, _d = _a.group, group = _d === void 0 ? false : _d, setMaxViewSize = _a.setMaxViewSize;
19
+ var localRef = useRef(null);
20
+ useImperativeHandle(parentRef, function () { return localRef.current; });
21
+ var viewSize = useTokens().viewSize;
22
+ var combinedClasses = [
23
+ 'v-stack',
24
+ group && 'is-group',
25
+ className,
26
+ "width-".concat(width),
27
+ "height-".concat(height),
28
+ align,
29
+ scroll && "scroll-".concat(scroll),
30
+ ]
31
+ .filter(Boolean)
32
+ .join(' ');
33
+ var content = (React.createElement("div", { ref: localRef, style: __assign({ maxWidth: maxWidth, display: viewSize !== undefined && showOn ? showOnSplit(showOn, viewSize) : undefined, transition: '.2s' }, style), className: combinedClasses }, children));
34
+ if (setMaxViewSize === 'medium' || setMaxViewSize === 'compact') {
35
+ return (React.createElement(Modifier, { setMaxViewSize: setMaxViewSize || 'compact' }, content));
36
+ }
37
+ return content;
38
+ });
39
+ VStack.displayName = 'VStack';
@@ -0,0 +1,2 @@
1
+ export { HStack } from './HStack';
2
+ export { VStack } from './VStack';
@@ -0,0 +1,2 @@
1
+ export { HStack } from './HStack';
2
+ export { VStack } from './VStack';
@@ -0,0 +1,27 @@
1
+ import React, { CSSProperties, ReactNode, RefObject } from 'react';
2
+ interface StackProps {
3
+ style?: CSSProperties;
4
+ maxWidth?: string | number;
5
+ children?: ReactNode;
6
+ className?: string;
7
+ align?: string;
8
+ ref?: RefObject<HTMLDivElement>;
9
+ showOn?: 'compact' | 'medium' | 'expanded' | 'compact medium' | 'medium expanded' | 'compact expanded';
10
+ width?: 'hug' | 'fill';
11
+ height?: 'hug' | 'fill';
12
+ showOverflowBar?: boolean;
13
+ wrap?: boolean;
14
+ group?: boolean;
15
+ setMaxViewSize?: 'compact' | 'medium';
16
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
17
+ }
18
+ export interface HStackProps extends StackProps {
19
+ gridExpanded?: number[];
20
+ gridMedium?: number[];
21
+ gridCompact?: number[];
22
+ sectionSpacing?: boolean;
23
+ }
24
+ export interface VStackProps extends StackProps {
25
+ scroll?: 'vertical' | 'horizontal' | 'both' | '';
26
+ }
27
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const showOnSplit: (showOnValue: string, viewportSize: string) => "flex" | "none";
2
+ export declare const generateGridTemplateColumns: (grid: any) => any;
@@ -0,0 +1,9 @@
1
+ // export const showOnSplit = (showOnValue: string) => {
2
+ // return showOnValue?.split(" ").map(word => `showOn${word.charAt(0).toUpperCase() + word.slice(1)}`).join(" ");
3
+ // }
4
+ export var showOnSplit = function (showOnValue, viewportSize) {
5
+ return showOnValue.split(' ').includes(viewportSize) ? 'flex' : 'none';
6
+ };
7
+ export var generateGridTemplateColumns = function (grid) {
8
+ return grid.map(function (value) { return "minmax(0, ".concat(value, "fr)"); }).join(' ');
9
+ };
@@ -0,0 +1,4 @@
1
+ export { HStack, VStack } from './Stack/index';
2
+ export { EnviroSniffer } from './EnviroSniffer';
3
+ export { LayoutProvider } from './Provider/index';
4
+ export { useTokens } from './Provider/Tokens';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { HStack, VStack } from './Stack/index';
2
+ export { EnviroSniffer } from './EnviroSniffer';
3
+ export { LayoutProvider } from './Provider/index';
4
+ export { useTokens } from './Provider/Tokens';
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@themeable/layout",
3
+ "author": "Themeable Systems",
4
+ "version": "1.0.2",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "license": "ISC",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "peerDependencies": {
13
+ "react": ">=16.8.0"
14
+ },
15
+ "devDependencies": {
16
+ "@swc/core": "^1.11.11",
17
+ "@types/react": "^18.3.18",
18
+ "typescript": "^5.5.4"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "push": "pnpm build && pnpm version patch && npm publish",
23
+ "watch": "chokidar \"src/**/*\" \"../shared/**/*\" -c \"pnpm build\""
24
+ }
25
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useThemeable } from "./Provider";
3
+ import { useTokens } from "./Provider/Tokens";
4
+
5
+ export const EnviroSniffer = () => {
6
+
7
+ const { viewSize } = useTokens();
8
+ const { browserViewport } = useThemeable()
9
+ return (
10
+ <>
11
+ <p>Browser size: {browserViewport}</p>
12
+ {/* <p>Color mode: {colorMode}</p> */}
13
+ <p>View size: {viewSize}</p>
14
+ {/* <p>Section is: {space.hSection}</p> */}
15
+ {/* <p>Spacing mode: {spacingMode}</p> */}
16
+ </>
17
+ )
18
+ }
@@ -0,0 +1,55 @@
1
+ import React, { createContext, useContext } from 'react';
2
+ import { ThemeableContext, useThemeable } from './index';
3
+
4
+ type ViewSize = 'compact' | 'medium' | 'expanded';
5
+ type ViewSizeLimit = 'compact' | 'medium';
6
+
7
+ type SpacingContextValue = {
8
+ spacingMode?: 'normal' | 'grouped';
9
+ setMaxViewSize?: ViewSizeLimit;
10
+ viewSize?: ViewSize;
11
+ };
12
+
13
+ const clampViewSize = (
14
+ actual: ViewSize,
15
+ limit?: ViewSizeLimit
16
+ ): ViewSize => {
17
+ if (!limit) return actual;
18
+ if (limit === 'compact') return 'compact';
19
+ return actual === 'compact' ? 'compact' : 'medium';
20
+ };
21
+
22
+ // @ts-ignore
23
+ const TokenContext = createContext<SpacingContextValue>({});
24
+
25
+ export const Modifier = ({
26
+ setMaxViewSize: localSetMaxViewSize,
27
+ children,
28
+ }: {
29
+ setMaxViewSize?: ViewSizeLimit;
30
+ children: React.ReactNode;
31
+ }) => {
32
+ const parent = useContext(TokenContext);
33
+ const { browserViewport } = useThemeable();
34
+
35
+ // Inherit from parent if not provided locally
36
+ const inheritedSetMaxViewSize = localSetMaxViewSize ?? parent.setMaxViewSize;
37
+
38
+ const maxViewSize = clampViewSize(browserViewport || 'expanded', inheritedSetMaxViewSize);
39
+
40
+ const value: SpacingContextValue = {
41
+ ...parent,
42
+ setMaxViewSize: inheritedSetMaxViewSize,
43
+ viewSize: maxViewSize,
44
+ };
45
+
46
+ return (
47
+ <TokenContext.Provider value={value}>
48
+ {children}
49
+ </TokenContext.Provider>
50
+ );
51
+ };
52
+
53
+ export const useTokens = () => {
54
+ return useContext(TokenContext);
55
+ };
@@ -0,0 +1,51 @@
1
+ import React, { createContext, useContext, useState, useEffect } from 'react';
2
+ import { Theme } from "./types"
3
+
4
+ export const ThemeableContext = createContext<Theme | undefined>(undefined);
5
+
6
+ interface ThemeConfigProps {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ export const LayoutProvider = ({
11
+ children,
12
+ }: ThemeConfigProps) => {
13
+
14
+ const [browserViewport, setBrowserViewport] = useState<'compact' | 'medium' | 'expanded'>('compact');
15
+
16
+ const prefResponsive = {
17
+ medium: 600,
18
+ expanded: 1200
19
+ }
20
+ useEffect(() => {
21
+ const updateViewportSize = () => {
22
+ const width = window.innerWidth;
23
+ if (width > prefResponsive.expanded) {
24
+ setBrowserViewport('expanded');
25
+ } else if (width > prefResponsive.medium) {
26
+ setBrowserViewport('medium');
27
+ } else {
28
+ setBrowserViewport('compact');
29
+ }
30
+ };
31
+ updateViewportSize();
32
+ window.addEventListener('resize', updateViewportSize);
33
+ return () => window.removeEventListener('resize', updateViewportSize);
34
+ }, [prefResponsive]);
35
+
36
+ return (
37
+ <ThemeableContext.Provider value={{
38
+ browserViewport
39
+ }}>
40
+ {children}
41
+ </ThemeableContext.Provider>
42
+ );
43
+ };
44
+
45
+ export const useThemeable = () => {
46
+ const context = useContext(ThemeableContext);
47
+ if (!context) {
48
+ throw new Error('useThemeable must be used within a ThemeableProvider');
49
+ }
50
+ return context;
51
+ };
@@ -0,0 +1,212 @@
1
+ // Define the structure of the color object (light and dark modes)
2
+ export interface ColorScheme {
3
+ textSecondaryAlpha: string;
4
+ textPrimary: string;
5
+ iconAlpha: string;
6
+ accentAlpha: string;
7
+ notice: string;
8
+ onPrimary: string;
9
+ fillContainer: string;
10
+ surfaceHoverAlpha: string;
11
+ negative: string;
12
+ primaryAlpha: string;
13
+ divider: string;
14
+ backgroundElevated: string;
15
+ dividerAlpha: string;
16
+ surfaceDown: string;
17
+ fillControl: string;
18
+ icon: string;
19
+ informative: string;
20
+ backgroundFlat: string;
21
+ border: string;
22
+ surfaceDownAlpha: string;
23
+ readOnly: string;
24
+ readOnlyAlpha: string;
25
+ surfaceHover: string;
26
+ fillContainerAlpha: string;
27
+ borderAlpha: string;
28
+ fillControlAlpha: string;
29
+ textSecondary: string;
30
+ textPrimaryAlpha: string;
31
+ positive: string;
32
+ onAccent: string;
33
+ accent: string;
34
+ primary: string;
35
+ }
36
+
37
+ // Define the structure of the radius object (compact, medium, expanded)
38
+ export interface Radius {
39
+ smallControl: number;
40
+ largeControl: number;
41
+ container: number;
42
+ page: number;
43
+ }
44
+
45
+ // Define the structure of the size object
46
+ export interface Size {
47
+ large: number;
48
+ small: number;
49
+ base: number;
50
+ }
51
+
52
+ export interface ResponsiveSize {
53
+ expanded: number;
54
+ medium: number;
55
+ maxPageBleed: number;
56
+ }
57
+
58
+ // Define the structure of the space object
59
+ export interface Space {
60
+ hExtra: number;
61
+ hSmall: number;
62
+ content: number;
63
+ vExtra: number;
64
+ hSection: number;
65
+ hUnify: number;
66
+ vSection: number;
67
+ vSmall: number;
68
+ vUnify: number;
69
+ }
70
+
71
+ // Define the structure of the text object
72
+ export interface Text {
73
+ title3LetterSpacing: number;
74
+ headlineLetterSpacing: number;
75
+ title2Size: number;
76
+ title1LineHeight: number;
77
+ subheadLineHeight: number;
78
+ bodyLineHeight: number;
79
+ title3LineHeight: number;
80
+ subheadSize: number;
81
+ caption1LetterSpacing: number;
82
+ caption2LineHeight: number;
83
+ caption1Size: number;
84
+ footnoteSize: number;
85
+ caption2LetterSpacing: number;
86
+ bodyLetterSpacing: number;
87
+ title2LineHeight: number;
88
+ headlineLineHeight: number;
89
+ title2LetterSpacing: number;
90
+ caption1LineHeight: number;
91
+ title3Size: number;
92
+ footnoteLetterSpacing: number;
93
+ title1Size: number;
94
+ footnoteLineHeight: number;
95
+ subheadLetterSpacing: number;
96
+ caption2Size: number;
97
+ bodySize: number;
98
+ headlineSize: number;
99
+ title1LetterSpacing: number;
100
+ }
101
+
102
+ export interface TextWeight {
103
+ footnoteBold: number;
104
+ bodyLight: number;
105
+ footnoteLight: number;
106
+ footnote: number;
107
+ caption2Bold: number;
108
+ bodyBold: number;
109
+ subhead: number;
110
+ title2Regular: number;
111
+ title2: number;
112
+ subheadLight: number;
113
+ title2Light: number;
114
+ caption2: number;
115
+ headlineBold: number;
116
+ footnoteRegular: number;
117
+ body: number;
118
+ title1: number;
119
+ title1Light: number;
120
+ title3Light: number;
121
+ subheadRegular: number;
122
+ subheadBold: number;
123
+ caption1Bold: number;
124
+ title3Bold: number;
125
+ caption1Light: number;
126
+ headline: number;
127
+ title3Regular: number;
128
+ headlineRegular: number;
129
+ caption1: number;
130
+ title2Bold: number;
131
+ title1Bold: number;
132
+ headlineLight: number;
133
+ bodyRegular: number;
134
+ caption2Light: number;
135
+ title1Regular: number;
136
+ caption2Regular: number;
137
+ caption1Regular: number;
138
+ title3: number;
139
+ }
140
+
141
+ export interface TextFamily {
142
+ name: string;
143
+ titleFallback: string;
144
+ fallback: string;
145
+ src: string;
146
+ titleName: string;
147
+ titleSrc: string;
148
+ }
149
+
150
+ // Define the structure of the foundations object
151
+ export interface Foundations {
152
+ radius: {
153
+ compact: Radius;
154
+ medium: Radius;
155
+ expanded: Radius;
156
+ };
157
+ size: {
158
+ responsive: ResponsiveSize;
159
+ compact: Size;
160
+ medium: Size;
161
+ expanded: Size;
162
+ };
163
+ space: {
164
+ compact: Space;
165
+ medium: Space;
166
+ expanded: Space;
167
+ };
168
+ color: {
169
+ light: ColorScheme;
170
+ dark: ColorScheme;
171
+ };
172
+ text: {
173
+ compact: Text;
174
+ medium: Text;
175
+ expanded: Text;
176
+ weight: TextWeight;
177
+ family: TextFamily;
178
+ };
179
+ }
180
+
181
+ // Define the structure of the entire fetched data object
182
+ export interface ThemeData {
183
+ id: string;
184
+ owner: string;
185
+ createdOn: {
186
+ _seconds: number;
187
+ _nanoseconds: number;
188
+ };
189
+ name: string;
190
+ foundations: Foundations;
191
+ css: string;
192
+ lastModified: {
193
+ _seconds: number;
194
+ _nanoseconds: number;
195
+ };
196
+ }
197
+
198
+ // Define the Theme interface for the context
199
+ export interface Theme {
200
+ color?: ColorScheme;
201
+ radius?: Radius;
202
+ textStyle?: Text;
203
+ textWeight?: TextWeight;
204
+ textFamily?: TextFamily;
205
+ space?: Space;
206
+ size?: Size;
207
+ viewportSize?: 'compact' | 'medium' | 'expanded';
208
+ svgPath?: string;
209
+ browserViewport?: 'compact' | 'medium' | 'expanded';
210
+ spacingMode?: 'normal' | 'grouped';
211
+ }
212
+
@@ -0,0 +1,109 @@
1
+ import React, { useMemo } from "react";
2
+ import { generateGridTemplateColumns, showOnSplit } from "./utils";
3
+ import { HStackProps } from "./types";
4
+ import '../../Stack.css';
5
+ import { Modifier, useTokens } from "../Provider/Tokens";
6
+ import { useThemeable } from "../Provider";
7
+
8
+ export const HStack: React.FC<HStackProps> = ({
9
+ style,
10
+ maxWidth = undefined,
11
+ children,
12
+ className,
13
+ align,
14
+ showOn,
15
+ gridExpanded,
16
+ gridMedium,
17
+ gridCompact,
18
+ ref,
19
+ width = 'fill',
20
+ height = 'hug',
21
+ wrap,
22
+ sectionSpacing = false,
23
+ group = false,
24
+ setMaxViewSize
25
+ }) => {
26
+ const { spacingMode, viewSize } = useTokens();
27
+
28
+
29
+ const grid = {
30
+ compact: gridCompact,
31
+ medium: gridMedium,
32
+ expanded: gridExpanded
33
+ };
34
+
35
+ const hstackStyles = useMemo(() => {
36
+ const display = (() => {
37
+ if (gridExpanded && gridMedium && gridCompact) return 'grid';
38
+ if (viewSize && showOn) return showOnSplit(showOn, viewSize);
39
+ return undefined;
40
+ })();
41
+
42
+ return {
43
+ maxWidth,
44
+ transition: '.2s',
45
+ display,
46
+ // paddingTop: group ? `${space.vExtra}px` : undefined,
47
+ // paddingBottom: group ? `${space.vExtra}px` : undefined,
48
+ // paddingRight: group ? `${space.hExtra}px` : undefined,
49
+ // paddingLeft: group ? `${space.hExtra}px` : undefined,
50
+ gridTemplateColumns:
51
+ gridCompact && gridMedium && gridExpanded && viewSize
52
+ ? generateGridTemplateColumns(grid[viewSize])
53
+ : undefined,
54
+ // gap: sectionSpacing ? `${space.vSection}px ${space.hSection}px` : undefined,
55
+ gap: sectionSpacing ? `${24}px ${24}px` : undefined,
56
+ ...style,
57
+ };
58
+ }, [
59
+ maxWidth,
60
+ style,
61
+ gridExpanded,
62
+ gridMedium,
63
+ gridCompact,
64
+ viewSize,
65
+ showOn,
66
+ sectionSpacing,
67
+ // space.vExtra,
68
+ // space.hExtra,
69
+ // space.vSection,
70
+ // space.hSection,
71
+ grid,
72
+ ]);
73
+
74
+ const classes = [
75
+ 'h-stack',
76
+ className,
77
+ group && 'is-group',
78
+ `width-${width}`,
79
+ `height-${height}`,
80
+ // showOn ? showOnSplit(showOn, viewSize || 'expanded') : '',
81
+ align,
82
+ wrap && 'wrap',
83
+ // group && 'group'
84
+ ]
85
+ .filter(Boolean)
86
+ .join(' ');
87
+
88
+ const content = (
89
+ <div
90
+ ref={ref}
91
+ style={hstackStyles}
92
+ className={classes}
93
+ >
94
+ {children}
95
+ </div>
96
+ );
97
+
98
+ if (setMaxViewSize === 'medium' || setMaxViewSize === 'compact') {
99
+ return (
100
+ <Modifier setMaxViewSize={setMaxViewSize}>
101
+ {content}
102
+ </Modifier>
103
+ );
104
+ }
105
+
106
+ return content;
107
+ };
108
+
109
+ HStack.displayName = 'HStack';
@@ -0,0 +1,71 @@
1
+ import React, { forwardRef, useRef, useEffect, useState, useImperativeHandle, useMemo } from "react";
2
+ import { VStackProps } from "./types";
3
+ // import { useViewportSize, ViewportSizeProvider } from "../ProviderResponsive";
4
+ import { showOnSplit } from "./utils";
5
+ import '../../Stack.css';
6
+ import { Modifier, useTokens } from "../Provider/Tokens";
7
+ import { useThemeable } from "../Provider";
8
+
9
+ export const VStack = forwardRef<HTMLDivElement, VStackProps>(
10
+ ({
11
+ style,
12
+ maxWidth,
13
+ children,
14
+ className,
15
+ align,
16
+ showOn,
17
+ height = 'hug',
18
+ width = 'fill',
19
+ scroll,
20
+ group = false,
21
+ setMaxViewSize,
22
+ }, parentRef) => {
23
+ const localRef = useRef<HTMLDivElement>(null);
24
+
25
+ useImperativeHandle(parentRef, () => localRef.current!);
26
+
27
+ const { viewSize } = useTokens();
28
+
29
+ const combinedClasses = [
30
+ 'v-stack',
31
+ group && 'is-group',
32
+ className,
33
+ `width-${width}`,
34
+ `height-${height}`,
35
+ align,
36
+ scroll && `scroll-${scroll}`,
37
+ ]
38
+ .filter(Boolean)
39
+ .join(' ');
40
+
41
+ const content = (
42
+ <div
43
+ ref={localRef}
44
+ style={{
45
+ maxWidth,
46
+ display: viewSize !== undefined && showOn ? showOnSplit(showOn, viewSize) : undefined,
47
+ transition: '.2s',
48
+ // paddingTop: group ? `${space.vExtra}px` : undefined,
49
+ // paddingBottom: group ? `${space.vExtra}px` : undefined,
50
+ // paddingRight: group ? `${space.hExtra}px` : undefined,
51
+ // paddingLeft: group ? `${space.hExtra}px` : undefined,
52
+ ...style
53
+ }}
54
+ className={combinedClasses}
55
+ >
56
+ {children}
57
+ </div>
58
+ );
59
+
60
+ if (setMaxViewSize === 'medium' || setMaxViewSize === 'compact') {
61
+ return (
62
+ <Modifier setMaxViewSize={setMaxViewSize || 'compact'}>
63
+ {content}
64
+ </Modifier>
65
+ );
66
+ }
67
+
68
+ return content;
69
+ }
70
+ );
71
+ VStack.displayName = 'VStack';
@@ -0,0 +1,2 @@
1
+ export { HStack } from './HStack'
2
+ export { VStack } from './VStack';
@@ -0,0 +1,30 @@
1
+ import React, { CSSProperties, ReactNode, RefObject, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
2
+
3
+ interface StackProps {
4
+ style?: CSSProperties;
5
+ maxWidth?: string | number;
6
+ children?: ReactNode;
7
+ className?: string;
8
+ align?: string;
9
+ ref?: RefObject<HTMLDivElement>;
10
+ showOn?: 'compact' | 'medium' | 'expanded' | 'compact medium' | 'medium expanded' | 'compact expanded';
11
+ width?: 'hug' | 'fill';
12
+ height?: 'hug' | 'fill';
13
+ showOverflowBar?: boolean;
14
+ wrap?: boolean;
15
+ group?: boolean;
16
+ setMaxViewSize?: 'compact' | 'medium'
17
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
18
+ }
19
+
20
+ export interface HStackProps extends StackProps {
21
+ gridExpanded?: number[];
22
+ gridMedium?: number[];
23
+ gridCompact?: number[];
24
+ sectionSpacing?: boolean;
25
+ }
26
+
27
+ // VStack related props
28
+ export interface VStackProps extends StackProps {
29
+ scroll?: 'vertical' | 'horizontal' | 'both' | ''
30
+ }
@@ -0,0 +1,12 @@
1
+ // export const showOnSplit = (showOnValue: string) => {
2
+ // return showOnValue?.split(" ").map(word => `showOn${word.charAt(0).toUpperCase() + word.slice(1)}`).join(" ");
3
+ // }
4
+
5
+ export const showOnSplit = (showOnValue: string, viewportSize: string) => {
6
+ return showOnValue.split(' ').includes(viewportSize) ? 'flex' : 'none';
7
+ }
8
+
9
+ export const generateGridTemplateColumns = (grid: any) => {
10
+ return grid.map((value: number) => `minmax(0, ${value}fr)`).join(' ')
11
+ };
12
+
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { HStack, VStack } from './Stack/index';
2
+ export { EnviroSniffer } from './EnviroSniffer'
3
+
4
+ export { LayoutProvider } from './Provider/index';
5
+ export { useTokens } from './Provider/Tokens'
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "module": "ESNext",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "jsx": "react",
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "moduleResolution": "node"
14
+ },
15
+ "include": [
16
+ "src/**/*"
17
+ ],
18
+ "exclude": [
19
+ "node_modules",
20
+ "dist"
21
+ ]
22
+ }