@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.
- package/dist/App.d.ts +6 -3
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/components/button/base.d.ts +1 -0
- package/dist/components/button/defaultMini.d.ts +4 -0
- package/dist/components/button/primaryMini.d.ts +4 -0
- package/dist/components/callout.d.ts +2 -0
- package/dist/components/dropdownContext/index.d.ts +13 -0
- package/dist/components/dropdownSelect/index.d.ts +17 -0
- package/dist/components/modal.d.ts +1 -0
- package/dist/components/toolbar/components.d.ts +4 -1
- package/dist/components/toolbar/index.d.ts +2 -0
- package/dist/components/toolbar/toolbar-item.d.ts +4 -0
- package/dist/components/tooltip.d.ts +2 -0
- package/dist/dataSource/dataSelection/config.d.ts +2 -0
- package/dist/dataSource/index.d.ts +1 -1
- package/dist/fields/components.d.ts +0 -1
- package/dist/fields/datasetFields/dimFields.d.ts +2 -2
- package/dist/fields/datasetFields/meaFields.d.ts +2 -2
- package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
- package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
- package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
- package/dist/fields/obComponents/obPill.d.ts +3 -3
- package/dist/graphic-walker.es.js +21205 -19397
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +181 -236
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/insightBoard/index.d.ts +1 -1
- package/dist/interfaces.d.ts +3 -0
- package/dist/renderer/index.d.ts +7 -3
- package/dist/store/visualSpecStore.d.ts +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/media.d.ts +3 -0
- package/dist/vis/react-vega.d.ts +5 -1
- package/dist/vis/theme.d.ts +146 -0
- package/dist/visualSettings/index.d.ts +2 -1
- package/package.json +2 -1
- package/src/App.tsx +24 -16
- package/src/components/button/base.ts +1 -0
- package/src/components/button/default.tsx +6 -2
- package/src/components/button/defaultMini.tsx +17 -0
- package/src/components/button/primary.tsx +6 -2
- package/src/components/button/primaryMini.tsx +21 -0
- package/src/components/callout.tsx +9 -4
- package/src/components/clickMenu.tsx +1 -5
- package/src/components/dataTable/index.tsx +42 -52
- package/src/components/dataTable/pagination.tsx +4 -4
- package/src/components/dataTypeIcon.tsx +1 -1
- package/src/components/dropdownContext/index.tsx +64 -0
- package/src/components/dropdownSelect/index.tsx +92 -0
- package/src/components/modal.tsx +20 -22
- package/src/components/sizeSetting.tsx +2 -2
- package/src/components/tabs/defaultTab.tsx +4 -4
- package/src/components/tabs/editableTab.tsx +5 -5
- package/src/components/toolbar/components.tsx +10 -8
- package/src/components/toolbar/index.tsx +16 -4
- package/src/components/toolbar/toolbar-button.tsx +8 -2
- package/src/components/toolbar/toolbar-item.tsx +18 -9
- package/src/components/toolbar/toolbar-select-button.tsx +21 -8
- package/src/components/toolbar/toolbar-toggle-button.tsx +8 -2
- package/src/components/tooltip.tsx +10 -3
- package/src/dataSource/dataSelection/config.ts +28 -0
- package/src/dataSource/dataSelection/csvData.tsx +77 -32
- package/src/dataSource/dataSelection/gwFile.tsx +0 -8
- package/src/dataSource/dataSelection/index.tsx +1 -2
- package/src/dataSource/dataSelection/publicData.tsx +2 -3
- package/src/dataSource/index.tsx +80 -61
- package/src/fields/aestheticFields.tsx +3 -1
- package/src/fields/components.tsx +20 -38
- package/src/fields/datasetFields/dimFields.tsx +43 -35
- package/src/fields/datasetFields/index.tsx +3 -4
- package/src/fields/datasetFields/meaFields.tsx +73 -47
- package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
- package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
- package/src/fields/filterField/filterEditDialog.tsx +63 -98
- package/src/fields/filterField/filterPill.tsx +1 -1
- package/src/fields/filterField/slider.tsx +2 -2
- package/src/fields/filterField/tabs.tsx +11 -21
- package/src/fields/obComponents/obPill.tsx +65 -35
- package/src/index.css +13 -0
- package/src/insightBoard/index.tsx +24 -23
- package/src/insightBoard/mainBoard.tsx +9 -2
- package/src/insightBoard/radioGroupButtons.tsx +7 -0
- package/src/interfaces.ts +5 -1
- package/src/lib/inferMeta.ts +1 -1
- package/src/locales/en-US.json +11 -5
- package/src/locales/i18n.ts +7 -0
- package/src/locales/ja-JP.json +195 -0
- package/src/locales/zh-CN.json +9 -3
- package/src/main.tsx +1 -1
- package/src/renderer/index.tsx +96 -70
- package/src/store/visualSpecStore.ts +16 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/media.ts +31 -0
- package/src/utils/normalization.ts +2 -1
- package/src/vis/react-vega.tsx +36 -5
- package/src/vis/theme.ts +124 -0
- package/src/visualSettings/index.tsx +29 -33
- package/dist/components/container.d.ts +0 -2
- 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-
|
|
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-
|
|
51
|
+
return "bg-sky-100 text-sky-800";
|
|
60
52
|
case "ordinal":
|
|
61
|
-
return "bg-
|
|
53
|
+
return "bg-indigo-100 text-indigo-800";
|
|
62
54
|
case "quantitative":
|
|
63
|
-
return "bg-
|
|
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
|
|
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
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
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:
|
|
115
|
+
analyticType: value as IMutField["analyticType"],
|
|
129
116
|
});
|
|
130
117
|
}}
|
|
131
118
|
>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
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:
|
|
135
|
+
semanticType: value as IMutField["semanticType"],
|
|
150
136
|
});
|
|
151
137
|
}}
|
|
152
138
|
>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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;
|
package/src/components/modal.tsx
CHANGED
|
@@ -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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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-
|
|
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
|
-
? "
|
|
68
|
-
: "text-gray-500 hover:text-gray-700",
|
|
69
|
-
"whitespace-nowrap border-gray-200 py-1 px-2
|
|
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-
|
|
98
|
-
--
|
|
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
|
})}
|