@kanaries/graphic-walker 0.2.14 → 0.2.15

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 (52) hide show
  1. package/dist/App.d.ts +3 -2
  2. package/dist/components/callout.d.ts +2 -0
  3. package/dist/components/toolbar/components.d.ts +4 -1
  4. package/dist/components/toolbar/index.d.ts +2 -0
  5. package/dist/components/toolbar/toolbar-item.d.ts +3 -0
  6. package/dist/components/tooltip.d.ts +2 -0
  7. package/dist/fields/components.d.ts +0 -1
  8. package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
  9. package/dist/graphic-walker.es.js +15103 -14997
  10. package/dist/graphic-walker.es.js.map +1 -1
  11. package/dist/graphic-walker.umd.js +134 -264
  12. package/dist/graphic-walker.umd.js.map +1 -1
  13. package/dist/interfaces.d.ts +2 -0
  14. package/dist/renderer/index.d.ts +6 -4
  15. package/dist/utils/media.d.ts +2 -1
  16. package/dist/vis/react-vega.d.ts +4 -2
  17. package/dist/vis/theme.d.ts +36 -20
  18. package/dist/visualSettings/index.d.ts +2 -1
  19. package/package.json +1 -1
  20. package/src/App.tsx +19 -14
  21. package/src/components/callout.tsx +9 -7
  22. package/src/components/clickMenu.tsx +1 -7
  23. package/src/components/modal.tsx +1 -15
  24. package/src/components/sizeSetting.tsx +2 -2
  25. package/src/components/tabs/editableTab.tsx +2 -2
  26. package/src/components/toolbar/components.tsx +8 -23
  27. package/src/components/toolbar/index.tsx +11 -4
  28. package/src/components/toolbar/toolbar-button.tsx +2 -1
  29. package/src/components/toolbar/toolbar-item.tsx +17 -12
  30. package/src/components/toolbar/toolbar-select-button.tsx +9 -13
  31. package/src/components/toolbar/toolbar-toggle-button.tsx +2 -1
  32. package/src/components/tooltip.tsx +10 -6
  33. package/src/dataSource/dataSelection/csvData.tsx +1 -1
  34. package/src/dataSource/index.tsx +2 -3
  35. package/src/fields/components.tsx +13 -50
  36. package/src/fields/datasetFields/index.tsx +3 -4
  37. package/src/fields/encodeFields/singleEncodeEditor.tsx +1 -1
  38. package/src/fields/filterField/filterEditDialog.tsx +63 -99
  39. package/src/fields/filterField/slider.tsx +1 -1
  40. package/src/insightBoard/mainBoard.tsx +9 -2
  41. package/src/interfaces.ts +4 -1
  42. package/src/locales/en-US.json +5 -2
  43. package/src/locales/i18n.ts +7 -0
  44. package/src/locales/ja-JP.json +195 -0
  45. package/src/locales/zh-CN.json +5 -2
  46. package/src/renderer/index.tsx +96 -71
  47. package/src/utils/media.ts +16 -11
  48. package/src/vis/react-vega.tsx +18 -3
  49. package/src/vis/theme.ts +23 -25
  50. package/src/visualSettings/index.tsx +12 -32
  51. package/dist/components/container.d.ts +0 -2
  52. package/src/components/container.tsx +0 -25
@@ -154,3 +154,5 @@ export declare enum ISegmentKey {
154
154
  vis = "vis",
155
155
  data = "data"
156
156
  }
157
+ export type IThemeKey = 'vega' | 'g2';
158
+ export type IDarkMode = 'media' | 'light' | 'dark';
@@ -1,6 +1,8 @@
1
- import React from 'react';
2
- import { IReactVegaHandler } from '../vis/react-vega';
1
+ import React from "react";
2
+ import { IReactVegaHandler } from "../vis/react-vega";
3
+ import { IDarkMode, IThemeKey } from "../interfaces";
3
4
  declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<Pick<{
4
- themeKey?: "vega" | "g2" | undefined;
5
- } & React.RefAttributes<IReactVegaHandler>, "key" | "themeKey"> & React.RefAttributes<IReactVegaHandler>>>;
5
+ themeKey?: IThemeKey | undefined;
6
+ dark?: IDarkMode | undefined;
7
+ } & React.RefAttributes<IReactVegaHandler>, "dark" | "key" | "themeKey"> & React.RefAttributes<IReactVegaHandler>>>;
6
8
  export default _default;
