diff --git a/package.json b/package.json index 0026a71431e..a3057828cf3 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-focus-scope": "^1.1.8", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-portal": "^1.1.10", @@ -56,6 +57,7 @@ "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d536e5531e..c45a8d0c58b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: '@radix-ui/react-focus-scope': specifier: ^1.1.8 version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-navigation-menu': specifier: ^1.2.14 version: 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -78,6 +81,9 @@ importers: '@radix-ui/react-select': specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-slot': specifier: ^1.2.4 version: 1.2.4(@types/react@19.2.14)(react@19.2.4) @@ -3506,6 +3512,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^19.2.4 + react-dom: ^19.2.4 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.16': resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} peerDependencies: @@ -3688,6 +3707,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^19.2.4 + react-dom: ^19.2.4 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -15605,6 +15637,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -15833,6 +15874,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) diff --git a/src/components/FindWalletProductTable/hooks/useWalletFilters.tsx b/src/components/FindWalletProductTable/hooks/useWalletFilters.tsx index d9c5f7320af..e0c0cc6b864 100644 --- a/src/components/FindWalletProductTable/hooks/useWalletFilters.tsx +++ b/src/components/FindWalletProductTable/hooks/useWalletFilters.tsx @@ -31,6 +31,7 @@ export const useWalletFilters = (): FilterOption[] => { filterLabel: t("page-find-wallet-mobile"), description: "", inputState: false, + optionsLegend: t("table:table-mobile-platforms") as string, input: (filterIndex, itemIndex, inputState, updateFilterState) => { return ( { filterLabel: t("page-find-wallet-desktop"), description: "", inputState: false, + optionsLegend: t("table:table-desktop-platforms") as string, input: (filterIndex, itemIndex, inputState, updateFilterState) => { return ( { filterLabel: t("page-find-wallet-browser"), description: "", inputState: false, + optionsLegend: t("table:table-browser-engines") as string, input: (filterIndex, itemIndex, inputState, updateFilterState) => { return ( {

{filter.title}

- {filter.items.map((item, itemIndex) => { - return ( -
- {item.input( - filterIndex, - itemIndex, - item.inputState, - handleChange - )} - {item.inputState === true && item.options.length ? ( -
- {item.options.map((option, optionIndex) => { - return ( - - {option.input( - filterIndex, - itemIndex, - optionIndex, - option.inputState, - handleChange - )} - - ) - })} -
- ) : null} -
- ) - })} +
+ {filter.title} + {filter.items.map((item, itemIndex) => { + return ( +
+ {item.input( + filterIndex, + itemIndex, + item.inputState, + handleChange + )} + {item.inputState === true && item.options.length ? ( +
+ {item.optionsLegend && ( + + {item.optionsLegend} + + )} + {item.options.map((option, optionIndex) => { + return ( + + {option.input( + filterIndex, + itemIndex, + optionIndex, + option.inputState, + handleChange + )} + + ) + })} +
+ ) : null} +
+ ) + })} +
) diff --git a/src/components/ProductTable/FilterInputs/SwitchFilterInput.tsx b/src/components/ProductTable/FilterInputs/SwitchFilterInput.tsx index 55305775463..60939e75f0c 100644 --- a/src/components/ProductTable/FilterInputs/SwitchFilterInput.tsx +++ b/src/components/ProductTable/FilterInputs/SwitchFilterInput.tsx @@ -1,8 +1,11 @@ -import { ReactElement } from "react" +"use client" + +import { ReactElement, useId } from "react" import type { LucideIcon } from "lucide-react" import { FilterInputState } from "@/lib/types" +import { Field, FieldDescription, FieldLabel } from "@/components/ui/field" import Switch from "@/components/ui/switch" interface SwitchFilterInputProps { @@ -28,26 +31,40 @@ const SwitchFilterInput = ({ inputState, updateFilterState, }: SwitchFilterInputProps) => { + const id = useId() + const descriptionId = description ? `${id}-description` : undefined return ( - <> -
-
-
+ +
+ + {Icon && ( )} -
-

{label}

-
+ + {label} + { updateFilterState(filterIndex, itemIndex, e as boolean) }} />
-

{description}

- + {description && ( + + {description} + + )} + ) } diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 00000000000..64921f8f070 --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,243 @@ +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" + +import { cn } from "@/lib/utils/cn" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4", + className + )} + {...props} + /> + ) +} + +const fieldVariants = cva( + "group/field data-[invalid=true]:text-error flex w-full gap-3", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start", + ], + responsive: [ + "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +