@douglasneuroinformatics/libui 3.5.2 → 3.6.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.
- package/dist/{chunk-AFNJZUOE.js → chunk-TVVMHFB6.js} +19 -1
- package/dist/chunk-TVVMHFB6.js.map +1 -0
- package/dist/components.d.ts +53 -2
- package/dist/components.js +1037 -724
- package/dist/components.js.map +1 -1
- package/dist/douglasneuroinformatics-libui-3.6.0.tgz +0 -0
- package/dist/hooks.d.ts +7 -58
- package/dist/hooks.js +3 -1
- package/dist/tailwind/config.cjs +2 -4
- package/dist/tailwind/config.cjs.map +1 -1
- package/dist/types-9zYgx7C8.d.ts +75 -0
- package/package.json +55 -57
- package/src/components/Chart/Chart.stories.tsx +55 -0
- package/src/components/Chart/Chart.tsx +14 -0
- package/src/components/Chart/ChartContainer.tsx +38 -0
- package/src/components/Chart/ChartLegendContent.tsx +56 -0
- package/src/components/Chart/ChartStyle.tsx +33 -0
- package/src/components/Chart/ChartTooltipContent.tsx +139 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/Chart/types.ts +19 -0
- package/src/components/Chart/utils.ts +26 -0
- package/src/components/Form/Form.stories.tsx +35 -19
- package/src/components/Form/Form.tsx +8 -3
- package/src/components/Form/NumberField/NumberFieldInput.tsx +29 -17
- package/src/components/NotificationHub/NotificationIcon.tsx +64 -6
- package/src/components/index.ts +1 -0
- package/src/context/ChartContext.tsx +9 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useChart/index.ts +1 -0
- package/src/hooks/useChart/useChart.ts +11 -0
- package/src/tailwind/config.cts +2 -4
- package/dist/chunk-AFNJZUOE.js.map +0 -1
- package/dist/douglasneuroinformatics-libui-3.5.2.tgz +0 -0
|
Binary file
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { C as ChartConfig, U as UseStorageOptions } from './types-9zYgx7C8.js';
|
|
2
|
+
export { D as DEFAULT_THEME, a as SYS_DARK_MEDIA_QUERY, S as StorageName, b as THEME_ATTRIBUTE, c as THEME_KEY, T as Theme, u as useStorage, d as useTheme } from './types-9zYgx7C8.js';
|
|
1
3
|
import { Promisable } from 'type-fest';
|
|
2
4
|
import { RefObject, useEffect, Dispatch, SetStateAction } from 'react';
|
|
3
5
|
import * as zustand from 'zustand';
|
|
4
6
|
import { T as TranslationNamespace, L as Language, a as TranslateFunction } from './types-DTkK8l-q.js';
|
|
5
7
|
|
|
8
|
+
declare function useChart(): {
|
|
9
|
+
config: ChartConfig;
|
|
10
|
+
};
|
|
11
|
+
|
|
6
12
|
type DownloadTextOptions = {
|
|
7
13
|
blobType: 'text/csv' | 'text/plain';
|
|
8
14
|
};
|
|
@@ -60,69 +66,12 @@ declare const useNotificationsStore: zustand.UseBoundStore<zustand.StoreApi<Noti
|
|
|
60
66
|
type Handler = (event: MouseEvent) => void;
|
|
61
67
|
declare function useOnClickOutside<T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent?: 'mousedown' | 'mouseup'): void;
|
|
62
68
|
|
|
63
|
-
type StorageName = 'localStorage' | 'sessionStorage';
|
|
64
|
-
type StorageEventMap = {
|
|
65
|
-
[K in StorageName]: CustomEvent;
|
|
66
|
-
};
|
|
67
|
-
declare global {
|
|
68
|
-
interface WindowEventMap extends StorageEventMap {
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Represents the options for customizing the behavior of serialization and deserialization.
|
|
73
|
-
* @template T - The type of the state to be stored in storage.
|
|
74
|
-
*/
|
|
75
|
-
type UseStorageOptions<T> = {
|
|
76
|
-
/** A function to deserialize the stored value. */
|
|
77
|
-
deserializer?: (value: string) => T;
|
|
78
|
-
/**
|
|
79
|
-
* If `true` (default), the hook will initialize reading the storage. In SSR, you should set it to `false`, returning the initial value initially.
|
|
80
|
-
* @default true
|
|
81
|
-
*/
|
|
82
|
-
initializeWithValue?: boolean;
|
|
83
|
-
/** A function to serialize the value before storing it. */
|
|
84
|
-
serializer?: (value: T) => string;
|
|
85
|
-
};
|
|
86
|
-
/**
|
|
87
|
-
* Custom hook that uses local or session storage to persist state across page reloads.
|
|
88
|
-
* @template T - The type of the state to be stored in storage.
|
|
89
|
-
* @param key - The key under which the value will be stored in storage.
|
|
90
|
-
* @param initialValue - The initial value of the state or a function that returns the initial value.
|
|
91
|
-
* @param options - Options for customizing the behavior of serialization and deserialization (optional).
|
|
92
|
-
* @returns A tuple containing the stored value and a function to set the value.
|
|
93
|
-
* @public
|
|
94
|
-
* @example
|
|
95
|
-
* ```tsx
|
|
96
|
-
* const [count, setCount] = useStorage('count', 0);
|
|
97
|
-
* // Access the `count` value and the `setCount` function to update it.
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
declare function useStorage<T>(key: string, initialValue: (() => T) | T, storageName: StorageName, options?: UseStorageOptions<T>): [T, Dispatch<SetStateAction<T>>];
|
|
101
|
-
|
|
102
69
|
/** Custom hook that uses local storage to persist state across page reloads */
|
|
103
70
|
declare function useLocalStorage<T>(key: string, initialValue: (() => T) | T, options?: UseStorageOptions<T>): [T, Dispatch<SetStateAction<T>>];
|
|
104
71
|
|
|
105
72
|
/** Custom hook that uses session storage to persist state across page reloads */
|
|
106
73
|
declare function useSessionStorage<T>(key: string, initialValue: (() => T) | T, options?: UseStorageOptions<T>): [T, Dispatch<SetStateAction<T>>];
|
|
107
74
|
|
|
108
|
-
type Theme = 'dark' | 'light';
|
|
109
|
-
type UpdateTheme = (theme: Theme) => void;
|
|
110
|
-
/** @private */
|
|
111
|
-
declare const DEFAULT_THEME: Theme;
|
|
112
|
-
/** @private */
|
|
113
|
-
declare const THEME_ATTRIBUTE = "data-mode";
|
|
114
|
-
/** @private */
|
|
115
|
-
declare const THEME_KEY = "theme";
|
|
116
|
-
/** @private */
|
|
117
|
-
declare const SYS_DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
|
|
118
|
-
/**
|
|
119
|
-
* Returns the current theme and a function to update the current theme
|
|
120
|
-
*
|
|
121
|
-
* The reason the implementation of this hook is rather convoluted is for
|
|
122
|
-
* cases where the theme is updated outside this hook
|
|
123
|
-
*/
|
|
124
|
-
declare function useTheme(): readonly [Theme, UpdateTheme];
|
|
125
|
-
|
|
126
75
|
declare function useTranslation<TNamespace extends TranslationNamespace | undefined = undefined>(namespace?: TNamespace): {
|
|
127
76
|
changeLanguage: (language: Language) => void;
|
|
128
77
|
resolvedLanguage: "fr" | "en";
|
|
@@ -135,4 +84,4 @@ type WindowSize = {
|
|
|
135
84
|
};
|
|
136
85
|
declare function useWindowSize(): WindowSize;
|
|
137
86
|
|
|
138
|
-
export {
|
|
87
|
+
export { type NotificationInterface, type NotificationsStore, UseStorageOptions, type WindowSize, useChart, useDownload, useEventCallback, useEventListener, useInterval, useIsomorphicLayoutEffect, useLocalStorage, useMediaQuery, useNotificationsStore, useOnClickOutside, useSessionStorage, useTranslation, useWindowSize };
|
package/dist/hooks.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
SYS_DARK_MEDIA_QUERY,
|
|
4
4
|
THEME_ATTRIBUTE,
|
|
5
5
|
THEME_KEY,
|
|
6
|
+
useChart,
|
|
6
7
|
useDownload,
|
|
7
8
|
useEventCallback,
|
|
8
9
|
useEventListener,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
useTheme,
|
|
18
19
|
useTranslation,
|
|
19
20
|
useWindowSize
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-TVVMHFB6.js";
|
|
21
22
|
import "./chunk-53GZFQK3.js";
|
|
22
23
|
import "./chunk-IX6RFIQL.js";
|
|
23
24
|
export {
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
SYS_DARK_MEDIA_QUERY,
|
|
26
27
|
THEME_ATTRIBUTE,
|
|
27
28
|
THEME_KEY,
|
|
29
|
+
useChart,
|
|
28
30
|
useDownload,
|
|
29
31
|
useEventCallback,
|
|
30
32
|
useEventListener,
|
package/dist/tailwind/config.cjs
CHANGED
|
@@ -23,13 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/tailwind/config.cts
|
|
26
|
-
var import_lodash_es = require("lodash-es");
|
|
27
26
|
var fs = require("fs");
|
|
28
27
|
var path = require("path");
|
|
29
28
|
var animate = require("tailwindcss-animate");
|
|
30
29
|
var containerQueries = require("@tailwindcss/container-queries");
|
|
31
|
-
var headlessui = require("@headlessui/tailwindcss");
|
|
32
30
|
var plugin = require("tailwindcss/plugin");
|
|
31
|
+
var _ = require("lodash-es");
|
|
33
32
|
var packageRoot = path.dirname(require.resolve("@douglasneuroinformatics/libui/package.json"));
|
|
34
33
|
var isDev = fs.existsSync(path.resolve(packageRoot, "src"));
|
|
35
34
|
var config = ({
|
|
@@ -54,7 +53,6 @@ var config = ({
|
|
|
54
53
|
plugins: [
|
|
55
54
|
animate,
|
|
56
55
|
containerQueries,
|
|
57
|
-
headlessui,
|
|
58
56
|
plugin((api) => {
|
|
59
57
|
api.addUtilities({
|
|
60
58
|
".scrollbar-none": {
|
|
@@ -77,7 +75,7 @@ var config = ({
|
|
|
77
75
|
xl: "3rem"
|
|
78
76
|
}
|
|
79
77
|
},
|
|
80
|
-
extend:
|
|
78
|
+
extend: _.merge(
|
|
81
79
|
{
|
|
82
80
|
animation: {
|
|
83
81
|
"accordion-down": "accordion-down 0.2s ease-out",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tailwind/config.cts"],"sourcesContent":["import type { Config } from 'tailwindcss';\nimport type { CustomThemeConfig, PluginsConfig } from 'tailwindcss/types/config';\n\nimport fs = require('node:fs');\nimport path = require('node:path');\n\nimport animate = require('tailwindcss-animate');\nimport containerQueries = require('@tailwindcss/container-queries');\nimport
|
|
1
|
+
{"version":3,"sources":["../../src/tailwind/config.cts"],"sourcesContent":["import type { Config } from 'tailwindcss';\nimport type { CustomThemeConfig, PluginsConfig } from 'tailwindcss/types/config';\n\nimport fs = require('node:fs');\nimport path = require('node:path');\n\nimport animate = require('tailwindcss-animate');\nimport containerQueries = require('@tailwindcss/container-queries');\nimport plugin = require('tailwindcss/plugin');\nimport _ = require('lodash-es');\n\nconst packageRoot = path.dirname(require.resolve('@douglasneuroinformatics/libui/package.json'));\n\nconst isDev = fs.existsSync(path.resolve(packageRoot, 'src'));\n\ntype ConfigOptions = {\n content?: string[];\n include?: string[];\n plugins?: PluginsConfig;\n root?: string;\n extend?: {\n theme?: Partial<CustomThemeConfig>;\n };\n};\n\nconst config = ({\n content = [],\n include = [],\n root = undefined,\n plugins = [],\n extend = {}\n}: ConfigOptions = {}): Config => {\n if (isDev) {\n content.push(path.resolve(packageRoot, 'src/**/*.{js,jsx,ts,tsx}'));\n } else {\n content.push(path.resolve(packageRoot, 'dist/**/*.js'));\n }\n\n for (const id of include) {\n const baseDir = path.dirname(require.resolve(`${id}/package.json`, { paths: root ? [root] : undefined }));\n content.push(path.resolve(baseDir, 'src/**/*.{js,ts,jsx,tsx}'));\n }\n\n return {\n content,\n darkMode: ['class', '[data-mode=\"dark\"]'],\n plugins: [\n animate,\n containerQueries,\n plugin((api) => {\n api.addUtilities({\n '.scrollbar-none': {\n '&::-webkit-scrollbar': {\n display: 'none'\n },\n '-ms-overflow-style': 'none',\n 'scrollbar-width': 'none'\n }\n });\n }),\n ...plugins\n ],\n theme: {\n container: {\n center: true,\n padding: {\n DEFAULT: '1rem',\n md: '2rem',\n xl: '3rem'\n }\n },\n extend: _.merge(\n {\n animation: {\n 'accordion-down': 'accordion-down 0.2s ease-out',\n 'accordion-up': 'accordion-up 0.2s ease-out',\n spinner: 'spinner-spin 1.7s infinite ease, round 1.7s infinite ease'\n },\n borderRadius: {\n lg: `var(--radius)`,\n md: `calc(var(--radius) - 2px)`,\n sm: 'calc(var(--radius) - 4px)'\n },\n colors: {\n accent: {\n DEFAULT: 'var(--accent)',\n foreground: 'var(--accent-foreground)'\n },\n background: 'var(--background)',\n border: 'var(--border)',\n card: {\n DEFAULT: 'var(--card)',\n foreground: 'var(--card-foreground)'\n },\n destructive: {\n DEFAULT: 'var(--destructive)',\n foreground: 'var(--destructive-foreground)'\n },\n foreground: 'var(--foreground)',\n input: 'var(--input)',\n muted: {\n DEFAULT: 'var(--muted)',\n foreground: 'var(--muted-foreground)'\n },\n popover: {\n DEFAULT: 'var(--popover)',\n foreground: 'var(--popover-foreground)'\n },\n primary: {\n DEFAULT: 'var(--primary)',\n foreground: 'var(--primary-foreground)'\n },\n ring: 'var(--ring)',\n secondary: {\n DEFAULT: 'var(--secondary)',\n foreground: 'var(--secondary-foreground)'\n }\n },\n keyframes: {\n 'accordion-down': {\n from: { height: '0' },\n to: { height: 'var(--radix-accordion-content-height)' }\n },\n 'accordion-up': {\n from: { height: 'var(--radix-accordion-content-height)' },\n to: { height: '0' }\n },\n round: {\n '0%': {\n transform: 'rotate(0deg)'\n },\n '100%': {\n transform: 'rotate(360deg)'\n }\n },\n 'spinner-spin': {\n '0%, 5%, 95%, 100%': {\n boxShadow: `0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em`\n },\n '10%, 59%': {\n boxShadow: `0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em`\n },\n '20%': {\n boxShadow: `0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em`\n },\n '38%': {\n boxShadow: `0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em`\n }\n }\n },\n screens: {\n '2xl': '1400px'\n }\n },\n extend.theme\n )\n }\n };\n};\n\nexport = config;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
|
|
3
|
+
type StorageName = 'localStorage' | 'sessionStorage';
|
|
4
|
+
type StorageEventMap = {
|
|
5
|
+
[K in StorageName]: CustomEvent;
|
|
6
|
+
};
|
|
7
|
+
declare global {
|
|
8
|
+
interface WindowEventMap extends StorageEventMap {
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Represents the options for customizing the behavior of serialization and deserialization.
|
|
13
|
+
* @template T - The type of the state to be stored in storage.
|
|
14
|
+
*/
|
|
15
|
+
type UseStorageOptions<T> = {
|
|
16
|
+
/** A function to deserialize the stored value. */
|
|
17
|
+
deserializer?: (value: string) => T;
|
|
18
|
+
/**
|
|
19
|
+
* If `true` (default), the hook will initialize reading the storage. In SSR, you should set it to `false`, returning the initial value initially.
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
initializeWithValue?: boolean;
|
|
23
|
+
/** A function to serialize the value before storing it. */
|
|
24
|
+
serializer?: (value: T) => string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Custom hook that uses local or session storage to persist state across page reloads.
|
|
28
|
+
* @template T - The type of the state to be stored in storage.
|
|
29
|
+
* @param key - The key under which the value will be stored in storage.
|
|
30
|
+
* @param initialValue - The initial value of the state or a function that returns the initial value.
|
|
31
|
+
* @param options - Options for customizing the behavior of serialization and deserialization (optional).
|
|
32
|
+
* @returns A tuple containing the stored value and a function to set the value.
|
|
33
|
+
* @public
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const [count, setCount] = useStorage('count', 0);
|
|
37
|
+
* // Access the `count` value and the `setCount` function to update it.
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function useStorage<T>(key: string, initialValue: (() => T) | T, storageName: StorageName, options?: UseStorageOptions<T>): [T, Dispatch<SetStateAction<T>>];
|
|
41
|
+
|
|
42
|
+
type Theme = 'dark' | 'light';
|
|
43
|
+
type UpdateTheme = (theme: Theme) => void;
|
|
44
|
+
/** @private */
|
|
45
|
+
declare const DEFAULT_THEME: Theme;
|
|
46
|
+
/** @private */
|
|
47
|
+
declare const THEME_ATTRIBUTE = "data-mode";
|
|
48
|
+
/** @private */
|
|
49
|
+
declare const THEME_KEY = "theme";
|
|
50
|
+
/** @private */
|
|
51
|
+
declare const SYS_DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
|
|
52
|
+
/**
|
|
53
|
+
* Returns the current theme and a function to update the current theme
|
|
54
|
+
*
|
|
55
|
+
* The reason the implementation of this hook is rather convoluted is for
|
|
56
|
+
* cases where the theme is updated outside this hook
|
|
57
|
+
*/
|
|
58
|
+
declare function useTheme(): readonly [Theme, UpdateTheme];
|
|
59
|
+
|
|
60
|
+
type ChartConfig = {
|
|
61
|
+
[key: string]: {
|
|
62
|
+
icon?: React.ComponentType;
|
|
63
|
+
label?: React.ReactNode;
|
|
64
|
+
} & ({
|
|
65
|
+
color?: never;
|
|
66
|
+
theme: {
|
|
67
|
+
[K in Theme]: string;
|
|
68
|
+
};
|
|
69
|
+
} | {
|
|
70
|
+
color?: string;
|
|
71
|
+
theme?: never;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export { type ChartConfig as C, DEFAULT_THEME as D, type StorageName as S, type Theme as T, type UseStorageOptions as U, SYS_DARK_MEDIA_QUERY as a, THEME_ATTRIBUTE as b, THEME_KEY as c, useTheme as d, useStorage as u };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
5
|
-
"packageManager": "pnpm@9.
|
|
4
|
+
"version": "3.6.0",
|
|
5
|
+
"packageManager": "pnpm@9.12.1",
|
|
6
6
|
"description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
|
|
7
7
|
"author": "Joshua Unrau",
|
|
8
8
|
"license": "Apache-2.0",
|
|
@@ -69,92 +69,90 @@
|
|
|
69
69
|
"zod": "^3.23.6"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@douglasneuroinformatics/libjs": "^0.
|
|
72
|
+
"@douglasneuroinformatics/libjs": "^0.8.0",
|
|
73
73
|
"@douglasneuroinformatics/libui-form-types": "^0.11.0",
|
|
74
|
-
"@
|
|
75
|
-
"@
|
|
76
|
-
"@radix-ui/react-
|
|
77
|
-
"@radix-ui/react-
|
|
78
|
-
"@radix-ui/react-
|
|
79
|
-
"@radix-ui/react-
|
|
80
|
-
"@radix-ui/react-
|
|
81
|
-
"@radix-ui/react-
|
|
82
|
-
"@radix-ui/react-
|
|
83
|
-
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
|
84
|
-
"@radix-ui/react-hover-card": "^1.1.1",
|
|
74
|
+
"@radix-ui/react-accordion": "^1.2.1",
|
|
75
|
+
"@radix-ui/react-alert-dialog": "^1.1.2",
|
|
76
|
+
"@radix-ui/react-avatar": "^1.1.1",
|
|
77
|
+
"@radix-ui/react-checkbox": "^1.1.2",
|
|
78
|
+
"@radix-ui/react-collapsible": "^1.1.1",
|
|
79
|
+
"@radix-ui/react-context-menu": "^2.2.2",
|
|
80
|
+
"@radix-ui/react-dialog": "^1.1.2",
|
|
81
|
+
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
|
82
|
+
"@radix-ui/react-hover-card": "^1.1.2",
|
|
85
83
|
"@radix-ui/react-label": "^2.1.0",
|
|
86
|
-
"@radix-ui/react-menubar": "^1.1.
|
|
87
|
-
"@radix-ui/react-popover": "^1.1.
|
|
84
|
+
"@radix-ui/react-menubar": "^1.1.2",
|
|
85
|
+
"@radix-ui/react-popover": "^1.1.2",
|
|
88
86
|
"@radix-ui/react-progress": "^1.1.0",
|
|
89
|
-
"@radix-ui/react-radio-group": "^1.2.
|
|
90
|
-
"@radix-ui/react-scroll-area": "^1.
|
|
91
|
-
"@radix-ui/react-select": "^2.1.
|
|
87
|
+
"@radix-ui/react-radio-group": "^1.2.1",
|
|
88
|
+
"@radix-ui/react-scroll-area": "^1.2.0",
|
|
89
|
+
"@radix-ui/react-select": "^2.1.2",
|
|
92
90
|
"@radix-ui/react-separator": "^1.1.0",
|
|
93
|
-
"@radix-ui/react-slider": "^1.2.
|
|
91
|
+
"@radix-ui/react-slider": "^1.2.1",
|
|
94
92
|
"@radix-ui/react-slot": "^1.1.0",
|
|
95
|
-
"@radix-ui/react-switch": "^1.1.
|
|
96
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
97
|
-
"@radix-ui/react-tooltip": "^1.1.
|
|
93
|
+
"@radix-ui/react-switch": "^1.1.1",
|
|
94
|
+
"@radix-ui/react-tabs": "^1.1.1",
|
|
95
|
+
"@radix-ui/react-tooltip": "^1.1.3",
|
|
98
96
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
99
97
|
"class-variance-authority": "^0.7.0",
|
|
100
98
|
"clsx": "^2.1.1",
|
|
101
99
|
"cmdk": "^1.0.0",
|
|
102
|
-
"framer-motion": "^11.
|
|
100
|
+
"framer-motion": "^11.11.7",
|
|
103
101
|
"lodash-es": "^4.17.21",
|
|
104
|
-
"lucide-react": "^0.
|
|
105
|
-
"react-dropzone": "^14.2.
|
|
102
|
+
"lucide-react": "^0.451.0",
|
|
103
|
+
"react-dropzone": "^14.2.9",
|
|
106
104
|
"react-error-boundary": "^4.0.13",
|
|
107
|
-
"react-resizable-panels": "^2.1.
|
|
105
|
+
"react-resizable-panels": "^2.1.4",
|
|
108
106
|
"recharts": "^2.12.7",
|
|
109
|
-
"tailwind-merge": "^2.5.
|
|
107
|
+
"tailwind-merge": "^2.5.3",
|
|
110
108
|
"tailwindcss-animate": "^1.0.7",
|
|
111
|
-
"ts-pattern": "^5.
|
|
112
|
-
"type-fest": "^4.26.
|
|
109
|
+
"ts-pattern": "^5.4.0",
|
|
110
|
+
"type-fest": "^4.26.1",
|
|
113
111
|
"vaul": "^0.9.1",
|
|
114
112
|
"zustand": "^4.5.5"
|
|
115
113
|
},
|
|
116
114
|
"devDependencies": {
|
|
117
|
-
"@commitlint/cli": "^19.
|
|
118
|
-
"@commitlint/config-conventional": "^19.
|
|
119
|
-
"@douglasneuroinformatics/eslint-config": "^5.1
|
|
115
|
+
"@commitlint/cli": "^19.5.0",
|
|
116
|
+
"@commitlint/config-conventional": "^19.5.0",
|
|
117
|
+
"@douglasneuroinformatics/eslint-config": "^5.2.1",
|
|
120
118
|
"@douglasneuroinformatics/prettier-config": "^0.0.1",
|
|
121
119
|
"@douglasneuroinformatics/semantic-release": "^0.0.1",
|
|
122
120
|
"@douglasneuroinformatics/tsconfig": "^1.0.2",
|
|
123
|
-
"@faker-js/faker": "^9.0.
|
|
124
|
-
"@storybook/addon-essentials": "^8.
|
|
125
|
-
"@storybook/addon-interactions": "^8.
|
|
126
|
-
"@storybook/addon-links": "^8.
|
|
127
|
-
"@storybook/blocks": "^8.
|
|
128
|
-
"@storybook/components": "^8.
|
|
129
|
-
"@storybook/icons": "^1.2.
|
|
130
|
-
"@storybook/manager-api": "^8.
|
|
131
|
-
"@storybook/react": "^8.
|
|
132
|
-
"@storybook/react-vite": "^8.
|
|
133
|
-
"@storybook/theming": "^8.
|
|
121
|
+
"@faker-js/faker": "^9.0.3",
|
|
122
|
+
"@storybook/addon-essentials": "^8.3.5",
|
|
123
|
+
"@storybook/addon-interactions": "^8.3.5",
|
|
124
|
+
"@storybook/addon-links": "^8.3.5",
|
|
125
|
+
"@storybook/blocks": "^8.3.5",
|
|
126
|
+
"@storybook/components": "^8.3.5",
|
|
127
|
+
"@storybook/icons": "^1.2.12",
|
|
128
|
+
"@storybook/manager-api": "^8.3.5",
|
|
129
|
+
"@storybook/react": "^8.3.5",
|
|
130
|
+
"@storybook/react-vite": "^8.3.5",
|
|
131
|
+
"@storybook/theming": "^8.3.5",
|
|
134
132
|
"@testing-library/dom": "^10.4.0",
|
|
135
133
|
"@testing-library/jest-dom": "^6.5.0",
|
|
136
134
|
"@testing-library/react": "16.0.1",
|
|
137
135
|
"@testing-library/user-event": "^14.5.2",
|
|
138
136
|
"@types/lodash-es": "^4.17.12",
|
|
139
137
|
"@types/node": "^20.14.2",
|
|
140
|
-
"@types/react": "^18.3.
|
|
138
|
+
"@types/react": "^18.3.11",
|
|
141
139
|
"@types/react-dom": "^18.3.0",
|
|
142
|
-
"@vitejs/plugin-react-swc": "^3.7.
|
|
143
|
-
"@vitest/coverage-v8": "^2.
|
|
140
|
+
"@vitejs/plugin-react-swc": "^3.7.1",
|
|
141
|
+
"@vitest/coverage-v8": "^2.1.2",
|
|
144
142
|
"autoprefixer": "^10.4.20",
|
|
145
|
-
"eslint": "^9.
|
|
143
|
+
"eslint": "^9.12.0",
|
|
146
144
|
"happy-dom": "^14.12.0",
|
|
147
|
-
"husky": "^9.1.
|
|
148
|
-
"jsdom": "25.0.
|
|
149
|
-
"postcss": "^8.4.
|
|
145
|
+
"husky": "^9.1.6",
|
|
146
|
+
"jsdom": "25.0.1",
|
|
147
|
+
"postcss": "^8.4.47",
|
|
150
148
|
"prettier": "^3.3.3",
|
|
151
|
-
"prettier-plugin-tailwindcss": "^0.6.
|
|
149
|
+
"prettier-plugin-tailwindcss": "^0.6.8",
|
|
152
150
|
"sort-json": "^2.0.1",
|
|
153
|
-
"storybook": "^8.
|
|
154
|
-
"tsup": "^8.
|
|
155
|
-
"typescript": "
|
|
156
|
-
"vite": "5.4.
|
|
157
|
-
"vitest": "^2.
|
|
151
|
+
"storybook": "^8.3.5",
|
|
152
|
+
"tsup": "^8.3.0",
|
|
153
|
+
"typescript": "5.5.x",
|
|
154
|
+
"vite": "5.4.8",
|
|
155
|
+
"vitest": "^2.1.2"
|
|
158
156
|
},
|
|
159
157
|
"commitlint": {
|
|
160
158
|
"extends": [
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts';
|
|
3
|
+
|
|
4
|
+
import { Chart } from './Chart';
|
|
5
|
+
|
|
6
|
+
import type { ChartConfig } from './types';
|
|
7
|
+
|
|
8
|
+
const chartData = [
|
|
9
|
+
{ desktop: 186, mobile: 80, month: 'January' },
|
|
10
|
+
{ desktop: 305, mobile: 200, month: 'February' },
|
|
11
|
+
{ desktop: 237, mobile: 120, month: 'March' },
|
|
12
|
+
{ desktop: 73, mobile: 190, month: 'April' },
|
|
13
|
+
{ desktop: 209, mobile: 130, month: 'May' },
|
|
14
|
+
{ desktop: 214, mobile: 140, month: 'June' }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const chartConfig = {
|
|
18
|
+
desktop: {
|
|
19
|
+
color: '#2563eb',
|
|
20
|
+
label: 'Desktop'
|
|
21
|
+
},
|
|
22
|
+
mobile: {
|
|
23
|
+
color: '#60a5fa',
|
|
24
|
+
label: 'Mobile'
|
|
25
|
+
}
|
|
26
|
+
} satisfies ChartConfig;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof Chart>;
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
component: Chart
|
|
32
|
+
} as Meta<typeof Chart>;
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
children: (
|
|
37
|
+
<BarChart accessibilityLayer data={chartData}>
|
|
38
|
+
<CartesianGrid vertical={false} />
|
|
39
|
+
<Chart.Tooltip content={<Chart.TooltipContent />} />
|
|
40
|
+
<Chart.Legend content={<Chart.LegendContent />} />
|
|
41
|
+
<XAxis
|
|
42
|
+
axisLine={false}
|
|
43
|
+
dataKey="month"
|
|
44
|
+
tickFormatter={(value: string) => value.slice(0, 3)}
|
|
45
|
+
tickLine={false}
|
|
46
|
+
tickMargin={10}
|
|
47
|
+
/>
|
|
48
|
+
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
49
|
+
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
50
|
+
</BarChart>
|
|
51
|
+
),
|
|
52
|
+
className: 'min-h-[200px] w-full',
|
|
53
|
+
config: chartConfig
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Legend, Tooltip } from 'recharts';
|
|
2
|
+
|
|
3
|
+
import { ChartContainer } from './ChartContainer';
|
|
4
|
+
import { ChartLegendContent } from './ChartLegendContent';
|
|
5
|
+
import { ChartStyle } from './ChartStyle';
|
|
6
|
+
import { ChartTooltipContent } from './ChartTooltipContent';
|
|
7
|
+
|
|
8
|
+
export const Chart = Object.assign(ChartContainer, {
|
|
9
|
+
Legend,
|
|
10
|
+
LegendContent: ChartLegendContent,
|
|
11
|
+
Style: ChartStyle,
|
|
12
|
+
Tooltip,
|
|
13
|
+
TooltipContent: ChartTooltipContent
|
|
14
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { forwardRef, useId } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ResponsiveContainer } from 'recharts';
|
|
4
|
+
|
|
5
|
+
import { ChartContext } from '@/context/ChartContext';
|
|
6
|
+
import { cn } from '@/utils';
|
|
7
|
+
|
|
8
|
+
import { Chart } from './Chart';
|
|
9
|
+
|
|
10
|
+
import type { ChartConfig } from './types';
|
|
11
|
+
|
|
12
|
+
export const ChartContainer = forwardRef<
|
|
13
|
+
HTMLDivElement,
|
|
14
|
+
{
|
|
15
|
+
children: React.ComponentProps<typeof ResponsiveContainer>['children'];
|
|
16
|
+
config: ChartConfig;
|
|
17
|
+
} & React.ComponentProps<'div'>
|
|
18
|
+
>(function ChartContainer({ children, className, config, id, ...props }, ref) {
|
|
19
|
+
const uniqueId = useId();
|
|
20
|
+
const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<ChartContext.Provider value={{ config }}>
|
|
24
|
+
<div
|
|
25
|
+
className={cn(
|
|
26
|
+
"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
data-chart={chartId}
|
|
30
|
+
ref={ref}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<Chart.Style config={config} id={chartId} />
|
|
34
|
+
<ResponsiveContainer>{children}</ResponsiveContainer>
|
|
35
|
+
</div>
|
|
36
|
+
</ChartContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { LegendProps } from 'recharts';
|
|
4
|
+
|
|
5
|
+
import { useChart } from '@/hooks/useChart';
|
|
6
|
+
import { cn } from '@/utils';
|
|
7
|
+
|
|
8
|
+
import { getPayloadConfigFromPayload } from './utils';
|
|
9
|
+
|
|
10
|
+
export const ChartLegendContent = forwardRef<
|
|
11
|
+
HTMLDivElement,
|
|
12
|
+
{
|
|
13
|
+
hideIcon?: boolean;
|
|
14
|
+
nameKey?: string;
|
|
15
|
+
} & Pick<LegendProps, 'payload' | 'verticalAlign'> &
|
|
16
|
+
React.ComponentProps<'div'>
|
|
17
|
+
>(function ChartLegendContent({ className, hideIcon = false, nameKey, payload, verticalAlign = 'bottom' }, ref) {
|
|
18
|
+
const { config } = useChart();
|
|
19
|
+
|
|
20
|
+
if (!payload?.length) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className={cn('flex items-center justify-center gap-4', verticalAlign === 'top' ? 'pb-3' : 'pt-3', className)}
|
|
27
|
+
ref={ref}
|
|
28
|
+
>
|
|
29
|
+
{payload.map((item) => {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
31
|
+
const key = `${nameKey ?? item.dataKey ?? 'value'}`;
|
|
32
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn('flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground')}
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
38
|
+
key={item.value}
|
|
39
|
+
>
|
|
40
|
+
{itemConfig?.icon && !hideIcon ? (
|
|
41
|
+
<itemConfig.icon />
|
|
42
|
+
) : (
|
|
43
|
+
<div
|
|
44
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
45
|
+
style={{
|
|
46
|
+
backgroundColor: item.color
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
{itemConfig?.label}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
})}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChartConfig } from './types';
|
|
2
|
+
|
|
3
|
+
// Format: { THEME_NAME: CSS_SELECTOR }
|
|
4
|
+
const THEMES = { dark: '.dark', light: '' } as const;
|
|
5
|
+
|
|
6
|
+
export const ChartStyle = ({ config, id }: { config: ChartConfig; id: string }) => {
|
|
7
|
+
const colorConfig = Object.entries(config).filter(([_, config]) => config.theme ?? config.color);
|
|
8
|
+
|
|
9
|
+
if (!colorConfig.length) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<style
|
|
15
|
+
dangerouslySetInnerHTML={{
|
|
16
|
+
__html: Object.entries(THEMES)
|
|
17
|
+
.map(
|
|
18
|
+
([theme, prefix]) => `
|
|
19
|
+
${prefix} [data-chart=${id}] {
|
|
20
|
+
${colorConfig
|
|
21
|
+
.map(([key, itemConfig]) => {
|
|
22
|
+
const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color;
|
|
23
|
+
return color ? ` --color-${key}: ${color};` : null;
|
|
24
|
+
})
|
|
25
|
+
.join('\n')}
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
)
|
|
29
|
+
.join('\n')
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|