@kanaries/graphic-walker 0.2.13 → 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 (99) hide show
  1. package/dist/App.d.ts +6 -3
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/components/button/base.d.ts +1 -0
  4. package/dist/components/button/defaultMini.d.ts +4 -0
  5. package/dist/components/button/primaryMini.d.ts +4 -0
  6. package/dist/components/callout.d.ts +2 -0
  7. package/dist/components/dropdownContext/index.d.ts +13 -0
  8. package/dist/components/dropdownSelect/index.d.ts +17 -0
  9. package/dist/components/modal.d.ts +1 -0
  10. package/dist/components/toolbar/components.d.ts +4 -1
  11. package/dist/components/toolbar/index.d.ts +2 -0
  12. package/dist/components/toolbar/toolbar-item.d.ts +4 -0
  13. package/dist/components/tooltip.d.ts +2 -0
  14. package/dist/dataSource/dataSelection/config.d.ts +2 -0
  15. package/dist/dataSource/index.d.ts +1 -1
  16. package/dist/fields/components.d.ts +0 -1
  17. package/dist/fields/datasetFields/dimFields.d.ts +2 -2
  18. package/dist/fields/datasetFields/meaFields.d.ts +2 -2
  19. package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
  20. package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
  21. package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
  22. package/dist/fields/obComponents/obPill.d.ts +3 -3
  23. package/dist/graphic-walker.es.js +21205 -19397
  24. package/dist/graphic-walker.es.js.map +1 -1
  25. package/dist/graphic-walker.umd.js +181 -236
  26. package/dist/graphic-walker.umd.js.map +1 -1
  27. package/dist/insightBoard/index.d.ts +1 -1
  28. package/dist/interfaces.d.ts +3 -0
  29. package/dist/renderer/index.d.ts +7 -3
  30. package/dist/store/visualSpecStore.d.ts +1 -0
  31. package/dist/utils/index.d.ts +2 -0
  32. package/dist/utils/media.d.ts +3 -0
  33. package/dist/vis/react-vega.d.ts +5 -1
  34. package/dist/vis/theme.d.ts +146 -0
  35. package/dist/visualSettings/index.d.ts +2 -1
  36. package/package.json +2 -1
  37. package/src/App.tsx +24 -16
  38. package/src/components/button/base.ts +1 -0
  39. package/src/components/button/default.tsx +6 -2
  40. package/src/components/button/defaultMini.tsx +17 -0
  41. package/src/components/button/primary.tsx +6 -2
  42. package/src/components/button/primaryMini.tsx +21 -0
  43. package/src/components/callout.tsx +9 -4
  44. package/src/components/clickMenu.tsx +1 -5
  45. package/src/components/dataTable/index.tsx +42 -52
  46. package/src/components/dataTable/pagination.tsx +4 -4
  47. package/src/components/dataTypeIcon.tsx +1 -1
  48. package/src/components/dropdownContext/index.tsx +64 -0
  49. package/src/components/dropdownSelect/index.tsx +92 -0
  50. package/src/components/modal.tsx +20 -22
  51. package/src/components/sizeSetting.tsx +2 -2
  52. package/src/components/tabs/defaultTab.tsx +4 -4
  53. package/src/components/tabs/editableTab.tsx +5 -5
  54. package/src/components/toolbar/components.tsx +10 -8
  55. package/src/components/toolbar/index.tsx +16 -4
  56. package/src/components/toolbar/toolbar-button.tsx +8 -2
  57. package/src/components/toolbar/toolbar-item.tsx +18 -9
  58. package/src/components/toolbar/toolbar-select-button.tsx +21 -8
  59. package/src/components/toolbar/toolbar-toggle-button.tsx +8 -2
  60. package/src/components/tooltip.tsx +10 -3
  61. package/src/dataSource/dataSelection/config.ts +28 -0
  62. package/src/dataSource/dataSelection/csvData.tsx +77 -32
  63. package/src/dataSource/dataSelection/gwFile.tsx +0 -8
  64. package/src/dataSource/dataSelection/index.tsx +1 -2
  65. package/src/dataSource/dataSelection/publicData.tsx +2 -3
  66. package/src/dataSource/index.tsx +80 -61
  67. package/src/fields/aestheticFields.tsx +3 -1
  68. package/src/fields/components.tsx +20 -38
  69. package/src/fields/datasetFields/dimFields.tsx +43 -35
  70. package/src/fields/datasetFields/index.tsx +3 -4
  71. package/src/fields/datasetFields/meaFields.tsx +73 -47
  72. package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
  73. package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
  74. package/src/fields/filterField/filterEditDialog.tsx +63 -98
  75. package/src/fields/filterField/filterPill.tsx +1 -1
  76. package/src/fields/filterField/slider.tsx +2 -2
  77. package/src/fields/filterField/tabs.tsx +11 -21
  78. package/src/fields/obComponents/obPill.tsx +65 -35
  79. package/src/index.css +13 -0
  80. package/src/insightBoard/index.tsx +24 -23
  81. package/src/insightBoard/mainBoard.tsx +9 -2
  82. package/src/insightBoard/radioGroupButtons.tsx +7 -0
  83. package/src/interfaces.ts +5 -1
  84. package/src/lib/inferMeta.ts +1 -1
  85. package/src/locales/en-US.json +11 -5
  86. package/src/locales/i18n.ts +7 -0
  87. package/src/locales/ja-JP.json +195 -0
  88. package/src/locales/zh-CN.json +9 -3
  89. package/src/main.tsx +1 -1
  90. package/src/renderer/index.tsx +96 -70
  91. package/src/store/visualSpecStore.ts +16 -0
  92. package/src/utils/index.ts +19 -0
  93. package/src/utils/media.ts +31 -0
  94. package/src/utils/normalization.ts +2 -1
  95. package/src/vis/react-vega.tsx +36 -5
  96. package/src/vis/theme.ts +124 -0
  97. package/src/visualSettings/index.tsx +29 -33
  98. package/dist/components/container.d.ts +0 -2
  99. package/src/components/container.tsx +0 -16
