diff --git a/apps/web/src/components/ui/combobox.test.tsx b/apps/web/src/components/ui/combobox.test.tsx index 7c89366f7..461fde40f 100644 --- a/apps/web/src/components/ui/combobox.test.tsx +++ b/apps/web/src/components/ui/combobox.test.tsx @@ -2,7 +2,6 @@ import type { ComboboxOption } from "./combobox"; import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; import { beforeAll, describe, expect, it } from "vitest"; import { i18n } from "@lingui/core"; import { I18nProvider } from "@lingui/react"; @@ -47,26 +46,6 @@ describe("Combobox", () => { expect(screen.getAllByText(/Gamma/).length).toBeGreaterThan(0); }); - it("renders grouped options with group labels", async () => { - const user = userEvent.setup(); - const groupedOptions: ComboboxOption[] = [ - { value: "alpha", label: "Alpha", group: "Primary" }, - { value: "beta", label: "Beta", group: { value: "secondary", label: "Secondary" } }, - { value: "gamma", label: "Gamma", group: { value: "secondary", label: "Secondary" } }, - ]; - - wrap(); - - await user.click(screen.getByRole("combobox")); - - expect(screen.getByText("Primary")).toBeInTheDocument(); - expect(screen.getByText("Secondary")).toBeInTheDocument(); - expect(screen.getByText("Alpha")).toBeInTheDocument(); - expect(screen.getByText("Beta")).toBeInTheDocument(); - expect(screen.getByText("Gamma")).toBeInTheDocument(); - expect(document.querySelectorAll("[data-slot=combobox-group]")).toHaveLength(2); - }); - it("renders nothing extra when given an empty options array (no crash)", () => { expect(() => wrap()).not.toThrow(); expect(screen.getByText("Empty")).toBeInTheDocument(); diff --git a/apps/web/src/components/ui/combobox.tsx b/apps/web/src/components/ui/combobox.tsx index e148b8c00..47f34541f 100644 --- a/apps/web/src/components/ui/combobox.tsx +++ b/apps/web/src/components/ui/combobox.tsx @@ -5,13 +5,10 @@ import React from "react"; import { Button } from "@reactive-resume/ui/components/button"; import { ComboboxClear, - ComboboxCollection, ComboboxContent, ComboboxEmpty, - ComboboxGroup, ComboboxInput, ComboboxItem, - ComboboxLabel, ComboboxList, ComboboxRoot, ComboboxTrigger, @@ -28,21 +25,10 @@ type ComboboxOption = { // Plain-text label used for the collapsed trigger and filtering when `label` is a ReactNode. // Without it, a JSX label falls back to String(value) (e.g. a raw enum or locale code) in the trigger. textValue?: string; - group?: string | ComboboxOptionGroup; keywords?: string[]; disabled?: boolean; }; -type ComboboxOptionGroup = { - value: string; - label: React.ReactNode; -}; - -type GroupedComboboxOption = ComboboxOptionGroup & { - key: string; - items: ComboboxOption[]; -}; - type SingleComboboxProps = { options: ComboboxOption[]; value?: TValue | null; @@ -52,7 +38,6 @@ type SingleComboboxProps = { disabled?: boolean; showClear?: boolean; placeholder?: string; - searchPlaceholder?: string; emptyMessage?: React.ReactNode; className?: string; id?: string; @@ -69,7 +54,6 @@ type MultiComboboxProps = { disabled?: boolean; showClear?: boolean; placeholder?: string; - searchPlaceholder?: string; emptyMessage?: React.ReactNode; className?: string; id?: string; @@ -85,13 +69,6 @@ const listContent = (item: ComboboxOption ); -const groupedListContent = (group: GroupedComboboxOption) => ( - - {group.label !== null && group.label !== undefined ? {group.label} : null} - {listContent} - -); - function Combobox(props: ComboboxProps) { const { options, @@ -99,7 +76,6 @@ function Combobox(props: ComboboxProps< disabled = false, showClear = false, placeholder, - searchPlaceholder, emptyMessage, className, id, @@ -111,40 +87,6 @@ function Combobox(props: ComboboxProps< const optionMap = React.useMemo(() => new Map(options.map((opt) => [String(opt.value), opt])), [options]); - const optionGroups = React.useMemo(() => { - const groups: GroupedComboboxOption[] = []; - const groupMap = new Map>(); - let ungroupedGroup: GroupedComboboxOption | null = null; - let hasGroupedOptions = false; - - for (const option of options) { - if (option.group === undefined) { - if (!ungroupedGroup) { - ungroupedGroup = { key: "ungrouped", value: "", label: null, items: [] }; - groups.push(ungroupedGroup); - } - - ungroupedGroup.items.push(option); - continue; - } - - hasGroupedOptions = true; - - const group = typeof option.group === "string" ? { value: option.group, label: option.group } : option.group; - let optionGroup = groupMap.get(group.value); - - if (!optionGroup) { - optionGroup = { ...group, key: `group:${group.value}`, items: [] }; - groupMap.set(group.value, optionGroup); - groups.push(optionGroup); - } - - optionGroup.items.push(option); - } - - return hasGroupedOptions ? groups : null; - }, [options]); - const findOption = React.useCallback( (v: TValue | TValue[] | null | undefined) => { if (multiple) { @@ -238,7 +180,7 @@ function Combobox(props: ComboboxProps< return ( [] & ComboboxOption} @@ -265,11 +207,11 @@ function Combobox(props: ComboboxProps< } /> {emptyMessage ?? t`No results found.`} - {optionGroups ? groupedListContent : listContent} + {listContent} ); diff --git a/apps/web/src/hooks/use-form-blocker.tsx b/apps/web/src/hooks/use-form-blocker.tsx index 5aa2fd0ee..b9c0621e2 100644 --- a/apps/web/src/hooks/use-form-blocker.tsx +++ b/apps/web/src/hooks/use-form-blocker.tsx @@ -1,3 +1,4 @@ +import type { AnyFormApi } from "@tanstack/react-form"; import { t } from "@lingui/core/macro"; import { useStore } from "@tanstack/react-form"; import { useCallback, useEffect, useRef } from "react"; @@ -8,29 +9,7 @@ interface UseFormBlockerOptions { shouldBlock?: () => boolean; } -type BlockableFormStore = { - get: () => { - isDirty: boolean; - isSubmitting: boolean; - }; - subscribe: { - (observer: { - next?: (value: { isDirty: boolean; isSubmitting: boolean }) => void; - error?: (error: unknown) => void; - complete?: () => void; - }): { unsubscribe: () => void }; - ( - next: (value: { isDirty: boolean; isSubmitting: boolean }) => void, - error?: (error: unknown) => void, - complete?: () => void, - ): { unsubscribe: () => void }; - }; -}; - -export function useFormBlocker( - form: { store: TStore }, - options?: UseFormBlockerOptions, -) { +export function useFormBlocker(form: Pick, options?: UseFormBlockerOptions) { const confirm = useConfirm(); const closeDialog = useDialogStore((state) => state.closeDialog); const setOnBeforeClose = useDialogStore((state) => state.setOnBeforeClose); diff --git a/apps/web/src/libs/query/client.test.ts b/apps/web/src/libs/query/client.test.ts index ddff476df..ea4aecfa0 100644 --- a/apps/web/src/libs/query/client.test.ts +++ b/apps/web/src/libs/query/client.test.ts @@ -29,21 +29,4 @@ describe("getQueryClient", () => { // json/meta envelope is included expect(hashA).toContain('"json"'); }); - - it("round-trips data through dehydrate/hydrate via oRPC serializer", () => { - const client = getQueryClient(); - const serializeData = client.getDefaultOptions().dehydrate?.serializeData; - const deserializeData = client.getDefaultOptions().hydrate?.deserializeData; - - expect(serializeData).toBeTypeOf("function"); - expect(deserializeData).toBeTypeOf("function"); - - const original = { id: "x", count: 3, when: new Date("2024-01-01T00:00:00Z") }; - const serialized = serializeData?.(original); - const restored = deserializeData?.(serialized) as typeof original; - - expect(restored.id).toBe(original.id); - expect(restored.count).toBe(original.count); - expect(restored.when.getTime()).toBe(original.when.getTime()); - }); }); diff --git a/apps/web/src/libs/query/client.ts b/apps/web/src/libs/query/client.ts index d72e33dfb..f00b1c007 100644 --- a/apps/web/src/libs/query/client.ts +++ b/apps/web/src/libs/query/client.ts @@ -14,17 +14,6 @@ export const getQueryClient = () => { return JSON.stringify({ json, meta }); }, }, - dehydrate: { - serializeData(data) { - const [json, meta] = serializer.serialize(data); - return { json, meta }; - }, - }, - hydrate: { - deserializeData(data) { - return serializer.deserialize(data.json, data.meta); - }, - }, }, mutationCache: new MutationCache({ onSettled: (_1, _2, _3, _4, _5, context) => { diff --git a/packages/ui/src/components/combobox.test.tsx b/packages/ui/src/components/combobox.test.tsx index 5ea6c4b52..33a9e4c56 100644 --- a/packages/ui/src/components/combobox.test.tsx +++ b/packages/ui/src/components/combobox.test.tsx @@ -3,13 +3,10 @@ import { describe, expect, it } from "vitest"; import { ComboboxChips, ComboboxChipsInput, - ComboboxCollection, ComboboxContent, ComboboxEmpty, - ComboboxGroup, ComboboxInput, ComboboxItem, - ComboboxLabel, ComboboxList, ComboboxRoot, ComboboxSeparator, @@ -74,29 +71,11 @@ describe("ComboboxItem", () => { }); }); -describe("ComboboxList / ComboboxGroup / ComboboxLabel", () => { +describe("ComboboxList", () => { it("ComboboxList renders with data-slot='combobox-list'", () => { renderCombobox({ open: true }); expect(document.querySelector("[data-slot=combobox-list]")).toBeInTheDocument(); }); - - it("ComboboxGroup and ComboboxLabel render with their slots", () => { - render( - - - - - - Fruits - Apple - - - - , - ); - expect(document.querySelector("[data-slot=combobox-group]")).toBeInTheDocument(); - expect(document.querySelector("[data-slot=combobox-label]")).toBeInTheDocument(); - }); }); describe("ComboboxSeparator and ComboboxEmpty", () => { @@ -129,29 +108,6 @@ describe("ComboboxSeparator and ComboboxEmpty", () => { }); }); -describe("ComboboxCollection", () => { - it("renders without throwing", () => { - expect(() => - render( - - - - - - {(item) => ( - - {item} - - )} - - - - , - ), - ).not.toThrow(); - }); -}); - describe("ComboboxClear", () => { it("renders inside ComboboxInput when showClear=true", () => { const { container } = render( diff --git a/packages/ui/src/components/combobox.tsx b/packages/ui/src/components/combobox.tsx index a0d8c743c..b46668873 100644 --- a/packages/ui/src/components/combobox.tsx +++ b/packages/ui/src/components/combobox.tsx @@ -7,7 +7,7 @@ import { InputGroupButton, InputGroupInput, } from "@reactive-resume/ui/components/input-group"; -import { menuLabelClassName, menuSeparatorClassName } from "@reactive-resume/ui/components/menu-styles"; +import { menuSeparatorClassName } from "@reactive-resume/ui/components/menu-styles"; import { cn } from "@reactive-resume/utils/style"; const ComboboxRoot = ComboboxPrimitive.Root; @@ -145,20 +145,6 @@ function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item. ); } -function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { - return ; -} - -function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) { - return ( - - ); -} - -function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { - return ; -} - function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { return (