fix: topic selector (#129)

* feat: add jazz globa group cons

* chore: remove topic selector atom

* chore: use jazz from constant

* chore: remove delete model and add new topic selector

* chore: use jazz group id form constant in search component

* chore: use jazz group id form constant in public home route

* fix: topic selector in link

* fix: topic section in detail topic

* chore: update la editor

* chore: content header tweak class

* chore: add btn variant to topic selector

* refactor: tweak border for link header

* chore: page header

* fix: page detail route
This commit is contained in:
Aslam
2024-09-04 05:32:37 +07:00
committed by GitHub
parent e383381ffc
commit a3913baff9
21 changed files with 573 additions and 437 deletions

View File

@@ -42,7 +42,7 @@ export const LinkHeader = React.memo(() => {
</ContentHeader>
{isTablet && (
<div className="border-b-primary/5 flex min-h-10 flex-row items-start justify-between border-b px-6 py-2 max-lg:pl-4">
<div className="flex min-h-10 flex-row items-start justify-between border-b px-6 py-2 max-lg:pl-4">
<LearningTab />
</div>
)}

View File

@@ -12,12 +12,13 @@ import { UrlInput } from "./url-input"
import { UrlBadge } from "./url-badge"
import { TitleInput } from "./title-input"
import { NotesSection } from "./notes-section"
import { TopicSelector } from "./topic-selector"
import { DescriptionInput } from "./description-input"
import { atom, useAtom } from "jotai"
import { linkLearningStateSelectorAtom, linkTopicSelectorAtom } from "@/store/link"
import { linkLearningStateSelectorAtom } from "@/store/link"
import { FormField, FormItem, FormLabel } from "@/components/ui/form"
import { LearningStateSelector } from "@/components/custom/learning-state-selector"
import { TopicSelector, topicSelectorAtom } from "@/components/custom/topic-selector"
import { JAZZ_GLOBAL_GROUP_ID } from "@/lib/constants"
export const globalLinkFormExceptionRefsAtom = atom<React.RefObject<HTMLElement>[]>([])
@@ -47,8 +48,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
onClose,
exceptionsRefs = []
}) => {
const [selectedTopic, setSelectedTopic] = React.useState<Topic | undefined>()
const [istopicSelectorOpen] = useAtom(linkTopicSelectorAtom)
const [istopicSelectorOpen] = useAtom(topicSelectorAtom)
const [islearningStateSelectorOpen] = useAtom(linkLearningStateSelectorAtom)
const [globalExceptionRefs] = useAtom(globalLinkFormExceptionRefsAtom)
@@ -65,6 +65,14 @@ export const LinkForm: React.FC<LinkFormProps> = ({
mode: "all"
})
const topicName = form.watch("topic")
const findTopic = React.useMemo(
() => me && Topic.findUnique({ topicName }, JAZZ_GLOBAL_GROUP_ID, me),
[topicName, me]
)
const selectedTopic = useCoState(Topic, findTopic, {})
const allExceptionRefs = React.useMemo(
() => [...exceptionsRefs, ...globalExceptionRefs],
[exceptionsRefs, globalExceptionRefs]
@@ -101,10 +109,11 @@ export const LinkForm: React.FC<LinkFormProps> = ({
description: selectedLink.description,
completed: selectedLink.completed,
notes: selectedLink.notes,
learningState: selectedLink.learningState
learningState: selectedLink.learningState,
topic: selectedLink.topic?.name
})
}
}, [selectedLink, form])
}, [selectedLink, selectedLink?.topic, form])
const fetchMetadata = async (url: string) => {
setIsFetching(true)
@@ -214,7 +223,20 @@ export const LinkForm: React.FC<LinkFormProps> = ({
</FormItem>
)}
/>
<TopicSelector onSelect={topic => setSelectedTopic(topic)} />
<FormField
control={form.control}
name="topic"
render={({ field }) => (
<FormItem className="space-y-0">
<FormLabel className="sr-only">Topic</FormLabel>
<TopicSelector
{...field}
renderSelectedText={() => <span>{selectedTopic?.prettyName || "Select a topic"}</span>}
/>
</FormItem>
)}
/>
</div>
</div>
@@ -222,17 +244,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
<UrlBadge urlFetched={urlFetched} handleResetUrl={handleResetUrl} />
</div>
<div
className="flex flex-row items-center justify-between gap-2 rounded-b-md border-t px-3 py-2"
onClick={e => {
if (!(e.target as HTMLElement).closest("button")) {
const notesInput = e.currentTarget.querySelector("input")
if (notesInput) {
notesInput.focus()
}
}
}}
>
<div className="flex flex-row items-center justify-between gap-2 rounded-b-md border-t px-3 py-2">
<NotesSection />
{isFetching ? (

View File

@@ -13,7 +13,7 @@ export const NotesSection: React.FC = () => {
control={form.control}
name="notes"
render={({ field }) => (
<FormItem className="relative space-y-0">
<FormItem className="relative grow space-y-0">
<FormLabel className="sr-only">Note</FormLabel>
<FormControl>
<>

View File

@@ -1,90 +0,0 @@
import { Button } from "@/components/ui/button"
import { Command, CommandInput, CommandList, CommandItem, CommandGroup } from "@/components/ui/command"
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"
import { ScrollArea } from "@/components/ui/scroll-area"
import { CheckIcon } from "lucide-react"
import { useFormContext } from "react-hook-form"
import { cn } from "@/lib/utils"
import { useAtom } from "jotai"
import { linkTopicSelectorAtom } from "@/store/link"
import { LinkFormValues } from "./schema"
import { useCoState } from "@/lib/providers/jazz-provider"
import { PublicGlobalGroup } from "@/lib/schema/master/public-group"
import { ID } from "jazz-tools"
import { LaIcon } from "@/components/custom/la-icon"
import { Topic } from "@/lib/schema"
interface TopicSelectorProps {
onSelect?: (value: Topic) => void
}
export const TopicSelector: React.FC<TopicSelectorProps> = ({ onSelect }) => {
const globalGroup = useCoState(
PublicGlobalGroup,
process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID<PublicGlobalGroup>,
{
root: {
topics: []
}
}
)
const [isTopicSelectorOpen, setIsTopicSelectorOpen] = useAtom(linkTopicSelectorAtom)
const form = useFormContext<LinkFormValues>()
const handleSelect = (value: string) => {
const topic = globalGroup?.root.topics.find(topic => topic?.name === value)
if (topic) {
onSelect?.(topic)
form?.setValue("topic", value)
}
setIsTopicSelectorOpen(false)
}
const selectedValue = form ? form.watch("topic") : null
return (
<Popover open={isTopicSelectorOpen} onOpenChange={setIsTopicSelectorOpen}>
<PopoverTrigger asChild>
<Button size="sm" type="button" role="combobox" variant="secondary" className="gap-x-2 text-sm">
<span className="truncate">
{selectedValue
? globalGroup?.root.topics.find(topic => topic?.id && topic.name === selectedValue)?.prettyName
: "Topic"}
</span>
<LaIcon name="ChevronDown" />
</Button>
</PopoverTrigger>
<PopoverContent
className="z-50 w-52 rounded-lg p-0"
side="bottom"
align="end"
onCloseAutoFocus={e => e.preventDefault()}
>
<Command>
<CommandInput placeholder="Search topic..." className="h-9" />
<CommandList>
<ScrollArea>
<CommandGroup>
{globalGroup?.root.topics.map(
topic =>
topic?.id && (
<CommandItem key={topic.id} value={topic.name} onSelect={handleSelect}>
{topic.prettyName}
<CheckIcon
size={16}
className={cn(
"absolute right-3",
topic.name === selectedValue ? "text-primary" : "text-transparent"
)}
/>
</CommandItem>
)
)}
</CommandGroup>
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}