@@ -3,6 +3,8 @@ import styled from "styled-components";
3
3
  import { IMutField, IRow } from "../../interfaces";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import Pagination from "./pagination";
6
+ import { ChevronUpDownIcon } from "@heroicons/react/24/outline";
7
+ import DropdownContext from "../dropdownContext";
6
8
 
7
9
  interface DataTableProps {
8
10
  size?: number;
@@ -18,16 +20,6 @@ const Container = styled.div`
18
20
  box-sizing: content-box;
19
21
  border-collapse: collapse;
20
22
  font-size: 12px;
21
- thead {
22
- th {
23
- }
24
- th.number {
25
- border-top: 3px solid #5cdbd3;
26
- }
27
- th.text {
28
- border-top: 3px solid #69c0ff;
29
- }
30
- }
31
23
  tbody {
32
24
  td {
33
25
  }
@@ -50,17 +42,17 @@ function getHeaderType(field: IMutField): "number" | "text" {
50
42
  }
51
43
 
52
44
  function getHeaderClassNames(field: IMutField) {
53
- return field.analyticType === "dimension" ? "border-t-4 border-blue-400" : "border-t-4 border-teal-400";
45
+ return field.analyticType === "dimension" ? "border-t-4 border-blue-400" : "border-t-4 border-purple-400";
54
46
  }
55
47
 
56
48
  function getSemanticColors(field: IMutField): string {
57
49
  switch (field.semanticType) {
58
50
  case "nominal":
59
- return "bg-indigo-100 text-indigo-800";
51
+ return "bg-sky-100 text-sky-800";
60
52
  case "ordinal":
61
- return "bg-purple-100 text-purple-800";
53
+ return "bg-indigo-100 text-indigo-800";
62
54
  case "quantitative":
63
- return "bg-green-100 text-green-800";
55
+ return "bg-purple-100 text-purple-800";
64
56
  case "temporal":
65
57
  return "bg-yellow-100 text-yellow-800";
66
58
  default:
@@ -91,7 +83,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
91
83
  const to = Math.min((pageIndex + 1) * size, data.length - 1);
92
84
 
93
85
  return (
94
- <Container className="rounded border-gray-200 border">
86
+ <Container className="rounded border-gray-200 dark:border-gray-700 border">
95
87
  <Pagination
96
88
  total={data.length}
97
89
  from={from + 1}
@@ -103,74 +95,72 @@ const DataTable: React.FC<DataTableProps> = (props) => {
103
95
  setPageIndex(Math.max(0, pageIndex - 1));
104
96
  }}
105
97
  />
106
- <table className="min-w-full divide-y divide-gray-30">
107
- <thead className="bg-gray-50">
108
- <tr className="divide-x divide-gray-200">
98
+ <table className="min-w-full divide-y">
99
+ <thead className="bg-gray-50 dark:bg-gray-900">
100
+ <tr className="divide-x divide-gray-200 dark:divide-gray-600">
109
101
  {metas.map((field, fIndex) => (
110
102
  <th key={field.fid} className={""}>
111
103
  <div
112
104
  className={
113
105
  getHeaderClassNames(field) +
114
- " whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 sm:pl-6"
106
+ " whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 dark:text-gray-50 sm:pl-6"
115
107
  }
116
108
  >
117
109
  <b>{field.name || field.fid}</b>
118
110
  <div>
119
- <select
120
- className={
121
- "px-2 py font-normal mt-2 rounded-full text-xs text-white " +
122
- (field.analyticType === "dimension" ? "bg-blue-500" : "bg-teal-500")
123
- }
124
- // className="border-b border-gray-200 bg-gray-50 pl-0 mt-2 font-light"
125
- value={field.analyticType}
126
- onChange={(e) => {
111
+ <DropdownContext
112
+ options={analyticTypeList}
113
+ onSelect={(value) => {
127
114
  onMetaChange(field.fid, fIndex, {
128
- analyticType: e.target.value as IMutField["analyticType"],
115
+ analyticType: value as IMutField["analyticType"],
129
116
  });
130
117
  }}
131
118
  >
132
- {analyticTypeList.map((type) => (
133
- <option key={type.value} value={type.value}>
134
- {type.label}
135
- </option>
136
- ))}
137
- </select>
119
+ <span
120
+ className={
121
+ "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white " +
122
+ (field.analyticType === "dimension" ? "bg-blue-500" : "bg-purple-500")
123
+ }
124
+ >
125
+ {field.analyticType}
126
+ <ChevronUpDownIcon className="ml-2 w-3" />
127
+ </span>
128
+ </DropdownContext>
138
129
  </div>
139
130
  <div>
140
- <select
141
- className={
142
- "inline-block px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white " +
143
- getSemanticColors(field)
144
- }
145
- // className="border-b border-gray-200 bg-gray-50 pl-0 mt-2 font-light"
146
- value={field.semanticType}
147
- onChange={(e) => {
131
+ <DropdownContext
132
+ options={semanticTypeList}
133
+ onSelect={(value) => {
148
134
  onMetaChange(field.fid, fIndex, {
149
- semanticType: e.target.value as IMutField["semanticType"],
135
+ semanticType: value as IMutField["semanticType"],
150
136
  });
151
137
  }}
152
138
  >
153
- {semanticTypeList.map((type) => (
154
- <option key={type.value} value={type.value}>
155
- {type.label}
156
- </option>
157
- ))}
158
- </select>
139
+ <span
140
+ className={
141
+ "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs " +
142
+ getSemanticColors(field)
143
+ }
144
+ >
145
+ {field.semanticType}
146
+ <ChevronUpDownIcon className="ml-2 w-3" />
147
+ </span>
148
+ </DropdownContext>
159
149
  </div>
160
150
  </div>
161
151
  </th>
162
152
  ))}
163
153
  </tr>
164
154
  </thead>
165
- <tbody className="divide-y divide-gray-200 bg-white">
155
+ <tbody className="divide-y divide-gray-100 dark:divide-gray-600 bg-white dark:bg-zinc-900">
166
156
  {data.slice(from, to).map((row, index) => (
167
- <tr className={"divide-x divide-gray-200 " + (index % 2 ? "bg-gray-50" : "")} key={index}>
157
+ <tr className={"divide-x divide-gray-200 dark:divide-gray-600 " + (index % 2 ? "bg-gray-50 dark:bg-gray-800" : "")} key={index}>
168
158
  {metas.map((field) => (
169
159
  <td
170
160
  key={field.fid + index}
171
161
  className={
172
162
  getHeaderType(field) +
173
- " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 sm:pl-6"
163
+ " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6"
174
164
  }
175
165
  >
176
166
  {row[field.fid]}
@@ -12,11 +12,11 @@ export default function Pagination(props: PaginationProps) {
12
12
  const { t } = useTranslation();
13
13
  return (
14
14
  <nav
15
- className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
15
+ className="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-zinc-900 px-4 py-3 sm:px-6"
16
16
  aria-label="Pagination"
17
17
  >
18
18
  <div className="hidden sm:block">
19
- <p className="text-sm text-gray-700">
19
+ <p className="text-sm text-gray-800 dark:text-gray-100">
20
20
  Showing <span className="font-medium">{from}</span> to <span className="font-medium">{to}</span> of{" "}
21
21
  <span className="font-medium">{total}</span> results
22
22
  </p>
@@ -26,7 +26,7 @@ export default function Pagination(props: PaginationProps) {
26
26
  onClick={() => {
27
27
  onPrev();
28
28
  }}
29
- className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
29
+ className="relative inline-flex items-center rounded-md border border-gray-300 bg-white dark:bg-zinc-900 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
30
30
  >
31
31
  {t('actions.prev')}
32
32
  </button>
@@ -34,7 +34,7 @@ export default function Pagination(props: PaginationProps) {
34
34
  onClick={() => {
35
35
  onNext()
36
36
  }}
37
- className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
37
+ className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white dark:bg-zinc-900 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
38
38
  >
39
39
  {t('actions.next')}
40
40
  </button>
@@ -6,7 +6,7 @@ const DataTypeIcon: React.FC<{ dataType: IMutField["semanticType"]; analyticType
6
6
  props
7
7
  ) => {
8
8
  const { dataType, analyticType } = props;
9
- const color = analyticType === "dimension" ? "text-blue-500" : "text-green-500";
9
+ const color = analyticType === "dimension" ? "text-blue-500" : "text-purple-500";
10
10
  const iconClassName = `w-3 inline-block mr-0.5 ${color}`;
11
11
  switch (dataType) {
12
12
  case "quantitative":
@@ -0,0 +1,64 @@
1
+ import React, { Fragment } from "react";
2
+ import { Menu, Transition } from "@headlessui/react";
3
+
4
+ function classNames(...classes: string[]) {
5
+ return classes.filter(Boolean).join(" ");
6
+ }
7
+
8
+ export interface IDropdownContextOption {
9
+ label: string;
10
+ value: string;
11
+ disabled?: boolean;
12
+ }
13
+ interface IDropdownContextProps {
14
+ options?: IDropdownContextOption[];
15
+ disable?: boolean;
16
+ onSelect?: (value: string, index: number) => void;
17
+ }
18
+ const DropdownContext: React.FC<IDropdownContextProps> = (props) => {
19
+ const { options = [], disable } = props;
20
+
21
+ if (disable) {
22
+ return <Fragment>{props.children}</Fragment>;
23
+ }
24
+
25
+ return (
26
+ <Menu as="span" className="relative block text-left">
27
+ <Menu.Button className="block w-full text-left">{props.children}</Menu.Button>
28
+
29
+ <Transition
30
+ as={Fragment}
31
+ enter="transition ease-out duration-100"
32
+ enterFrom="transform opacity-0 scale-95"
33
+ enterTo="transform opacity-100 scale-100"
34
+ leave="transition ease-in duration-75"
35
+ leaveFrom="transform opacity-100 scale-100"
36
+ leaveTo="transform opacity-0 scale-95"
37
+ >
38
+ <Menu.Items className="absolute left-0 z-50 mt-2 w-56 origin-top-right rounded-md bg-white dark:bg-zinc-900 shadow-lg border border-gray-50 dark:border-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none">
39
+ <div className="py-1">
40
+ {options.map((option, index) => (
41
+ <Menu.Item key={option.value}>
42
+ {(p) => (
43
+ <span
44
+ className={classNames(
45
+ p.active ? "bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50" : "text-gray-700 dark:text-gray-200",
46
+ "block px-4 py-2 text-sm"
47
+ )}
48
+ onClick={() => {
49
+ props.onSelect && !props.disable && props.onSelect(option.value, index);
50
+ }}
51
+ >
52
+ {option.label}
53
+ </span>
54
+ )}
55
+ </Menu.Item>
56
+ ))}
57
+ </div>
58
+ </Menu.Items>
59
+ </Transition>
60
+ </Menu>
61
+ );
62
+ };
63
+
64
+ export default DropdownContext;
@@ -0,0 +1,92 @@
1
+ import React, { Fragment } from "react";
2
+ import { Listbox, Menu, Transition } from "@headlessui/react";
3
+ import { CheckIcon, ChevronDownIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
4
+
5
+ function classNames(...classes: string[]) {
6
+ return classes.filter(Boolean).join(" ");
7
+ }
8
+
9
+ export interface IDropdownSelectOption {
10
+ label: string;
11
+ value: string;
12
+ disabled?: boolean;
13
+ }
14
+ interface IDropdownSelectProps {
15
+ options?: IDropdownSelectOption[];
16
+ disable?: boolean;
17
+ selectedKey: string;
18
+ onSelect?: (value: string) => void;
19
+ placeholder?: string;
20
+ className?: string;
21
+ buttonClassName?: string;
22
+ }
23
+ const DropdownSelect: React.FC<IDropdownSelectProps> = (props) => {
24
+ const { options = [], disable, selectedKey, onSelect, placeholder = "Select an option", className, buttonClassName } = props;
25
+
26
+ const selectedItem = options.find((op) => op.value === selectedKey);
27
+
28
+ if (disable) {
29
+ return <Fragment>{props.children}</Fragment>;
30
+ }
31
+ let rootClassName = "relative";
32
+ let btnComputedClassName = "relative cursor-default text-xs rounded-lg bg-white dark:bg-zinc-900 px-2.5 py-1.5 pr-10 text-left border border-gray-200 dark:border-gray-700 focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 truncate"
33
+ if (buttonClassName) {
34
+ btnComputedClassName = btnComputedClassName + " " + buttonClassName;
35
+ }
36
+ if (className) {
37
+ rootClassName = rootClassName + " " + className;
38
+ }
39
+ return (
40
+ <Listbox
41
+ value={selectedKey}
42
+ onChange={(newKey) => {
43
+ onSelect && onSelect(newKey);
44
+ }}
45
+ >
46
+ <div className={rootClassName}>
47
+ <Listbox.Button className={btnComputedClassName}>
48
+ <span className="block truncate dark:text-white">{selectedItem?.label || ""}</span>
49
+ { selectedItem === undefined && <span className="block truncate text-gray-400">{placeholder}</span>}
50
+ <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
51
+ <ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
52
+ </span>
53
+ </Listbox.Button>
54
+ <Transition
55
+ as={Fragment}
56
+ leave="transition ease-in duration-100"
57
+ leaveFrom="opacity-100"
58
+ leaveTo="opacity-0"
59
+ >
60
+ <Listbox.Options className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
61
+ {options.map((op, opIndex) => (
62
+ <Listbox.Option
63
+ key={op.value}
64
+ className={({ active }) =>
65
+ `relative cursor-default select-none py-2 pl-10 pr-4 ${
66
+ active ? "bg-amber-100 text-amber-900 dark:bg-amber-800 dark:text-amber-50" : "text-gray-900 dark:text-gray-50"
67
+ }`
68
+ }
69
+ value={op.value}
70
+ >
71
+ {({ selected }) => (
72
+ <>
73
+ <span className={`block truncate ${selected ? "font-medium" : "font-normal"}`}>
74
+ {op.label}
75
+ </span>
76
+ {selected && (
77
+ <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600 dark:text-amber-400">
78
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
79
+ </span>
80
+ )}
81
+ </>
82
+ )}
83
+ </Listbox.Option>
84
+ ))}
85
+ </Listbox.Options>
86
+ </Transition>
87
+ </div>
88
+ </Listbox>
89
+ );
90
+ };
91
+
92
+ export default DropdownSelect;
@@ -1,6 +1,9 @@
1
1
  import React, { useRef } from "react";
2
2
  import styled from "styled-components";
3
3
  import { XCircleIcon } from "@heroicons/react/24/outline";
4
+ import { Fragment, useState } from "react";
5
+ import { Dialog, Transition } from "@headlessui/react";
6
+ import { ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/outline";
4
7
 
5
8
  const Background = styled.div({
6
9
  position: "fixed",
@@ -22,33 +25,25 @@ const Container = styled.div`
22
25
  }
