tinywidgets 1.5.0 → 1.6.1
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/package.json +14 -14
- package/src/components/App/index.css.ts +35 -26
- package/src/components/App/index.tsx +31 -6
- package/src/components/Axis/index.css.ts +1 -1
- package/src/components/Button/index.css.ts +37 -23
- package/src/components/Button/index.tsx +14 -0
- package/src/components/Card/index.css.ts +69 -3
- package/src/components/Card/index.tsx +86 -5
- package/src/components/Checkbox/index.css.ts +27 -8
- package/src/components/Checkbox/index.tsx +17 -2
- package/src/components/Code/index.css.ts +2 -2
- package/src/components/Collapsible/index.css.ts +6 -6
- package/src/components/Detail/index.css.ts +2 -2
- package/src/components/Flyout/index.css.ts +5 -5
- package/src/components/Hr/index.css.ts +1 -1
- package/src/components/Image/index.css.ts +12 -12
- package/src/components/Loading/index.css.ts +28 -28
- package/src/components/Row/index.css.ts +8 -8
- package/src/components/Select/index.css.ts +20 -9
- package/src/components/Select/index.tsx +22 -3
- package/src/components/Summary/index.css.ts +1 -1
- package/src/components/Table/index.css.ts +8 -8
- package/src/components/Tag/index.css.ts +8 -8
- package/src/components/TextInput/index.css.ts +48 -17
- package/src/components/TextInput/index.tsx +39 -5
- package/src/css/classes.css.ts +42 -0
- package/src/css/code.css.ts +18 -18
- package/src/css/colors.css.ts +17 -17
- package/src/css/dimensions.css.ts +2 -2
- package/src/css/global.css.ts +2 -2
- package/src/index.css.ts +1 -0
- package/src/index.ts +5 -1
- package/src/stores/RouteStore.tsx +52 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinywidgets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"author": "jamesgpearce",
|
|
5
5
|
"repository": "github:tinyplex/tinywidgets",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,30 +19,30 @@
|
|
|
19
19
|
"prePublishPackage": "prettier --check . && eslint . && cspell --quiet . && tsc && cp ../README.md ./README.md"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@eslint/js": "^
|
|
23
|
-
"@types/react": "^19.2.
|
|
22
|
+
"@eslint/js": "^10.0.1",
|
|
23
|
+
"@types/react": "^19.2.17",
|
|
24
24
|
"@types/react-dom": "^19.2.3",
|
|
25
|
-
"cspell": "^10.0.
|
|
26
|
-
"eslint": "^
|
|
25
|
+
"cspell": "^10.0.1",
|
|
26
|
+
"eslint": "^10.6.0",
|
|
27
27
|
"eslint-plugin-import": "^2.32.0",
|
|
28
28
|
"eslint-plugin-react": "^7.37.5",
|
|
29
|
-
"eslint-plugin-react-hooks": "^
|
|
30
|
-
"globals": "^17.
|
|
31
|
-
"prettier": "^3.
|
|
29
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
30
|
+
"globals": "^17.7.0",
|
|
31
|
+
"prettier": "^3.9.4",
|
|
32
32
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
33
|
-
"react": "^19.2.
|
|
34
|
-
"react-dom": "^19.2.
|
|
33
|
+
"react": "^19.2.7",
|
|
34
|
+
"react-dom": "^19.2.7",
|
|
35
35
|
"typescript": "^6.0.3",
|
|
36
|
-
"typescript-eslint": "^8.
|
|
36
|
+
"typescript-eslint": "^8.62.1"
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|
|
39
39
|
".": "./src/index.ts",
|
|
40
40
|
"./css": "./src/index.css.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@vanilla-extract/css": "^1.
|
|
44
|
-
"lucide-react": "^1.
|
|
45
|
-
"tinybase": "^
|
|
43
|
+
"@vanilla-extract/css": "^1.21.1",
|
|
44
|
+
"lucide-react": "^1.23.0",
|
|
45
|
+
"tinybase": "^9.0.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"react": "^19.2.4",
|
|
@@ -12,27 +12,29 @@ export const appLayout = style({
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
export const header = style({
|
|
15
|
-
justifyContent: 'space-between',
|
|
16
|
-
gap: dimensions.padding,
|
|
17
|
-
padding: dimensions.padding,
|
|
18
|
-
position: 'fixed',
|
|
19
|
-
boxShadow: colors.shadow,
|
|
20
15
|
backdropFilter: 'blur(8px)',
|
|
21
16
|
backgroundColor: colors.backgroundHaze,
|
|
17
|
+
borderBottom: colors.border,
|
|
18
|
+
boxShadow: colors.shadow,
|
|
19
|
+
gap: dimensions.padding,
|
|
20
|
+
height: dimensions.topNavHeight,
|
|
21
|
+
justifyContent: 'space-between',
|
|
22
22
|
left: 0,
|
|
23
|
+
padding: dimensions.padding,
|
|
24
|
+
position: 'fixed',
|
|
23
25
|
right: 0,
|
|
24
|
-
height: dimensions.topNavHeight,
|
|
25
|
-
borderBottom: colors.border,
|
|
26
26
|
zIndex: 2,
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
export const topNav = style({
|
|
30
|
-
justifyContent: 'space-between',
|
|
31
|
-
gap: dimensions.padding,
|
|
32
30
|
flex: 1,
|
|
31
|
+
gap: dimensions.padding,
|
|
32
|
+
justifyContent: 'space-between',
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
export const
|
|
35
|
+
export const sideNavButtonResponsive = style(
|
|
36
|
+
large({display: 'none!important'}),
|
|
37
|
+
);
|
|
36
38
|
|
|
37
39
|
export const title = style({
|
|
38
40
|
...large({
|
|
@@ -41,26 +43,29 @@ export const title = style({
|
|
|
41
43
|
});
|
|
42
44
|
|
|
43
45
|
export const sideNav = style({
|
|
44
|
-
position: 'fixed',
|
|
45
|
-
padding: dimensions.padding,
|
|
46
46
|
backgroundColor: colors.background2,
|
|
47
|
-
overflow: 'auto',
|
|
48
47
|
borderRight: colors.border,
|
|
49
|
-
width: dimensions.sideNavWidth,
|
|
50
48
|
bottom: 0,
|
|
51
|
-
left: `calc(-1.2 * ${dimensions.sideNavWidth})`,
|
|
52
|
-
top: dimensions.topNavHeight,
|
|
53
49
|
height: `calc(100dvh - ${dimensions.topNavHeight})`,
|
|
50
|
+
left: `calc(-1.2 * ${dimensions.sideNavWidth})`,
|
|
51
|
+
overflow: 'auto',
|
|
54
52
|
overscrollBehavior: 'contain',
|
|
53
|
+
padding: dimensions.padding,
|
|
54
|
+
position: 'fixed',
|
|
55
|
+
top: dimensions.topNavHeight,
|
|
55
56
|
transition: 'left .2s ease-in-out',
|
|
56
|
-
|
|
57
|
+
width: dimensions.sideNavWidth,
|
|
57
58
|
});
|
|
58
59
|
|
|
59
60
|
export const sideNavOpen = style({left: 0});
|
|
60
61
|
|
|
62
|
+
export const sideNavResponsive = style(large({left: 0}));
|
|
63
|
+
|
|
64
|
+
export const sideNavNever = style({left: 0});
|
|
65
|
+
|
|
61
66
|
export const main = style({
|
|
62
|
-
flex: 1,
|
|
63
67
|
backgroundColor: colors.background,
|
|
68
|
+
flex: 1,
|
|
64
69
|
overflow: 'auto',
|
|
65
70
|
padding: dimensions.padding,
|
|
66
71
|
paddingTop: `calc(${dimensions.topNavHeight} + ${dimensions.padding})`,
|
|
@@ -72,22 +77,26 @@ export const mainHasSideNav = style(
|
|
|
72
77
|
}),
|
|
73
78
|
);
|
|
74
79
|
|
|
80
|
+
export const mainHasSideNavNever = style({
|
|
81
|
+
paddingLeft: `calc(${dimensions.sideNavWidth} + ${dimensions.padding})`,
|
|
82
|
+
});
|
|
83
|
+
|
|
75
84
|
export const mainHasFooter = style({
|
|
76
85
|
paddingBottom: `calc(${dimensions.footerHeight} + ${dimensions.padding})`,
|
|
77
86
|
});
|
|
78
87
|
|
|
79
88
|
export const footer = style({
|
|
80
|
-
|
|
89
|
+
backdropFilter: 'blur(8px)',
|
|
90
|
+
backgroundColor: colors.backgroundHaze,
|
|
91
|
+
borderTop: colors.border,
|
|
92
|
+
bottom: 0,
|
|
93
|
+
boxShadow: colors.shadow,
|
|
81
94
|
gap: dimensions.padding,
|
|
95
|
+
height: dimensions.footerHeight,
|
|
96
|
+
justifyContent: 'right',
|
|
97
|
+
left: 0,
|
|
82
98
|
paddingLeft: dimensions.padding,
|
|
83
99
|
paddingRight: dimensions.padding,
|
|
84
100
|
position: 'fixed',
|
|
85
|
-
bottom: 0,
|
|
86
|
-
left: 0,
|
|
87
101
|
right: 0,
|
|
88
|
-
height: dimensions.footerHeight,
|
|
89
|
-
backgroundColor: colors.backgroundHaze,
|
|
90
|
-
borderTop: colors.border,
|
|
91
|
-
boxShadow: colors.shadow,
|
|
92
|
-
backdropFilter: 'blur(8px)',
|
|
93
102
|
});
|
|
@@ -35,9 +35,12 @@ import {
|
|
|
35
35
|
main,
|
|
36
36
|
mainHasFooter,
|
|
37
37
|
mainHasSideNav,
|
|
38
|
+
mainHasSideNavNever,
|
|
38
39
|
sideNav,
|
|
39
|
-
|
|
40
|
+
sideNavButtonResponsive,
|
|
41
|
+
sideNavNever,
|
|
40
42
|
sideNavOpen,
|
|
43
|
+
sideNavResponsive,
|
|
41
44
|
title,
|
|
42
45
|
topNav,
|
|
43
46
|
} from './index.css.ts';
|
|
@@ -46,6 +49,7 @@ const {Provider} = UiReact as UiReact.WithSchemas<OptionalSchemas>;
|
|
|
46
49
|
|
|
47
50
|
const darkIcons = [Sun, Moon, SunMoon];
|
|
48
51
|
const darkChoices = ['Light always', 'Dark always', 'Auto'];
|
|
52
|
+
type SideNavToggle = 'never' | 'responsive' | 'always';
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
55
|
* The `App` component is the root component of a TinyWidgets application.
|
|
@@ -83,10 +87,17 @@ export const App = (props: {
|
|
|
83
87
|
*/
|
|
84
88
|
readonly topNavRight?: ComponentType | ReactNode;
|
|
85
89
|
/**
|
|
86
|
-
* An optional component, element, or string which renders the left side
|
|
87
|
-
* of the application.
|
|
90
|
+
* An optional component, element, or string which renders the left side
|
|
91
|
+
* navigation of the application.
|
|
88
92
|
*/
|
|
89
93
|
readonly sideNav?: ComponentType | ReactNode;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the side navigation can toggle. One of:
|
|
96
|
+
* - `never`: always show the side navigation.
|
|
97
|
+
* - `responsive`: toggle the side navigation on narrow screens only; default.
|
|
98
|
+
* - `always`: toggle the side navigation at all screen widths.
|
|
99
|
+
*/
|
|
100
|
+
readonly sideNavToggle?: SideNavToggle;
|
|
90
101
|
/**
|
|
91
102
|
* An optional component, element, or string which renders the main part of
|
|
92
103
|
* the application.
|
|
@@ -122,6 +133,7 @@ const Layout = ({
|
|
|
122
133
|
topNavLeft: topNavLeftComponentOrNode,
|
|
123
134
|
topNavRight: topNavRightComponentOrNode,
|
|
124
135
|
sideNav: sideNavComponentOrNode,
|
|
136
|
+
sideNavToggle = 'responsive',
|
|
125
137
|
main: mainComponentOrNode,
|
|
126
138
|
footer: footerComponentOrNode,
|
|
127
139
|
className,
|
|
@@ -148,6 +160,8 @@ const Layout = ({
|
|
|
148
160
|
].some((componentOrNode) => componentOrNode);
|
|
149
161
|
const hasSideNav = sideNavComponentOrNode != null;
|
|
150
162
|
const hasFooter = footerComponentOrNode != null;
|
|
163
|
+
const sideNavCanToggle = hasSideNav && sideNavToggle != 'never';
|
|
164
|
+
const sideNavIsAlwaysVisible = hasSideNav && sideNavToggle == 'never';
|
|
151
165
|
|
|
152
166
|
const ref = useRef<HTMLDivElement>(null);
|
|
153
167
|
|
|
@@ -167,12 +181,16 @@ const Layout = ({
|
|
|
167
181
|
{sessionStoreIsReady && routeStoreIsReady && localStoreIsReady ? (
|
|
168
182
|
<>
|
|
169
183
|
<Axis as="header" className={header}>
|
|
170
|
-
{
|
|
184
|
+
{sideNavCanToggle ? (
|
|
171
185
|
<Button
|
|
172
186
|
variant="icon"
|
|
173
187
|
onClick={toggleSideNavIsOpen}
|
|
174
188
|
icon={sideNavIsOpen ? X : Menu}
|
|
175
|
-
className={
|
|
189
|
+
className={
|
|
190
|
+
sideNavToggle == 'responsive'
|
|
191
|
+
? sideNavButtonResponsive
|
|
192
|
+
: undefined
|
|
193
|
+
}
|
|
176
194
|
/>
|
|
177
195
|
) : null}
|
|
178
196
|
<nav className={title}>
|
|
@@ -192,6 +210,8 @@ const Layout = ({
|
|
|
192
210
|
<nav
|
|
193
211
|
className={classNames(
|
|
194
212
|
sideNav,
|
|
213
|
+
sideNavToggle == 'responsive' && sideNavResponsive,
|
|
214
|
+
sideNavIsAlwaysVisible && sideNavNever,
|
|
195
215
|
sideNavIsOpen && sideNavOpen,
|
|
196
216
|
)}
|
|
197
217
|
>
|
|
@@ -202,7 +222,12 @@ const Layout = ({
|
|
|
202
222
|
<main
|
|
203
223
|
className={classNames(
|
|
204
224
|
main,
|
|
205
|
-
hasSideNav &&
|
|
225
|
+
hasSideNav &&
|
|
226
|
+
(sideNavToggle == 'never'
|
|
227
|
+
? mainHasSideNavNever
|
|
228
|
+
: sideNavToggle == 'responsive'
|
|
229
|
+
? mainHasSideNav
|
|
230
|
+
: false),
|
|
206
231
|
hasFooter && mainHasFooter,
|
|
207
232
|
)}
|
|
208
233
|
>
|
|
@@ -1,45 +1,43 @@
|
|
|
1
|
-
import {style, styleVariants} from '@vanilla-extract/css';
|
|
1
|
+
import {createVar, style, styleVariants} from '@vanilla-extract/css';
|
|
2
2
|
import {colors} from '../../css/colors.css';
|
|
3
3
|
import {dimensions} from '../../css/dimensions.css';
|
|
4
4
|
|
|
5
|
+
const titleFlex = createVar();
|
|
6
|
+
|
|
5
7
|
export const button = style({
|
|
6
|
-
display: 'inline-flex',
|
|
7
|
-
justifyContent: 'space-between',
|
|
8
8
|
alignItems: 'center',
|
|
9
|
-
|
|
9
|
+
alignSelf: 'center',
|
|
10
|
+
background: 'none',
|
|
11
|
+
border: '1px solid transparent',
|
|
10
12
|
borderRadius: dimensions.radius,
|
|
11
|
-
textAlign: 'left',
|
|
12
|
-
cursor: 'pointer',
|
|
13
|
-
padding: '0.5rem 1rem',
|
|
14
|
-
outlineOffset: '-2px',
|
|
15
13
|
color: 'inherit',
|
|
16
|
-
|
|
14
|
+
cursor: 'pointer',
|
|
15
|
+
display: 'inline-flex',
|
|
16
|
+
flexShrink: 0,
|
|
17
17
|
fontFamily: 'inherit',
|
|
18
|
+
fontWeight: 'inherit',
|
|
19
|
+
gap: dimensions.padding,
|
|
20
|
+
justifyContent: 'space-between',
|
|
21
|
+
outlineOffset: '-2px',
|
|
18
22
|
overflow: 'hidden',
|
|
19
|
-
|
|
20
|
-
transition: 'background-color 0.1s,border-color 0.1s',
|
|
21
|
-
flexShrink: 0,
|
|
22
|
-
border: '1px solid transparent',
|
|
23
|
-
alignSelf: 'center',
|
|
24
|
-
background: 'none',
|
|
23
|
+
padding: '0.5rem 1rem',
|
|
25
24
|
selectors: {
|
|
26
25
|
'&:hover': {
|
|
27
26
|
backgroundColor: colors.backgroundHover,
|
|
28
27
|
color: colors.foregroundBright,
|
|
29
28
|
},
|
|
30
29
|
},
|
|
30
|
+
textAlign: 'left',
|
|
31
|
+
transition: 'background-color 0.1s,border-color 0.1s',
|
|
32
|
+
vars: {[titleFlex]: '1 1 auto'},
|
|
33
|
+
whiteSpace: 'nowrap',
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
export const buttonVariants = styleVariants({
|
|
34
|
-
default: {
|
|
35
|
-
boxShadow: colors.shadow,
|
|
36
|
-
border: colors.border,
|
|
37
|
-
backgroundColor: colors.background,
|
|
38
|
-
},
|
|
39
37
|
accent: {
|
|
40
|
-
boxShadow: colors.shadow,
|
|
41
38
|
backgroundColor: colors.accent,
|
|
42
39
|
border: colors.border,
|
|
40
|
+
boxShadow: colors.shadow,
|
|
43
41
|
color: colors.accentContrast,
|
|
44
42
|
selectors: {
|
|
45
43
|
'&:hover': {
|
|
@@ -47,9 +45,25 @@ export const buttonVariants = styleVariants({
|
|
|
47
45
|
},
|
|
48
46
|
},
|
|
49
47
|
},
|
|
48
|
+
default: {
|
|
49
|
+
backgroundColor: colors.background,
|
|
50
|
+
border: colors.border,
|
|
51
|
+
boxShadow: colors.shadow,
|
|
52
|
+
},
|
|
50
53
|
ghost: {},
|
|
51
|
-
item: {width: '100%'},
|
|
52
54
|
icon: {padding: '0.25rem'},
|
|
55
|
+
item: {width: '100%'},
|
|
56
|
+
toolbar: {
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
backgroundColor: colors.background,
|
|
59
|
+
border: colors.border,
|
|
60
|
+
boxShadow: colors.shadow,
|
|
61
|
+
flexDirection: 'column',
|
|
62
|
+
gap: '0.25rem',
|
|
63
|
+
justifyContent: 'center',
|
|
64
|
+
textAlign: 'center',
|
|
65
|
+
vars: {[titleFlex]: '0 1 auto'},
|
|
66
|
+
},
|
|
53
67
|
});
|
|
54
68
|
|
|
55
69
|
export const currentStyle = style({
|
|
@@ -58,7 +72,7 @@ export const currentStyle = style({
|
|
|
58
72
|
});
|
|
59
73
|
|
|
60
74
|
export const titleStyle = style({
|
|
61
|
-
flex:
|
|
75
|
+
flex: titleFlex,
|
|
62
76
|
overflow: 'hidden',
|
|
63
77
|
textOverflow: 'ellipsis',
|
|
64
78
|
});
|
|
@@ -101,6 +101,16 @@ import {
|
|
|
101
101
|
* ```
|
|
102
102
|
* This example shows the `item` variant of the Button component, marked as
|
|
103
103
|
* 'current'.
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* <Button
|
|
107
|
+
* variant="toolbar"
|
|
108
|
+
* icon={Lucide.Grid3x3}
|
|
109
|
+
* title="New Grid"
|
|
110
|
+
* />
|
|
111
|
+
* ```
|
|
112
|
+
* This example shows the `toolbar` variant of the Button component, with the
|
|
113
|
+
* icon stacked above the title.
|
|
104
114
|
* @icon Lucide.RectangleHorizontal
|
|
105
115
|
*/
|
|
106
116
|
export const Button = ({
|
|
@@ -148,6 +158,7 @@ export const Button = ({
|
|
|
148
158
|
* - `accent`
|
|
149
159
|
* - `ghost`
|
|
150
160
|
* - `item`
|
|
161
|
+
* - `toolbar`
|
|
151
162
|
*/
|
|
152
163
|
readonly variant?: keyof typeof buttonVariants;
|
|
153
164
|
/**
|
|
@@ -172,6 +183,9 @@ export const Button = ({
|
|
|
172
183
|
* An name for the component to be used as an anchor for other elements.
|
|
173
184
|
*/
|
|
174
185
|
readonly anchorName?: string;
|
|
186
|
+
/**
|
|
187
|
+
* A ref to the underlying button element.
|
|
188
|
+
*/
|
|
175
189
|
ref?: React.RefObject<HTMLButtonElement | null>;
|
|
176
190
|
}) => {
|
|
177
191
|
const hrefClick = useCallback(
|
|
@@ -1,12 +1,78 @@
|
|
|
1
|
-
import {style} from '@vanilla-extract/css';
|
|
1
|
+
import {style, styleVariants} from '@vanilla-extract/css';
|
|
2
2
|
import {colors} from '../../css/colors.css';
|
|
3
3
|
import {dimensions} from '../../css/dimensions.css';
|
|
4
|
+
import {
|
|
5
|
+
buttonOpen,
|
|
6
|
+
collapsible,
|
|
7
|
+
collapsibleOpen,
|
|
8
|
+
content,
|
|
9
|
+
} from '../Collapsible/index.css';
|
|
4
10
|
|
|
5
11
|
export const card = style({
|
|
6
|
-
|
|
12
|
+
border: colors.border,
|
|
7
13
|
borderRadius: dimensions.radius,
|
|
8
14
|
boxShadow: colors.shadow,
|
|
9
|
-
border: colors.border,
|
|
10
15
|
height: 'fit-content',
|
|
11
16
|
overflow: 'auto',
|
|
17
|
+
padding: dimensions.padding,
|
|
12
18
|
});
|
|
19
|
+
|
|
20
|
+
export const titledCard = style([collapsible, collapsibleOpen]);
|
|
21
|
+
|
|
22
|
+
const titleButtonBase = style([
|
|
23
|
+
buttonOpen,
|
|
24
|
+
{
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
alignSelf: 'center',
|
|
27
|
+
backgroundColor: colors.backgroundHover,
|
|
28
|
+
border: colors.border,
|
|
29
|
+
borderRadius: dimensions.radius,
|
|
30
|
+
borderBottomLeftRadius: 0,
|
|
31
|
+
borderBottomRightRadius: 0,
|
|
32
|
+
boxShadow: 'none',
|
|
33
|
+
color: colors.foregroundBright,
|
|
34
|
+
display: 'inline-flex',
|
|
35
|
+
flexShrink: 0,
|
|
36
|
+
fontFamily: 'inherit',
|
|
37
|
+
fontWeight: 'inherit',
|
|
38
|
+
gap: dimensions.padding,
|
|
39
|
+
justifyContent: 'space-between',
|
|
40
|
+
lineHeight: 'normal',
|
|
41
|
+
margin: '-1px',
|
|
42
|
+
outlineOffset: '-2px',
|
|
43
|
+
overflow: 'hidden',
|
|
44
|
+
padding: '0.5rem 1rem',
|
|
45
|
+
textAlign: 'left',
|
|
46
|
+
transition: 'background-color 0.1s,border-color 0.1s',
|
|
47
|
+
whiteSpace: 'nowrap',
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
export const titleButton = styleVariants({
|
|
52
|
+
closable: [
|
|
53
|
+
titleButtonBase,
|
|
54
|
+
{
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
selectors: {
|
|
57
|
+
'&:hover': {
|
|
58
|
+
backgroundColor: colors.backgroundHover,
|
|
59
|
+
color: colors.foregroundBright,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
static: [titleButtonBase],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const title = style({
|
|
68
|
+
flex: '1 1 auto',
|
|
69
|
+
overflow: 'hidden',
|
|
70
|
+
textOverflow: 'ellipsis',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const titleRight = style({
|
|
74
|
+
flex: '0 0 auto',
|
|
75
|
+
overflow: 'hidden',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const titledCardContent = style([content]);
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {X} from 'lucide-react';
|
|
2
|
+
import type {ComponentType, ReactNode} from 'react';
|
|
3
|
+
import {classNames, renderComponentOrNode} from '../../common/functions';
|
|
4
|
+
import {iconSize} from '../../css/dimensions.css';
|
|
5
|
+
import {
|
|
6
|
+
card,
|
|
7
|
+
titleButton,
|
|
8
|
+
titledCard,
|
|
9
|
+
titledCardContent,
|
|
10
|
+
title as titleStyle,
|
|
11
|
+
titleRight as titleStyleRight,
|
|
12
|
+
} from './index.css';
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
|
-
* The `Card` component displays a simple rectangular container.
|
|
15
|
+
* The `Card` component displays a simple rectangular container. If a `title `
|
|
16
|
+
* prop is provided, it is displayed in the same style as a `Collapsible`
|
|
17
|
+
* component, but without the collapsible behavior.
|
|
7
18
|
*
|
|
8
19
|
* @param props The props for the component.
|
|
9
20
|
* @returns The Card component.
|
|
@@ -16,12 +27,55 @@ import {card} from './index.css';
|
|
|
16
27
|
* </Card>
|
|
17
28
|
* ```
|
|
18
29
|
* This example shows a simple card.
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <Card
|
|
33
|
+
* title="TinyWidgets"
|
|
34
|
+
* icon={Lucide.Grid3x3}
|
|
35
|
+
* >
|
|
36
|
+
* <p>Always open</p>
|
|
37
|
+
* </Card>
|
|
38
|
+
* ```
|
|
39
|
+
* This example shows a titled card.
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <Card
|
|
43
|
+
* title="TinyWidgets"
|
|
44
|
+
* onClose={() => console.log('Closed')}
|
|
45
|
+
* >
|
|
46
|
+
* <p>Close me</p>
|
|
47
|
+
* </Card>
|
|
48
|
+
* ```
|
|
49
|
+
* This example shows a card with a close button.
|
|
19
50
|
* @icon Lucide.Square
|
|
20
51
|
*/
|
|
21
52
|
export const Card = ({
|
|
53
|
+
icon: Icon,
|
|
54
|
+
title,
|
|
55
|
+
titleRight,
|
|
56
|
+
onClose,
|
|
22
57
|
className,
|
|
23
58
|
children,
|
|
24
59
|
}: {
|
|
60
|
+
/**
|
|
61
|
+
* An optional component which renders an icon for the top of the card, and
|
|
62
|
+
* which must accept a className prop.
|
|
63
|
+
*/
|
|
64
|
+
readonly icon?: ComponentType<{className?: string}>;
|
|
65
|
+
/**
|
|
66
|
+
* An optional component, element, or string which renders the title of
|
|
67
|
+
* the top of the card.
|
|
68
|
+
*/
|
|
69
|
+
readonly title?: ComponentType | ReactNode;
|
|
70
|
+
/**
|
|
71
|
+
* An optional component, element, or string which renders a second title
|
|
72
|
+
* on the right side of the top of the card.
|
|
73
|
+
*/
|
|
74
|
+
readonly titleRight?: ComponentType | ReactNode;
|
|
75
|
+
/**
|
|
76
|
+
* A handler called when the user clicks on the close button.
|
|
77
|
+
*/
|
|
78
|
+
readonly onClose?: () => void;
|
|
25
79
|
/**
|
|
26
80
|
* An extra CSS class name for the component.
|
|
27
81
|
*/
|
|
@@ -30,4 +84,31 @@ export const Card = ({
|
|
|
30
84
|
* The children of the component that go inside the card.
|
|
31
85
|
*/
|
|
32
86
|
readonly children: ReactNode;
|
|
33
|
-
}) =>
|
|
87
|
+
}) => {
|
|
88
|
+
const hasHeader =
|
|
89
|
+
Icon != null || title != null || titleRight != null || onClose != null;
|
|
90
|
+
|
|
91
|
+
return hasHeader ? (
|
|
92
|
+
<div className={classNames(titledCard, className)}>
|
|
93
|
+
<div
|
|
94
|
+
className={titleButton[onClose ? 'closable' : 'static']}
|
|
95
|
+
onClick={onClose}
|
|
96
|
+
{...(onClose ? {title: 'Close'} : {})}
|
|
97
|
+
>
|
|
98
|
+
{Icon ? <Icon className={iconSize} /> : null}
|
|
99
|
+
{title ? (
|
|
100
|
+
<span className={titleStyle}>{renderComponentOrNode(title)}</span>
|
|
101
|
+
) : null}
|
|
102
|
+
{titleRight ? (
|
|
103
|
+
<span className={titleStyleRight}>
|
|
104
|
+
{renderComponentOrNode(titleRight)}
|
|
105
|
+
</span>
|
|
106
|
+
) : null}
|
|
107
|
+
{onClose ? <X className={iconSize} /> : null}
|
|
108
|
+
</div>
|
|
109
|
+
<div className={titledCardContent}>{children}</div>
|
|
110
|
+
</div>
|
|
111
|
+
) : (
|
|
112
|
+
<div className={classNames(card, className)}>{children}</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
import {style} from '@vanilla-extract/css';
|
|
1
|
+
import {globalStyle, style, styleVariants} from '@vanilla-extract/css';
|
|
2
2
|
import {dimensions} from '../../css/dimensions.css';
|
|
3
3
|
import {colors} from '../../index.css';
|
|
4
4
|
|
|
5
5
|
export const checkbox = style({
|
|
6
|
-
|
|
7
|
-
boxShadow: colors.shadow + ' inset',
|
|
8
|
-
borderRadius: dimensions.radius,
|
|
6
|
+
alignItems: 'center',
|
|
9
7
|
border: colors.border,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
borderRadius: dimensions.radius,
|
|
9
|
+
boxSizing: 'border-box',
|
|
10
|
+
boxShadow: colors.shadow + ' inset',
|
|
11
|
+
display: 'inline-flex',
|
|
12
|
+
justifyContent: 'center',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const checkboxVariants = styleVariants({
|
|
16
|
+
default: {
|
|
17
|
+
height: '2.5rem',
|
|
18
|
+
margin: 0,
|
|
19
|
+
padding: '0.5rem',
|
|
20
|
+
width: '2.5rem',
|
|
21
|
+
},
|
|
22
|
+
small: {
|
|
23
|
+
height: '1.5rem',
|
|
24
|
+
margin: 0,
|
|
25
|
+
padding: '0.25rem',
|
|
26
|
+
width: '1.5rem',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
globalStyle(`${checkboxVariants.small} svg`, {
|
|
31
|
+
height: '0.85rem',
|
|
32
|
+
width: '0.85rem',
|
|
14
33
|
});
|