tinywidgets 0.0.10 → 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.
Files changed (80) hide show
  1. package/.eslintrc.json +2 -1
  2. package/bun.lockb +0 -0
  3. package/package.json +19 -14
  4. package/src/common/functions.tsx +54 -0
  5. package/src/components/App/index.css.ts +81 -0
  6. package/src/components/App/index.tsx +177 -0
  7. package/src/components/Button/index.css.ts +66 -0
  8. package/src/components/Button/index.tsx +206 -0
  9. package/src/components/Card/index.css.ts +12 -0
  10. package/src/components/Card/index.tsx +34 -0
  11. package/src/components/Code/index.css.ts +49 -0
  12. package/src/components/Code/index.tsx +89 -0
  13. package/src/components/Collapsible/index.css.ts +37 -0
  14. package/src/components/Collapsible/index.tsx +132 -0
  15. package/src/{Detail → components/Detail}/index.css.ts +3 -3
  16. package/src/components/Detail/index.tsx +48 -0
  17. package/src/{Hr → components/Hr}/index.css.ts +3 -2
  18. package/src/components/Hr/index.tsx +26 -0
  19. package/src/components/Image/index.css.ts +38 -0
  20. package/src/components/Image/index.tsx +85 -0
  21. package/src/components/Metric/index.tsx +59 -0
  22. package/src/components/Row/index.css.ts +30 -0
  23. package/src/components/Row/index.tsx +104 -0
  24. package/src/components/Summary/index.css.ts +16 -0
  25. package/src/components/Summary/index.tsx +59 -0
  26. package/src/{Tag → components/Tag}/index.css.ts +9 -6
  27. package/src/components/Tag/index.tsx +73 -0
  28. package/src/css/code.css.ts +112 -0
  29. package/src/css/colors.css.ts +124 -0
  30. package/src/css/dimensions.css.ts +67 -0
  31. package/src/css/global.css.ts +11 -0
  32. package/src/css/screens.ts +15 -0
  33. package/src/index.css.ts +4 -137
  34. package/src/index.ts +16 -24
  35. package/src/stores/LocalStore.tsx +52 -19
  36. package/src/stores/RouteStore.tsx +103 -0
  37. package/src/stores/SessionStore.tsx +40 -25
  38. package/src/stores/common.ts +6 -0
  39. package/tsconfig.json +19 -0
  40. package/.prettierrc +0 -5
  41. package/index.css.ts +0 -1
  42. package/index.ts +0 -12
  43. package/src/App/index.tsx +0 -18
  44. package/src/Avatar/index.css.ts +0 -17
  45. package/src/Avatar/index.tsx +0 -25
  46. package/src/Axis/index.css.ts +0 -19
  47. package/src/Axis/index.tsx +0 -36
  48. package/src/Button/index.css.ts +0 -57
  49. package/src/Button/index.tsx +0 -63
  50. package/src/Card/index.css.ts +0 -9
  51. package/src/Card/index.tsx +0 -13
  52. package/src/Collapsible/index.css.ts +0 -34
  53. package/src/Collapsible/index.tsx +0 -75
  54. package/src/Detail/index.tsx +0 -26
  55. package/src/Hr/index.tsx +0 -8
  56. package/src/Metric/index.tsx +0 -26
  57. package/src/Summary/index.css.ts +0 -17
  58. package/src/Summary/index.tsx +0 -32
  59. package/src/Tag/index.tsx +0 -26
  60. package/src/Ui/Header/DarkButton/index.tsx +0 -19
  61. package/src/Ui/Header/SideNav/index.css.ts +0 -23
  62. package/src/Ui/Header/SideNav/index.tsx +0 -14
  63. package/src/Ui/Header/SideNavButton/index.css.ts +0 -4
  64. package/src/Ui/Header/SideNavButton/index.tsx +0 -19
  65. package/src/Ui/Header/Title/index.css.ts +0 -10
  66. package/src/Ui/Header/Title/index.tsx +0 -9
  67. package/src/Ui/Header/TopNav/index.css.ts +0 -9
  68. package/src/Ui/Header/TopNav/index.tsx +0 -18
  69. package/src/Ui/Header/index.css.ts +0 -18
  70. package/src/Ui/Header/index.tsx +0 -32
  71. package/src/Ui/Main/Article/index.css.ts +0 -13
  72. package/src/Ui/Main/Article/index.tsx +0 -9
  73. package/src/Ui/Main/Footer/index.css.ts +0 -12
  74. package/src/Ui/Main/Footer/index.tsx +0 -9
  75. package/src/Ui/Main/index.css.ts +0 -16
  76. package/src/Ui/Main/index.tsx +0 -24
  77. package/src/Ui/index.css.ts +0 -9
  78. package/src/Ui/index.tsx +0 -50
  79. package/utils.ts +0 -1
  80. /package/src/{Metric → components/Metric}/index.css.ts +0 -0