23
26
  max-height: 800px;
24
27
  overflow: auto;
25
- > div.header {
26
- background-color: #f0f0f0;
27
- display: flex;
28
- padding: 12px;
29
- font-size: 14px;
30
- align-items: center;
31
- }
32
28
  > div.container {
33
- padding: 1em;
29
+ padding: 0.5em 1em 1em 1em;
34
30
  }
35
31
  position: fixed;
36
32
  left: 50%;
37
33
  top: 50%;
38
34
  transform: translate(-50%, -50%);
39
- background-color: #fff;
40
35
  /* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */
41
36
  border-radius: 4px;
42
37
  z-index: 999;
43
38
  `;
44
39
  interface ModalProps {
45
40
  onClose?: () => void;
41
+ show?: boolean;
46
42
  title?: string;
47
43
  }
48
44
  const Modal: React.FC<ModalProps> = (props) => {
49
- const { onClose, title } = props;
45
+ const { onClose, title, show } = props;
50
46
  const prevMouseDownTimeRef = useRef(0);
51
-
52
47
  return (
53
48
  <Background
54
49
  // This is a safer replacement of onClick handler.
@@ -56,7 +51,7 @@ const Modal: React.FC<ModalProps> = (props) => {
56
51
  // at a different element and then released when the mouse is moved on the target element.
57
52
  // This case is required to be prevented, especially disturbing when interacting
58
53
  // with a Slider component.
59
- className="border border-gray-300"
54
+ className={"border border-gray-300 dark:border-gray-600 " + (show ? "block" : "hidden")}
60
55
  onMouseDown={() => (prevMouseDownTimeRef.current = Date.now())}
61
56
  onMouseOut={() => (prevMouseDownTimeRef.current = 0)}
62
57
  onMouseUp={() => {
@@ -65,17 +60,20 @@ const Modal: React.FC<ModalProps> = (props) => {
65
60
  }
66
61
  }}
67
62
  >
68
- <Container role="dialog" className="shadow-lg" onMouseDown={(e) => e.stopPropagation()}>
69
- <div className="header relative h-9">
70
- <header className="font-bold">{title}</header>
71
- <XCircleIcon
72
- className="text-red-600 absolute right-2 w-6 cursor-pointer"
73
- role="button"
74
- tabIndex={0}
75
- aria-label="close dialog"
76
- onClick={onClose}
77
- />
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()}>
64
+ <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
65
+ <button
66
+ type="button"
67
+ className="rounded-md bg-white dark:bg-zinc-900 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
68
+ onClick={() => {
69
+ onClose?.();
70
+ }}
71
+ >
72
+ <span className="sr-only">Close</span>
73
+ <XMarkIcon className="h-6 w-6" aria-hidden="true" />
74
+ </button>
78
75
  </div>
76
+ <div className="px-6 pt-4 text-base font-semibold leading-6 text-gray-900 dark:text-gray-50">{title}</div>
79
77
  <div className="container">{props.children}</div>
80
78
  </Container>
81
79
  </Background>
@@ -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
 
@@ -19,7 +19,7 @@ export default function Default(props: DefaultProps) {
19
19
  const { tabs, selectedKey, onSelected } = props;
20
20
 
21
21
  return (
22
- <div className="border-b border-gray-200 mb-2" >
22
+ <div className="border-b border-gray-200 dark:border-gray-700 mb-2" >
23
23
  <nav className="-mb-px flex space-x-8" role="tablist" aria-label="Tabs">
24
24
  {tabs.map((tab, tabIndex) => (
25
25
  <span
@@ -31,9 +31,9 @@ export default function Default(props: DefaultProps) {
31
31
  key={tab.key}
32
32
  className={classNames(
33
33
  tab.key === selectedKey
34
- ? 'border-indigo-500 text-indigo-600'
35
- : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
36
- 'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm'
34
+ ? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-300'
35
+ : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:text-gray-400',
36
+ 'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer'
37
37
  )}
38
38
  >{tab.label}</span>
39
39
  ))}
@@ -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 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
36
- <nav className="-mb-px flex h-8 border-gray-200 border-l" 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"
@@ -64,9 +64,9 @@ export default function EditableTabs(props: EditableTabsProps) {
64
64
  key={tab.key}
65
65
  className={classNames(
66
66
  tab.key === selectedKey
67
- ? "text-black bg-gray-100"
68
- : "text-gray-500 hover:text-gray-700",
69
- "whitespace-nowrap border-gray-200 py-1 px-2 border-r border-t border-b pr-6 text-sm cursor-pointer"
67
+ ? "border rounded-t"
68
+ : "text-gray-500 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:text-gray-200 dark:hover:bg-gray-800",
69
+ "whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-pointer dark:text-white"
70
70
  )}
71
71
  />
72
72
  ))}
@@ -39,13 +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
+ border: 1px solid;
50
+ border-color: ${({ dark }) => dark ? '#4b5563' : '#e5e7eb'};
49
51
  /* box-shadow: 0px 1px 3px 1px rgba(136, 136, 136, 0.1); */
50
52
  border-radius: 2px;
51
53
  overflow: hidden;
@@ -65,7 +67,7 @@ export const ToolbarSplitter = styled.div`
65
67
  background: #bbbbbb50;
