@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.
Files changed (33) hide show
  1. package/dist/{chunk-AFNJZUOE.js → chunk-TVVMHFB6.js} +19 -1
  2. package/dist/chunk-TVVMHFB6.js.map +1 -0
  3. package/dist/components.d.ts +53 -2
  4. package/dist/components.js +1037 -724
  5. package/dist/components.js.map +1 -1
  6. package/dist/douglasneuroinformatics-libui-3.6.0.tgz +0 -0
  7. package/dist/hooks.d.ts +7 -58
  8. package/dist/hooks.js +3 -1
  9. package/dist/tailwind/config.cjs +2 -4
  10. package/dist/tailwind/config.cjs.map +1 -1
  11. package/dist/types-9zYgx7C8.d.ts +75 -0
  12. package/package.json +55 -57
  13. package/src/components/Chart/Chart.stories.tsx +55 -0
  14. package/src/components/Chart/Chart.tsx +14 -0
  15. package/src/components/Chart/ChartContainer.tsx +38 -0
  16. package/src/components/Chart/ChartLegendContent.tsx +56 -0
  17. package/src/components/Chart/ChartStyle.tsx +33 -0
  18. package/src/components/Chart/ChartTooltipContent.tsx +139 -0
  19. package/src/components/Chart/index.ts +2 -0
  20. package/src/components/Chart/types.ts +19 -0
  21. package/src/components/Chart/utils.ts +26 -0
  22. package/src/components/Form/Form.stories.tsx +35 -19
  23. package/src/components/Form/Form.tsx +8 -3
  24. package/src/components/Form/NumberField/NumberFieldInput.tsx +29 -17
  25. package/src/components/NotificationHub/NotificationIcon.tsx +64 -6
  26. package/src/components/index.ts +1 -0
  27. package/src/context/ChartContext.tsx +9 -0
  28. package/src/hooks/index.ts +1 -0
  29. package/src/hooks/useChart/index.ts +1 -0
  30. package/src/hooks/useChart/useChart.ts +11 -0
  31. package/src/tailwind/config.cts +2 -4
  32. package/dist/chunk-AFNJZUOE.js.map +0 -1
  33. package/dist/douglasneuroinformatics-libui-3.5.2.tgz +0 -0
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 { DEFAULT_THEME, type NotificationInterface, type NotificationsStore, SYS_DARK_MEDIA_QUERY, type StorageName, THEME_ATTRIBUTE, THEME_KEY, type Theme, type UseStorageOptions, type WindowSize, useDownload, useEventCallback, useEventListener, useInterval, useIsomorphicLayoutEffect, useLocalStorage, useMediaQuery, useNotificationsStore, useOnClickOutside, useSessionStorage, useStorage, useTheme, useTranslation, useWindowSize };
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-AFNJZUOE.js";
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,
@@ -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: (0, import_lodash_es.merge)(
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 headlessui = require('@headlessui/tailwindcss');\nimport plugin = require('tailwindcss/plugin');\nimport { merge } from '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 headlessui,\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":[]}
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.2",
5
- "packageManager": "pnpm@9.11.0",
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.7.0",
72
+ "@douglasneuroinformatics/libjs": "^0.8.0",
73
73
  "@douglasneuroinformatics/libui-form-types": "^0.11.0",
74
- "@headlessui/tailwindcss": "^0.2.1",
75
- "@heroicons/react": "^2.1.5",
76
- "@radix-ui/react-accordion": "^1.2.0",
77
- "@radix-ui/react-alert-dialog": "^1.1.1",
78
- "@radix-ui/react-avatar": "^1.1.0",
79
- "@radix-ui/react-checkbox": "^1.1.1",
80
- "@radix-ui/react-collapsible": "^1.1.0",
81
- "@radix-ui/react-context-menu": "^2.2.1",
82
- "@radix-ui/react-dialog": "^1.1.1",
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.1",
87
- "@radix-ui/react-popover": "^1.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.0",
90
- "@radix-ui/react-scroll-area": "^1.1.0",
91
- "@radix-ui/react-select": "^2.1.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.0",
91
+ "@radix-ui/react-slider": "^1.2.1",
94
92
  "@radix-ui/react-slot": "^1.1.0",
95
- "@radix-ui/react-switch": "^1.1.0",
96
- "@radix-ui/react-tabs": "^1.1.0",
97
- "@radix-ui/react-tooltip": "^1.1.2",
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.5.2",
100
+ "framer-motion": "^11.11.7",
103
101
  "lodash-es": "^4.17.21",
104
- "lucide-react": "^0.438.0",
105
- "react-dropzone": "^14.2.3",
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.2",
105
+ "react-resizable-panels": "^2.1.4",
108
106
  "recharts": "^2.12.7",
109
- "tailwind-merge": "^2.5.2",
107
+ "tailwind-merge": "^2.5.3",
110
108
  "tailwindcss-animate": "^1.0.7",
111
- "ts-pattern": "^5.3.1",
112
- "type-fest": "^4.26.0",
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.4.1",
118
- "@commitlint/config-conventional": "^19.4.1",
119
- "@douglasneuroinformatics/eslint-config": "^5.1.7",
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.1",
124
- "@storybook/addon-essentials": "^8.2.9",
125
- "@storybook/addon-interactions": "^8.2.9",
126
- "@storybook/addon-links": "^8.2.9",
127
- "@storybook/blocks": "^8.2.9",
128
- "@storybook/components": "^8.2.9",
129
- "@storybook/icons": "^1.2.10",
130
- "@storybook/manager-api": "^8.2.9",
131
- "@storybook/react": "^8.2.9",
132
- "@storybook/react-vite": "^8.2.9",
133
- "@storybook/theming": "^8.2.9",
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.5",
138
+ "@types/react": "^18.3.11",
141
139
  "@types/react-dom": "^18.3.0",
142
- "@vitejs/plugin-react-swc": "^3.7.0",
143
- "@vitest/coverage-v8": "^2.0.5",
140
+ "@vitejs/plugin-react-swc": "^3.7.1",
141
+ "@vitest/coverage-v8": "^2.1.2",
144
142
  "autoprefixer": "^10.4.20",
145
- "eslint": "^9.9.1",
143
+ "eslint": "^9.12.0",
146
144
  "happy-dom": "^14.12.0",
147
- "husky": "^9.1.5",
148
- "jsdom": "25.0.0",
149
- "postcss": "^8.4.45",
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.6",
149
+ "prettier-plugin-tailwindcss": "^0.6.8",
152
150
  "sort-json": "^2.0.1",
153
- "storybook": "^8.2.9",
154
- "tsup": "^8.2.4",
155
- "typescript": "~5.5.4",
156
- "vite": "5.4.3",
157
- "vitest": "^2.0.5"
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
+ };