@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.
@@ -0,0 +1,422 @@
1
+ import type { BrighteTheme, ResponsiveProp } from '@spark-web/theme';
2
+ import { useTheme } from '@spark-web/theme';
3
+
4
+ // TODO perf review
5
+ // TODO: review responsive props! Now that we're using object syntax, un-mapped properties don't behave as expected
6
+
7
+ // types
8
+
9
+ type ValidGapKeys = keyof Omit<BrighteTheme['spacing'], 'none' | 'full'>;
10
+ type ResponsiveSpacing = ResponsiveProp<keyof BrighteTheme['spacing']>;
11
+ type ResponsiveSizing = ResponsiveProp<keyof BrighteTheme['sizing']>;
12
+
13
+ export type ResponsiveBoxProps = {
14
+ /**
15
+ * The `margin` shorthand property sets the margin area on all four sides of
16
+ * an element at once.
17
+ */
18
+ margin?: ResponsiveSpacing;
19
+ /**
20
+ * The `marginTop` property sets the margin area on the top side of an
21
+ * element.
22
+ */
23
+ marginTop?: ResponsiveSpacing;
24
+ /**
25
+ * The `marginRight` property sets the margin area on the right side of an
26
+ * element.
27
+ */
28
+ marginRight?: ResponsiveSpacing;
29
+ /**
30
+ * The `marginBottom` property sets the margin area on the bottom side of an
31
+ * element.
32
+ */
33
+ marginBottom?: ResponsiveSpacing;
34
+ /**
35
+ * The `marginLeft` property sets the margin area on the left side of an
36
+ * element.
37
+ */
38
+ marginLeft?: ResponsiveSpacing;
39
+ /**
40
+ * The `marginY` shorthand property sets the margin area on the top and
41
+ * bottom of the element.
42
+ */
43
+ marginY?: ResponsiveSpacing;
44
+ /**
45
+ * The `marginY` shorthand property sets the margin area on the left and
46
+ * right of the element.
47
+ */
48
+ marginX?: ResponsiveSpacing;
49
+
50
+ // ==============================
51
+ // PADDING
52
+ // ==============================
53
+
54
+ /**
55
+ * The `padding` shorthand property sets the padding area on all four sides
56
+ * of an element at once.
57
+ */
58
+ padding?: ResponsiveSpacing;
59
+ /**
60
+ * The `paddingTop` property sets the height of the padding area on the top
61
+ * of an element.
62
+ */
63
+ paddingTop?: ResponsiveSpacing;
64
+ /**
65
+ * The `paddingRight` property sets the width of the padding area on the
66
+ * right of an element.
67
+ */
68
+ paddingRight?: ResponsiveSpacing;
69
+ /**
70
+ * The `paddingBottom` property sets the height of the padding area on the
71
+ * bottom of an element.
72
+ */
73
+ paddingBottom?: ResponsiveSpacing;
74
+ /**
75
+ * The `paddingLeft` property sets the width of the padding area on the left
76
+ * of an element.
77
+ */
78
+ paddingLeft?: ResponsiveSpacing;
79
+ /**
80
+ * The `paddingY` shorthand property sets the padding area on the top and
81
+ * bottom of the element.
82
+ */
83
+ paddingY?: ResponsiveSpacing;
84
+ /**
85
+ * The `paddingX` shorthand property sets the padding area on the left and
86
+ * right of the element.
87
+ */
88
+ paddingX?: ResponsiveSpacing;
89
+
90
+ // ==============================
91
+ // BORDER
92
+ // ==============================
93
+
94
+ /** The `border` property sets the color of an element's border. */
95
+ border?: ResponsiveProp<keyof BrighteTheme['border']['color']>;
96
+ /**
97
+ * The `borderRadius` property rounds the corners of an element's outer
98
+ * border edge.
99
+ */
100
+ borderRadius?: ResponsiveProp<keyof BrighteTheme['border']['radius']>;
101
+ /**
102
+ * The `borderWidth` property sets the width of an element's border.
103
+ */
104
+ borderWidth?: ResponsiveProp<keyof BrighteTheme['border']['width']>;
105
+
106
+ // ==============================
107
+ // DIMENSIONS
108
+ // ==============================
109
+
110
+ /** Sets the element's height. */
111
+ height?: ResponsiveSizing;
112
+ /** Sets the element's width. */
113
+ width?: ResponsiveSizing;
114
+
115
+ // ==============================
116
+ // FLEX: Parent
117
+ // ==============================
118
+
119
+ /** Controls the alignment of items on the cross axis. */
120
+ alignItems?: ResponsiveProp<keyof typeof flexMap.alignItems>;
121
+ /** The size of the gap between each child element. */
122
+ gap?: ResponsiveProp<ValidGapKeys>;
123
+ /** Defines the main axis, or how the children are placed. */
124
+ flexDirection?: ResponsiveProp<keyof typeof flexMap.flexDirection>;
125
+ /**
126
+ * defines how the browser distributes space between and around content items
127
+ * along the main-axis.
128
+ */
129
+ justifyContent?: ResponsiveProp<keyof typeof flexMap.justifyContent>;
130
+ /** Allow flex items to flow onto multiple lines. */
131
+ flexWrap?: ResponsiveProp<'nowrap' | 'wrap'>;
132
+
133
+ // ==============================
134
+ // FLEX: Child
135
+ // ==============================
136
+
137
+ /**
138
+ * Overrides the parent's `align-items` value. Controls the alignment of
139
+ * item's on the cross axis.
140
+ */
141
+ alignSelf?: ResponsiveProp<keyof typeof flexMap.alignItems>;
142
+ /**
143
+ * The `flex` shorthand property sets how a flex item will grow or shrink to
144
+ * fit the space available in its flex container.
145
+ */
146
+ flex?: ResponsiveProp<0 | 1>;
147
+ /** The `flexGrow` property sets the flex grow factor of a flex item main size. */
148
+ flexGrow?: ResponsiveProp<0 | 1>;
149
+ /**
150
+ * The `flexShrink` property sets the flex shrink factor of a flex item. If
151
+ * the size of all flex items is larger than the flex container, items shrink
152
+ * to fit according to `flex-shrink`.
153
+ */
154
+ flexShrink?: ResponsiveProp<0 | 1>;
155
+
156
+ // ==============================
157
+ // POSITION
158
+ // ==============================
159
+
160
+ /**
161
+ * The `position` property sets how an element is positioned in a document.
162
+ * The `top`, `right`, `bottom`, and `left` properties determine the final
163
+ * location of positioned elements.
164
+ */
165
+ position?: ResponsiveProp<'absolute' | 'fixed' | 'relative' | 'sticky'>;
166
+ /**
167
+ * The `top` property participates in specifying the vertical position of a
168
+ * positioned element. It has no effect on non-positioned elements.
169
+ */
170
+ top?: ResponsiveProp<0>;
171
+ /**
172
+ * The `right` property participates in specifying the horizontal position of
173
+ * a positioned element. It has no effect on non-positioned elements.
174
+ */
175
+ right?: ResponsiveProp<0>;
176
+ /**
177
+ * The `bottom` property participates in setting the vertical position of a
178
+ * positioned element. It has no effect on non-positioned elements.
179
+ */
180
+ bottom?: ResponsiveProp<0>;
181
+ /**
182
+ * The `left` property participates in specifying the horizontal position of a
183
+ * positioned element. It has no effect on non-positioned elements.
184
+ */
185
+ left?: ResponsiveProp<0>;
186
+ /**
187
+ * The `zIndex` property sets the "z-order" of a positioned element and its
188
+ * descendants or flex items. Overlapping elements with a larger z-index cover
189
+ * those with a smaller one.
190
+ */
191
+ zIndex?: ResponsiveProp<keyof BrighteTheme['elevation']>;
192
+
193
+ // ==============================
194
+ // MISC...
195
+ // ==============================
196
+
197
+ /**
198
+ * Sets whether an element is treated as a block or inline element and the
199
+ * layout used for its children.
200
+ */
201
+ display?: ResponsiveProp<
202
+ 'block' | 'flex' | 'inline' | 'inline-block' | 'inline-flex' | 'none'
203
+ >;
204
+ /**
205
+ * Sets the opacity of the element. Opacity is the degree to which content
206
+ * behind an element is hidden, and is the opposite of transparency.
207
+ */
208
+ opacity?: ResponsiveProp<number>;
209
+ };
210
+
211
+ type UnresponsiveBoxProps = {
212
+ /** The `background` property sets the background color of an element. */
213
+ background?: keyof BrighteTheme['color']['background'];
214
+ /**
215
+ * The `cursor` property sets the type of mouse cursor, if any, to show when
216
+ * the mouse pointer is over an element.
217
+ */
218
+ cursor?: 'default' | 'pointer';
219
+ /**
220
+ * The `minHeight` property sets the minimum height of an element. It prevents
221
+ * the used value of the height property from becoming smaller than the value
222
+ * specified for `minHeight`.
223
+ */
224
+ minHeight?: 0;
225
+ /**
226
+ * The `minWidth` property sets the minimum width of an element. It prevents
227
+ * the used value of the width property from becoming smaller than the value
228
+ * specified for `minWidth`.
229
+ */
230
+ minWidth?: 0;
231
+ /**
232
+ * The `overflow` shorthand property sets the desired behavior for an
233
+ * element's overflow — i.e. when an element's content is too big to fit in
234
+ * its block formatting context — in both directions.
235
+ */
236
+ overflow?: 'hidden' | 'scroll' | 'visible' | 'auto';
237
+ /** The `boxShadow` property adds shadow effects around an element's frame. */
238
+ shadow?: keyof BrighteTheme['shadow'];
239
+ /** The `userSelect` property controls whether the user can select text. */
240
+ userSelect?: 'none';
241
+ };
242
+
243
+ export type BoxStyleProps = UnresponsiveBoxProps & ResponsiveBoxProps;
244
+
245
+ // Hook
246
+ // ------------------------------
247
+
248
+ export const useBoxStyles = ({
249
+ alignItems,
250
+ alignSelf,
251
+ background,
252
+ border,
253
+ borderRadius,
254
+ borderWidth = 'standard',
255
+ bottom,
256
+ cursor,
257
+ display,
258
+ flex,
259
+ flexDirection,
260
+ flexGrow,
261
+ flexShrink,
262
+ flexWrap,
263
+ gap,
264
+ height,
265
+ justifyContent,
266
+ left,
267
+ margin,
268
+ marginBottom,
269
+ marginLeft,
270
+ marginRight,
271
+ marginTop,
272
+ marginX,
273
+ marginY,
274
+ minHeight,
275
+ minWidth,
276
+ opacity,
277
+ overflow,
278
+ padding,
279
+ paddingBottom,
280
+ paddingLeft,
281
+ paddingRight,
282
+ paddingTop,
283
+ paddingX,
284
+ paddingY,
285
+ position,
286
+ right,
287
+ shadow,
288
+ top,
289
+ userSelect,
290
+ width,
291
+ zIndex,
292
+ }: BoxStyleProps) => {
293
+ const theme = useTheme();
294
+
295
+ const unresponsiveProps = {
296
+ background: background ? theme.color.background[background] : undefined,
297
+ boxShadow: shadow ? theme.shadow[shadow] : undefined,
298
+ cursor,
299
+ minHeight,
300
+ minWidth,
301
+ opacity,
302
+ overflow,
303
+ userSelect,
304
+ };
305
+
306
+ const conditionalBorderStyles = border
307
+ ? {
308
+ borderStyle: 'solid',
309
+ borderColor: theme.utils.mapResponsiveScale(border, theme.border.color),
310
+ borderWidth: theme.utils.mapResponsiveScale(
311
+ borderWidth,
312
+ theme.border.width
313
+ ),
314
+ }
315
+ : null;
316
+
317
+ return theme.utils.resolveResponsiveProps({
318
+ ...unresponsiveProps,
319
+ ...conditionalBorderStyles,
320
+
321
+ // allow padding and height/width props to play nice
322
+ display: theme.utils.mapResponsiveProp(display),
323
+
324
+ // margin
325
+ marginBottom: theme.utils.mapResponsiveScale(
326
+ marginBottom || marginY || margin,
327
+ theme.spacing
328
+ ),
329
+ marginTop: theme.utils.mapResponsiveScale(
330
+ marginTop || marginY || margin,
331
+ theme.spacing
332
+ ),
333
+ marginLeft: theme.utils.mapResponsiveScale(
334
+ marginLeft || marginX || margin,
335
+ theme.spacing
336
+ ),
337
+ marginRight: theme.utils.mapResponsiveScale(
338
+ marginRight || marginX || margin,
339
+ theme.spacing
340
+ ),
341
+
342
+ // padding
343
+ paddingBottom: theme.utils.mapResponsiveScale(
344
+ paddingBottom || paddingY || padding,
345
+ theme.spacing
346
+ ),
347
+ paddingTop: theme.utils.mapResponsiveScale(
348
+ paddingTop || paddingY || padding,
349
+ theme.spacing
350
+ ),
351
+ paddingLeft: theme.utils.mapResponsiveScale(
352
+ paddingLeft || paddingX || padding,
353
+ theme.spacing
354
+ ),
355
+ paddingRight: theme.utils.mapResponsiveScale(
356
+ paddingRight || paddingX || padding,
357
+ theme.spacing
358
+ ),
359
+
360
+ // border
361
+ borderRadius: theme.utils.mapResponsiveScale(
362
+ borderRadius,
363
+ theme.border.radius
364
+ ),
365
+
366
+ // flex: parent
367
+ alignItems: theme.utils.mapResponsiveScale(alignItems, flexMap.alignItems),
368
+ gap: theme.utils.mapResponsiveScale(gap, theme.spacing),
369
+ flexDirection: theme.utils.mapResponsiveScale(
370
+ flexDirection,
371
+ flexMap.flexDirection
372
+ ),
373
+ justifyContent: theme.utils.mapResponsiveScale(
374
+ justifyContent,
375
+ flexMap.justifyContent
376
+ ),
377
+ flexWrap: theme.utils.mapResponsiveProp(flexWrap),
378
+
379
+ // flex: child
380
+ alignSelf: theme.utils.mapResponsiveScale(alignSelf, flexMap.alignItems),
381
+ flex: theme.utils.mapResponsiveProp(flex),
382
+ flexGrow: theme.utils.mapResponsiveProp(flexGrow),
383
+ flexShrink: theme.utils.mapResponsiveProp(flexShrink),
384
+
385
+ // dimension
386
+ height: theme.utils.mapResponsiveScale(height, theme.sizing),
387
+ width: theme.utils.mapResponsiveScale(width, theme.sizing),
388
+
389
+ // position
390
+ position: theme.utils.mapResponsiveProp(position),
391
+ bottom: theme.utils.mapResponsiveProp(bottom),
392
+ left: theme.utils.mapResponsiveProp(left),
393
+ right: theme.utils.mapResponsiveProp(right),
394
+ top: theme.utils.mapResponsiveProp(top),
395
+ zIndex: theme.utils.mapResponsiveScale(zIndex, theme.elevation),
396
+ });
397
+ };
398
+
399
+ // Flex shorthand / adjustments
400
+ // ------------------------------
401
+
402
+ const flexMap = {
403
+ alignItems: {
404
+ start: 'flex-start',
405
+ center: 'center',
406
+ end: 'flex-end',
407
+ stretch: 'stretch',
408
+ },
409
+ justifyContent: {
410
+ start: 'flex-start',
411
+ center: 'center',
412
+ end: 'flex-end',
413
+ spaceBetween: 'space-between',
414
+ stretch: 'stretch',
415
+ },
416
+ flexDirection: {
417
+ row: 'row',
418
+ rowReverse: 'row-reverse',
419
+ column: 'column',
420
+ columnReverse: 'column-reverse',
421
+ },
422
+ } as const;