@@ -0,0 +1,49 @@
1
+ import {globalStyle, style} from '@vanilla-extract/css';
2
+ import {code} from '../../css/code.css';
3
+ import {colors} from '../../css/colors.css';
4
+ import {dimensions} from '../../css/dimensions.css';
5
+
6
+ export const pre = style({
7
+ padding: dimensions.padding,
8
+ borderRadius: dimensions.radius,
9
+ background: colors.background2,
10
+ border: colors.border,
11
+ lineHeight: '1.25rem',
12
+ overflowX: 'auto',
13
+ });
14
+
15
+ globalStyle('.token.comment, .token.prolog, .token.cdata', {
16
+ color: code['mono-3'],
17
+ });
18
+
19
+ globalStyle('.token.doctype, .token.punctuation, .token.entity', {
20
+ color: code['mono-1'],
21
+ });
22
+
23
+ globalStyle(
24
+ // eslint-disable-next-line max-len
25
+ '.token.attr-name, .token.class-name, .token.boolean, .token.constant, .token.number, .token.atrule',
26
+ {color: code['hue-6']},
27
+ );
28
+
29
+ globalStyle('.token.keyword', {color: code['hue-3']});
30
+
31
+ globalStyle(
32
+ // eslint-disable-next-line max-len
33
+ '.token.property, .token.tag, .token.symbol, .token.deleted, .token.important',
34
+ {color: code['hue-5']},
35
+ );
36
+
37
+ globalStyle(
38
+ // eslint-disable-next-line max-len
39
+ '.token.selector, .token.string, .token.char, .token.builtin, .token.inserted, .token.regex, .token.attr-value, .token.attr-value > .token.punctuation',
40
+ {color: code['hue-4']},
41
+ );
42
+
43
+ globalStyle('.token.variable, .token.operator, .token.function', {
44
+ color: code['hue-2'],
45
+ });
46
+
47
+ globalStyle('.token.url', {
48
+ color: code['hue-1'],
49
+ });
@@ -0,0 +1,89 @@
1
+ /* eslint-disable max-len */
2
+ import 'prismjs';
3
+ import 'prismjs/components/prism-jsx';
4
+ import 'prismjs/components/prism-typescript';
5
+ import 'prismjs/components/prism-tsx';
6
+ import Prism from 'prismjs';
7
+ import React from 'react';
8
+ import {classNames} from '../../common/functions.tsx';
9
+ import {pre} from './index.css.ts';
10
+
11
+ const {highlight, languages} = Prism;
12
+
13
+ /**
14
+ * The `Code` component displays a block of pre-formatted code, and uses PrismJS
15
+ * to parse its syntax. The coloring is based on the [prism-one-dark](https://github.com/PrismJS/prism-themes/blob/master/themes/prism-one-dark.css) and [prism-one-light](https://github.com/PrismJS/prism-themes/blob/master/themes/prism-one-light.css) themes.
16
+ *
17
+ * This component supports the default PrismJS languages (`markup`, `html`
18
+ * , `xml`, `svg`, `mathml`, `ssml`, `atom`, `rss`, `css`, `clike`, `javascript`
19
+ * , `js`), and specific additional languages (`jsx`, `typescript`, `ts`
20
+ * , `tsx`).
21
+ * Others can be added if there is demand! Please open an issue on GitHub.
22
+ *
23
+ * @param props The props for the component.
24
+ * @returns The Code component.
25
+ * @example
26
+ * ```tsx
27
+ * <Code
28
+ * code={`
29
+ * import React from 'react';
30
+ * const App = () => (
31
+ * <div>Hello, world!</div>
32
+ * );
33
+ * `}
34
+ * />
35
+ * ```
36
+ * This example shows a simple block of code, defaulting to the `jsx` language.
37
+ * @example
38
+ * ```tsx
39
+ * <Code
40
+ * code={`
41
+ * h1 {
42
+ * font-size: 1.5rem;
43
+ * }
44
+ * `}
45
+ * language="css"
46
+ * />
47
+ * ```
48
+ * This example shows the use of the `css` language.
49
+ * @example
50
+ * ```tsx
51
+ * <Code
52
+ * code={`
53
+ * const a: number = 1;
54
+ * `}
55
+ * language="typescript"
56
+ * />
57
+ * ```
58
+ * This example shows the use of the `tsx` language.
59
+ * @icon Lucide.SquareCode
60
+ */
61
+ export const Code = ({
62
+ code,
63
+ language = 'jsx',
64
+ className,
65
+ }: {
66
+ /**
67
+ * The code to display, as a string.
68
+ */
69
+ readonly code: string;
70
+ /**
71
+ * An optional indication of the language. Defaults to `jsx`.
72
+ */
73
+ readonly language?: string;
74
+ /**
75
+ * An extra CSS class name for the component.
76
+ */
77
+ readonly className?: string;
78
+ }) => {
79
+ return (
80
+ <pre className={classNames(pre, className)}>
81
+ <code
82
+ // eslint-disable-next-line react/no-danger
83
+ dangerouslySetInnerHTML={{
84
+ __html: highlight(code.trim(), languages[language], language),
85
+ }}
86
+ />
87
+ </pre>
88
+ );
89
+ };
@@ -0,0 +1,37 @@
1
+ import {colors} from '../../css/colors.css';
2
+ import {dimensions} from '../../css/dimensions.css';
3
+ import {style} from '@vanilla-extract/css';
4
+
5
+ export const collapsible = style({
6
+ width: '100%',
7
+ alignSelf: 'start',
8
+ borderRadius: dimensions.radius,
9
+ boxShadow: colors.shadow,
10
+ border: colors.border,
11
+ display: 'grid',
12
+ gridTemplateRows: 'max-content minmax(0, 0fr)',
13
+ transition: '.2s grid-template-rows ease-in-out',
14
+ overflow: 'hidden',
15
+ marginBottom: dimensions.padding,
16
+ selectors: {
17
+ '&:last-child': {
18
+ marginBottom: 0,
19
+ },
20
+ },
21
+ });
22
+
23
+ export const collapsibleOpen = style({
24
+ gridTemplateRows: 'max-content minmax(0, 1fr)',
25
+ });
26
+
27
+ export const button = style({
28
+ margin: '-1px',
29
+ boxShadow: 'none',
30
+ });
31
+
32
+ export const buttonOpen = style({
33
+ borderBottomLeftRadius: 0,
34
+ borderBottomRightRadius: 0,
35
+ });
36
+
37
+ export const content = style({padding: dimensions.padding, overflow: 'hidden'});
@@ -0,0 +1,132 @@
1
+ import {ChevronDown, ChevronRight} from 'lucide-react';
2
+ import type {ComponentType, ReactNode} from 'react';
3
+ import React, {useCallback, useRef, useState} from 'react';
4
+ import {
5
+ button,
6
+ buttonOpen,
7
+ collapsible,
8
+ collapsibleOpen,
9
+ content,
10
+ } from './index.css.ts';
11
+ import {
12
+ useCollapsibleIsOpen,
13
+ useSetCollapsibleIsOpenCallback,
14
+ } from '../../stores/SessionStore.tsx';
15
+ import {Button} from '../Button/index.tsx';
16
+ import {classNames} from '../../common/functions.tsx';
17
+
18
+ /**
19
+ * The `Collapsible` component displays a titled box that can be expanded or
20
+ * collapsed. If the `id` prop is provided, its state is preserved between page
21
+ * reloads.
22
+ *
23
+ * @param props The props for the component.
24
+ * @returns The Collapsible component.
25
+ * @example
26
+ * ```tsx
27
+ * <Collapsible
28
+ * title="TinyWidgets"
29
+ * >
30
+ * <p>Peekaboo!</p>
31
+ * </Collapsible>
32
+ * ```
33
+ * This example shows the basic Collapsible component.
34
+ * @example
35
+ * ```tsx
36
+ * <Collapsible
37
+ * title="TinyWidgets"
38
+ * icon={Lucide.Grid3x3}
39
+ * startOpen={true}
40
+ * >
41
+ * <p>Already open</p>
42
+ * </Collapsible>
43
+ * ```
44
+ * This example shows a Collapsible component with an icon and which starts
45
+ * open.
46
+ * @example
47
+ * ```tsx
48
+ * <Collapsible
49
+ * title="TinyWidgets"
50
+ * id="example"
51
+ * >
52
+ * <p>Reload the window</p>
53
+ * </Collapsible>
54
+ * ```
55
+ * This example shows a Collapsible component where its state is preserved
56
+ * between page reloads.
57
+ * @icon Lucide.PanelTopOpen
58
+ */
59
+ export const Collapsible = ({
60
+ icon: Icon,
61
+ title,
62
+ titleRight,
63
+ startOpen = false,
64
+ id = '',
65
+ className,
66
+ children,
67
+ }: {
68
+ /**
69
+ * An optional component which renders an icon for the top of the collapsible
70
+ * component, and which must accept a className prop.
71
+ */
72
+ readonly icon?: ComponentType<{className?: string}>;
73
+ /**
74
+ * An optional component, element, or string which renders the title of
75
+ * the top of the component.
76
+ */
77
+ readonly title?: ComponentType | ReactNode;
78
+ /**
79
+ * An optional component, element, or string which renders a second title
80
+ * on the right side of the top of the component.
81
+ */
82
+ readonly titleRight?: ComponentType | ReactNode;
83
+ /**
84
+ * Whether the section should start opened up.
85
+ */
86
+ readonly startOpen?: boolean;
87
+ /**
88
+ * An Id which will allow the state to be preserved between page reloads.
89
+ */
90
+ readonly id?: string;
91
+ /**
92
+ * An extra CSS class name for the component.
93
+ */
94
+ readonly className?: string;
95
+ /**
96
+ * The children of the component, that go inside the collapsible section.
97
+ */
98
+ readonly children: ReactNode;
99
+ }) => {
100
+ const storedIsOpen = useCollapsibleIsOpen(id) ?? startOpen;
101
+ const setStoredIsOpen = useSetCollapsibleIsOpenCallback(id);
102
+ const [stateIsOpen, setStateIsOpen] = useState(startOpen);
103
+
104
+ const isOpen = id ? storedIsOpen : stateIsOpen;
105
+ const setIsOpen = id ? setStoredIsOpen : setStateIsOpen;
106
+
107
+ const [render, setRender] = useState(isOpen);
108
+ const timer = useRef<ReturnType<typeof setTimeout>>();
109
+
110
+ const toggle = useCallback(() => {
111
+ setIsOpen(!isOpen);
112
+ clearTimeout(timer.current);
113
+ timer.current = setTimeout(() => setRender(!isOpen), isOpen ? 200 : 0);
114
+ }, [setIsOpen, isOpen]);
115
+
116
+ return (
117
+ <div className={classNames(collapsible, isOpen && collapsibleOpen)}>
118
+ <Button
119
+ onClick={toggle}
120
+ icon={Icon}
121
+ title={title}
122
+ titleRight={titleRight}
123
+ iconRight={isOpen ? ChevronDown : ChevronRight}
124
+ className={classNames(button, render && buttonOpen)}
125
+ current={render}
126
+ />
127
+ {render ? (
128
+ <div className={classNames(content, className)}>{children}</div>
129
+ ) : null}
130
+ </div>
131
+ );
132
+ };
@@ -1,5 +1,5 @@
1
+ import {colors} from '../../css/colors.css';
1
2
  import {style} from '@vanilla-extract/css';