@@ -1,2 +1,3 @@
1
+ import { IDarkMode } from "../interfaces";
1
2
  export declare function currentMediaTheme(): "dark" | "light";
2
- export declare function useCurrentMediaTheme(): "dark" | "light";
3
+ export declare function useCurrentMediaTheme(mode?: IDarkMode | undefined): "dark" | "light";
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { IViewField, IRow, IStackMode } from '../interfaces';
2
+ import { IViewField, IRow, IStackMode, IDarkMode, IThemeKey } from '../interfaces';
3
3
  export interface IReactVegaHandler {
4
4
  getSVGData: () => Promise<string[]>;
5
5
  getCanvasData: () => Promise<string[]>;
@@ -28,7 +28,8 @@ interface ReactVegaProps {
28
28
  selectEncoding: SingleViewProps['selectEncoding'];
29
29
  brushEncoding: SingleViewProps['brushEncoding'];
30
30
  /** @default "vega" */
31
- themeKey?: 'vega' | 'g2';
31
+ themeKey?: IThemeKey;
32
+ dark?: IDarkMode;
32
33
  }
33
34
  interface SingleViewProps {
34
35
  x: IViewField;
@@ -50,6 +51,7 @@ interface SingleViewProps {
50
51
  asCrossFilterTrigger: boolean;
51
52
  selectEncoding: 'default' | 'none';
52
53
  brushEncoding: 'x' | 'y' | 'default' | 'none';
54
+ hideLegend?: boolean;
53
55
  }
54
56
  declare const ReactVega: React.ForwardRefExoticComponent<ReactVegaProps & React.RefAttributes<IReactVegaHandler>>;
55
57
  export default ReactVega;
@@ -3,16 +3,24 @@ export declare const VegaTheme: {
3
3
  readonly background: "transparent";
4
4
  };
5
5
  readonly dark: {
6
- readonly background: "transparent";
7
- readonly axis: {
8
- readonly gridColor: "#666";
9
- readonly domainColor: "#d1d5db";
10
- readonly tickColor: "#d1d5db";
11
- readonly labelColor: "#d1d5db";
6
+ background: string;
7
+ header: {
8
+ titleColor: string;
9
+ labelColor: string;
12
10
  };
13
- readonly legend: {
14
- readonly labelColor: "#d1d5db";
15
- readonly titleColor: "#d1d5db";
11
+ axis: {
12
+ gridColor: string;
13
+ domainColor: string;
14
+ tickColor: string;
15
+ labelColor: string;
16
+ titleColor: string;
17
+ };
18
+ legend: {
19
+ labelColor: string;
20
+ titleColor: string;
21
+ };
22
+ view: {
23
+ stroke: string;
16
24
  };
17
25
  };
18
26
  };
@@ -98,17 +106,6 @@ export declare const AntVTheme: {
98
106
  readonly arc: {
99
107
  readonly fill: "#5B8FF9";
100
108
  };
101
- readonly background: "transparent";
102
- readonly axis: {
103
- readonly gridColor: "#666";
104
- readonly domainColor: "#d1d5db";
105
- readonly tickColor: "#d1d5db";
106
- readonly labelColor: "#d1d5db";
107
- };
108
- readonly legend: {
109
- readonly labelColor: "#d1d5db";
110
- readonly titleColor: "#d1d5db";
111
- };
112
109
  readonly range: {
113
110
  readonly category: readonly ["#5B8FF9", "#61DDAA", "#65789B", "#F6BD16", "#7262FD", "#78D3F8", "#9661BC", "#F6903D", "#008685", "#F08BB4"];
114
111
  readonly diverging: readonly ["#7b3294", "#c2a5cf", "#f7f7f7", "#a6dba0", "#008837"];
@@ -120,6 +117,25 @@ export declare const AntVTheme: {
120
117
  readonly range: readonly ["#f7fbff", "#08306b"];
121
118
  };
122
119
  };
120
+ readonly background: string;
121
+ readonly header: {
122
+ titleColor: string;
123
+ labelColor: string;
124
+ };
125
+ readonly axis: {
126
+ gridColor: string;
127
+ domainColor: string;
128
+ tickColor: string;
129
+ labelColor: string;
130
+ titleColor: string;
131
+ };
132
+ readonly legend: {
133
+ labelColor: string;
134
+ titleColor: string;
135
+ };
136
+ readonly view: {
137
+ stroke: string;
138
+ };
123
139
  };
124
140
  };
125
141
  export declare const builtInThemes: {
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
+ import { IDarkMode } from '../interfaces';
2
3
  import { IReactVegaHandler } from '../vis/react-vega';
3
- export declare const LiteContainer: import("styled-components").StyledComponent<"div", any, {}, never>;
4
4
  interface IVisualSettings {
5
+ darkModePreference: IDarkMode;
5
6
  rendererHandler?: React.RefObject<IReactVegaHandler>;
6
7
  }
7
8
  declare const _default: React.FunctionComponent<IVisualSettings>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "scripts": {
5
5
  "dev:front_end": "vite --host",
6
6
  "dev": "npm run dev:front_end",
package/src/App.tsx CHANGED
@@ -4,10 +4,9 @@ import { observer } from "mobx-react-lite";
4
4
  import { LightBulbIcon } from "@heroicons/react/24/outline";
5
5
  import { toJS } from "mobx";
6
6
  import { useTranslation } from "react-i18next";
7
- import { IMutField, IRow, ISegmentKey } from "./interfaces";
7
+ import { IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey } from "./interfaces";
8
8
  import type { IReactVegaHandler } from "./vis/react-vega";
9
9
  import VisualSettings from "./visualSettings";
10
- import { Container, NestContainer } from "./components/container";
11
10
  import ClickMenu from "./components/clickMenu";
12
11
  import InsightBoard from "./insightBoard/index";
13
12
  import PosFields from "./fields/posFields";
@@ -23,6 +22,7 @@ import FilterField from "./fields/filterField";
23
22
  import { guardDataKeys } from "./utils/dataPrep";
24
23
  import SegmentNav from "./segments/segmentNav";
25
24
  import DatasetConfig from "./dataSource/datasetConfig";
25
+ import { useCurrentMediaTheme } from "./utils/media";
26
26
 
27
27
  export interface IGWProps {
28
28
  dataSource?: IRow[];
@@ -37,7 +37,8 @@ export interface IGWProps {
37
37
  */
38
38
  fieldKeyGuard?: boolean;
39
39
  /** @default "vega" */
40
- themeKey?: 'vega' | 'g2';
40
+ themeKey?: IThemeKey;
41
+ dark?: IDarkMode;
41
42
  }
42
43
 
43
44
  const App = observer<IGWProps>(function App (props) {
@@ -50,6 +51,7 @@ const App = observer<IGWProps>(function App (props) {
50
51
  hideDataSourceConfig,
51
52
  fieldKeyGuard = true,
52
53
  themeKey = 'vega',
54
+ dark = 'media',
53
55
  } = props;
54
56
  const { commonStore, vizStore } = useGlobalStore();
55
57
  const [insightReady, setInsightReady] = useState<boolean>(true);
@@ -117,14 +119,16 @@ const App = observer<IGWProps>(function App (props) {
117
119
  };
118
120
  }, [currentDataset, spec]);
119
121
 
122
+ const darkMode = useCurrentMediaTheme(dark);
123
+
120
124
  const rendererRef = useRef<IReactVegaHandler>(null);
121
125
 
122
126
  return (
123
- <div className="App font-sans dark:bg-zinc-900 dark:text-white m-0 p-0">
127
+ <div className={`${darkMode === 'dark' ? 'dark' : ''} App font-sans bg-white dark:bg-zinc-900 dark:text-white m-0 p-0`}>
124
128
  {/* <div className="grow-0">
125
129
  <PageNav />
126
130
  </div> */}
127
- <div className="">
131
+ <div className="bg-white dark:bg-zinc-900 dark:text-white">
128
132
  {!hideDataSourceConfig && <DataSourceSegment preWorkDone={insightReady} />}
129
133
  <div className="px-2 mx-2">
130
134
  <SegmentNav />
@@ -133,8 +137,8 @@ const App = observer<IGWProps>(function App (props) {
133
137
  }
134
138
  </div>
135
139
  {segmentKey === ISegmentKey.vis && (
136
- <Container style={{ marginTop: "0em", borderTop: "none" }}>
137
- <VisualSettings rendererHandler={rendererRef} />
140
+ <div style={{ marginTop: "0em", borderTop: "none" }} className="m-4 p-4 border border-gray-200 dark:border-gray-700">
141
+ <VisualSettings rendererHandler={rendererRef} darkModePreference={dark} />
138
142
  <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
139
143
  <div className="md:col-span-3 xl:col-span-1">
140
144
  <DatasetFields />
@@ -147,7 +151,8 @@ const App = observer<IGWProps>(function App (props) {
147
151
  <div>
148
152
  <PosFields />
149
153
  </div>
150
- <NestContainer
154
+ <div
155
+ className="m-0.5 p-1 border border-gray-200 dark:border-gray-700"
151
156
  style={{ minHeight: "600px", overflow: "auto" }}
152
157
  onMouseLeave={() => {
153
158
  vizEmbededMenu.show && commonStore.closeEmbededMenu();
@@ -156,12 +161,12 @@ const App = observer<IGWProps>(function App (props) {
156
161
  vizEmbededMenu.show && commonStore.closeEmbededMenu();
157
162
  }}
158
163
  >
159
- {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} themeKey={themeKey} />}
164
+ {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} />}
160
165
  <InsightBoard />
161
166
  {vizEmbededMenu.show && (
162
167
  <ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
163
168
  <div
164
- className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100"
169
+ className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
165
170
  onClick={() => {
166
171
  commonStore.closeEmbededMenu();
167
172
  commonStore.setShowInsightBoard(true);
@@ -174,15 +179,15 @@ const App = observer<IGWProps>(function App (props) {
174
179
  </div>
175
180
  </ClickMenu>
176
181
  )}
177
- </NestContainer>
182
+ </div>
178
183
  </div>
179
184
  </div>
180
- </Container>
185
+ </div>
181
186
  )}
182
187
  {segmentKey === ISegmentKey.data && (
183
- <Container style={{ marginTop: "0em", borderTop: "none" }}>
188
+ <div className="m-4 p-4 border border-gray-200 dark:border-gray-700" style={{ marginTop: "0em", borderTop: "none" }}>
184
189
  <DatasetConfig />
185
- </Container>
190
+ </div>
186
191
  )}
187
192
  </div>
188
193
  </div>
@@ -1,14 +1,17 @@
1
1
  import React, { memo, ReactNode, useContext, useEffect, useState } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import styled from "styled-components";
4
+ import type { IDarkMode } from "../interfaces";
4
5
  import { ShadowDomContext } from "..";
6
+ import { useCurrentMediaTheme } from "../utils/media";
5
7
 
6
8
  export interface CalloutProps {
7
9
  target: string;
8
10
  children: ReactNode;
11
+ darkModePreference?: IDarkMode;
9
12
  }
10
13
 
11
- const Bubble = styled.div`
14
+ const Bubble = styled.div<{ dark: boolean }>`
12
15
  border-radius: 1px;
13
16
  transform: translate(-50%, 0);
14
17
  filter: drop-shadow(0 1.6px 1px rgba(0, 0, 0, 0.24)) drop-shadow(0 -1px 0.8px rgba(0, 0, 0, 0.19));
@@ -25,15 +28,12 @@ const Bubble = styled.div`
25
28
  width: 8px;
26
29
  height: 8px;
27
30
  transform: translate(-50%, -50%) rotate(45deg);
28
- background-color: #fff;
31
+ background-color: ${({ dark }) => dark ? "#000" : "#fff"};
29
32
  border-radius: 1px;
30
- @media (prefers-color-scheme: dark) {
31
- background-color: #000;
32
- }
33
33
  }
34
34
  `;
35
35
 
36
- const Callout = memo<CalloutProps>(function Callout({ target, children }) {
36
+ const Callout = memo<CalloutProps>(function Callout({ target, children, darkModePreference = 'media' }) {
37
37
  const shadowDomMeta = useContext(ShadowDomContext);
38
38
  const { root } = shadowDomMeta;
39
39
  const [pos, setPos] = useState<[number, number] | null>(null);
@@ -48,11 +48,13 @@ const Callout = memo<CalloutProps>(function Callout({ target, children }) {
48
48
  }
49
49
  }, [target, root]);
50
50
 
51
+ const darkMode = useCurrentMediaTheme(darkModePreference);
52
+
51
53
  return (
52
54
  root &&
53
55
  pos &&
54
56
  createPortal(
55
- <Bubble role="dialog" className="fixed bg-white dark:bg-zinc-900 z-50" style={{ left: pos[0], top: pos[1] + 4 }}>
57
+ <Bubble role="dialog" dark={darkMode === 'dark'} className="fixed bg-white dark:bg-zinc-900 z-50" style={{ left: pos[0], top: pos[1] + 4 }}>
56
58
  {children}
57
59
  </Bubble>,
58
60
  root
@@ -3,16 +3,10 @@ import styled from "styled-components";
3
3
 
4
4
  const MenuContainer = styled.div`
5
5
  min-width: 100px;
6
- background-color: #fff;
7
- border: 1px solid #f0f0f0;
8
6
  position: absolute;
9
7
  z-index: 99;
10
8
  cursor: pointer;
11
9
  padding: 4px;
12
- @media (prefers-color-scheme: dark) {
13
- background-color: #000;
14
- border: 1px solid #4b5563;
15
- }
16
10
  `;
17
11
  interface ClickMenuProps {
18
12
  x: number;
@@ -22,7 +16,7 @@ interface ClickMenuProps {
22
16
  const ClickMenu: React.FC<ClickMenuProps> = (props) => {
23
17
  const { x, y, children } = props;
24
18
  return (
25
- <MenuContainer className="shadow-lg text-sm" style={{ left: x + "px", top: y + "px" }}>
19
+ <MenuContainer className="shadow-lg text-sm bg-white border border-gray-100 dark:bg-black dark:border-gray-700" style={{ left: x + "px", top: y + "px" }}>
26
20
  {children}
27
21
  </MenuContainer>
28
22
  );
@@ -25,16 +25,6 @@ const Container = styled.div`
25
25
  }
26
26
  max-height: 800px;
27
27
  overflow: auto;
28
- > div.header {
29
- background-color: #f0f0f0;
30
- display: flex;
31
- padding: 12px;
32
- font-size: 14px;
33
- align-items: center;
34
- @media (prefers-color-scheme: dark) {
35
- background-color: #000;
36
- }
37
- }
38
28
  > div.container {
39
29
  padding: 0.5em 1em 1em 1em;
40
30
  }
@@ -42,10 +32,6 @@ const Container = styled.div`
42
32
  left: 50%;
43
33
  top: 50%;
44
34
  transform: translate(-50%, -50%);
45
- background-color: #fff;
46
- @media (prefers-color-scheme: dark) {
47
- background-color: #000;
48
- }
49
35
  /* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */
50
36
  border-radius: 4px;
51
37
  z-index: 999;
@@ -74,7 +60,7 @@ const Modal: React.FC<ModalProps> = (props) => {
74
60
  }
75
61
  }}
76
62
  >
77
- <Container role="dialog" className="shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
63
+ <Container role="dialog" className="bg-white dark:bg-zinc-900 shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
78
64
  <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
79
65
  <button
80
66
  type="button"
@@ -14,7 +14,7 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
14
14
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.settings.size_setting" });
15
15
 
16
16
  return (
17
- <>
17
+ <div className="text-zinc-400">
18
18
  {children}
19
19
  <div className="mt-4 w-60">
20
20
  <input
@@ -52,7 +52,7 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
52
52
  {`${t("height")}: ${height}`}
53
53
  </output>
54
54
  </div>
55
- </>
55
+ </div>
56
56
  );
57
57
  };
58
58
 
@@ -32,8 +32,8 @@ export default function EditableTabs(props: EditableTabsProps) {
32
32
  }, [clearEditStatus]);
33
33
 
34
34
  return (
35
- <div className="border-b border-gray-200 dark:border-gray-800 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
36
- <nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-800" role="tablist" aria-label="Tabs">
35
+ <div className="border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
36
+ <nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-700" role="tablist" aria-label="Tabs">
37
37
  {tabs.map((tab, tabIndex) => (
38
38
  <span
39
39
  role="tab"
@@ -39,21 +39,15 @@ export const useHandlers = (action: () => void, disabled: boolean, triggerKeys:
39
39
  }), [allowPropagation]);
40
40
  };
41
41
 
42
- export const ToolbarContainer = styled.div`
42
+ export const ToolbarContainer = styled.div<{ dark: boolean }>`
43
43
  --height: 36px;
44
44
  --icon-size: 18px;
45
45
  width: 100%;
46
46
  height: var(--height);
47
- background-color: var(--background-color);
48
- color: var(--color);
47
+ background-color: ${({ dark }) => dark ? 'var(--background-color-dark)' : 'var(--background-color)'};
48
+ color: ${({ dark }) => dark ? 'var(--color-dark)' : 'var(--color)'};
49
49
  border: 1px solid;
50
- border-color: #e5e7eb;
51
- // dark mode
52
- @media (prefers-color-scheme: dark) {
53
- background-color: var(--background-color-dark);
54
- color: var(--color-dark);
55
- border-color: #4b5563;
56
- }
50
+ border-color: ${({ dark }) => dark ? '#4b5563' : '#e5e7eb'};
57
51
  /* box-shadow: 0px 1px 3px 1px rgba(136, 136, 136, 0.1); */
58
52
  border-radius: 2px;
59
53
  overflow: hidden;
@@ -73,7 +67,7 @@ export const ToolbarSplitter = styled.div`
73
67
  background: #bbbbbb50;
74
68
  `;
75
69
 
76
- export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
70
+ export const ToolbarItemContainerElement = styled.div<{ split: boolean; dark: boolean }>`
77
71
  display: inline-flex;
78
72
  flex-direction: row;
79
73
  user-select: none;
@@ -81,7 +75,7 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
81
75
  width: ${({ split }) => split ? 'calc(var(--height) + 10px)' : 'var(--height)'};
82
76
  height: var(--height);
83
77
  overflow: hidden;
84
- color: var(--color);
78
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color)' : 'var(--color)'};
85
79
  position: relative;
86
80
  > svg {
87
81
  flex-grow: 0;
@@ -102,8 +96,8 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
102
96
  &[aria-disabled=false] {
103
97
  cursor: pointer;
104
98
  :hover, :focus, &.open {
105
- --background-color: #FEFEFE;
106
- color: var(--color-hover);
99
+ --background-color: ${({ dark }) => dark ? '#202020' : '#FEFEFE'};
100
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color-hover)' : 'var(--color-hover)'};
107
101
  &.split * svg {
108
102
  pointer-events: none;
109
103
  transform: translate(-50%, -20%);
@@ -114,14 +108,5 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
114
108
  background-color: var(--background-color);
115
109
  }
116
110
  }
117
- @media (prefers-color-scheme: dark) {
118
- color: var(--dark-mode-color);
119
- &[aria-disabled=false] {
120
- :hover, :focus, &.open {
121
- --background-color: #202020;
122
- color: var(--dark-mode-color-hover);
123
- }
124
- }
125
- }
126
111
  transition: color 100ms, background-image 100ms;
127
112
  `;
@@ -1,10 +1,12 @@
1
1
  import React, { CSSProperties, memo, ReactNode, useState } from "react";
2
2
  import styled from "styled-components";
3
+ import type { IDarkMode } from "../../interfaces";
4
+ import { useCurrentMediaTheme } from "../../utils/media";
3
5
  import { ToolbarContainer, ToolbarSplitter } from "./components";
4
6
  import ToolbarItem, { ToolbarItemProps, ToolbarItemSplitter } from "./toolbar-item";
5
7
 
6
8
 
7
- const Root = styled.div`
9
+ const Root = styled.div<{ darkModePreference: IDarkMode }>`
8
10
  width: 100%;
9
11
  --background-color: #f7f7f7;
10
12
  --color: #777;
@@ -16,9 +18,11 @@ const Root = styled.div`
16
18
  --dark-mode-color-hover: #ccc;
17
19
  --dark-mode-blue: #282958;
18
20
  --dark-mode-blue-dark: #1d1e38;
21
+ --dark-mode-preference: ${({ darkModePreference }) => darkModePreference};
19
22
  `;
20
23
 
21
24
  export interface ToolbarProps {
25
+ darkModePreference?: IDarkMode;
22
26
  items: ToolbarItemProps[];
23
27
  styles?: Partial<{
24
28
  root: CSSProperties & Record<string, string>;
@@ -29,13 +33,15 @@ export interface ToolbarProps {
29
33
  }>;
30
34
  }
31
35
 
32
- const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
36
+ const Toolbar = memo<ToolbarProps>(function Toolbar ({ darkModePreference = 'media', items, styles }) {
33
37
  const [openedKey, setOpenedKey] = useState<string | null>(null);
34
38
  const [slot, setSlot] = useState<ReactNode>(null);
35
39
 
40
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
41
+
36
42
  return (
37
- <Root style={styles?.root}>
38
- <ToolbarContainer style={styles?.container}>
43
+ <Root darkModePreference={darkModePreference} style={styles?.root}>
44
+ <ToolbarContainer dark={dark} style={styles?.container}>
39
45
  {items.map((item, i) => {
40
46
  if (item === ToolbarItemSplitter) {
41
47
  return <ToolbarSplitter key={i} />;
@@ -48,6 +54,7 @@ const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
48
54
  openedKey={openedKey}
49
55
  setOpenedKey={setOpenedKey}
50
56
  renderSlot={node => setSlot(node)}
57
+ darkModePreference={darkModePreference}
51
58
  />
52
59
  );
53
60
  })}
@@ -8,7 +8,7 @@ export interface ToolbarButtonItem extends IToolbarItem {
8
8
  }
9
9
 
10
10
  const ToolbarButton = memo<IToolbarProps<ToolbarButtonItem>>(function ToolbarButton(props) {
11
- const { item, styles } = props;
11
+ const { item, styles, darkModePreference } = props;
12
12
  const { icon: Icon, label, disabled, onClick } = item;
13
13
  const handlers = useHandlers(() => onClick?.(), disabled ?? false);
14
14
 
@@ -22,6 +22,7 @@ const ToolbarButton = memo<IToolbarProps<ToolbarButtonItem>>(function ToolbarBut
22
22
  <ToolbarItemContainer
23
23
  props={props}
24
24
  handlers={onClick ? handlers : null}
25
+ darkModePreference={darkModePreference}
25
26
  >
26
27
  <Icon style={mergedIconStyles} />
27
28
  </ToolbarItemContainer>
@@ -8,6 +8,7 @@ import { ToolbarContainer, ToolbarItemContainerElement, ToolbarSplitter, useHand
8
8
  import Toolbar, { ToolbarProps } from ".";
9
9
  import Tooltip from "../tooltip";
10
10
  import Callout from "../callout";
11
+ import { useCurrentMediaTheme } from "../../utils/media";
11
12
 
12
13
 
13
14
  const ToolbarSplit = styled.div<{ open: boolean }>`
@@ -34,10 +35,7 @@ const ToolbarSplit = styled.div<{ open: boolean }>`
34
35
  const FormContainer = styled(ToolbarContainer)`
35
36
  width: max-content;
36
37
  height: max-content;
37
- background-color: #fff;
38
- @media (prefers-color-scheme: dark) {
39
- background-color: #000;
40
- }
38
+ background-color: ${({ dark }) => dark ? '#000' : '#fff'};
41
39
  `;
42
40
 
43
41
  export interface IToolbarItem {
@@ -65,6 +63,7 @@ export type ToolbarItemProps = (
65
63
 
66
64
  export interface IToolbarProps<P extends Exclude<ToolbarItemProps, typeof ToolbarItemSplitter> = Exclude<ToolbarItemProps, typeof ToolbarItemSplitter>> {
67
65
  item: P;
66
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
68
67
  styles?: ToolbarProps['styles'];
69
68
  openedKey: string | null;
70
69
  setOpenedKey: (key: string | null) => void;
@@ -74,6 +73,7 @@ export interface IToolbarProps<P extends Exclude<ToolbarItemProps, typeof Toolba
74
73
  let idFlag = 0;
75
74
 
76
75
  export const ToolbarItemContainer = memo<{
76
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
77
77
  props: IToolbarProps;
78
78
  handlers: ReturnType<typeof useHandlers> | null;
79
79
  children: unknown;
@@ -84,6 +84,7 @@ export const ToolbarItemContainer = memo<{
84
84
  styles, openedKey, setOpenedKey, renderSlot,
85
85
  },
86
86
  handlers,
87
+ darkModePreference,
87
88
  children,
88
89
  ...props
89
90
  }
@@ -130,15 +131,18 @@ export const ToolbarItemContainer = memo<{
130
131
 
131
132
  useEffect(() => {
132
133
  if (opened && menu) {
133
- renderSlot(<Toolbar {...menu} />);
134
+ renderSlot(<Toolbar {...menu} darkModePreference={darkModePreference} />);
134
135
  return () => renderSlot(null);
135
136
  }
136
137
  }, [opened, menu, renderSlot]);
137
138
 
139
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
140
+
138
141
  return (
139
142
  <>
140
- <Tooltip content={label}>
143
+ <Tooltip content={label} darkModePreference={darkModePreference}>
141
144
  <ToolbarItemContainerElement
145
+ dark={dark}
142
146
  role="button" tabIndex={disabled ? undefined : 0} aria-label={label} aria-disabled={disabled ?? false}
143
147
  split={Boolean(form || menu)}
144
148
  style={styles?.item}
@@ -190,8 +194,8 @@ export const ToolbarItemContainer = memo<{
190
194
  </ToolbarItemContainerElement>
191
195
  </Tooltip>
192
196
  {opened && form && (
193
- <Callout target={`#${id}`}>
194
- <FormContainer onMouseDown={e => e.stopPropagation()}>
197
+ <Callout target={`#${id}`} darkModePreference={darkModePreference}>
198
+ <FormContainer dark={dark} onMouseDown={e => e.stopPropagation()}>
195
199
  {form}
196
200
  </FormContainer>
197
201
  </Callout>
@@ -202,20 +206,21 @@ export const ToolbarItemContainer = memo<{
202
206
 
203
207
  const ToolbarItem = memo<{
204
208
  item: ToolbarItemProps;
209
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
205
210
  styles?: ToolbarProps['styles'];
206
211
  openedKey: string | null;
207
212
  setOpenedKey: (key: string | null) => void;
208
213
  renderSlot: (node: ReactNode) => void;
209
- }>(function ToolbarItem ({ item, styles, openedKey, setOpenedKey, renderSlot }) {
214
+ }>(function ToolbarItem ({ item, styles, openedKey, setOpenedKey, renderSlot, darkModePreference }) {
210
215
  if (item === ToolbarItemSplitter) {
211
216
  return <ToolbarSplitter />;
212
217
  }
213
218
  if ('checked' in item) {
214
- return <ToolbarToggleButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
219
+ return <ToolbarToggleButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
215
220
  } else if ('options' in item) {
216
- return <ToolbarSelectButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
221
+ return <ToolbarSelectButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
217
222
  }
218
- return <ToolbarButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
223
+ return <ToolbarButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
219
224
  });
220
225
 
221
226