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
package/.eslintrc.json CHANGED
@@ -86,6 +86,7 @@
86
86
  "react/require-default-props": 0,
87
87
  "react/sort-comp": 0,
88
88
  "react/forbid-component-props": 0,
89
- "react/button-has-type": 0
89
+ "react/button-has-type": 0,
90
+ "react/no-array-index-key": 0
90
91
  }
91
92
  }
package/bun.lockb CHANGED
Binary file
package/package.json CHANGED
@@ -1,33 +1,38 @@
1
1
  {
2
2
  "name": "tinywidgets",
3
- "version": "0.0.10",
3
+ "description": "A collection of tiny, reusable, React components",
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "prePublishPackage": "eslint . && tsc"
7
+ },
4
8
  "author": "jamesgpearce",
5
9
  "repository": "github:tinyplex/tinywidgets",
6
- "module": "index.ts",
10
+ "module": "./src/index.ts",
7
11
  "devDependencies": {
8
- "@types/react": "^18.3.5",
12
+ "@types/react": "^18.3.8",
9
13
  "@types/react-dom": "^18.3.0",
10
- "@typescript-eslint/eslint-plugin": "^8.5.0",
11
- "@typescript-eslint/parser": "^8.5.0",
14
+ "@typescript-eslint/eslint-plugin": "^8.7.0",
15
+ "@typescript-eslint/parser": "^8.7.0",
12
16
  "eslint": "^8.57.0",
13
17
  "eslint-config-prettier": "^9.1.0",
14
18
  "eslint-plugin-react": "^7.36.1",
15
19
  "eslint-plugin-react-hooks": "^4.6.2",
16
20
  "prettier": "^3.3.3",
17
21
  "typescript": "^5.6.2",
18
- "@vanilla-extract/css": "^1.15.5",
19
- "react": "^18.3.1",
20
- "react-dom": "^18.3.1",
21
- "tinybase": "^5.3.0-beta.3"
22
+ "@vanilla-extract/css": "^1.15.5"
22
23
  },
23
24
  "exports": {
24
- ".": "./index.ts",
25
- "./utils": "./utils.ts",
26
- "./css": "./index.css.ts"
25
+ ".": "./src/index.ts",
26
+ "./css": "./src/index.css.ts"
27
27
  },
28
- "description": "reserved",
29
28
  "license": "MIT",
30
29
  "dependencies": {
31
- "lucide-react": "^0.441.0"
30
+ "eslint-plugin-react-refresh": "^0.4.12",
31
+ "lucide-react": "^0.445.0",
32
+ "prism-react-renderer": "^2.4.0",
33
+ "prismjs": "^1.29.0",
34
+ "react": "^18.3.1",
35
+ "react-dom": "^18.3.1",
36
+ "tinybase": "^5.3.1"
32
37
  }
33
38
  }
