tinywidgets 0.0.0 → 0.0.2

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 (51) hide show
  1. package/.eslintrc.json +91 -0
  2. package/.prettierrc +5 -0
  3. package/bun.lockb +0 -0
  4. package/index.css.ts +1 -0
  5. package/index.ts +11 -0
  6. package/media.ts +1 -0
  7. package/package.json +27 -2
  8. package/src/Avatar/index.css.ts +17 -0
  9. package/src/Avatar/index.tsx +28 -0
  10. package/src/Axis/index.css.ts +19 -0
  11. package/src/Axis/index.tsx +38 -0
  12. package/src/Button/index.css.ts +57 -0
  13. package/src/Button/index.tsx +65 -0
  14. package/src/Card/index.css.ts +9 -0
  15. package/src/Card/index.tsx +15 -0
  16. package/src/Collapsible/index.css.ts +34 -0
  17. package/src/Collapsible/index.tsx +69 -0
  18. package/src/Detail/index.css.ts +19 -0
  19. package/src/Detail/index.tsx +28 -0
  20. package/src/Hr/index.css.ts +10 -0
  21. package/src/Hr/index.tsx +10 -0
  22. package/src/Metric/index.css.ts +18 -0
  23. package/src/Metric/index.tsx +28 -0
  24. package/src/Summary/index.css.ts +17 -0
  25. package/src/Summary/index.tsx +34 -0
  26. package/src/Tag/index.css.ts +27 -0
  27. package/src/Tag/index.tsx +28 -0
  28. package/src/Ui/Layout/Header/DarkButton/index.tsx +20 -0
  29. package/src/Ui/Layout/Header/SideNav/index.css.ts +23 -0
  30. package/src/Ui/Layout/Header/SideNav/index.tsx +16 -0
  31. package/src/Ui/Layout/Header/SideNavButton/index.css.ts +4 -0
  32. package/src/Ui/Layout/Header/SideNavButton/index.tsx +17 -0
  33. package/src/Ui/Layout/Header/Title/index.css.ts +10 -0
  34. package/src/Ui/Layout/Header/Title/index.tsx +10 -0
  35. package/src/Ui/Layout/Header/TopNav/index.css.ts +9 -0
  36. package/src/Ui/Layout/Header/TopNav/index.tsx +19 -0
  37. package/src/Ui/Layout/Header/index.css.ts +18 -0
  38. package/src/Ui/Layout/Header/index.tsx +33 -0
  39. package/src/Ui/Layout/Main/Article/index.css.ts +13 -0
  40. package/src/Ui/Layout/Main/Article/index.tsx +10 -0
  41. package/src/Ui/Layout/Main/Footer/index.css.ts +12 -0
  42. package/src/Ui/Layout/Main/Footer/index.tsx +10 -0
  43. package/src/Ui/Layout/Main/index.css.ts +16 -0
  44. package/src/Ui/Layout/Main/index.tsx +26 -0
  45. package/src/Ui/Layout/index.css.ts +9 -0
  46. package/src/Ui/Layout/index.tsx +52 -0
  47. package/src/Ui/LocalStore.tsx +55 -0
  48. package/src/Ui/SessionStore.tsx +60 -0
  49. package/src/Ui/index.tsx +26 -0
  50. package/src/index.css.ts +125 -0
  51. package/src/index.ts +12 -0