66
68
  `;
67
69
 
68
- export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
70
+ export const ToolbarItemContainerElement = styled.div<{ split: boolean; dark: boolean }>`
69
71
  display: inline-flex;
70
72
  flex-direction: row;
71
73
  user-select: none;
@@ -73,7 +75,7 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
73
75
  width: ${({ split }) => split ? 'calc(var(--height) + 10px)' : 'var(--height)'};
74
76
  height: var(--height);
75
77
  overflow: hidden;
76
- color: var(--color);
78
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color)' : 'var(--color)'};
77
79
  position: relative;
78
80
  > svg {
79
81
  flex-grow: 0;
@@ -94,9 +96,8 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
94
96
  &[aria-disabled=false] {
95
97
  cursor: pointer;
96
98
  :hover, :focus, &.open {
97
- background-image: linear-gradient(#FFFFFFCC, #FEFEFECC);
98
- --background-color: #FEFEFE;
99
- color: var(--color-hover);
99
+ --background-color: ${({ dark }) => dark ? '#202020' : '#FEFEFE'};
100
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color-hover)' : 'var(--color-hover)'};
100
101
  &.split * svg {
101
102
  pointer-events: none;
102
103
  transform: translate(-50%, -20%);
@@ -104,6 +105,7 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
104
105
  & svg {
105
106
  text-shadow: 0 0 1.5px var(--shadow-color);
106
107
  }
108
+ background-color: var(--background-color);
107
109
  }
108
110
  }
109
111
  transition: color 100ms, background-image 100ms;
@@ -1,19 +1,28 @@
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;
11
13
  --color-hover: #555;
12
14
  --blue: #282958;
13
15
  --blue-dark: #1d1e38;
16
+ --dark-mode-background-color: #1f1f1f;
17
+ --dark-mode-color: #aaa;
18
+ --dark-mode-color-hover: #ccc;
19
+ --dark-mode-blue: #282958;
20
+ --dark-mode-blue-dark: #1d1e38;
21
+ --dark-mode-preference: ${({ darkModePreference }) => darkModePreference};
14
22
  `;
15
23
 
16
24
  export interface ToolbarProps {
25
+ darkModePreference?: IDarkMode;
17
26
  items: ToolbarItemProps[];
18
27
  styles?: Partial<{
19
28
  root: CSSProperties & Record<string, string>;
@@ -24,13 +33,15 @@ export interface ToolbarProps {
24
33
  }>;
25
34
  }
26
35
 
27
- const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
36
+ const Toolbar = memo<ToolbarProps>(function Toolbar ({ darkModePreference = 'media', items, styles }) {
28
37
  const [openedKey, setOpenedKey] = useState<string | null>(null);
29
38
  const [slot, setSlot] = useState<ReactNode>(null);
30
39
 
40
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
41
+
31
42
  return (
32
- <Root style={styles?.root}>
33
- <ToolbarContainer style={styles?.container}>
43
+ <Root darkModePreference={darkModePreference} style={styles?.root}>
44
+ <ToolbarContainer dark={dark} style={styles?.container}>
34
45
  {items.map((item, i) => {
35
46
  if (item === ToolbarItemSplitter) {
36
47
  return <ToolbarSplitter key={i} />;
@@ -43,6 +54,7 @@ const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
43
54
  openedKey={openedKey}
44
55
  setOpenedKey={setOpenedKey}
45
56
  renderSlot={node => setSlot(node)}
57
+ darkModePreference={darkModePreference}
46
58
  />
47
59
  );
48
60
  })}