@@ -0,0 +1,54 @@
1
+ import React, {type ComponentType, type ReactNode} from 'react';
2
+ import type {StyleRule} from '@vanilla-extract/css';
3
+ import {screens} from '../css/screens';
4
+
5
+ /**
6
+ * The `classNames` function returns a concatenated list of class names,
7
+ * filtering out any `null` or `undefined` values.
8
+ *
9
+ * This allows you to easily toggle classes based on conditions.
10
+ *
11
+ * @returns A string of space-separated class names.
12
+ * @example
13
+ * ```tsx
14
+ * const classes = classNames(
15
+ * 'class1',
16
+ * false && 'class2',
17
+ * true && 'class3',
18
+ * );
19
+ * // ...
20
+ * <Card className={classes}>
21
+ * <code>{classes}</code>
22
+ * </Card>
23
+ * ```
24
+ * This example shows the function returning a class name list.
25
+ */
26
+ export const classNames = (
27
+ ...classes: (string | boolean | null | undefined)[]
28
+ ) => classes.filter(Boolean).join(' ');
29
+
30
+ export const renderComponentOrNode = (
31
+ ComponentOrNode: ComponentType | ReactNode,
32
+ fallback: ReactNode = null,
33
+ ) =>
34
+ ComponentOrNode instanceof Function ? (
35
+ <ComponentOrNode />
36
+ ) : (
37
+ (ComponentOrNode ?? fallback)
38
+ );
39
+
40
+ export const large = (style: StyleRule) => ({
41
+ '@media': {[`screen and (min-width: ${screens.large}px)`]: style},
42
+ });
43
+
44
+ export const notLarge = (style: StyleRule) => ({
45
+ '@media': {[`screen and (max-width: ${screens.large}px)`]: style},
46
+ });
47
+
48
+ export const small = (style: StyleRule) => ({
49
+ '@media': {[`screen and (max-width: ${screens.small}px)`]: style},
50
+ });
51
+
52
+ export const notSmall = (style: StyleRule) => ({
53
+ '@media': {[`screen and (min-width: ${screens.small}px)`]: style},
54
+ });
@@ -0,0 +1,81 @@
1
+ import {dimensions, dimensionsClass} from '../../css/dimensions.css.ts';
2
+ import {colors} from '../../css/colors.css.ts';
3
+ import {large} from '../../common/functions.tsx';
4
+ import {style} from '@vanilla-extract/css';
5
+
6
+ export const app = style([dimensionsClass, {color: colors.foreground}]);
7
+
8
+ export const appLayout = style({
9
+ display: 'flex',
10
+ height: '100dvh',
11
+ width: '100dvw',
12
+ });
13
+
14
+ export const header = style({
15
+ display: 'flex',
16
+ justifyContent: 'space-between',
17
+ alignItems: 'center',
18
+ gap: dimensions.padding,
19
+ padding: dimensions.padding,
20
+ position: 'fixed',
21
+ boxShadow: colors.shadow,
22
+ backdropFilter: 'blur(8px)',
23
+ backgroundColor: colors.backgroundHaze,
24
+ left: 0,
25
+ right: 0,
26
+ height: dimensions.topNavHeight,
27
+ borderBottom: colors.border,
28
+ zIndex: 2,
29
+ });
30
+
31
+ export const topNav = style({
32
+ display: 'flex',
33
+ justifyContent: 'space-between',
34
+ alignItems: 'center',
35
+ gap: dimensions.padding,
36
+
37
+ flex: 1,
38
+ });
39
+
40
+ export const sideNavButton = style(large({display: 'none'}));
41
+
42
+ export const title = style({
43
+ display: 'flex',
44
+ alignItems: 'center',
45
+ gap: dimensions.padding,
46
+ ...large({
47
+ width: `calc(${dimensions.sideNavWidth} - 2 * ${dimensions.padding})`,
48
+ }),
49
+ });
50
+
51
+ export const sideNav = style({
52
+ position: 'fixed',
53
+ padding: dimensions.padding,
54
+ backgroundColor: colors.background2,
55
+ overflow: 'auto',
56
+ borderRight: colors.border,
57
+ width: dimensions.sideNavWidth,
58
+ bottom: 0,
59
+ left: `calc(-1.2 * ${dimensions.sideNavWidth})`,
60
+ top: dimensions.topNavHeight,
61
+ height: `calc(100dvh - ${dimensions.topNavHeight})`,
62
+ overscrollBehavior: 'contain',
63
+ transition: 'left .2s ease-in-out',
64
+ ...large({left: 0}),
65
+ });
66
+
67
+ export const sideNavOpen = style({left: 0});
68
+
69
+ export const main = style({
70
+ flex: 1,
71
+ backgroundColor: colors.background,
72
+ overflow: 'auto',
73
+ padding: dimensions.padding,
74
+ paddingTop: `calc(${dimensions.topNavHeight} + ${dimensions.padding})`,
75
+ });
76
+
77
+ export const mainHasSideNav = style(
78
+ large({
79
+ paddingLeft: `calc(${dimensions.sideNavWidth} + ${dimensions.padding})`,
80
+ }),
81
+ );
@@ -0,0 +1,177 @@
1
+ import type {ComponentType, ReactNode} from 'react';
2
+ import {
3
+ LocalStore,
4
+ useDark,
5
+ useDarkChoice,
6
+ useLocalStoreIsReady,
7
+ useToggleDarkChoiceCallback,
8
+ } from '../../stores/LocalStore.tsx';
9
+ import {Menu, Moon, Sun, SunMoon, X} from 'lucide-react';
10
+ import {RouteStore, useRouteStoreIsReady} from '../../stores/RouteStore.tsx';
11
+ import {
12
+ SessionStore,
13
+ useSessionStoreIsReady,
14
+ useSideNavIsOpen,
15
+ useToggleSideNavIsOpenCallback,
16
+ } from '../../stores/SessionStore.tsx';
17
+ import {
18
+ app,
19
+ appLayout,
20
+ header,
21
+ main,
22
+ mainHasSideNav,
23
+ sideNav,
24
+ sideNavButton,
25
+ sideNavOpen,
26
+ title,
27
+ topNav,
28
+ } from './index.css.ts';
29
+ import {classNames, renderComponentOrNode} from '../../common/functions.tsx';
30
+ import {codeDark, codeLight} from '../../css/code.css.ts';
31
+ import {colorsDark, colorsLight} from '../../css/colors.css.ts';
32
+ import {Button} from '../Button/index.tsx';
33
+ import {Provider} from 'tinybase/ui-react';
34
+ import React from 'react';
35
+
36
+ const darkIcons = [Sun, Moon, SunMoon];
37
+ const darkChoices = ['Light always', 'Dark always', 'Auto'];
38
+
39
+ /**
40
+ * The `App` component is the root component of a TinyWidgets application.
41
+ *
42
+ * It can be provided with optional components for different parts of the app
43
+ * layout, including the top left title, the side bar and the main content.
44
+ *
45
+ * If none of these props are present, the TinyWidgets layout won't be used, but
46
+ * you will still be able to enjoy its state management features, and any
47
+ * components within it will be correctly styled.
48
+ * @param props The props for the component.
49
+ * @returns The App component.
50
+ * @example
51
+ * ```tsx
52
+ * <App />
53
+ * ```
54
+ * This shows an empty App, but if you visit [the TinyWidgets
55
+ * website](https://tinywidgets.org) you'll see one in its full glory!
56
+ * @icon Lucide.PanelsTopLeft
57
+ */
58
+ export const App = (props: {
59
+ /**
60
+ * An optional component, element, or string which renders the top left title
61
+ * of the application.
62
+ */
63
+ readonly title?: ComponentType | ReactNode;
64
+ /**
65
+ * An optional component, element, or string which renders navigation on the
66
+ * left side of the top navigation bar of the application.
67
+ */
68
+ readonly topNavLeft?: ComponentType | ReactNode;
69
+ /**
70
+ * An optional component, element, or string which renders navigation on the
71
+ * right side of the top navigation bar of the application.
72
+ */
73
+ readonly topNavRight?: ComponentType | ReactNode;
74
+ /**
75
+ * An optional component, element, or string which renders the left side bar
76
+ * of the application.
77
+ */
78
+ readonly sideNav?: ComponentType | ReactNode;
79
+ /**
80
+ * An optional component, element, or string which renders the main part of
81
+ * the application.
82
+ */
83
+ readonly main?: ComponentType | ReactNode;
84
+ /**
85
+ * An extra CSS class name for the component.
86
+ */
87
+ readonly className?: string;
88
+ }) => {
89
+ return (
90
+ <Provider>
91
+ <Layout {...props} />
92
+ <SessionStore />
93
+ <LocalStore />
94
+ <RouteStore />
95
+ </Provider>
96
+ );
97
+ };
98
+
99
+ const Layout = ({
100
+ title: titleComponentOrNode,
101
+ topNavLeft: topNavLeftComponentOrNode,
102
+ topNavRight: topNavRightComponentOrNode,
103
+ sideNav: sideNavComponentOrNode,
104
+ main: mainComponentOrNode,
105
+ className,
106
+ }: Parameters<typeof App>[0]) => {
107
+ const sessionStoreIsReady = useSessionStoreIsReady();
108
+ const routeStoreIsReady = useRouteStoreIsReady();
109
+ const localStoreIsReady = useLocalStoreIsReady();
110
+
111
+ const toggleDarkChoice = useToggleDarkChoiceCallback();
112
+
113
+ const toggleSideNavIsOpen = useToggleSideNavIsOpenCallback();
114
+ const sideNavIsOpen = useSideNavIsOpen();
115
+
116
+ const darkChoice = useDarkChoice();
117
+ const dark = useDark();
118
+
119
+ const hasLayout = [
120
+ titleComponentOrNode,
121
+ topNavLeftComponentOrNode,
122
+ topNavRightComponentOrNode,
123
+ sideNavComponentOrNode,
124
+ mainComponentOrNode,
125
+ ].some((componentOrNode) => componentOrNode);
126
+ const hasSideNav = sideNavComponentOrNode != null;
127
+
128
+ return sessionStoreIsReady && routeStoreIsReady && localStoreIsReady ? (
129
+ <div
130
+ className={classNames(
131
+ app,
132
+ hasLayout && appLayout,
133
+ dark ? colorsDark : colorsLight,
134
+ dark ? codeDark : codeLight,
135
+ className,
136
+ )}
137
+ >
138
+ {hasLayout ? (
139
+ <>
140
+ <header className={header}>
141
+ {hasSideNav ? (
142
+ <Button
143
+ variant="icon"
144
+ onClick={toggleSideNavIsOpen}
145
+ icon={sideNavIsOpen ? X : Menu}
146
+ className={sideNavButton}
147
+ />
148
+ ) : null}
149
+ <nav className={title}>
150
+ {renderComponentOrNode(titleComponentOrNode)}
151
+ </nav>
152
+ <nav className={topNav}>
153
+ {renderComponentOrNode(topNavLeftComponentOrNode, <div />)}
154
+ {renderComponentOrNode(topNavRightComponentOrNode, <div />)}
155
+ </nav>
156
+ <Button
157
+ variant="icon"
158
+ onClick={toggleDarkChoice}
159
+ icon={darkIcons[darkChoice]}
160
+ alt={darkChoices[darkChoice]}
161
+ />
162
+ {hasSideNav ? (
163
+ <nav
164
+ className={classNames(sideNav, sideNavIsOpen && sideNavOpen)}
165
+ >
166
+ {renderComponentOrNode(sideNavComponentOrNode)}
167
+ </nav>
168
+ ) : null}
169
+ </header>
170
+ <main className={classNames(main, hasSideNav && mainHasSideNav)}>
171
+ {renderComponentOrNode(mainComponentOrNode)}
172
+ </main>
173
+ </>
174
+ ) : null}
175
+ </div>
176
+ ) : null;
177
+ };
@@ -0,0 +1,66 @@
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 button = style({
6
+ display: 'inline-flex',
7
+ justifyContent: 'space-between',
8
+ alignItems: 'center',
9
+ gap: dimensions.padding,
10
+ borderRadius: dimensions.radius,
11
+ textAlign: 'left',
12
+ cursor: 'pointer',
13
+ padding: '0.5rem 1rem',
14
+ outlineOffset: '-2px',
15
+ color: 'inherit',
16
+ overflow: 'hidden',
17
+ whiteSpace: 'nowrap',
18
+ transition: 'background-color 0.1s,border-color 0.1s',
19
+ flexShrink: 0,
20
+ border: 0,
21
+ alignSelf: 'center',
22
+ background: 'none',
23
+ selectors: {
24
+ '&:hover': {
25
+ backgroundColor: colors.backgroundHover,
26
+ color: colors.foregroundBright,
27
+ },
28
+ },
29
+ });
30
+
31
+ export const buttonVariants = styleVariants({
32
+ default: {
33
+ boxShadow: colors.shadow,
34
+ border: colors.border,
35
+ backgroundColor: colors.background,
36
+ },
37
+ accent: {
38
+ boxShadow: colors.shadow,
39
+ backgroundColor: colors.accent,
40
+ color: colors.accentContrast,
41
+ selectors: {
42
+ '&:hover': {
43
+ backgroundColor: colors.accentHover,
44
+ },
45
+ },
46
+ },
47
+ ghost: {},
48
+ item: {width: '100%'},
49
+ icon: {padding: '0.25rem'},
50
+ });
51
+
52
+ export const currentStyle = style({
53
+ backgroundColor: colors.backgroundHover,
54
+ color: colors.foregroundBright,
55
+ });
56
+
57
+ export const titleStyle = style({
58
+ flex: '1 1 auto',
59
+ overflow: 'hidden',
60
+ textOverflow: 'ellipsis',
61
+ });
62
+
63
+ export const titleStyleRight = style({
64
+ flex: '0 0 auto',
65
+ overflow: 'hidden',
66
+ });
@@ -0,0 +1,206 @@
1
+ import type {ComponentType, ReactNode, Ref} from 'react';
2
+ import React, {forwardRef, useCallback} from 'react';
3
+ import {
4
+ button,
5
+ buttonVariants,
6
+ currentStyle,
7
+ titleStyle,
8
+ titleStyleRight,
9
+ } from './index.css.ts';
10
+ import {classNames, renderComponentOrNode} from '../../common/functions.tsx';
11
+ import {iconSize} from '../../css/dimensions.css.ts';
12
+
13
+ /**
14
+ * The `Button` component displays an button, with a number of common variants.
15
+ *
16
+ * @param props The props for the component.
17
+ * @returns The Button component.
18
+ * @example
19
+ * ```tsx
20
+ * <Button
21
+ * title="TinyWidgets"
22
+ * onClick={() => alert('Clicked!')}
23
+ * />
24
+ * ```
25
+ * This example shows the `default` variant of the Button component.
26
+ * @example
27
+ * ```tsx
28
+ * <Button
29
+ * title="TinyWidgets"
30
+ * icon={Lucide.Grid3x3}
31
+ * />
32
+ * ```
33
+ * This example shows the `default` variant of the Button component with a left
34
+ * icon.
35
+ * @example
36
+ * ```tsx
37
+ * <Button
38
+ * title="TinyWidgets"
39
+ * iconRight={Lucide.LogIn}
40
+ * />
41
+ * ```
42
+ * This example shows the `default` variant of the Button component with a right
43
+ * icon.
44
+ * @example
45
+ * ```tsx
46
+ * <Button
47
+ * icon={Lucide.Sun}
48
+ * variant="icon"
49
+ * />
50
+ * ```
51
+ * This example shows the `icon` variant of the Button component. It is best
52
+ * used without a title.
53
+ * @example
54
+ * ```tsx
55
+ * <Button
56
+ * title="TinyWidgets"
57
+ * variant="accent"
58
+ * />
59
+ * ```
60
+ * This example shows the `accent` variant of the Button component.
61
+ * @example
62
+ * ```tsx
63
+ * <Button
64
+ * title="tinybase.org"
65
+ * iconRight={Lucide.SquareArrowOutUpRight}
66
+ * variant="accent"
67
+ * href="https://tinybase.org"
68
+ * />
69
+ * ```
70
+ * This example shows the `accent` variant of the Button component with an icon,
71
+ * and that launches a new link.
72
+ * @example
73
+ * ```tsx
74
+ * <Button
75
+ * title="TinyWidgets"
76
+ * variant="ghost"
77
+ * />
78
+ * ```
79
+ * This example shows the `ghost` variant of the Button component.
80
+ * @example
81
+ * ```tsx
82
+ * <Button
83
+ * variant="item"
84
+ * icon={Lucide.Grid3x3}
85
+ * title="TinyWidgets"
86
+ * titleRight={<i>4</i>}
87
+ * iconRight={Lucide.CircleHelp}
88
+ * />
89
+ * ```
90
+ * This example shows the `item` variant of the Button component with both left
91
+ * and right titles and icons. This is suitable for use as a link in a
92
+ * navigational list, such as a side bar.
93
+ * @example
94
+ * ```tsx
95
+ * <Button
96
+ * variant="item"
97
+ * icon={Lucide.Grid3x3}
98
+ * title="TinyWidgets"
99
+ * current={true}
100
+ * />
101
+ * ```
102
+ * This example shows the `item` variant of the Button component, marked as
103
+ * 'current'.
104
+ * @icon Lucide.RectangleHorizontal
105
+ */
106
+ export const Button = forwardRef(
107
+ (
108
+ {
109
+ icon: Icon,
110
+ title: titleComponentOrNode,
111
+ titleRight: titleRightComponentOrNode,
112
+ iconRight: IconRight,
113
+ onClick,
114
+ variant = 'default',
115
+ current,
116
+ href,
117
+ alt,
118
+ className,
119
+ }: {
120
+ /**
121
+ * An optional component which renders an icon for the button, and which
122
+ * must accept a className prop.
123
+ */
124
+ readonly icon?: ComponentType<{className?: string}>;
125
+ /**
126
+ * An optional component, element, or string which renders the title of
127
+ * the button.
128
+ */
129
+ readonly title?: ComponentType | ReactNode;
130
+ /**
131
+ * An optional component, element, or string which renders a second title
132
+ * on the right side of the button.
133
+ */
134
+ readonly titleRight?: ComponentType | ReactNode;
135
+ /**
136
+ * An optional component which renders a second icon for the button, and
137
+ * which must accept a className prop.
138
+ */
139
+ readonly iconRight?: ComponentType<{className?: string}>;
140
+ /**
141
+ * A handler called when the user clicks on the button.
142
+ */
143
+ readonly onClick?: () => void;
144
+ /**
145
+ * A variant of the button, one of:
146
+ * - `default`
147
+ * - `icon`
148
+ * - `accent`
149
+ * - `ghost`
150
+ * - `item`
151
+ */
152
+ readonly variant?: keyof typeof buttonVariants;
153
+ /**
154
+ * A flag that indicates that an `item` button is 'current' and therefore
155
+ * highlighted.
156
+ */
157
+ readonly current?: boolean;
158
+ /**
159
+ * A URL that can be used instead of an `onClick` to launch a new web
160
+ * page, much like a link.
161
+ */
162
+ readonly href?: string;
163
+ /**
164
+ * Alternative text shown when the user hovers over the button.
165
+ */
166
+ readonly alt?: string;
167
+ /**
168
+ * An extra CSS class name for the component.
169
+ */
170
+ readonly className?: string;
171
+ },
172
+ ref: Ref<HTMLButtonElement>,
173
+ ) => {
174
+ const hrefClick = useCallback(
175
+ () => (href ? open(href, '_blank', 'noreferrer') : null),
176
+ [href],
177
+ );
178
+
179
+ return (
180
+ <button
181
+ className={classNames(
182
+ button,
183
+ buttonVariants[variant],
184
+ current && currentStyle,
185
+ className,
186
+ )}
187
+ onClick={onClick ?? hrefClick}
188
+ title={alt}
189
+ ref={ref}
190
+ >
191
+ {Icon ? <Icon className={iconSize} /> : null}
192
+ {titleComponentOrNode ? (
193
+ <span className={titleStyle}>
194
+ {renderComponentOrNode(titleComponentOrNode)}
195
+ </span>
196
+ ) : null}
197
+ {titleRightComponentOrNode ? (
198
+ <span className={titleStyleRight}>
199
+ {renderComponentOrNode(titleRightComponentOrNode)}
200
+ </span>
201
+ ) : null}
202
+ {IconRight ? <IconRight className={iconSize} /> : null}
203
+ </button>
204
+ );
205
+ },
206
+ );
@@ -0,0 +1,12 @@
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 card = style({
6
+ padding: dimensions.padding,
7
+ borderRadius: dimensions.radius,
8
+ boxShadow: colors.shadow,
9
+ border: colors.border,
10
+ height: 'fit-content',
11
+ overflow: 'auto',
12
+ });
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import type {ReactNode} from 'react';
3
+ import {card} from './index.css';
4
+ import {classNames} from '../../common/functions';
5
+
6
+ /**
7
+ * The `Card` component displays a simple rectangular container.
8
+ *
9
+ * @param props The props for the component.
10
+ * @returns Card Row component.
11
+ * @example
12
+ * ```tsx
13
+ * <Card>
14
+ * <h1>Welcome</h1>
15
+ * <Hr />
16
+ * <p>We hope you enjoy TinyWidgets</p>
17
+ * </Card>
18
+ * ```
19
+ * This example shows a simple card.
20
+ * @icon Lucide.Square
21
+ */
22
+ export const Card = ({
23
+ className,
24
+ children,
25
+ }: {
26
+ /**
27
+ * An extra CSS class name for the component.
28
+ */
29
+ readonly className?: string;
30
+ /**
31
+ * The children of the component, that go inside the card.
32
+ */
33
+ readonly children: ReactNode;
34
+ }) => <div className={classNames(card, className)}>{children}</div>;