Skip to content

Commit f6d5abf

Browse files
authored
datasets modal (#612)
* split * cleaning * right * glow * tabs to gpr
1 parent dcdf848 commit f6d5abf

9 files changed

Lines changed: 585 additions & 268 deletions

File tree

src/app/globals.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,22 @@ a[href]:hover::after {
435435
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
436436
--sidebar-border: hsl(240 3.7% 15.9%);
437437
--sidebar-ring: hsl(217.2 91.2% 59.8%);
438+
}
439+
440+
@keyframes glow-pulse {
441+
0%, 100% {
442+
filter: drop-shadow(0 0 2px rgba(236, 72, 153, 0.5))
443+
drop-shadow(0 0 3px rgba(249, 115, 22, 0.3))
444+
drop-shadow(0 0 5px rgba(234, 179, 8, 0.2));
445+
}
446+
50% {
447+
filter: drop-shadow(0 0 5px rgba(236, 72, 153, 0.8))
448+
drop-shadow(0 0 7px rgba(249, 115, 22, 0.6))
449+
drop-shadow(0 0 12px rgba(234, 179, 8, 0.4));
450+
}
451+
}
452+
453+
.glow-effect {
454+
animation: glow-pulse 2s ease-in-out infinite;
455+
border-radius: 0.275rem;
438456
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"use client";
2+
3+
import React, { useState } from 'react';
4+
import { DATASETS_COLLECTION } from './DatasetCollection';
5+
import {
6+
Command,
7+
CommandEmpty,
8+
CommandGroup,
9+
CommandInput,
10+
CommandItem,
11+
CommandList,
12+
} from "@/components/ui/command";
13+
import { ChevronDown } from 'lucide-react';
14+
15+
const PAGE_SIZE = 5;
16+
17+
type Props = {
18+
activeOption: string;
19+
setActiveOption: (key: string) => void;
20+
setInitStore: (v: string) => void;
21+
onOpenDescription: () => void;
22+
};
23+
24+
const CuratedDatasets = ({
25+
activeOption,
26+
setActiveOption,
27+
setInitStore,
28+
onOpenDescription,
29+
}: Props) => {
30+
const [search, setSearch] = useState('');
31+
const [showAll, setShowAll] = useState(false);
32+
33+
const filtered = DATASETS_COLLECTION.filter(ds =>
34+
ds.label.toLowerCase().includes(search.toLowerCase()) ||
35+
ds.subtitle.toLowerCase().includes(search.toLowerCase())
36+
);
37+
38+
const visible = showAll ? filtered : filtered.slice(0, PAGE_SIZE);
39+
const hiddenCount = filtered.length - PAGE_SIZE;
40+
const hasMore = hiddenCount > 0;
41+
42+
return (
43+
<Command shouldFilter={false} className="rounded-lg border">
44+
<CommandInput
45+
placeholder="Search datasets..."
46+
value={search}
47+
onValueChange={(v) => {
48+
setSearch(v);
49+
setShowAll(false);
50+
}}
51+
/>
52+
<CommandList className='py-2'>
53+
<CommandEmpty>No datasets found.</CommandEmpty>
54+
<CommandGroup>
55+
{visible.map(ds => (
56+
<CommandItem
57+
key={ds.key}
58+
value={ds.key}
59+
onSelect={() => {
60+
setActiveOption(ds.key);
61+
setInitStore(ds.store);
62+
onOpenDescription();
63+
}}
64+
className={`flex flex-col items-start gap-0.5 mb-2 cursor-pointer ${
65+
activeOption === ds.key ? 'bg-accent' : ''
66+
}`}
67+
>
68+
<span className="font-medium text-sm">{ds.label}</span>
69+
<span className="text-xs text-muted-foreground leading-snug ">
70+
{ds.subtitle}
71+
</span>
72+
</CommandItem>
73+
))}
74+
75+
{!showAll && hasMore && (
76+
<CommandItem
77+
value="__show_more__"
78+
onSelect={() => setShowAll(true)}
79+
className="flex items-center justify-center gap-1.5 text-xs text-muted-foreground cursor-pointer py-2 border-t border-dashed border-border mt-1 hover:text-foreground"
80+
>
81+
<ChevronDown className="size-3.5" />
82+
{hiddenCount} more dataset{hiddenCount > 1 ? 's' : ''} available
83+
</CommandItem>
84+
)}
85+
86+
{showAll && hasMore && (
87+
<CommandItem
88+
value="__show_less__"
89+
onSelect={() => setShowAll(false)}
90+
className="flex items-center justify-center gap-1.5 text-xs text-muted-foreground cursor-pointer py-2 border-t border-dashed border-border mt-1 hover:text-foreground"
91+
>
92+
<ChevronDown className="size-3.5 rotate-180" />
93+
Show less
94+
</CommandItem>
95+
)}
96+
</CommandGroup>
97+
</CommandList>
98+
</Command>
99+
);
100+
};
101+
102+
export default CuratedDatasets;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"use client";
2+
import React, { useState } from 'react';
3+
import { useGlobalStore } from '@/GlobalStates/GlobalStore';
4+
import { useShallow } from 'zustand/shallow';
5+
import {
6+
Dialog,
7+
DialogContent,
8+
DialogTitle,
9+
} from "@/components/ui/dialog";
10+
import {
11+
ButtonGroup,
12+
} from "@/components/ui/button-group";
13+
import { Button } from "@/components/ui/button-enhanced";
14+
import { DescriptionContent } from './DescriptionContent';
15+
import CuratedDatasets from './CuratedDatasets';
16+
import RemoteZarr from './RemoteZarr';
17+
import LocalContent from './LocalContent';
18+
19+
type Tab = 'curated' | 'remote' | 'local';
20+
21+
const TABS: { value: Tab; label: string }[] = [
22+
{ value: 'curated', label: 'Curated' },
23+
{ value: 'remote', label: 'Remote Zarr' },
24+
{ value: 'local', label: 'Local' },
25+
];
26+
27+
type Props = {
28+
open: boolean;
29+
onOpenChange: (v: boolean) => void;
30+
isSafari: boolean;
31+
};
32+
33+
const DatasetsModal = ({ open, onOpenChange, isSafari }: Props) => {
34+
const [activeOption, setActiveOption] = useState<string>('');
35+
const [showDescription, setShowDescription] = useState(false);
36+
const [activeTab, setActiveTab] = useState<Tab>('curated');
37+
38+
const { initStore, setInitStore, setOpenVariables } = useGlobalStore(
39+
useShallow(state => ({
40+
setInitStore: state.setInitStore,
41+
setOpenVariables: state.setOpenVariables,
42+
initStore: state.initStore,
43+
}))
44+
);
45+
46+
const openDescription = () => setShowDescription(true);
47+
48+
const handleTabChange = (tab: Tab) => {
49+
setActiveTab(tab);
50+
setShowDescription(false);
51+
};
52+
53+
return (
54+
<Dialog open={open} onOpenChange={onOpenChange}>
55+
<DialogContent className="max-w-md max-h-[80vh] overflow-y-auto p-0">
56+
<DialogTitle className="sr-only">Open Dataset</DialogTitle>
57+
58+
{/* Header */}
59+
<div className="px-4 pt-4 pb-3 border-b border-border">
60+
<p className="text-xs font-medium mb-2 uppercase tracking-wide">
61+
Open dataset
62+
</p>
63+
<ButtonGroup className="justify-between border-1 rounded-md">
64+
{TABS.map(({ value, label }) => (
65+
<Button
66+
key={value}
67+
className='cursor-pointer'
68+
variant={activeTab === value ? 'secondary' : 'ghost'}
69+
size="sm"
70+
onClick={() => handleTabChange(value)}
71+
>
72+
{label}
73+
</Button>
74+
))}
75+
</ButtonGroup>
76+
</div>
77+
78+
<div className="p-4 flex flex-col gap-1 -mt-3">
79+
{activeTab === 'curated' && (
80+
<CuratedDatasets
81+
activeOption={activeOption}
82+
setActiveOption={setActiveOption}
83+
setInitStore={setInitStore}
84+
onOpenDescription={openDescription}
85+
/>
86+
)}
87+
{activeTab === 'remote' && (
88+
<RemoteZarr
89+
initStore={initStore}
90+
setInitStore={setInitStore}
91+
onOpenDescription={openDescription}
92+
/>
93+
)}
94+
{activeTab === 'local' && (
95+
<LocalContent
96+
setInitStore={setInitStore}
97+
onOpenDescription={openDescription}
98+
isSafari={isSafari}
99+
/>
100+
)}
101+
{showDescription && (
102+
<DescriptionContent
103+
setOpenVariables={setOpenVariables}
104+
onCloseDialog={() => {
105+
setShowDescription(false);
106+
onOpenChange(false);
107+
}}
108+
/>
109+
)}
110+
</div>
111+
</DialogContent>
112+
</Dialog>
113+
);
114+
};
115+
116+
export default DatasetsModal;

0 commit comments

Comments
 (0)