tinywidgets 1.3.4 → 1.3.6
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/bun.lock +716 -77
- package/package.json +13 -12
- package/src/components/App/index.tsx +68 -47
- package/src/components/Button/index.tsx +7 -0
- package/src/components/Card/index.tsx +2 -2
- package/src/components/Checkbox/index.css.ts +14 -0
- package/src/components/Checkbox/index.tsx +67 -0
- package/src/components/Collapsible/index.tsx +1 -1
- package/src/components/Detail/index.tsx +1 -1
- package/src/components/Flyout/index.css.ts +25 -0
- package/src/components/Flyout/index.tsx +148 -0
- package/src/components/Select/index.css.ts +2 -0
- package/src/components/Select/index.tsx +2 -5
- package/src/components/Table/index.css.ts +26 -0
- package/src/components/Table/index.tsx +33 -0
- package/src/components/TextInput/index.css.ts +3 -3
- package/src/components/TextInput/index.tsx +2 -8
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinywidgets",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"author": "jamesgpearce",
|
|
5
5
|
"repository": "github:tinyplex/tinywidgets",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,28 +19,29 @@
|
|
|
19
19
|
"prePublishPackage": "prettier --check . && eslint . && cspell --quiet . && tsc && cp ../README.md ./README.md"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@eslint/js": "^9.
|
|
23
|
-
"@types/react": "^19.1.
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"eslint
|
|
22
|
+
"@eslint/js": "^9.30.1",
|
|
23
|
+
"@types/react": "^19.1.8",
|
|
24
|
+
"@types/react-dom": "^19.1.6",
|
|
25
|
+
"cspell": "^9.1.3",
|
|
26
|
+
"eslint": "^9.30.1",
|
|
27
|
+
"eslint-plugin-import": "^2.32.0",
|
|
27
28
|
"eslint-plugin-react": "^7.37.5",
|
|
28
29
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
29
|
-
"globals": "^16.
|
|
30
|
-
"prettier": "^3.
|
|
30
|
+
"globals": "^16.3.0",
|
|
31
|
+
"prettier": "^3.6.2",
|
|
31
32
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
32
33
|
"typescript": "^5.8.3",
|
|
33
|
-
"typescript-eslint": "^8.
|
|
34
|
+
"typescript-eslint": "^8.36.0"
|
|
34
35
|
},
|
|
35
36
|
"exports": {
|
|
36
37
|
".": "./src/index.ts",
|
|
37
38
|
"./css": "./src/index.css.ts"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@vanilla-extract/css": "^1.17.
|
|
41
|
-
"lucide-react": "^0.
|
|
41
|
+
"@vanilla-extract/css": "^1.17.4",
|
|
42
|
+
"lucide-react": "^0.525.0",
|
|
42
43
|
"react": "^19.1.0",
|
|
43
44
|
"react-dom": "^19.1.0",
|
|
44
|
-
"tinybase": "^6.
|
|
45
|
+
"tinybase": "^6.4.0"
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import {Menu, Moon, Sun, SunMoon, X} from 'lucide-react';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useRef,
|
|
6
|
+
type ComponentType,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react';
|
|
3
9
|
import * as UiReact from 'tinybase/ui-react/with-schemas';
|
|
4
10
|
import type {OptionalSchemas} from 'tinybase/with-schemas';
|
|
5
11
|
import {classNames, renderComponentOrNode} from '../../common/functions.tsx';
|
|
@@ -105,6 +111,11 @@ export const App = (props: {
|
|
|
105
111
|
);
|
|
106
112
|
};
|
|
107
113
|
|
|
114
|
+
const LayoutContext = createContext<{readonly portal: HTMLDivElement | null}>({
|
|
115
|
+
portal: null,
|
|
116
|
+
});
|
|
117
|
+
export const usePortal = () => useContext(LayoutContext).portal;
|
|
118
|
+
|
|
108
119
|
const Layout = ({
|
|
109
120
|
title: titleComponentOrNode,
|
|
110
121
|
topNavLeft: topNavLeftComponentOrNode,
|
|
@@ -137,8 +148,11 @@ const Layout = ({
|
|
|
137
148
|
const hasSideNav = sideNavComponentOrNode != null;
|
|
138
149
|
const hasFooter = footerComponentOrNode != null;
|
|
139
150
|
|
|
140
|
-
|
|
151
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
152
|
+
|
|
153
|
+
return (
|
|
141
154
|
<div
|
|
155
|
+
ref={ref}
|
|
142
156
|
className={classNames(
|
|
143
157
|
app,
|
|
144
158
|
hasLayout && appLayout,
|
|
@@ -148,53 +162,60 @@ const Layout = ({
|
|
|
148
162
|
)}
|
|
149
163
|
>
|
|
150
164
|
{hasLayout ? (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
<LayoutContext.Provider value={{portal: ref.current}}>
|
|
166
|
+
{sessionStoreIsReady && routeStoreIsReady && localStoreIsReady ? (
|
|
167
|
+
<>
|
|
168
|
+
<header className={header}>
|
|
169
|
+
{hasSideNav ? (
|
|
170
|
+
<Button
|
|
171
|
+
variant="icon"
|
|
172
|
+
onClick={toggleSideNavIsOpen}
|
|
173
|
+
icon={sideNavIsOpen ? X : Menu}
|
|
174
|
+
className={sideNavButton}
|
|
175
|
+
/>
|
|
176
|
+
) : null}
|
|
177
|
+
<nav className={title}>
|
|
178
|
+
{renderComponentOrNode(titleComponentOrNode)}
|
|
179
|
+
</nav>
|
|
180
|
+
<nav className={topNav}>
|
|
181
|
+
{renderComponentOrNode(topNavLeftComponentOrNode, <div />)}
|
|
182
|
+
{renderComponentOrNode(topNavRightComponentOrNode, <div />)}
|
|
183
|
+
</nav>
|
|
184
|
+
<Button
|
|
185
|
+
variant="icon"
|
|
186
|
+
onClick={toggleDarkChoice}
|
|
187
|
+
icon={darkIcons[darkChoice]}
|
|
188
|
+
alt={darkChoices[darkChoice]}
|
|
189
|
+
/>
|
|
190
|
+
{hasSideNav ? (
|
|
191
|
+
<nav
|
|
192
|
+
className={classNames(
|
|
193
|
+
sideNav,
|
|
194
|
+
sideNavIsOpen && sideNavOpen,
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
{renderComponentOrNode(sideNavComponentOrNode)}
|
|
198
|
+
</nav>
|
|
199
|
+
) : null}
|
|
200
|
+
</header>
|
|
201
|
+
<main
|
|
202
|
+
className={classNames(
|
|
203
|
+
main,
|
|
204
|
+
hasSideNav && mainHasSideNav,
|
|
205
|
+
hasFooter && mainHasFooter,
|
|
206
|
+
)}
|
|
177
207
|
>
|
|
178
|
-
{renderComponentOrNode(
|
|
179
|
-
</
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
hasFooter && mainHasFooter,
|
|
187
|
-
)}
|
|
188
|
-
>
|
|
189
|
-
{renderComponentOrNode(mainComponentOrNode)}
|
|
190
|
-
</main>
|
|
191
|
-
{hasFooter ? (
|
|
192
|
-
<footer className={footer}>
|
|
193
|
-
{renderComponentOrNode(footerComponentOrNode)}
|
|
194
|
-
</footer>
|
|
208
|
+
{renderComponentOrNode(mainComponentOrNode)}
|
|
209
|
+
</main>
|
|
210
|
+
{hasFooter ? (
|
|
211
|
+
<footer className={footer}>
|
|
212
|
+
{renderComponentOrNode(footerComponentOrNode)}
|
|
213
|
+
</footer>
|
|
214
|
+
) : null}
|
|
215
|
+
</>
|
|
195
216
|
) : null}
|
|
196
|
-
|
|
217
|
+
</LayoutContext.Provider>
|
|
197
218
|
) : null}
|
|
198
219
|
</div>
|
|
199
|
-
)
|
|
220
|
+
);
|
|
200
221
|
};
|
|
@@ -114,6 +114,7 @@ export const Button = ({
|
|
|
114
114
|
href,
|
|
115
115
|
alt,
|
|
116
116
|
className,
|
|
117
|
+
anchorName,
|
|
117
118
|
ref,
|
|
118
119
|
}: {
|
|
119
120
|
/**
|
|
@@ -167,6 +168,10 @@ export const Button = ({
|
|
|
167
168
|
* An extra CSS class name for the component.
|
|
168
169
|
*/
|
|
169
170
|
readonly className?: string;
|
|
171
|
+
/**
|
|
172
|
+
* An name for the component to be used as an anchor for other elements.
|
|
173
|
+
*/
|
|
174
|
+
readonly anchorName?: string;
|
|
170
175
|
ref?: React.RefObject<HTMLButtonElement | null>;
|
|
171
176
|
}) => {
|
|
172
177
|
const hrefClick = useCallback(
|
|
@@ -175,6 +180,7 @@ export const Button = ({
|
|
|
175
180
|
);
|
|
176
181
|
|
|
177
182
|
return (
|
|
183
|
+
// @ts-expect-error anchorName not typed for React yet
|
|
178
184
|
<button
|
|
179
185
|
className={classNames(
|
|
180
186
|
button,
|
|
@@ -185,6 +191,7 @@ export const Button = ({
|
|
|
185
191
|
onClick={onClick ?? hrefClick}
|
|
186
192
|
title={alt}
|
|
187
193
|
ref={ref}
|
|
194
|
+
{...(anchorName ? {style: {anchorName}} : {})}
|
|
188
195
|
>
|
|
189
196
|
{Icon ? <Icon className={iconSize} /> : null}
|
|
190
197
|
{titleComponentOrNode ? (
|
|
@@ -6,7 +6,7 @@ import {card} from './index.css';
|
|
|
6
6
|
* The `Card` component displays a simple rectangular container.
|
|
7
7
|
*
|
|
8
8
|
* @param props The props for the component.
|
|
9
|
-
* @returns Card
|
|
9
|
+
* @returns The Card component.
|
|
10
10
|
* @example
|
|
11
11
|
* ```tsx
|
|
12
12
|
* <Card>
|
|
@@ -27,7 +27,7 @@ export const Card = ({
|
|
|
27
27
|
*/
|
|
28
28
|
readonly className?: string;
|
|
29
29
|
/**
|
|
30
|
-
* The children of the component
|
|
30
|
+
* The children of the component that go inside the card.
|
|
31
31
|
*/
|
|
32
32
|
readonly children: ReactNode;
|
|
33
33
|
}) => <div className={classNames(card, className)}>{children}</div>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {style} from '@vanilla-extract/css';
|
|
2
|
+
import {dimensions} from '../../css/dimensions.css';
|
|
3
|
+
import {colors} from '../../index.css';
|
|
4
|
+
|
|
5
|
+
export const checkbox = style({
|
|
6
|
+
display: 'inline-block',
|
|
7
|
+
boxShadow: colors.shadow + ' inset',
|
|
8
|
+
borderRadius: dimensions.radius,
|
|
9
|
+
border: colors.border,
|
|
10
|
+
padding: '0.5rem',
|
|
11
|
+
height: '2rem',
|
|
12
|
+
width: '2rem',
|
|
13
|
+
margin: '5px',
|
|
14
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {LucideCheck} from 'lucide-react';
|
|
2
|
+
import {useCallback, useState} from 'react';
|
|
3
|
+
import {classNames} from '../../common/functions.tsx';
|
|
4
|
+
import {Button} from '../Button/index.tsx';
|
|
5
|
+
import {checkbox} from './index.css.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The `Checkbox` component displays a managed checkbox with an optional default
|
|
9
|
+
* checked state.
|
|
10
|
+
*
|
|
11
|
+
* @param props The props for the component.
|
|
12
|
+
* @returns The Checkbox component.
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Checkbox onChange={(value) => console.log(value)} />
|
|
16
|
+
* ```
|
|
17
|
+
* This example shows the Checkbox component without a default checked state.
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <Checkbox
|
|
21
|
+
* initialChecked={true}
|
|
22
|
+
* onChange={(value) => console.log(value)}
|
|
23
|
+
* />
|
|
24
|
+
* ```
|
|
25
|
+
* This example shows the Checkbox component with a default checked state.
|
|
26
|
+
* @icon Lucide.LucideCheckSquare
|
|
27
|
+
*/
|
|
28
|
+
export const Checkbox = ({
|
|
29
|
+
initialChecked,
|
|
30
|
+
onChange,
|
|
31
|
+
alt,
|
|
32
|
+
className,
|
|
33
|
+
ref,
|
|
34
|
+
}: {
|
|
35
|
+
/**
|
|
36
|
+
* An optional value for whether the box is checked.
|
|
37
|
+
*/
|
|
38
|
+
readonly initialChecked?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* A handler called when the text is changed.
|
|
41
|
+
*/
|
|
42
|
+
readonly onChange?: (checked: boolean) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Alternative text shown when the user hovers over the input.
|
|
45
|
+
*/
|
|
46
|
+
readonly alt?: string;
|
|
47
|
+
/**
|
|
48
|
+
* An extra CSS class name for the component.
|
|
49
|
+
*/
|
|
50
|
+
readonly className?: string;
|
|
51
|
+
ref?: React.RefObject<HTMLButtonElement | null>;
|
|
52
|
+
}) => {
|
|
53
|
+
const [checked, setChecked] = useState(initialChecked ?? false);
|
|
54
|
+
const handleClick = useCallback(() => {
|
|
55
|
+
setChecked(!checked);
|
|
56
|
+
onChange?.(!checked);
|
|
57
|
+
}, [checked, onChange]);
|
|
58
|
+
return (
|
|
59
|
+
<Button
|
|
60
|
+
className={classNames(checkbox, className)}
|
|
61
|
+
onClick={handleClick}
|
|
62
|
+
alt={alt}
|
|
63
|
+
ref={ref}
|
|
64
|
+
icon={checked ? LucideCheck : undefined}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -93,7 +93,7 @@ export const Collapsible = ({
|
|
|
93
93
|
*/
|
|
94
94
|
readonly className?: string;
|
|
95
95
|
/**
|
|
96
|
-
* The children of the component
|
|
96
|
+
* The children of the component that go inside the collapsible section.
|
|
97
97
|
*/
|
|
98
98
|
readonly children: ReactNode;
|
|
99
99
|
}) => {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {style} from '@vanilla-extract/css';
|
|
2
|
+
import {colors} from '../../css/colors.css';
|
|
3
|
+
import {dimensions} from '../../css/dimensions.css';
|
|
4
|
+
|
|
5
|
+
export const wrapper = style({
|
|
6
|
+
position: 'relative',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const flyout = style({
|
|
10
|
+
padding: dimensions.padding,
|
|
11
|
+
borderRadius: dimensions.radius,
|
|
12
|
+
boxShadow: colors.shadow,
|
|
13
|
+
border: colors.border,
|
|
14
|
+
height: 'fit-content',
|
|
15
|
+
overflow: 'auto',
|
|
16
|
+
backgroundColor: colors.background,
|
|
17
|
+
position: 'absolute',
|
|
18
|
+
top: 'calc(2rem + 2px)',
|
|
19
|
+
left: 0,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const anchoredFlyout = style({
|
|
23
|
+
top: 'anchor(bottom)',
|
|
24
|
+
left: 'anchor(left)',
|
|
25
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentType,
|
|
3
|
+
useCallback,
|
|
4
|
+
useMemo,
|
|
5
|
+
useState,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import {createPortal} from 'react-dom';
|
|
9
|
+
import {getUniqueId} from 'tinybase';
|
|
10
|
+
import {classNames} from '../../common/functions';
|
|
11
|
+
import {
|
|
12
|
+
useCollapsibleIsOpen,
|
|
13
|
+
useSetCollapsibleIsOpenCallback,
|
|
14
|
+
} from '../../stores/SessionStore';
|
|
15
|
+
import {usePortal} from '../App';
|
|
16
|
+
import {Button} from '../Button';
|
|
17
|
+
import {buttonVariants} from '../Button/index.css';
|
|
18
|
+
import {anchoredFlyout, flyout, wrapper} from './index.css';
|
|
19
|
+
|
|
20
|
+
const supportsAnchors = CSS.supports('anchor-name', '--');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The `Flyout` component displays a simple rectangular container that pops up
|
|
24
|
+
* out of an icon.
|
|
25
|
+
*
|
|
26
|
+
* @param props The props for the component.
|
|
27
|
+
* @returns Flyout Row component.
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Flyout icon={Lucide.LucideHelpCircle}>
|
|
31
|
+
* <h1>Welcome</h1>
|
|
32
|
+
* <Hr />
|
|
33
|
+
* <p>We hope you enjoy TinyWidgets</p>
|
|
34
|
+
* </Flyout>
|
|
35
|
+
* ```
|
|
36
|
+
* This example shows a flyout from a simple button.
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <Flyout
|
|
40
|
+
* title="Toggle"
|
|
41
|
+
* icon={Lucide.LucidePanelTopOpen}
|
|
42
|
+
* openIcon={Lucide.LucidePanelTopClose}
|
|
43
|
+
* startOpen={true}
|
|
44
|
+
* >
|
|
45
|
+
* <p>We hope you enjoy TinyWidgets</p>
|
|
46
|
+
* </Flyout>
|
|
47
|
+
* ```
|
|
48
|
+
* This example shows a flyout, starting off open, with a title on the button,
|
|
49
|
+
* and with a different icon for open and closed states.
|
|
50
|
+
* @icon Lucide.LucideArrowDownSquare
|
|
51
|
+
*/
|
|
52
|
+
export const Flyout = ({
|
|
53
|
+
icon,
|
|
54
|
+
openIcon,
|
|
55
|
+
title,
|
|
56
|
+
variant,
|
|
57
|
+
startOpen,
|
|
58
|
+
id = '',
|
|
59
|
+
className,
|
|
60
|
+
children,
|
|
61
|
+
}: {
|
|
62
|
+
/**
|
|
63
|
+
* An icon to click on to open up the flyout, and which must accept a
|
|
64
|
+
* className prop.
|
|
65
|
+
*/
|
|
66
|
+
readonly icon: ComponentType<{className?: string}>;
|
|
67
|
+
/**
|
|
68
|
+
* An optional icon to show when the flyout is open, and which must accept a
|
|
69
|
+
* className prop.
|
|
70
|
+
*/
|
|
71
|
+
readonly openIcon?: ComponentType<{className?: string}>;
|
|
72
|
+
/**
|
|
73
|
+
* An optional component, element, or string which renders the title of
|
|
74
|
+
* the button.
|
|
75
|
+
*/
|
|
76
|
+
readonly title?: ComponentType | ReactNode;
|
|
77
|
+
/**
|
|
78
|
+
* A variant of the button used for the flyout, one of:
|
|
79
|
+
* - `default`
|
|
80
|
+
* - `icon`
|
|
81
|
+
* - `accent`
|
|
82
|
+
* - `ghost`
|
|
83
|
+
* - `item`
|
|
84
|
+
*/
|
|
85
|
+
readonly variant?: keyof typeof buttonVariants;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the flyout should start opened up.
|
|
88
|
+
*/
|
|
89
|
+
readonly startOpen?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* An Id which will allow the state to be preserved between page reloads.
|
|
92
|
+
*/
|
|
93
|
+
readonly id?: string;
|
|
94
|
+
/**
|
|
95
|
+
* An extra CSS class name for the component.
|
|
96
|
+
*/
|
|
97
|
+
readonly className?: string;
|
|
98
|
+
/**
|
|
99
|
+
* The children of the component that go inside the card.
|
|
100
|
+
*/
|
|
101
|
+
readonly children: ReactNode;
|
|
102
|
+
}) => {
|
|
103
|
+
const storedIsOpen = useCollapsibleIsOpen(id) ?? startOpen;
|
|
104
|
+
const setStoredIsOpen = useSetCollapsibleIsOpenCallback(id);
|
|
105
|
+
const [stateIsOpen, setStateIsOpen] = useState(startOpen);
|
|
106
|
+
|
|
107
|
+
const isOpen = id ? storedIsOpen : stateIsOpen;
|
|
108
|
+
const setIsOpen = id ? setStoredIsOpen : setStateIsOpen;
|
|
109
|
+
|
|
110
|
+
const handleClick = useCallback(
|
|
111
|
+
() => setIsOpen(!isOpen),
|
|
112
|
+
[setIsOpen, isOpen],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const anchor = useMemo(() => '--' + getUniqueId(5), []);
|
|
116
|
+
const portal = usePortal();
|
|
117
|
+
|
|
118
|
+
const buttonProps = {
|
|
119
|
+
icon: isOpen ? (openIcon ?? icon) : icon,
|
|
120
|
+
title,
|
|
121
|
+
variant,
|
|
122
|
+
onClick: handleClick,
|
|
123
|
+
};
|
|
124
|
+
return supportsAnchors ? (
|
|
125
|
+
<>
|
|
126
|
+
<Button {...buttonProps} anchorName={anchor} />
|
|
127
|
+
{isOpen && portal
|
|
128
|
+
? createPortal(
|
|
129
|
+
<div
|
|
130
|
+
className={classNames(flyout, anchoredFlyout, className)}
|
|
131
|
+
// @ts-expect-error positionAnchor not typed for React yet
|
|
132
|
+
style={{positionAnchor: anchor}}
|
|
133
|
+
>
|
|
134
|
+
{children}
|
|
135
|
+
</div>,
|
|
136
|
+
portal,
|
|
137
|
+
)
|
|
138
|
+
: null}
|
|
139
|
+
</>
|
|
140
|
+
) : (
|
|
141
|
+
<div className={wrapper}>
|
|
142
|
+
<Button {...buttonProps} />
|
|
143
|
+
{isOpen ? (
|
|
144
|
+
<div className={classNames(flyout, className)}>{children}</div>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
@@ -58,17 +58,14 @@ export const Select = ({
|
|
|
58
58
|
);
|
|
59
59
|
return (
|
|
60
60
|
<select
|
|
61
|
+
value={option}
|
|
61
62
|
className={classNames(select, className)}
|
|
62
63
|
onChange={handleChange}
|
|
63
64
|
title={alt}
|
|
64
65
|
ref={ref}
|
|
65
66
|
>
|
|
66
67
|
{Object.entries(options).map(([eachOption, label]) => (
|
|
67
|
-
<option
|
|
68
|
-
key={eachOption}
|
|
69
|
-
value={eachOption}
|
|
70
|
-
{...(eachOption === option ? {selected: true} : {})}
|
|
71
|
-
>
|
|
68
|
+
<option key={eachOption} value={eachOption}>
|
|
72
69
|
{label}
|
|
73
70
|
</option>
|
|
74
71
|
))}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {globalStyle, style} from '@vanilla-extract/css';
|
|
2
|
+
import {colors} from '../../css/colors.css';
|
|
3
|
+
|
|
4
|
+
export const table = style({
|
|
5
|
+
width: '100%',
|
|
6
|
+
height: '100%',
|
|
7
|
+
padding: 0,
|
|
8
|
+
borderCollapse: 'collapse',
|
|
9
|
+
tableLayout: 'fixed',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
globalStyle(`${table} th, ${table} td`, {
|
|
13
|
+
overflow: 'hidden',
|
|
14
|
+
padding: '0.25rem 1rem',
|
|
15
|
+
whiteSpace: 'nowrap',
|
|
16
|
+
textOverflow: 'ellipsis',
|
|
17
|
+
borderWidth: '1px 0',
|
|
18
|
+
borderStyle: 'solid',
|
|
19
|
+
borderColor: colors.borderColor,
|
|
20
|
+
textAlign: 'left',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
globalStyle(`${table} th`, {
|
|
24
|
+
fontWeight: 'inherit',
|
|
25
|
+
backgroundColor: colors.backgroundHover,
|
|
26
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {ReactNode} from 'react';
|
|
2
|
+
import {classNames} from '../../common/functions';
|
|
3
|
+
import {table} from './index.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `Table` component displays a table with some simple default styling.
|
|
7
|
+
*
|
|
8
|
+
* @param props The props for the component.
|
|
9
|
+
* @returns The Table component.
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Table>
|
|
13
|
+
* <tr><th>Column 1</th><th>Column 2</th></tr>
|
|
14
|
+
* <tr><td>Cell A</td><td>Cell B</td></tr>
|
|
15
|
+
* <tr><td>Cell C</td><td>Cell D</td></tr>
|
|
16
|
+
* </Table>
|
|
17
|
+
* ```
|
|
18
|
+
* This example shows a simple table.
|
|
19
|
+
* @icon Lucide.LucideTable2
|
|
20
|
+
*/
|
|
21
|
+
export const Table = ({
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
}: {
|
|
25
|
+
/**
|
|
26
|
+
* An extra CSS class name for the component.
|
|
27
|
+
*/
|
|
28
|
+
readonly className?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The children (`tr` rows) that go inside the table.
|
|
31
|
+
*/
|
|
32
|
+
readonly children: ReactNode;
|
|
33
|
+
}) => <table className={classNames(table, className)}>{children}</table>;
|
|
@@ -6,10 +6,9 @@ export const wrapper = style({
|
|
|
6
6
|
flexShrink: 0,
|
|
7
7
|
alignSelf: 'center',
|
|
8
8
|
position: 'relative',
|
|
9
|
+
display: 'inline-block',
|
|
9
10
|
});
|
|
10
11
|
|
|
11
|
-
export const wrapperWithIcon = style({});
|
|
12
|
-
|
|
13
12
|
export const input = style({
|
|
14
13
|
borderRadius: dimensions.radius,
|
|
15
14
|
padding: '0.5rem',
|
|
@@ -20,6 +19,7 @@ export const input = style({
|
|
|
20
19
|
boxShadow: colors.shadow + ' inset',
|
|
21
20
|
border: colors.border,
|
|
22
21
|
backgroundColor: colors.background,
|
|
22
|
+
lineHeight: '1.5rem',
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
export const inputWithIcon = style({
|
|
@@ -29,7 +29,7 @@ export const inputWithIcon = style({
|
|
|
29
29
|
export const icon = style({
|
|
30
30
|
position: 'absolute',
|
|
31
31
|
left: `calc(${dimensions.icon} * .5)`,
|
|
32
|
-
top: `calc(${dimensions.icon} * .
|
|
32
|
+
top: `calc(${dimensions.icon} * .8)`,
|
|
33
33
|
color: colors.foregroundDim,
|
|
34
34
|
backgroundColor: colors.background,
|
|
35
35
|
borderRight: `calc(${dimensions.icon} * .25) solid ${colors.background}`,
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import {useCallback, useState, type ComponentType} from 'react';
|
|
2
2
|
import {classNames} from '../../common/functions.tsx';
|
|
3
3
|
import {iconSize} from '../../css/dimensions.css.ts';
|
|
4
|
-
import {
|
|
5
|
-
icon,
|
|
6
|
-
input,
|
|
7
|
-
inputWithIcon,
|
|
8
|
-
wrapper,
|
|
9
|
-
wrapperWithIcon,
|
|
10
|
-
} from './index.css.ts';
|
|
4
|
+
import {icon, input, inputWithIcon, wrapper} from './index.css.ts';
|
|
11
5
|
|
|
12
6
|
/**
|
|
13
7
|
* The `TextInput` component displays a managed text input with an existing
|
|
@@ -76,7 +70,7 @@ export const TextInput = ({
|
|
|
76
70
|
[onChange],
|
|
77
71
|
);
|
|
78
72
|
return (
|
|
79
|
-
<div className={
|
|
73
|
+
<div className={wrapper}>
|
|
80
74
|
{Icon ? <Icon className={classNames(iconSize, icon)} /> : null}
|
|
81
75
|
<input
|
|
82
76
|
value={text}
|