@spark-web/box 1.0.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.
- package/CHANGELOG.md +16 -0
- package/README.md +106 -0
- package/dist/declarations/src/BackgroundProvider.d.ts +7 -0
- package/dist/declarations/src/Box.d.ts +38 -0
- package/dist/declarations/src/context.d.ts +7 -0
- package/dist/declarations/src/index.d.ts +5 -0
- package/dist/declarations/src/useBoxProps.d.ts +6 -0
- package/dist/declarations/src/useBoxStyles.d.ts +219 -0
- package/dist/spark-web-box.cjs.d.ts +1 -0
- package/dist/spark-web-box.cjs.dev.js +320 -0
- package/dist/spark-web-box.cjs.js +7 -0
- package/dist/spark-web-box.cjs.prod.js +320 -0
- package/dist/spark-web-box.esm.js +309 -0
- package/package.json +20 -0
- package/src/BackgroundProvider.tsx +19 -0
- package/src/Box.stories.tsx +26 -0
- package/src/Box.tsx +45 -0
- package/src/context.tsx +54 -0
- package/src/index.ts +8 -0
- package/src/useBoxProps.ts +99 -0
- package/src/useBoxStyles.ts +422 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import { useTheme } from '@spark-web/theme';
|
|
3
|
+
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
4
|
+
import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
|
|
5
|
+
import { cx, css } from '@emotion/css';
|
|
6
|
+
import { resetElementStyles } from '@spark-web/utils/internal';
|
|
7
|
+
import { forwardRefWithAs } from '@spark-web/utils/ts';
|
|
8
|
+
import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
|
|
9
|
+
|
|
10
|
+
var __jsx$1 = React.createElement;
|
|
11
|
+
// prepare context
|
|
12
|
+
var backgroundContext = /*#__PURE__*/createContext('body');
|
|
13
|
+
var InternalBackgroundProvider = backgroundContext.Provider;
|
|
14
|
+
var useBackground = function useBackground() {
|
|
15
|
+
return useContext(backgroundContext);
|
|
16
|
+
}; // conditional provider
|
|
17
|
+
|
|
18
|
+
function renderBackgroundProvider(background, element) {
|
|
19
|
+
return background ? __jsx$1(InternalBackgroundProvider, {
|
|
20
|
+
value: background
|
|
21
|
+
}, element) : element;
|
|
22
|
+
} // a11y contrast utility
|
|
23
|
+
|
|
24
|
+
var useBackgroundLightness = function useBackgroundLightness(backgroundOverride) {
|
|
25
|
+
var backgroundFromContext = useBackground();
|
|
26
|
+
var background = backgroundOverride || backgroundFromContext;
|
|
27
|
+
|
|
28
|
+
var _useTheme = useTheme(),
|
|
29
|
+
backgroundLightness = _useTheme.backgroundLightness;
|
|
30
|
+
|
|
31
|
+
var defaultLightness = backgroundLightness.body; // used by the consumer-facing/external BackgroundProvider
|
|
32
|
+
|
|
33
|
+
if (background === 'UNKNOWN_DARK') {
|
|
34
|
+
return 'dark';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (background === 'UNKNOWN_LIGHT') {
|
|
38
|
+
return 'light';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return background ? backgroundLightness[background] || defaultLightness : defaultLightness;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Enforce background "lightness" without applying a background color. */
|
|
45
|
+
var BackgroundProvider = function BackgroundProvider(_ref) {
|
|
46
|
+
var type = _ref.type,
|
|
47
|
+
children = _ref.children;
|
|
48
|
+
return renderBackgroundProvider(type === 'dark' ? 'UNKNOWN_DARK' : 'UNKNOWN_LIGHT', children);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
52
|
+
|
|
53
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
54
|
+
// TODO: review responsive props! Now that we're using object syntax, un-mapped properties don't behave as expected
|
|
55
|
+
// types
|
|
56
|
+
|
|
57
|
+
// Hook
|
|
58
|
+
// ------------------------------
|
|
59
|
+
var useBoxStyles = function useBoxStyles(_ref) {
|
|
60
|
+
var alignItems = _ref.alignItems,
|
|
61
|
+
alignSelf = _ref.alignSelf,
|
|
62
|
+
background = _ref.background,
|
|
63
|
+
border = _ref.border,
|
|
64
|
+
borderRadius = _ref.borderRadius,
|
|
65
|
+
_ref$borderWidth = _ref.borderWidth,
|
|
66
|
+
borderWidth = _ref$borderWidth === void 0 ? 'standard' : _ref$borderWidth,
|
|
67
|
+
bottom = _ref.bottom,
|
|
68
|
+
cursor = _ref.cursor,
|
|
69
|
+
display = _ref.display,
|
|
70
|
+
flex = _ref.flex,
|
|
71
|
+
flexDirection = _ref.flexDirection,
|
|
72
|
+
flexGrow = _ref.flexGrow,
|
|
73
|
+
flexShrink = _ref.flexShrink,
|
|
74
|
+
flexWrap = _ref.flexWrap,
|
|
75
|
+
gap = _ref.gap,
|
|
76
|
+
height = _ref.height,
|
|
77
|
+
justifyContent = _ref.justifyContent,
|
|
78
|
+
left = _ref.left,
|
|
79
|
+
margin = _ref.margin,
|
|
80
|
+
marginBottom = _ref.marginBottom,
|
|
81
|
+
marginLeft = _ref.marginLeft,
|
|
82
|
+
marginRight = _ref.marginRight,
|
|
83
|
+
marginTop = _ref.marginTop,
|
|
84
|
+
marginX = _ref.marginX,
|
|
85
|
+
marginY = _ref.marginY,
|
|
86
|
+
minHeight = _ref.minHeight,
|
|
87
|
+
minWidth = _ref.minWidth,
|
|
88
|
+
opacity = _ref.opacity,
|
|
89
|
+
overflow = _ref.overflow,
|
|
90
|
+
padding = _ref.padding,
|
|
91
|
+
paddingBottom = _ref.paddingBottom,
|
|
92
|
+
paddingLeft = _ref.paddingLeft,
|
|
93
|
+
paddingRight = _ref.paddingRight,
|
|
94
|
+
paddingTop = _ref.paddingTop,
|
|
95
|
+
paddingX = _ref.paddingX,
|
|
96
|
+
paddingY = _ref.paddingY,
|
|
97
|
+
position = _ref.position,
|
|
98
|
+
right = _ref.right,
|
|
99
|
+
shadow = _ref.shadow,
|
|
100
|
+
top = _ref.top,
|
|
101
|
+
userSelect = _ref.userSelect,
|
|
102
|
+
width = _ref.width,
|
|
103
|
+
zIndex = _ref.zIndex;
|
|
104
|
+
var theme = useTheme();
|
|
105
|
+
var unresponsiveProps = {
|
|
106
|
+
background: background ? theme.color.background[background] : undefined,
|
|
107
|
+
boxShadow: shadow ? theme.shadow[shadow] : undefined,
|
|
108
|
+
cursor: cursor,
|
|
109
|
+
minHeight: minHeight,
|
|
110
|
+
minWidth: minWidth,
|
|
111
|
+
opacity: opacity,
|
|
112
|
+
overflow: overflow,
|
|
113
|
+
userSelect: userSelect
|
|
114
|
+
};
|
|
115
|
+
var conditionalBorderStyles = border ? {
|
|
116
|
+
borderStyle: 'solid',
|
|
117
|
+
borderColor: theme.utils.mapResponsiveScale(border, theme.border.color),
|
|
118
|
+
borderWidth: theme.utils.mapResponsiveScale(borderWidth, theme.border.width)
|
|
119
|
+
} : null;
|
|
120
|
+
return theme.utils.resolveResponsiveProps(_objectSpread(_objectSpread(_objectSpread({}, unresponsiveProps), conditionalBorderStyles), {}, {
|
|
121
|
+
// allow padding and height/width props to play nice
|
|
122
|
+
display: theme.utils.mapResponsiveProp(display),
|
|
123
|
+
// margin
|
|
124
|
+
marginBottom: theme.utils.mapResponsiveScale(marginBottom || marginY || margin, theme.spacing),
|
|
125
|
+
marginTop: theme.utils.mapResponsiveScale(marginTop || marginY || margin, theme.spacing),
|
|
126
|
+
marginLeft: theme.utils.mapResponsiveScale(marginLeft || marginX || margin, theme.spacing),
|
|
127
|
+
marginRight: theme.utils.mapResponsiveScale(marginRight || marginX || margin, theme.spacing),
|
|
128
|
+
// padding
|
|
129
|
+
paddingBottom: theme.utils.mapResponsiveScale(paddingBottom || paddingY || padding, theme.spacing),
|
|
130
|
+
paddingTop: theme.utils.mapResponsiveScale(paddingTop || paddingY || padding, theme.spacing),
|
|
131
|
+
paddingLeft: theme.utils.mapResponsiveScale(paddingLeft || paddingX || padding, theme.spacing),
|
|
132
|
+
paddingRight: theme.utils.mapResponsiveScale(paddingRight || paddingX || padding, theme.spacing),
|
|
133
|
+
// border
|
|
134
|
+
borderRadius: theme.utils.mapResponsiveScale(borderRadius, theme.border.radius),
|
|
135
|
+
// flex: parent
|
|
136
|
+
alignItems: theme.utils.mapResponsiveScale(alignItems, flexMap.alignItems),
|
|
137
|
+
gap: theme.utils.mapResponsiveScale(gap, theme.spacing),
|
|
138
|
+
flexDirection: theme.utils.mapResponsiveScale(flexDirection, flexMap.flexDirection),
|
|
139
|
+
justifyContent: theme.utils.mapResponsiveScale(justifyContent, flexMap.justifyContent),
|
|
140
|
+
flexWrap: theme.utils.mapResponsiveProp(flexWrap),
|
|
141
|
+
// flex: child
|
|
142
|
+
alignSelf: theme.utils.mapResponsiveScale(alignSelf, flexMap.alignItems),
|
|
143
|
+
flex: theme.utils.mapResponsiveProp(flex),
|
|
144
|
+
flexGrow: theme.utils.mapResponsiveProp(flexGrow),
|
|
145
|
+
flexShrink: theme.utils.mapResponsiveProp(flexShrink),
|
|
146
|
+
// dimension
|
|
147
|
+
height: theme.utils.mapResponsiveScale(height, theme.sizing),
|
|
148
|
+
width: theme.utils.mapResponsiveScale(width, theme.sizing),
|
|
149
|
+
// position
|
|
150
|
+
position: theme.utils.mapResponsiveProp(position),
|
|
151
|
+
bottom: theme.utils.mapResponsiveProp(bottom),
|
|
152
|
+
left: theme.utils.mapResponsiveProp(left),
|
|
153
|
+
right: theme.utils.mapResponsiveProp(right),
|
|
154
|
+
top: theme.utils.mapResponsiveProp(top),
|
|
155
|
+
zIndex: theme.utils.mapResponsiveScale(zIndex, theme.elevation)
|
|
156
|
+
}));
|
|
157
|
+
}; // Flex shorthand / adjustments
|
|
158
|
+
// ------------------------------
|
|
159
|
+
|
|
160
|
+
var flexMap = {
|
|
161
|
+
alignItems: {
|
|
162
|
+
start: 'flex-start',
|
|
163
|
+
center: 'center',
|
|
164
|
+
end: 'flex-end',
|
|
165
|
+
stretch: 'stretch'
|
|
166
|
+
},
|
|
167
|
+
justifyContent: {
|
|
168
|
+
start: 'flex-start',
|
|
169
|
+
center: 'center',
|
|
170
|
+
end: 'flex-end',
|
|
171
|
+
spaceBetween: 'space-between',
|
|
172
|
+
stretch: 'stretch'
|
|
173
|
+
},
|
|
174
|
+
flexDirection: {
|
|
175
|
+
row: 'row',
|
|
176
|
+
rowReverse: 'row-reverse',
|
|
177
|
+
column: 'column',
|
|
178
|
+
columnReverse: 'column-reverse'
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
var _excluded$1 = ["alignItems", "alignSelf", "background", "border", "borderRadius", "borderWidth", "bottom", "cursor", "display", "flex", "flexDirection", "flexGrow", "flexShrink", "flexWrap", "gap", "height", "justifyContent", "left", "margin", "marginBottom", "marginLeft", "marginRight", "marginTop", "marginX", "marginY", "minHeight", "minWidth", "opacity", "overflow", "padding", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "paddingX", "paddingY", "position", "right", "shadow", "top", "userSelect", "width", "zIndex"];
|
|
183
|
+
/** Separate the style properties from the element attributes. */
|
|
184
|
+
|
|
185
|
+
function useBoxProps(props) {
|
|
186
|
+
var alignItems = props.alignItems,
|
|
187
|
+
alignSelf = props.alignSelf,
|
|
188
|
+
background = props.background,
|
|
189
|
+
border = props.border,
|
|
190
|
+
borderRadius = props.borderRadius,
|
|
191
|
+
borderWidth = props.borderWidth,
|
|
192
|
+
bottom = props.bottom,
|
|
193
|
+
cursor = props.cursor,
|
|
194
|
+
display = props.display,
|
|
195
|
+
flex = props.flex,
|
|
196
|
+
flexDirection = props.flexDirection,
|
|
197
|
+
flexGrow = props.flexGrow,
|
|
198
|
+
flexShrink = props.flexShrink,
|
|
199
|
+
flexWrap = props.flexWrap,
|
|
200
|
+
gap = props.gap,
|
|
201
|
+
height = props.height,
|
|
202
|
+
justifyContent = props.justifyContent,
|
|
203
|
+
left = props.left,
|
|
204
|
+
margin = props.margin,
|
|
205
|
+
marginBottom = props.marginBottom,
|
|
206
|
+
marginLeft = props.marginLeft,
|
|
207
|
+
marginRight = props.marginRight,
|
|
208
|
+
marginTop = props.marginTop,
|
|
209
|
+
marginX = props.marginX,
|
|
210
|
+
marginY = props.marginY,
|
|
211
|
+
minHeight = props.minHeight,
|
|
212
|
+
minWidth = props.minWidth,
|
|
213
|
+
opacity = props.opacity,
|
|
214
|
+
overflow = props.overflow,
|
|
215
|
+
padding = props.padding,
|
|
216
|
+
paddingBottom = props.paddingBottom,
|
|
217
|
+
paddingLeft = props.paddingLeft,
|
|
218
|
+
paddingRight = props.paddingRight,
|
|
219
|
+
paddingTop = props.paddingTop,
|
|
220
|
+
paddingX = props.paddingX,
|
|
221
|
+
paddingY = props.paddingY,
|
|
222
|
+
position = props.position,
|
|
223
|
+
right = props.right,
|
|
224
|
+
shadow = props.shadow,
|
|
225
|
+
top = props.top,
|
|
226
|
+
userSelect = props.userSelect,
|
|
227
|
+
width = props.width,
|
|
228
|
+
zIndex = props.zIndex,
|
|
229
|
+
attributes = _objectWithoutProperties(props, _excluded$1);
|
|
230
|
+
|
|
231
|
+
var styles = useBoxStyles({
|
|
232
|
+
alignItems: alignItems,
|
|
233
|
+
alignSelf: alignSelf,
|
|
234
|
+
background: background,
|
|
235
|
+
border: border,
|
|
236
|
+
borderRadius: borderRadius,
|
|
237
|
+
borderWidth: borderWidth,
|
|
238
|
+
bottom: bottom,
|
|
239
|
+
cursor: cursor,
|
|
240
|
+
display: display,
|
|
241
|
+
flex: flex,
|
|
242
|
+
flexDirection: flexDirection,
|
|
243
|
+
flexGrow: flexGrow,
|
|
244
|
+
flexShrink: flexShrink,
|
|
245
|
+
flexWrap: flexWrap,
|
|
246
|
+
gap: gap,
|
|
247
|
+
height: height,
|
|
248
|
+
justifyContent: justifyContent,
|
|
249
|
+
left: left,
|
|
250
|
+
margin: margin,
|
|
251
|
+
marginBottom: marginBottom,
|
|
252
|
+
marginLeft: marginLeft,
|
|
253
|
+
marginRight: marginRight,
|
|
254
|
+
marginTop: marginTop,
|
|
255
|
+
marginX: marginX,
|
|
256
|
+
marginY: marginY,
|
|
257
|
+
minHeight: minHeight,
|
|
258
|
+
minWidth: minWidth,
|
|
259
|
+
opacity: opacity,
|
|
260
|
+
overflow: overflow,
|
|
261
|
+
padding: padding,
|
|
262
|
+
paddingBottom: paddingBottom,
|
|
263
|
+
paddingLeft: paddingLeft,
|
|
264
|
+
paddingRight: paddingRight,
|
|
265
|
+
paddingTop: paddingTop,
|
|
266
|
+
paddingX: paddingX,
|
|
267
|
+
paddingY: paddingY,
|
|
268
|
+
position: position,
|
|
269
|
+
right: right,
|
|
270
|
+
shadow: shadow,
|
|
271
|
+
top: top,
|
|
272
|
+
userSelect: userSelect,
|
|
273
|
+
width: width,
|
|
274
|
+
zIndex: zIndex
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
styles: styles,
|
|
278
|
+
attributes: attributes
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
var _excluded = ["as", "asElement", "className", "id"];
|
|
283
|
+
var __jsx = React.createElement;
|
|
284
|
+
|
|
285
|
+
/** Exposes a prop-based API for adding styles to a view, within the constraints of the theme. */
|
|
286
|
+
var Box = forwardRefWithAs(function (_ref, forwardedRef) {
|
|
287
|
+
var _ref$as = _ref.as,
|
|
288
|
+
Tag = _ref$as === void 0 ? 'div' : _ref$as,
|
|
289
|
+
asElement = _ref.asElement,
|
|
290
|
+
className = _ref.className,
|
|
291
|
+
id = _ref.id,
|
|
292
|
+
props = _objectWithoutProperties(_ref, _excluded);
|
|
293
|
+
|
|
294
|
+
var _useBoxProps = useBoxProps(props),
|
|
295
|
+
styles = _useBoxProps.styles,
|
|
296
|
+
attributes = _useBoxProps.attributes;
|
|
297
|
+
|
|
298
|
+
var resetStyles = resetElementStyles(asElement !== null && asElement !== void 0 ? asElement : Tag);
|
|
299
|
+
|
|
300
|
+
var element = __jsx(Tag, _extends({
|
|
301
|
+
ref: forwardedRef,
|
|
302
|
+
id: id,
|
|
303
|
+
className: cx(css(resetStyles), css(styles), className)
|
|
304
|
+
}, attributes));
|
|
305
|
+
|
|
306
|
+
return renderBackgroundProvider(props.background, element);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
export { BackgroundProvider, Box, useBackground, useBackgroundLightness };
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spark-web/box",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "dist/spark-web-box.cjs.js",
|
|
6
|
+
"module": "dist/spark-web-box.esm.js",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/react": "^17.0.12"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@babel/runtime": "^7.14.6",
|
|
12
|
+
"@emotion/css": "^11.7.1",
|
|
13
|
+
"@spark-web/theme": "^1.0.0",
|
|
14
|
+
"@spark-web/utils": "^1.0.0",
|
|
15
|
+
"react": "^17.0.2"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">= 14.13"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { renderBackgroundProvider } from './context';
|
|
4
|
+
|
|
5
|
+
export type BackgroundProviderProps = {
|
|
6
|
+
type: 'light' | 'dark';
|
|
7
|
+
children: ReactElement;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Enforce background "lightness" without applying a background color. */
|
|
11
|
+
export const BackgroundProvider = ({
|
|
12
|
+
type,
|
|
13
|
+
children,
|
|
14
|
+
}: BackgroundProviderProps) => {
|
|
15
|
+
return renderBackgroundProvider(
|
|
16
|
+
type === 'dark' ? 'UNKNOWN_DARK' : 'UNKNOWN_LIGHT',
|
|
17
|
+
children
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Text } from '@spark-web/text';
|
|
2
|
+
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import type { BoxProps } from './Box';
|
|
5
|
+
import { Box } from './Box';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Page & Layout / Box',
|
|
9
|
+
component: Box,
|
|
10
|
+
} as ComponentMeta<typeof Box>;
|
|
11
|
+
|
|
12
|
+
const BoxStory: ComponentStory<typeof Box> = (
|
|
13
|
+
args: Omit<BoxProps, 'className'>
|
|
14
|
+
) => (
|
|
15
|
+
<Box {...args}>
|
|
16
|
+
<Text>I'm some text inside a Box</Text>
|
|
17
|
+
</Box>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const Default = BoxStory.bind({});
|
|
21
|
+
|
|
22
|
+
Default.args = {
|
|
23
|
+
padding: 'small',
|
|
24
|
+
shadow: 'medium',
|
|
25
|
+
borderRadius: 'medium',
|
|
26
|
+
} as BoxProps;
|
package/src/Box.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { resetElementStyles } from '@spark-web/utils/internal';
|
|
3
|
+
import { forwardRefWithAs } from '@spark-web/utils/ts';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
import { renderBackgroundProvider } from './context';
|
|
7
|
+
import { useBoxProps } from './useBoxProps';
|
|
8
|
+
import type { BoxStyleProps } from './useBoxStyles';
|
|
9
|
+
|
|
10
|
+
export type BoxProps = {
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
|
|
13
|
+
/** An identifier which must be unique in the whole document. */
|
|
14
|
+
id?: string;
|
|
15
|
+
|
|
16
|
+
// experiment
|
|
17
|
+
className?: string;
|
|
18
|
+
|
|
19
|
+
// TODO: this API is less than ideal, consider alternative
|
|
20
|
+
/**
|
|
21
|
+
* When providing a component using the "as" prop, optionally tell the system
|
|
22
|
+
* what the underlying element will be. Used internally to manage reset
|
|
23
|
+
* styles.
|
|
24
|
+
*/
|
|
25
|
+
asElement?: keyof HTMLElementTagNameMap;
|
|
26
|
+
} & BoxStyleProps;
|
|
27
|
+
|
|
28
|
+
/** Exposes a prop-based API for adding styles to a view, within the constraints of the theme. */
|
|
29
|
+
export const Box = forwardRefWithAs<'div', BoxProps>(
|
|
30
|
+
({ as: Tag = 'div', asElement, className, id, ...props }, forwardedRef) => {
|
|
31
|
+
const { styles, attributes } = useBoxProps(props);
|
|
32
|
+
const resetStyles = resetElementStyles(asElement ?? Tag);
|
|
33
|
+
|
|
34
|
+
const element = (
|
|
35
|
+
<Tag
|
|
36
|
+
ref={forwardedRef}
|
|
37
|
+
id={id}
|
|
38
|
+
className={cx(css(resetStyles), css(styles), className)}
|
|
39
|
+
{...attributes}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return renderBackgroundProvider(props.background, element);
|
|
44
|
+
}
|
|
45
|
+
);
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useTheme } from '@spark-web/theme';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
|
|
5
|
+
import type { BoxStyleProps } from './useBoxStyles';
|
|
6
|
+
|
|
7
|
+
export type BackgroundVariant =
|
|
8
|
+
| NonNullable<BoxStyleProps['background']>
|
|
9
|
+
| 'UNKNOWN_DARK'
|
|
10
|
+
| 'UNKNOWN_LIGHT';
|
|
11
|
+
|
|
12
|
+
// prepare context
|
|
13
|
+
|
|
14
|
+
const backgroundContext = createContext<BackgroundVariant>('body');
|
|
15
|
+
export const InternalBackgroundProvider = backgroundContext.Provider;
|
|
16
|
+
export const useBackground = () => useContext(backgroundContext);
|
|
17
|
+
|
|
18
|
+
// conditional provider
|
|
19
|
+
|
|
20
|
+
export function renderBackgroundProvider(
|
|
21
|
+
background: BackgroundVariant | undefined,
|
|
22
|
+
element: ReactElement | null
|
|
23
|
+
) {
|
|
24
|
+
return background ? (
|
|
25
|
+
<InternalBackgroundProvider value={background}>
|
|
26
|
+
{element}
|
|
27
|
+
</InternalBackgroundProvider>
|
|
28
|
+
) : (
|
|
29
|
+
element
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// a11y contrast utility
|
|
34
|
+
|
|
35
|
+
export const useBackgroundLightness = (
|
|
36
|
+
backgroundOverride?: ReturnType<typeof useBackground>
|
|
37
|
+
) => {
|
|
38
|
+
const backgroundFromContext = useBackground();
|
|
39
|
+
const background = backgroundOverride || backgroundFromContext;
|
|
40
|
+
const { backgroundLightness } = useTheme();
|
|
41
|
+
const defaultLightness = backgroundLightness.body;
|
|
42
|
+
|
|
43
|
+
// used by the consumer-facing/external BackgroundProvider
|
|
44
|
+
if (background === 'UNKNOWN_DARK') {
|
|
45
|
+
return 'dark';
|
|
46
|
+
}
|
|
47
|
+
if (background === 'UNKNOWN_LIGHT') {
|
|
48
|
+
return 'light';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return background
|
|
52
|
+
? backgroundLightness[background] || defaultLightness
|
|
53
|
+
: defaultLightness;
|
|
54
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BackgroundProvider } from './BackgroundProvider';
|
|
2
|
+
export { Box } from './Box';
|
|
3
|
+
export { useBackground, useBackgroundLightness } from './context';
|
|
4
|
+
|
|
5
|
+
// types
|
|
6
|
+
|
|
7
|
+
export type { BackgroundProviderProps } from './BackgroundProvider';
|
|
8
|
+
export type { BoxProps } from './Box';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { BoxStyleProps } from './useBoxStyles';
|
|
2
|
+
import { useBoxStyles } from './useBoxStyles';
|
|
3
|
+
|
|
4
|
+
/** Separate the style properties from the element attributes. */
|
|
5
|
+
export function useBoxProps(props: BoxStyleProps) {
|
|
6
|
+
const {
|
|
7
|
+
alignItems,
|
|
8
|
+
alignSelf,
|
|
9
|
+
background,
|
|
10
|
+
border,
|
|
11
|
+
borderRadius,
|
|
12
|
+
borderWidth,
|
|
13
|
+
bottom,
|
|
14
|
+
cursor,
|
|
15
|
+
display,
|
|
16
|
+
flex,
|
|
17
|
+
flexDirection,
|
|
18
|
+
flexGrow,
|
|
19
|
+
flexShrink,
|
|
20
|
+
flexWrap,
|
|
21
|
+
gap,
|
|
22
|
+
height,
|
|
23
|
+
justifyContent,
|
|
24
|
+
left,
|
|
25
|
+
margin,
|
|
26
|
+
marginBottom,
|
|
27
|
+
marginLeft,
|
|
28
|
+
marginRight,
|
|
29
|
+
marginTop,
|
|
30
|
+
marginX,
|
|
31
|
+
marginY,
|
|
32
|
+
minHeight,
|
|
33
|
+
minWidth,
|
|
34
|
+
opacity,
|
|
35
|
+
overflow,
|
|
36
|
+
padding,
|
|
37
|
+
paddingBottom,
|
|
38
|
+
paddingLeft,
|
|
39
|
+
paddingRight,
|
|
40
|
+
paddingTop,
|
|
41
|
+
paddingX,
|
|
42
|
+
paddingY,
|
|
43
|
+
position,
|
|
44
|
+
right,
|
|
45
|
+
shadow,
|
|
46
|
+
top,
|
|
47
|
+
userSelect,
|
|
48
|
+
width,
|
|
49
|
+
zIndex,
|
|
50
|
+
...attributes
|
|
51
|
+
} = props;
|
|
52
|
+
const styles = useBoxStyles({
|
|
53
|
+
alignItems,
|
|
54
|
+
alignSelf,
|
|
55
|
+
background,
|
|
56
|
+
border,
|
|
57
|
+
borderRadius,
|
|
58
|
+
borderWidth,
|
|
59
|
+
bottom,
|
|
60
|
+
cursor,
|
|
61
|
+
display,
|
|
62
|
+
flex,
|
|
63
|
+
flexDirection,
|
|
64
|
+
flexGrow,
|
|
65
|
+
flexShrink,
|
|
66
|
+
flexWrap,
|
|
67
|
+
gap,
|
|
68
|
+
height,
|
|
69
|
+
justifyContent,
|
|
70
|
+
left,
|
|
71
|
+
margin,
|
|
72
|
+
marginBottom,
|
|
73
|
+
marginLeft,
|
|
74
|
+
marginRight,
|
|
75
|
+
marginTop,
|
|
76
|
+
marginX,
|
|
77
|
+
marginY,
|
|
78
|
+
minHeight,
|
|
79
|
+
minWidth,
|
|
80
|
+
opacity,
|
|
81
|
+
overflow,
|
|
82
|
+
padding,
|
|
83
|
+
paddingBottom,
|
|
84
|
+
paddingLeft,
|
|
85
|
+
paddingRight,
|
|
86
|
+
paddingTop,
|
|
87
|
+
paddingX,
|
|
88
|
+
paddingY,
|
|
89
|
+
position,
|
|
90
|
+
right,
|
|
91
|
+
shadow,
|
|
92
|
+
top,
|
|
93
|
+
userSelect,
|
|
94
|
+
width,
|
|
95
|
+
zIndex,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return { styles, attributes };
|
|
99
|
+
}
|