@@ -0,0 +1,34 @@
1
+ /** @jsx createElement */
2
+
3
+ import React from 'react';
4
+ import {classNames} from '../';
5
+ import {Avatar} from '../Avatar';
6
+ import {summary, summaryContent, summaryImage} from './index.css';
7
+
8
+ const {createElement} = React;
9
+
10
+ export const Summary = ({
11
+ icon: Icon,
12
+ src,
13
+ label,
14
+ children,
15
+ className,
16
+ }: {
17
+ icon?: React.ComponentType<{className?: string}>;
18
+ src?: string;
19
+ label: React.ReactNode;
20
+ children: React.ReactNode;
21
+ className?: string;
22
+ }) => (
23
+ <div className={classNames(summary, className)}>
24
+ {src ? (
25
+ <Avatar src={src} className={summaryImage} />
26
+ ) : Icon ? (
27
+ <Icon className={summaryImage} />
28
+ ) : null}
29
+ <div className={summaryContent}>
30
+ <h2>{label}</h2>
31
+ {children}
32
+ </div>
33
+ </div>
34
+ );
@@ -0,0 +1,27 @@
1
+ import {style, styleVariants} from '@vanilla-extract/css';
2
+ import {theme} from '../index.css';
3
+
4
+ export const tag = style({
5
+ fontSize: '0.625rem',
6
+ lineHeight: '0.625rem',
7
+ padding: '0.1rem 0.25rem',
8
+ borderRadius: '0.25rem',
9
+ gap: '0.25rem',
10
+ flexShrink: 0,
11
+ });
12
+
13
+ export const tagVariant = styleVariants({
14
+ default: {
15
+ backgroundColor: theme.backgroundHover,
16
+ color: theme.foreground2,
17
+ },
18
+ accent: {
19
+ backgroundColor: theme.accent,
20
+ color: theme.accentContrast,
21
+ },
22
+ });
23
+
24
+ export const tagIcon = style({
25
+ width: '0.75rem',
26
+ height: '0.75rem',
27
+ });
@@ -0,0 +1,28 @@
1
+ /** @jsx createElement */
2
+
3
+ import React from 'react';
4
+ import {classNames} from '../';
5
+ import {Axis} from '../Axis';
6
+ import {tag, tagIcon, tagVariant} from './index.css';
7
+
8
+ const {createElement} = React;
9
+
10
+ export const Tag = ({
11
+ icon: Icon,
12
+ label,
13
+ title,
14
+ variant = 'default',
15
+ }: {
16
+ icon?: React.ComponentType<{className?: string}>;
17
+ label?: React.ReactNode;
18
+ title?: string;
19
+ variant?: keyof typeof tagVariant;
20
+ }) => {
21
+ const icon = Icon ? <Icon className={tagIcon} /> : null;
22
+ return (
23
+ <Axis className={classNames(tag, tagVariant[variant])} title={title}>
24
+ {icon}
25
+ {label}
26
+ </Axis>
27
+ );
28
+ };
@@ -0,0 +1,20 @@
1
+ /** @jsx createElement */
2
+
3
+ import React from 'react';
4
+ import {Moon, Sun, SunMoon} from 'lucide-react';
5
+ import {Button} from '../../../../Button/index.tsx';
6
+ import {
7
+ useDarkChoice,
8
+ useToggleDarkChoiceCallback,
9
+ } from '../../../LocalStore.tsx';
10
+
11
+ const {createElement} = React;
12
+ const icons = [Sun, Moon, SunMoon];
13
+
14
+ export const DarkButton = () => (
15
+ <Button
16
+ variant="icon"
17
+ onClick={useToggleDarkChoiceCallback()}
18
+ icon={icons[useDarkChoice()]}
19
+ />
20
+ );
@@ -0,0 +1,23 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {dimensions, paddingLike, theme} from '../../../../index.css.ts';
3
+ import {large} from '../../../../index.ts';
4
+
5
+ export const sideNav = style([
6
+ paddingLike,
7
+ {
8
+ position: 'fixed',
9
+ backgroundColor: theme.background2,
10
+ overflow: 'auto',
11
+ borderRight: `1px solid ${theme.border}`,
12
+ width: dimensions.sideNavWidth,
13
+ bottom: 0,
14
+ left: '-' + dimensions.sideNavWidth,
15
+ top: dimensions.topNavHeight,
16
+ height: `calc(100vh - ${dimensions.topNavHeight})`,
17
+ overscrollBehavior: 'contain',
18
+ transition: 'left .2s ease-in-out',
19
+ },
20
+ large({left: 0}),
21
+ ]);
22
+
23
+ export const open = style({left: 0});
@@ -0,0 +1,16 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {classNames} from '../../../../index.ts';
5
+ import {useSideNavOpen} from '../../../SessionStore.tsx';
6
+ import {open, sideNav} from './index.css.ts';
7
+
8
+ const {createElement} = React;
9
+
10
+ export const SideNav = ({sideNav: sideNavComponents}: {sideNav: ReactNode}) => {
11
+ return (
12
+ <nav className={classNames(sideNav, useSideNavOpen() && open)}>
13
+ {sideNavComponents}
14
+ </nav>
15
+ );
16
+ };
@@ -0,0 +1,4 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {large} from '../../../../index.ts';
3
+
4
+ export const sideNavButton = style(large({display: 'none'}));
@@ -0,0 +1,17 @@
1
+ /** @jsx createElement */
2
+ import React from 'react';
3
+ import {Menu, X} from 'lucide-react';
4
+ import {Button} from '../../../../Button/index.tsx';
5
+ import {useSideNavOpen, useToggleSideNavOpen} from '../../../SessionStore.tsx';
6
+ import {sideNavButton} from './index.css';
7
+
8
+ const {createElement} = React;
9
+
10
+ export const SideNavButton = () => (
11
+ <Button
12
+ variant="icon"
13
+ onClick={useToggleSideNavOpen()}
14
+ icon={useSideNavOpen() ? X : Menu}
15
+ className={sideNavButton}
16
+ />
17
+ );
@@ -0,0 +1,10 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {dimensions} from '../../../../index.css';
3
+ import {large} from '../../../../index.ts';
4
+
5
+ export const title = style({
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ gap: dimensions.padding,
9
+ ...large({width: dimensions.titleWidth}),
10
+ });
@@ -0,0 +1,10 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {title} from './index.css.ts';
5
+
6
+ const {createElement} = React;
7
+
8
+ export const Title = ({title: titleComponents}: {title: ReactNode}) => {
9
+ return <nav className={title}>{titleComponents}</nav>;
10
+ };
@@ -0,0 +1,9 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {axisLike} from '../../../../index.css';
3
+
4
+ export const topNav = style([
5
+ {
6
+ ...axisLike,
7
+ flex: 1,
8
+ },
9
+ ]);
@@ -0,0 +1,19 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {topNav} from './index.css.ts';
5
+
6
+ const {createElement} = React;
7
+
8
+ export const TopNav = ({
9
+ topNavLeft = <div />,
10
+ topNavRight = <div />,
11
+ }: {
12
+ topNavLeft?: ReactNode;
13
+ topNavRight?: ReactNode;
14
+ }) => (
15
+ <nav className={topNav}>
16
+ {topNavLeft}
17
+ {topNavRight}
18
+ </nav>
19
+ );
@@ -0,0 +1,18 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {axisLike, dimensions, paddingLike, theme} from '../../../index.css';
3
+
4
+ export const header = style([
5
+ paddingLike,
6
+ axisLike,
7
+ {
8
+ position: 'fixed',
9
+ boxShadow: theme.shadow,
10
+ backdropFilter: 'blur(8px)',
11
+ backgroundColor: theme.backgroundHaze,
12
+ left: 0,
13
+ right: 0,
14
+ height: dimensions.topNavHeight,
15
+ borderBottom: `1px solid ${theme.border}`,
16
+ zIndex: 2,
17
+ },
18
+ ]);
@@ -0,0 +1,33 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {DarkButton} from './DarkButton/index.tsx';
5
+ import {header} from './index.css.ts';
6
+ import {SideNav} from './SideNav/index.tsx';
7
+ import {SideNavButton} from './SideNavButton/index.tsx';
8
+ import {Title} from './Title/index.tsx';
9
+ import {TopNav} from './TopNav/index.tsx';
10
+
11
+ const {createElement} = React;
12
+
13
+ export const Header = ({
14
+ title,
15
+ topNavLeft,
16
+ topNavRight,
17
+ sideNav,
18
+ }: {
19
+ title: ReactNode;
20
+ topNavLeft?: ReactNode;
21
+ topNavRight?: ReactNode;
22
+ sideNav?: ReactNode;
23
+ }) => {
24
+ return (
25
+ <header className={header}>
26
+ {sideNav ? <SideNavButton /> : null}
27
+ <Title title={title} />
28
+ <TopNav topNavLeft={topNavLeft} topNavRight={topNavRight} />
29
+ <DarkButton />
30
+ {sideNav ? <SideNav sideNav={sideNav} /> : null}
31
+ </header>
32
+ );
33
+ };
@@ -0,0 +1,13 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {dimensions, paddingLike} from '../../../../index.css';
3
+
4
+ export const article = style([
5
+ paddingLike,
6
+ {
7
+ paddingTop: dimensions.padding,
8
+ flex: 1,
9
+ width: '100%',
10
+ // maxWidth: dimensions.articleWidth,
11
+ margin: '0 auto',
12
+ },
13
+ ]);
@@ -0,0 +1,10 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {article} from './index.css.ts';
5
+
6
+ const {createElement} = React;
7
+
8
+ export const Article = ({article: articleComponents}: {article: ReactNode}) => {
9
+ return <article className={article}>{articleComponents}</article>;
10
+ };
@@ -0,0 +1,12 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {axisLike, dimensions, paddingLike, theme} from '../../../../index.css';
3
+
4
+ export const footer = style([
5
+ {
6
+ ...axisLike,
7
+ ...paddingLike,
8
+ background: theme.background,
9
+ height: dimensions.footerHeight,
10
+ borderTop: `1px solid ${theme.border}`,
11
+ },
12
+ ]);
@@ -0,0 +1,10 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {footer} from './index.css.ts';
5
+
6
+ const {createElement} = React;
7
+
8
+ export const Footer = ({footer: footerComponents}: {footer: ReactNode}) => {
9
+ return <footer className={footer}>{footerComponents}</footer>;
10
+ };
@@ -0,0 +1,16 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {dimensions, theme} from '../../../index.css';
3
+ import {large} from '../../../index.ts';
4
+
5
+ export const main = style({
6
+ display: 'flex',
7
+ flex: 1,
8
+ flexDirection: 'column',
9
+ backgroundColor: theme.background,
10
+ overflow: 'auto',
11
+ paddingTop: dimensions.topNavHeight,
12
+ });
13
+
14
+ export const mainHasSideNav = style(
15
+ large({paddingLeft: dimensions.sideNavWidth}),
16
+ );
@@ -0,0 +1,26 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {classNames} from '../../../index.ts';
5
+ import {Article} from './Article/index.tsx';
6
+ import {Footer} from './Footer/index.tsx';
7
+ import {main, mainHasSideNav} from './index.css.ts';
8
+
9
+ const {createElement} = React;
10
+
11
+ export const Main = ({
12
+ article,
13
+ footer,
14
+ hasSideNav,
15
+ }: {
16
+ article?: ReactNode;
17
+ footer?: ReactNode;
18
+ hasSideNav: boolean;
19
+ }) => {
20
+ return (
21
+ <main className={classNames(main, hasSideNav && mainHasSideNav)}>
22
+ {article ? <Article article={article} /> : null}
23
+ {footer ? <Footer footer={footer} /> : null}
24
+ </main>
25
+ );
26
+ };
@@ -0,0 +1,9 @@
1
+ import {style} from '@vanilla-extract/css';
2
+ import {theme} from '../../index.css.ts';
3
+
4
+ export const layout = style({
5
+ display: 'flex',
6
+ height: '100vh',
7
+ width: '100vw',
8
+ color: theme.foreground,
9
+ });
@@ -0,0 +1,52 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {themeDark, themeLight} from '../../index.css.ts';
5
+ import {classNames} from '../../index.ts';
6
+ import {useDarkChoice, useDarkPreference} from '../LocalStore.tsx';
7
+ import {Header} from './Header/index.tsx';
8
+ import {layout} from './index.css.ts';
9
+ import {Main} from './Main/index.tsx';
10
+
11
+ const {createElement} = React;
12
+
13
+ export const Layout = ({
14
+ title,
15
+ topNavLeft,
16
+ topNavRight,
17
+ sideNav,
18
+ article,
19
+ footer,
20
+ }: {
21
+ title: ReactNode;
22
+ topNavLeft?: ReactNode;
23
+ topNavRight?: ReactNode;
24
+ sideNav?: ReactNode;
25
+ article?: ReactNode;
26
+ footer?: ReactNode;
27
+ }) => {
28
+ const darkPreference = useDarkPreference();
29
+ const darkChoice = useDarkChoice();
30
+ return (
31
+ <div
32
+ className={classNames(
33
+ layout,
34
+ darkChoice == 1 || (darkChoice == 2 && darkPreference)
35
+ ? themeDark
36
+ : themeLight,
37
+ )}
38
+ >
39
+ <Header
40
+ title={title}
41
+ topNavLeft={topNavLeft}
42
+ topNavRight={topNavRight}
43
+ sideNav={sideNav}
44
+ />
45
+ <Main
46
+ article={article}
47
+ footer={footer}
48
+ hasSideNav={sideNav ? true : false}
49
+ />
50
+ </div>
51
+ );
52
+ };
@@ -0,0 +1,55 @@
1
+ /** @jsx createElement */
2
+ import React from 'react';
3
+ import {createStore} from 'tinybase';
4
+ import {createLocalPersister} from 'tinybase/persisters/persister-browser';
5
+ import {
6
+ useCreatePersister,
7
+ useCreateStore,
8
+ useProvideStore,
9
+ useSetValueCallback,
10
+ useValue,
11
+ } from 'tinybase/ui-react';
12
+
13
+ const {useEffect} = React;
14
+
15
+ const PREFERS_DARK = matchMedia?.('(prefers-color-scheme: dark)');
16
+
17
+ const LOCAL_STORE = 'uiLocal';
18
+ const DARK_PREFERENCE_VALUE = 'darkPreference';
19
+ const DARK_CHOICE_VALUE = 'darkChoice';
20
+
21
+ export const useDarkPreference = () =>
22
+ useValue(DARK_PREFERENCE_VALUE, LOCAL_STORE) as boolean;
23
+
24
+ export const useDarkChoice = () =>
25
+ (useValue(DARK_CHOICE_VALUE, LOCAL_STORE) ?? 2) as 0 | 1 | 2;
26
+ export const useToggleDarkChoiceCallback = () =>
27
+ useSetValueCallback(
28
+ DARK_CHOICE_VALUE,
29
+ () => (value) => (((value ?? 2) as number) + 1) % 3,
30
+ [],
31
+ LOCAL_STORE,
32
+ );
33
+
34
+ export const LocalStore = () => {
35
+ const localStore = useCreateStore(createStore);
36
+ useCreatePersister(
37
+ localStore,
38
+ (localStore) => createLocalPersister(localStore, LOCAL_STORE),
39
+ [],
40
+ async (persister) => {
41
+ await persister.startAutoLoad();
42
+ await persister.startAutoSave();
43
+ },
44
+ );
45
+ useEffect(() => {
46
+ const preferenceListener = () =>
47
+ localStore.setValue(DARK_PREFERENCE_VALUE, PREFERS_DARK.matches);
48
+ PREFERS_DARK.addEventListener('change', preferenceListener);
49
+ preferenceListener();
50
+ return () => PREFERS_DARK.removeEventListener('change', preferenceListener);
51
+ }, [localStore]);
52
+
53
+ useProvideStore(LOCAL_STORE, localStore);
54
+ return null;
55
+ };
@@ -0,0 +1,60 @@
1
+ import {createStore, type Id} from 'tinybase';
2
+ import {createSessionPersister} from 'tinybase/persisters/persister-browser';
3
+ import {
4
+ useCell,
5
+ useCreatePersister,
6
+ useCreateStore,
7
+ useProvideStore,
8
+ useSetCellCallback,
9
+ useSetValueCallback,
10
+ useValue,
11
+ } from 'tinybase/ui-react';
12
+
13
+ const SESSION_STORE = 'uiSession';
14
+
15
+ const COLLAPSIBLE_TABLE = 'collapsible';
16
+ const COLLAPSIBLE_OPEN_CELL = 'open';
17
+
18
+ const SIDE_NAV_OPEN_VALUE = 'sideNavOpen';
19
+
20
+ export const useCollapsibleOpen = (collapsibleId: Id) =>
21
+ useCell(
22
+ COLLAPSIBLE_TABLE,
23
+ collapsibleId,
24
+ COLLAPSIBLE_OPEN_CELL,
25
+ SESSION_STORE,
26
+ ) as any;
27
+ export const useSetCollapsibleOpen = (collapsibleId: Id) =>
28
+ useSetCellCallback(
29
+ COLLAPSIBLE_TABLE,
30
+ collapsibleId,
31
+ COLLAPSIBLE_OPEN_CELL,
32
+ (open: boolean) => open,
33
+ [collapsibleId],
34
+ SESSION_STORE,
35
+ );
36
+
37
+ export const useSideNavOpen = () =>
38
+ useValue(SIDE_NAV_OPEN_VALUE, SESSION_STORE) as any;
39
+ export const useToggleSideNavOpen = () =>
40
+ useSetValueCallback(
41
+ SIDE_NAV_OPEN_VALUE,
42
+ () => (value) => !value,
43
+ [],
44
+ SESSION_STORE,
45
+ );
46
+
47
+ export const SessionStore = () => {
48
+ const sessionStore = useCreateStore(createStore);
49
+ useCreatePersister(
50
+ sessionStore,
51
+ (sessionStore) => createSessionPersister(sessionStore, SESSION_STORE),
52
+ [],
53
+ async (persister) => {
54
+ await persister.startAutoLoad();
55
+ await persister.startAutoSave();
56
+ },
57
+ );
58
+ useProvideStore(SESSION_STORE, sessionStore);
59
+ return null;
60
+ };
@@ -0,0 +1,26 @@
1
+ /** @jsx createElement */
2
+
3
+ import React, {type ReactNode} from 'react';
4
+ import {Provider} from 'tinybase/ui-react';
5
+ import {Layout} from './Layout/index.tsx';
6
+ import {LocalStore} from './LocalStore.tsx';
7
+ import {SessionStore} from './SessionStore.tsx';
8
+
9
+ const {createElement} = React;
10
+
11
+ export const Ui = (props: {
12
+ title: ReactNode;
13
+ topNavLeft?: ReactNode;
14
+ topNavRight?: ReactNode;
15
+ sideNav?: ReactNode;
16
+ article?: ReactNode;
17
+ footer?: ReactNode;
18
+ }) => {
19
+ return (
20
+ <Provider>
21
+ <Layout {...props} />
22
+ <SessionStore />
23
+ <LocalStore />
24
+ </Provider>
25
+ );
26
+ };