2
- import {theme} from '../index.css';
3
3
 
4
4
  export const detailTable = style({
5
5
  width: '100%',
@@ -8,12 +8,12 @@ export const detailTable = style({
8
8
  });
9
9
 
10
10
  export const detailRow = style({
11
- borderBottom: `1px solid ${theme.border}`,
11
+ borderBottom: colors.border,
12
12
  selectors: {'&:last-child': {borderBottom: 'none'}},
13
13
  });
14
14
 
15
15
  export const detailCell = style({
16
16
  padding: '0.5rem 1rem',
17
17
  verticalAlign: 'top',
18
- selectors: {'&:is(th)': {textAlign: 'right'}},
18
+ selectors: {'&:is(th)': {textAlign: 'right', width: '30%'}},
19
19
  });
@@ -0,0 +1,48 @@
1
+ import {detailCell, detailRow, detailTable} from './index.css';
2
+ import React from 'react';
3
+ import type {ReactNode} from 'react';
4
+ import {classNames} from '../../common/functions';
5
+
6
+ /**
7
+ * The `Detail` component displays a set of key-value pairs in a two-column
8
+ * table.
9
+ *
10
+ * @param props The props for the component.
11
+ * @returns The Detail component.
12
+ * @example
13
+ * ```tsx
14
+ * <Detail
15
+ * data={{
16
+ * normal: 'lorem ipsum',
17
+ * italic: <i>lorem ipsum</i>,
18
+ * underline: <u>lorem ipsum</u>,
19
+ * }}
20
+ * />
21
+ * ```
22
+ * This example shows the basic Collapsible component.
23
+ * @icon Lucide.Table
24
+ */
25
+ export const Detail = ({
26
+ data,
27
+ className,
28
+ }: {
29
+ /**
30
+ * The data to display in the detail table. The values can be any React node.
31
+ */
32
+ readonly data: Record<string, ReactNode>;
33
+ /**
34
+ * An extra CSS class name for the component.
35
+ */
36
+ readonly className?: string;
37
+ }) => (
38
+ <table className={classNames(detailTable, className)}>
39
+ <tbody>
40
+ {Object.entries(data).map(([key, value]) => (
41
+ <tr key={key} className={detailRow}>
42
+ <th className={detailCell}>{key}</th>
43
+ <td className={detailCell}>{value}</td>
44
+ </tr>
45
+ ))}
46
+ </tbody>
47
+ </table>
48
+ );
@@ -1,9 +1,10 @@
1
+ import {colors} from '../../css/colors.css';
2
+ import {dimensions} from '../../css/dimensions.css';
1
3
  import {style} from '@vanilla-extract/css';
2
- import {dimensions, theme} from '../index.css';
3
4
 
4
5
  export const hr = style({
5
6
  border: 'none',
6
- borderBottom: `solid 1px ${theme.border}`,
7
+ borderBottom: colors.border,
7
8
  margin: `${dimensions.padding} 0`,
8
9
  height: '1px',
9
10
  width: '100%',
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import {classNames} from '../../common/functions';
3
+ import {hr} from './index.css';
4
+
5
+ /**
6
+ * The `Hr` component displays a styled horizontal rule.
7
+ *
8
+ * @param props The props for the component.
9
+ * @returns The Hr component.
10
+ * @example
11
+ * ```tsx
12
+ * <Hr />
13
+ * ```
14
+ * This example shows the basic Hr component.
15
+ * @icon Lucide.Minus
16
+ */
17
+ export const Hr = ({
18
+ className,
19
+ }: {
20
+ /**
21
+ * An extra CSS class name for the component.
22
+ */
23
+ readonly className?: string;
24
+ }) => {
25
+ return <hr className={classNames(hr, className)} />;
26
+ };
@@ -0,0 +1,38 @@
1
+ import {style, styleVariants} from '@vanilla-extract/css';
2
+ import {colors} from '../../css/colors.css';
3
+ import {dimensions} from '../../css/dimensions.css';
4
+
5
+ export const image = style({
6
+ display: 'inline-block',
7
+ flexShrink: 0,
8
+ width: '100%',
9
+ maxHeight: 'inherit',
10
+ selectors: {
11
+ '&:hover': {
12
+ backgroundColor: colors.backgroundHover,
13
+ },
14
+ },
15
+ });
16
+
17
+ export const imageVariants = styleVariants({
18
+ default: {
19
+ borderRadius: dimensions.radius,
20
+ boxShadow: colors.shadow,
21
+ },
22
+ logo: {
23
+ width: dimensions.logo,
24
+ height: dimensions.logo,
25
+ },
26
+ avatar: {
27
+ borderRadius: '50%',
28
+ border: colors.border,
29
+ width: dimensions.avatar,
30
+ height: dimensions.avatar,
31
+ boxShadow: colors.shadow,
32
+ },
33
+ icon: {
34
+ border: colors.border,
35
+ width: dimensions.icon,
36
+ height: dimensions.icon,
37
+ },
38
+ });
@@ -0,0 +1,85 @@
1
+ import {image, imageVariants} from './index.css.ts';
2
+ import React from 'react';
3
+ import {classNames} from '../../common/functions.tsx';
4
+
5
+ /**
6
+ * The `Image` component displays an image, with a number of common variants.
7
+ *
8
+ * @param props The props for the component.
9
+ * @returns The Image component.
10
+ * @example
11
+ * ```tsx
12
+ * <Image
13
+ * src="/favicon.svg"
14
+ * onClick={() => alert('Clicked!')}
15
+ * alt="TinyWidgets"
16
+ * />
17
+ * ```
18
+ * This example shows the `default` variant of the Image component.
19
+ * @example
20
+ * ```tsx
21
+ * <Image
22
+ * src="/favicon.svg"
23
+ * variant="logo"
24
+ * />
25
+ * ```
26
+ * This example shows the `logo` variant of the Image component.
27
+ * @example
28
+ * ```tsx
29
+ * <Image
30
+ * src="/favicon.svg"
31
+ * variant="avatar"
32
+ * />
33
+ * ```
34
+ * This example shows the `avatar` variant of the Image component.
35
+ * @example
36
+ * ```tsx
37
+ * <Image
38
+ * src="/favicon.svg"
39
+ * variant="icon"
40
+ * />
41
+ * ```
42
+ * This example shows the `icon` variant of the Image component.
43
+ * @icon Lucide.Image
44
+ */
45
+ export const Image = ({
46
+ src,
47
+ onClick,
48
+ variant = 'default',
49
+ alt,
50
+ className,
51
+ }: {
52
+ /**
53
+ * The source of the image.
54
+ */
55
+ readonly src: string;
56
+ /**
57
+ * A handler called when the user clicks on the image.
58
+ */
59
+ readonly onClick?: () => void;
60
+ /**
61
+ * A variant of the image, one of:
62
+ * - `default`
63
+ * - `logo`
64
+ * - `avatar`
65
+ * - `icon`
66
+ */
67
+ readonly variant?: keyof typeof imageVariants;
68
+ /**
69
+ * Alternative text shown when the user hovers over the image.
70
+ */
71
+ readonly alt?: string;
72
+ /**
73
+ * An extra CSS class name for the component.
74
+ */
75
+ readonly className?: string;
76
+ }) => {
77
+ return (
78
+ <img
79
+ src={src}
80
+ onClick={onClick}
81
+ title={alt}
82
+ className={classNames(image, imageVariants[variant], className)}
83
+ />
84
+ );
85
+ };
@@ -0,0 +1,59 @@
1
+ import type {ComponentType, ReactNode} from 'react';
2
+ import {classNames, renderComponentOrNode} from '../../common/functions';
3
+ import {metric, metricLabel, metricNumber} from './index.css';
4
+ import React from 'react';
5
+ import {iconSize} from '../../css/dimensions.css';
6
+
7
+ /**
8
+ * The `Metric` component displays a metric as a prominent numerical value with
9
+ * a label above.
10
+ *
11
+ * @param props The props for the component.
12
+ * @returns The Metric component.
13
+ * @example
14
+ * ```tsx
15
+ * <Metric
16
+ * icon={Lucide.ChartLine}
17
+ * title="Number"
18
+ * number="57"
19
+ * />
20
+ * ```
21
+ * This example shows the basic usage of the Metric component.
22
+ * @icon Lucide.SquareSigma
23
+ */
24
+ export const Metric = ({
25
+ icon: Icon,
26
+ title: titleComponentOrNode,
27
+ number: numberComponentOrNode,
28
+ className,
29
+ }: {
30
+ /**
31
+ * An optional component which renders an icon for the metric panel, and which
32
+ * must accept a className prop.
33
+ */
34
+ readonly icon?: ComponentType<{className?: string}>;
35
+ /**
36
+ * An optional component, element, or string which renders the title of
37
+ * the metric panel.
38
+ */
39
+ readonly title?: ComponentType | ReactNode;
40
+ /**
41
+ * An optional component, element, or string which renders the number of
42
+ * the metric panel.
43
+ */
44
+ readonly number?: ComponentType | ReactNode;
45
+ /**
46
+ * An extra CSS class name for the component.
47
+ */
48
+ readonly className?: string;
49
+ }) => (
50
+ <div className={classNames(metric, className)}>
51
+ <div className={metricLabel}>
52
+ {Icon ? <Icon className={iconSize} /> : null}
53
+ {renderComponentOrNode(titleComponentOrNode)}
54
+ </div>
55
+ <div className={metricNumber}>
56
+ {renderComponentOrNode(numberComponentOrNode)}
57
+ </div>
58
+ </div>
59
+ );
@@ -0,0 +1,30 @@
1
+ import {style, styleVariants} from '@vanilla-extract/css';
2
+ import {dimensions} from '../../css/dimensions.css.ts';
3
+ import {notLarge} from '../../common/functions.tsx';
4
+
5
+ export const row = style({
6
+ display: 'grid',
7
+ width: '100%',
8
+ gap: dimensions.padding,
9
+ ...notLarge({gridTemplateColumns: '1fr'}),
10
+ });
11
+
12
+ export const rowVariants = styleVariants({
13
+ '1|1': {gridTemplateColumns: `calc((100% - ${dimensions.padding})/2) 1fr`},
14
+ '1|2': {
15
+ gridTemplateColumns: `calc((100% - ${dimensions.padding}*2)/3) 1fr`,
16
+ },
17
+ '2|1': {
18
+ gridTemplateColumns: `1fr calc((100% - ${dimensions.padding}*2)/3)`,
19
+ },
20
+ '1|1|1': {
21
+ gridTemplateColumns: `1fr 1fr 1fr`,
22
+ },
23
+ '1|3': {
24
+ gridTemplateColumns: `1fr calc((100% - ${dimensions.padding}*3)/4)`,
25
+ },
26
+ '3|1': {
27
+ gridTemplateColumns: `calc((100% - ${dimensions.padding}*3)/4) 1fr`,
28
+ },
29
+ '1|1|1|1': {gridTemplateColumns: '1fr 1fr 1fr 1fr'},
30
+ });