torch-glare 2.1.2 → 2.1.3
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/apps/lib/components/Avatar.tsx +1 -1
- package/apps/lib/components/Card.tsx +68 -54
- package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +56 -45
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +130 -28
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +32 -2
- package/apps/lib/components/DataViews/FilterPanel.tsx +148 -3
- package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
- package/apps/lib/components/DataViews/InboxView.tsx +263 -282
- package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
- package/apps/lib/components/DataViews/KanbanView.tsx +264 -153
- package/apps/lib/components/DataViews/PanelControls.tsx +10 -41
- package/apps/lib/components/DataViews/TreeView.tsx +220 -191
- package/apps/lib/components/DataViews/index.ts +6 -0
- package/apps/lib/components/DataViews/types.ts +30 -1
- package/apps/lib/components/Radio.tsx +18 -21
- package/apps/lib/components/Switch.tsx +3 -1
- package/apps/lib/components/Table.tsx +1 -1
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +160 -137
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +221 -93
- package/apps/lib/components/TreeFolder/types.ts +9 -0
- package/apps/lib/layouts/DataViewCard.tsx +76 -0
- package/dist/src/shared/copyComponentsRecursively.js +9 -1
- package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
- package/docs/components/data-views-config-panel.md +204 -0
- package/docs/components/data-views-layout.md +270 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
useRef,
|
|
9
9
|
useState,
|
|
10
|
+
type ReactNode,
|
|
10
11
|
} from "react";
|
|
11
12
|
import { List, LayoutGrid, Inbox as InboxIcon, Network } from "lucide-react";
|
|
12
13
|
import type {
|
|
@@ -41,6 +42,8 @@ export type DataViewsLayoutProps = {
|
|
|
41
42
|
inboxConfig?: InboxConfig;
|
|
42
43
|
treeConfig?: TreeConfig;
|
|
43
44
|
kanbanGroupBy?: string;
|
|
45
|
+
kanbanTitleField?: string;
|
|
46
|
+
onKanbanColumnAction?: (columnId: string) => void;
|
|
44
47
|
|
|
45
48
|
views?: ViewVisibility;
|
|
46
49
|
|
|
@@ -57,6 +60,14 @@ export type DataViewsLayoutProps = {
|
|
|
57
60
|
onAddNew?: () => void;
|
|
58
61
|
addNewLabel?: string;
|
|
59
62
|
|
|
63
|
+
inboxItemHref?: (item: DynamicRecord, id: any) => string;
|
|
64
|
+
inboxSelectedId?: any;
|
|
65
|
+
inboxRenderDetail?: (item: DynamicRecord | null) => ReactNode;
|
|
66
|
+
|
|
67
|
+
searchValue?: string;
|
|
68
|
+
onSearchChange?: (value: string) => void;
|
|
69
|
+
searchPlaceholder?: string;
|
|
70
|
+
|
|
60
71
|
className?: string;
|
|
61
72
|
theme?: Themes;
|
|
62
73
|
};
|
|
@@ -83,6 +94,8 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
83
94
|
inboxConfig,
|
|
84
95
|
treeConfig,
|
|
85
96
|
kanbanGroupBy,
|
|
97
|
+
kanbanTitleField,
|
|
98
|
+
onKanbanColumnAction,
|
|
86
99
|
views,
|
|
87
100
|
columns,
|
|
88
101
|
filters,
|
|
@@ -94,6 +107,12 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
94
107
|
showTitle = true,
|
|
95
108
|
onAddNew,
|
|
96
109
|
addNewLabel,
|
|
110
|
+
inboxItemHref,
|
|
111
|
+
inboxSelectedId,
|
|
112
|
+
inboxRenderDetail,
|
|
113
|
+
searchValue,
|
|
114
|
+
onSearchChange,
|
|
115
|
+
searchPlaceholder,
|
|
97
116
|
className,
|
|
98
117
|
theme,
|
|
99
118
|
} = props;
|
|
@@ -180,7 +199,10 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
180
199
|
className={cn(
|
|
181
200
|
// Shell is always black (matches Figma): the dark header and config
|
|
182
201
|
// rail sit on it; the Master Container is the white surface inside.
|
|
183
|
-
|
|
202
|
+
// `overflow-hidden` traps any child overflow — without it, a tall
|
|
203
|
+
// config-panel body escapes and creates a page-level scrollbar in
|
|
204
|
+
// addition to the panel's own.
|
|
205
|
+
"flex h-screen gap-2 overflow-hidden bg-black text-content-presentation-global-primary",
|
|
184
206
|
className,
|
|
185
207
|
)}
|
|
186
208
|
>
|
|
@@ -197,13 +219,16 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
197
219
|
onToggleSettings={togglePanel}
|
|
198
220
|
onAddNew={onAddNew}
|
|
199
221
|
addNewLabel={addNewLabel}
|
|
222
|
+
searchValue={searchValue}
|
|
223
|
+
onSearchChange={onSearchChange}
|
|
224
|
+
searchPlaceholder={searchPlaceholder}
|
|
200
225
|
/>
|
|
201
226
|
)}
|
|
202
227
|
|
|
203
228
|
<main className="flex min-h-0 flex-1 overflow-hidden ">
|
|
204
229
|
{/* Master Container — white card, 16px radius, #D4D4D4 hairline
|
|
205
230
|
border. Fixed surface (matches header chrome). */}
|
|
206
|
-
<div className="flex flex-1 overflow-hidden rounded-[16px]
|
|
231
|
+
<div className="flex flex-1 overflow-hidden rounded-[16px]">
|
|
207
232
|
{/* Clip the scrollable surface to the parent radius MINUS the 1px
|
|
208
233
|
border (16 − 1 = 15px). Using the full 16px here let the
|
|
209
234
|
opaque view background sit flush with the parent's outer edge
|
|
@@ -234,6 +259,8 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
234
259
|
config={effectiveConfig}
|
|
235
260
|
onDataUpdate={onDataUpdate}
|
|
236
261
|
groupByField={effectiveKanbanGroupBy}
|
|
262
|
+
titleField={kanbanTitleField}
|
|
263
|
+
onColumnAction={onKanbanColumnAction}
|
|
237
264
|
/>
|
|
238
265
|
)}
|
|
239
266
|
{currentView === "inbox" && enabledViews.inbox && (
|
|
@@ -248,6 +275,9 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
|
248
275
|
filterState={filterState}
|
|
249
276
|
onFilterChange={setFilterState}
|
|
250
277
|
showFilters={false}
|
|
278
|
+
itemHref={inboxItemHref}
|
|
279
|
+
selectedItemId={inboxSelectedId}
|
|
280
|
+
renderDetail={inboxRenderDetail}
|
|
251
281
|
/>
|
|
252
282
|
)}
|
|
253
283
|
{currentView === "tree" && enabledViews.tree && (
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
import { Fragment } from "react"
|
|
4
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
|
3
5
|
import { Button } from "../Button"
|
|
4
6
|
import { Badge } from "../Badge"
|
|
5
7
|
import { X } from "lucide-react"
|
|
6
8
|
import { Checkbox } from "../Checkbox"
|
|
7
9
|
import { Divider } from "../Divider"
|
|
8
10
|
import { Label } from "../Label"
|
|
11
|
+
import { DataViewRadio } from "./DataViewRadio"
|
|
12
|
+
import { cn } from "../../utils/cn"
|
|
9
13
|
import type {
|
|
10
14
|
DynamicRecord,
|
|
11
15
|
DynamicFilterConfig,
|
|
@@ -37,6 +41,13 @@ type FilterPanelProps = {
|
|
|
37
41
|
onFilterChange: (path: string, value: FilterValue) => void
|
|
38
42
|
onClearAll: () => void
|
|
39
43
|
filterConfig?: DynamicFilterConfig[]
|
|
44
|
+
/**
|
|
45
|
+
* "default": standalone left-rail style (border, padding, light surface).
|
|
46
|
+
* "panel": matches the Config tab inside DataViewsConfigPanel — no outer
|
|
47
|
+
* chrome, white section headers, categorical options inside a #1C1D1F
|
|
48
|
+
* rounded container, sections separated by #2C2D2E dividers.
|
|
49
|
+
*/
|
|
50
|
+
variant?: "default" | "panel"
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
const NUMERIC_TYPES: FieldType[] = [
|
|
@@ -172,6 +183,7 @@ export function FilterPanel({
|
|
|
172
183
|
onFilterChange,
|
|
173
184
|
onClearAll,
|
|
174
185
|
filterConfig,
|
|
186
|
+
variant = "default",
|
|
175
187
|
}: FilterPanelProps) {
|
|
176
188
|
const entries = buildFilterableEntries(data, fields, filterConfig)
|
|
177
189
|
|
|
@@ -198,6 +210,57 @@ export function FilterPanel({
|
|
|
198
210
|
|
|
199
211
|
const countBadge = resolveBadgeVariant("gray")
|
|
200
212
|
|
|
213
|
+
if (variant === "panel") {
|
|
214
|
+
return (
|
|
215
|
+
<div className="flex flex-col gap-6 px-3 py-4">
|
|
216
|
+
<div className="flex items-center justify-between">
|
|
217
|
+
<div className="flex items-center gap-2">
|
|
218
|
+
<h3 className="text-[18px] font-[510] leading-[1.32] tracking-[-0.01em] text-white">
|
|
219
|
+
Filters
|
|
220
|
+
</h3>
|
|
221
|
+
{totalFilters > 0 && (
|
|
222
|
+
<Badge
|
|
223
|
+
{...countBadge}
|
|
224
|
+
label={String(totalFilters)}
|
|
225
|
+
className="h-5 min-w-[20px] rounded-full p-0 text-xs"
|
|
226
|
+
size="XS"
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
{totalFilters > 0 && (
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
onClick={onClearAll}
|
|
234
|
+
className="flex items-center gap-1 rounded-[4px] bg-white/[0.15] px-1.5 py-0.5 text-[12px] font-[510] text-white transition-colors hover:bg-white/25"
|
|
235
|
+
>
|
|
236
|
+
<X className="h-3 w-3" />
|
|
237
|
+
Clear
|
|
238
|
+
</button>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{entries.map((entry, index) => (
|
|
243
|
+
<Fragment key={entry.path}>
|
|
244
|
+
{index > 0 && <div className="h-px w-full bg-[#2C2D2E]" />}
|
|
245
|
+
<div className="space-y-3">
|
|
246
|
+
<h3 className="text-[18px] font-[510] leading-[1.32] tracking-[-0.01em] text-white">
|
|
247
|
+
{entry.label}
|
|
248
|
+
</h3>
|
|
249
|
+
<FilterBody
|
|
250
|
+
entry={entry}
|
|
251
|
+
data={data}
|
|
252
|
+
value={filters[entry.path]}
|
|
253
|
+
onCategoricalToggle={(opt) => toggleCategorical(entry.path, opt)}
|
|
254
|
+
onSetFilter={(v) => setFilter(entry.path, v)}
|
|
255
|
+
variant="panel"
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
</Fragment>
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
201
264
|
return (
|
|
202
265
|
<div className="w-64 border-r border-border-presentation-global-primary bg-background-presentation-body-overlay-primary p-4 overflow-y-auto">
|
|
203
266
|
<div className="flex items-center justify-between mb-4">
|
|
@@ -209,7 +272,7 @@ export function FilterPanel({
|
|
|
209
272
|
label={String(totalFilters)}
|
|
210
273
|
className="h-5 min-w-[20px] rounded-full p-0 text-xs"
|
|
211
274
|
size="XS"
|
|
212
|
-
|
|
275
|
+
|
|
213
276
|
/>
|
|
214
277
|
)}
|
|
215
278
|
</div>
|
|
@@ -248,12 +311,14 @@ function FilterBody({
|
|
|
248
311
|
value,
|
|
249
312
|
onCategoricalToggle,
|
|
250
313
|
onSetFilter,
|
|
314
|
+
variant = "default",
|
|
251
315
|
}: {
|
|
252
316
|
entry: Entry
|
|
253
317
|
data: DynamicRecord[]
|
|
254
318
|
value: FilterValue | undefined
|
|
255
319
|
onCategoricalToggle: (option: string) => void
|
|
256
320
|
onSetFilter: (next: FilterValue) => void
|
|
321
|
+
variant?: "default" | "panel"
|
|
257
322
|
}) {
|
|
258
323
|
if (entry.kind === "numeric-range" && entry.field) {
|
|
259
324
|
const extremes = computeNumericExtremes(data, entry.path)
|
|
@@ -293,12 +358,92 @@ function FilterBody({
|
|
|
293
358
|
|
|
294
359
|
const opts = getCategoricalOptions(data, entry.path, entry.field, entry.legacy)
|
|
295
360
|
const selected = Array.isArray(value) ? value : []
|
|
361
|
+
const isSingle = entry.field?.filterMode === "single"
|
|
362
|
+
|
|
363
|
+
if (isSingle) {
|
|
364
|
+
const current = selected[0] ?? ""
|
|
365
|
+
const onSingleChange = (next: string) => onSetFilter(next ? [next] : [])
|
|
366
|
+
return (
|
|
367
|
+
<RadioGroupPrimitive.Root
|
|
368
|
+
value={current}
|
|
369
|
+
onValueChange={onSingleChange}
|
|
370
|
+
className={cn(
|
|
371
|
+
"flex flex-col space-y-0 rounded-[12px] bg-[#1C1D1F] p-1",
|
|
372
|
+
// Hide the divider directly above and below the hovered row.
|
|
373
|
+
"[&>div:has(>[role=radio]:hover)>.dv-divider]:opacity-0",
|
|
374
|
+
"[&>div:has(>[role=radio]:hover)+div>.dv-divider]:opacity-0",
|
|
375
|
+
)}
|
|
376
|
+
>
|
|
377
|
+
{opts.map((opt, i) => {
|
|
378
|
+
const isSelected = current === opt
|
|
379
|
+
const badgeVariant = entry.field?.variants?.[opt]
|
|
380
|
+
const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
|
|
381
|
+
return (
|
|
382
|
+
<div key={opt}>
|
|
383
|
+
{i > 0 && (
|
|
384
|
+
<div className="dv-divider h-px bg-[#2C2D2E]" />
|
|
385
|
+
)}
|
|
386
|
+
<DataViewRadio value={opt}>
|
|
387
|
+
{entry.legacy?.render
|
|
388
|
+
? entry.legacy.render(opt, isSelected)
|
|
389
|
+
: badgeProps
|
|
390
|
+
? <Badge {...badgeProps} label={opt} size="XS" />
|
|
391
|
+
: opt}
|
|
392
|
+
</DataViewRadio>
|
|
393
|
+
</div>
|
|
394
|
+
)
|
|
395
|
+
})}
|
|
396
|
+
</RadioGroupPrimitive.Root>
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (variant === "panel") {
|
|
401
|
+
return (
|
|
402
|
+
<div
|
|
403
|
+
className={cn(
|
|
404
|
+
"flex flex-col rounded-[12px] bg-[#1C1D1F] p-1",
|
|
405
|
+
// Hide the divider directly above and below the hovered row.
|
|
406
|
+
"[&>div:has(>label:hover)>.dv-divider]:opacity-0",
|
|
407
|
+
"[&>div:has(>label:hover)+div>.dv-divider]:opacity-0",
|
|
408
|
+
)}
|
|
409
|
+
>
|
|
410
|
+
{opts.map((opt, i) => {
|
|
411
|
+
const isSelected = selected.includes(opt)
|
|
412
|
+
const badgeVariant = entry.field?.variants?.[opt]
|
|
413
|
+
const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
|
|
414
|
+
return (
|
|
415
|
+
<div key={opt}>
|
|
416
|
+
{i > 0 && <div className="dv-divider h-px bg-[#2C2D2E]" />}
|
|
417
|
+
<label
|
|
418
|
+
htmlFor={`${entry.path}-${opt}`}
|
|
419
|
+
className="flex cursor-pointer items-center gap-2 rounded-[8px] px-2 py-2 text-[14px] text-white hover:bg-white/5"
|
|
420
|
+
>
|
|
421
|
+
<Checkbox
|
|
422
|
+
id={`${entry.path}-${opt}`}
|
|
423
|
+
checked={isSelected}
|
|
424
|
+
onCheckedChange={() => onCategoricalToggle(opt)}
|
|
425
|
+
/>
|
|
426
|
+
<span className="flex-1 leading-none">
|
|
427
|
+
{entry.legacy?.render
|
|
428
|
+
? entry.legacy.render(opt, isSelected)
|
|
429
|
+
: badgeProps
|
|
430
|
+
? <Badge {...badgeProps} label={opt} size="XS" />
|
|
431
|
+
: opt}
|
|
432
|
+
</span>
|
|
433
|
+
</label>
|
|
434
|
+
</div>
|
|
435
|
+
)
|
|
436
|
+
})}
|
|
437
|
+
</div>
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
296
441
|
return (
|
|
297
442
|
<div className="space-y-2">
|
|
298
443
|
{opts.map((opt) => {
|
|
299
444
|
const isSelected = selected.includes(opt)
|
|
300
|
-
const
|
|
301
|
-
const badgeProps =
|
|
445
|
+
const badgeVariant = entry.field?.variants?.[opt]
|
|
446
|
+
const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
|
|
302
447
|
return (
|
|
303
448
|
<div key={opt} className="flex items-center space-x-2">
|
|
304
449
|
<Checkbox
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Search } from "lucide-react";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { Button } from "../Button";
|
|
6
|
+
|
|
7
|
+
export type HeaderSearchProps = {
|
|
8
|
+
value: string;
|
|
9
|
+
onChange: (value: string) => void;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function HeaderSearch({
|
|
14
|
+
value,
|
|
15
|
+
onChange,
|
|
16
|
+
placeholder = "Search...",
|
|
17
|
+
}: HeaderSearchProps) {
|
|
18
|
+
const [open, setOpen] = useState(false);
|
|
19
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
20
|
+
const wrapRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (open) inputRef.current?.focus();
|
|
24
|
+
}, [open]);
|
|
25
|
+
|
|
26
|
+
// Auto-collapse on outside click only when the input is empty — keeps the
|
|
27
|
+
// expanded state if the user has typed a query but clicks away.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!open) return;
|
|
30
|
+
function onPointerDown(e: MouseEvent) {
|
|
31
|
+
if (!wrapRef.current) return;
|
|
32
|
+
if (wrapRef.current.contains(e.target as Node)) return;
|
|
33
|
+
if (!value) setOpen(false);
|
|
34
|
+
}
|
|
35
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
36
|
+
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
37
|
+
}, [open, value]);
|
|
38
|
+
|
|
39
|
+
function clearAndCollapse() {
|
|
40
|
+
onChange("");
|
|
41
|
+
setOpen(false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!open) {
|
|
45
|
+
return (
|
|
46
|
+
<Button
|
|
47
|
+
variant="BluContStyle"
|
|
48
|
+
size="M"
|
|
49
|
+
buttonType="icon"
|
|
50
|
+
aria-label="Open search"
|
|
51
|
+
onClick={() => setOpen(true)}
|
|
52
|
+
className="shrink-0 rounded-[6px] border border-border-presentation-global-primary"
|
|
53
|
+
>
|
|
54
|
+
<Search className="h-[18px] w-[18px]" />
|
|
55
|
+
</Button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
ref={wrapRef}
|
|
62
|
+
className="relative flex h-[28px] w-[260px] shrink-0 items-center justify-center rounded-[6px] border border-border-presentation-state-focus bg-background-presentation-form-field-primary px-1 shadow-[0_1px_6px_0_rgba(0,0,0,0.30)] transition-all duration-150 ease-in-out"
|
|
63
|
+
>
|
|
64
|
+
<input
|
|
65
|
+
ref={inputRef}
|
|
66
|
+
type="text"
|
|
67
|
+
value={value}
|
|
68
|
+
placeholder={placeholder}
|
|
69
|
+
onChange={(e) => onChange(e.target.value)}
|
|
70
|
+
onKeyDown={(e) => {
|
|
71
|
+
if (e.key === "Escape") clearAndCollapse();
|
|
72
|
+
}}
|
|
73
|
+
size={1}
|
|
74
|
+
className="min-w-0 flex-1 bg-transparent text-[14px] leading-none text-white caret-[#1E7AFE] placeholder:text-content-presentation-global-tertiary focus:outline-none"
|
|
75
|
+
/>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
aria-label="Clear search"
|
|
79
|
+
onClick={clearAndCollapse}
|
|
80
|
+
className="flex shrink-0 items-center justify-center self-stretch px-1"
|
|
81
|
+
>
|
|
82
|
+
<svg
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
84
|
+
width="16"
|
|
85
|
+
height="16"
|
|
86
|
+
viewBox="0 0 16 16"
|
|
87
|
+
fill="none"
|
|
88
|
+
>
|
|
89
|
+
<path
|
|
90
|
+
d="M7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325C11.6818 1.33325 14.6666 4.31802 14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666ZM7.99992 7.05712L6.1143 5.17149L5.17149 6.1143L7.05712 7.99992L5.17149 9.88552L6.1143 10.8283L7.99992 8.94272L9.88552 10.8283L10.8283 9.88552L8.94272 7.99992L10.8283 6.1143L9.88552 5.17149L7.99992 7.05712Z"
|
|
91
|
+
fill="white"
|
|
92
|
+
/>
|
|
93
|
+
</svg>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|