diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index b88b375..3ab77aa 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -26,8 +26,8 @@ import type * as seed from "../seed.js"; import type * as topics from "../topics.js"; import type * as userFormData from "../userFormData.js"; import type * as userQuests from "../userQuests.js"; -import type * as users from "../users.js"; import type * as userSettings from "../userSettings.js"; +import type * as users from "../users.js"; import type * as validators from "../validators.js"; /** @@ -52,8 +52,8 @@ declare const fullApi: ApiFromModules<{ topics: typeof topics; userFormData: typeof userFormData; userQuests: typeof userQuests; - users: typeof users; userSettings: typeof userSettings; + users: typeof users; validators: typeof validators; }>; export declare const api: FilterApi< diff --git a/package.json b/package.json index 06c3cc9..a281d67 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,9 @@ "@auth/core": "0.37.4", "@convex-dev/auth": "^0.0.77", "@faker-js/faker": "^9.3.0", + "@maskito/core": "^3.2.0", + "@maskito/react": "^3.2.0", + "@tailwindcss/container-queries": "^0.1.1", "@tanstack/react-router": "^1.90.0", "@tiptap/extension-blockquote": "^2.10.3", "@tiptap/extension-bold": "^2.10.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09960b4..86a74e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,15 @@ importers: '@faker-js/faker': specifier: ^9.3.0 version: 9.3.0 + '@maskito/core': + specifier: ^3.2.0 + version: 3.2.0 + '@maskito/react': + specifier: ^3.2.0 + version: 3.2.0(@maskito/core@3.2.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tailwindcss/container-queries': + specifier: ^0.1.1 + version: 0.1.1(tailwindcss@3.4.16) '@tanstack/react-router': specifier: ^1.90.0 version: 1.90.0(@tanstack/router-generator@1.87.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1221,6 +1230,16 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true + '@maskito/core@3.2.0': + resolution: {integrity: sha512-c8GNwuz4PQmZqf5CSXIncwIkZSwBeU2FZtpBJtD21DaIe4uBQ541C0BncHMqEbLP2+n+HyFKEnT90m03BBlGrw==} + + '@maskito/react@3.2.0': + resolution: {integrity: sha512-njL/Pmc2cSU9fC3FaX5iwesXaBBdgZH/xgTqyEhc1UCRP3zN1me2XFww0FK2LmdFK76wSQ2MC5ggFXKvP3rzTw==} + peerDependencies: + '@maskito/core': ^3.2.0 + react: '>=16.8' + react-dom: '>=16.8' + '@node-rs/argon2-android-arm-eabi@1.7.0': resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} engines: {node: '>= 10'} @@ -2258,6 +2277,11 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@tailwindcss/container-queries@0.1.1': + resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==} + peerDependencies: + tailwindcss: '>=3.2.0' + '@tanstack/history@1.90.0': resolution: {integrity: sha512-riNhDGm+fAwxgZRJ0J/36IZis1UDHsDCNIxfEodbw6BgTWJr0ah+G20V4HT91uBXiCqYFvX3somlfTLhS5yHDA==} engines: {node: '>=12'} @@ -6242,6 +6266,14 @@ snapshots: - supports-color optional: true + '@maskito/core@3.2.0': {} + + '@maskito/react@3.2.0(@maskito/core@3.2.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@maskito/core': 3.2.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@node-rs/argon2-android-arm-eabi@1.7.0': optional: true @@ -7633,6 +7665,10 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.16)': + dependencies: + tailwindcss: 3.4.16 + '@tanstack/history@1.90.0': {} '@tanstack/react-router@1.90.0(@tanstack/router-generator@1.87.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': diff --git a/src/components/common/Button/Button.tsx b/src/components/common/Button/Button.tsx index a79ed06..b935f5c 100644 --- a/src/components/common/Button/Button.tsx +++ b/src/components/common/Button/Button.tsx @@ -11,7 +11,7 @@ export interface ButtonProps extends AriaButtonProps { children?: React.ReactNode; icon?: LucideIcon; variant?: "primary" | "secondary" | "destructive" | "icon" | "ghost"; - size?: "small" | "medium"; + size?: "small" | "medium" | "large"; } export const buttonStyles = tv({ @@ -29,6 +29,7 @@ export const buttonStyles = tv({ size: { small: "h-8 px-2", medium: "h-10 px-3", + large: "h-12 px-3.5 text-lg", }, isDisabled: { false: "cursor-pointer", @@ -73,7 +74,12 @@ export function Button({ }), )} > - {Icon && } + {Icon && ( + + )} {children} ); diff --git a/src/components/common/Field/Field.tsx b/src/components/common/Field/Field.tsx index f936bab..1ea207d 100644 --- a/src/components/common/Field/Field.tsx +++ b/src/components/common/Field/Field.tsx @@ -1,14 +1,14 @@ import { composeTailwindRenderProps, focusRing } from "@/components/utils"; import { FieldError as AriaFieldError, + type GroupProps as AriaGroupProps, Input as AriaInput, + type InputProps as AriaInputProps, Label as AriaLabel, + type LabelProps as AriaLabelProps, TextArea as AriaTextArea, type FieldErrorProps, Group, - type GroupProps, - type InputProps, - type LabelProps, Text, type TextAreaProps, type TextProps, @@ -17,14 +17,28 @@ import { import { twMerge } from "tailwind-merge"; import { tv } from "tailwind-variants"; -export function Label(props: LabelProps) { +interface LabelProps extends AriaLabelProps { + size?: "medium" | "large"; +} + +const labelStyles = tv({ + base: "text-sm text-gray-dim cursor-default w-fit", + variants: { + size: { + medium: "text-sm", + large: "text-base", + }, + }, + defaultVariants: { + size: "medium", + }, +}); + +export function Label({ size, ...props }: LabelProps) { return ( ); } @@ -68,29 +82,58 @@ export const fieldBorderStyles = tv({ export const fieldGroupStyles = tv({ extend: focusRing, - base: "group h-10 flex items-center bg-gray-subtle forced-colors:bg-[Field] border rounded-lg overflow-hidden", - variants: fieldBorderStyles.variants, + base: "group flex items-center bg-gray-subtle forced-colors:bg-[Field] border rounded-lg overflow-hidden", + variants: { + ...fieldBorderStyles.variants, + size: { + medium: "h-10", + large: "h-12", + }, + }, + defaultVariants: { + size: "medium", + }, }); -export function FieldGroup(props: GroupProps) { +interface GroupProps extends AriaGroupProps { + size?: "medium" | "large"; +} + +export function FieldGroup({ size, ...props }: GroupProps) { return ( - fieldGroupStyles({ ...renderProps, className }), + fieldGroupStyles({ ...renderProps, size, className }), )} /> ); } -export const inputStyles = - "px-3 h-10 flex-1 min-w-0 outline outline-0 bg-transparent text-gray-normal disabled:text-gray-dim"; +interface InputProps extends Omit { + size?: "medium" | "large"; +} -export function Input(props: InputProps) { +export const inputStyles = tv({ + base: "flex-1 min-w-0 outline outline-0 bg-transparent text-gray-normal disabled:text-gray-dim", + variants: { + size: { + medium: "px-3 h-10", + large: "px-3.5 h-12 text-lg", + }, + }, + defaultVariants: { + size: "medium", + }, +}); + +export function Input({ size, ...props }: InputProps) { return ( + inputStyles({ ...renderProps, size, className }), + )} /> ); } @@ -99,7 +142,9 @@ export function InputTextArea(props: TextAreaProps) { return ( + inputStyles({ ...renderProps, className }), + )} /> ); } diff --git a/src/components/common/Link/Link.tsx b/src/components/common/Link/Link.tsx index 17e3fc1..956bb1e 100644 --- a/src/components/common/Link/Link.tsx +++ b/src/components/common/Link/Link.tsx @@ -9,7 +9,7 @@ import { type ButtonProps, buttonStyles } from "../Button"; export interface LinkProps extends AriaLinkProps { variant?: "primary" | "secondary"; - button?: ButtonProps; + button?: Omit; } const linkStyles = tv({ diff --git a/src/components/common/Select/Select.tsx b/src/components/common/Select/Select.tsx index a62fd36..bc96618 100644 --- a/src/components/common/Select/Select.tsx +++ b/src/components/common/Select/Select.tsx @@ -1,16 +1,15 @@ -import { composeTailwindRenderProps, focusRing } from "@/components/utils"; +import { composeTailwindRenderProps } from "@/components/utils"; import { ChevronDown } from "lucide-react"; import type React from "react"; import { Select as AriaSelect, type SelectProps as AriaSelectProps, - Button, ListBox, type ListBoxItemProps, SelectValue, type ValidationResult, } from "react-aria-components"; -import { tv } from "tailwind-variants"; +import { Button } from "../Button"; import { FieldDescription, FieldError, Label } from "../Field"; import { DropdownItem, @@ -19,17 +18,10 @@ import { } from "../ListBox"; import { Popover } from "../Popover"; -const styles = tv({ - extend: focusRing, - base: "flex items-center text-start gap-4 w-full cursor-pointer border border-black/10 dark:border-white/10 rounded-lg pl-3 pr-2 py-2 min-w-[150px] transition bg-gray-ui", - variants: { - isDisabled: { - false: - "hover:bg-gray-1 pressed:bg-gray-2 dark:hover:bg-graydark-1 dark:pressed:bg-graydark-2 group-invalid:border-red- forced-colors:group-invalid:border-[Mark]", - true: "opacity-50 forced-colors:text-[GrayText] forced-colors:border-[GrayText] cursor-default", - }, - }, -}); +// const selectStyles = tv({ +// extend: focusRing, +// base: "flex items-center text-start gap-4 w-full cursor-pointer min-w-[150px] transition", +// }); export interface SelectProps extends Omit, "children"> { @@ -37,6 +29,7 @@ export interface SelectProps description?: string; errorMessage?: string | ((validation: ValidationResult) => string); items?: Iterable; + size?: "medium" | "large"; children: React.ReactNode | ((item: T) => React.ReactNode); } @@ -46,6 +39,7 @@ export function Select({ errorMessage, children, items, + size = "medium", ...props }: SelectProps) { return ( @@ -56,12 +50,12 @@ export function Select({ "group flex flex-col gap-1.5", )} > - {label && } - {description && {description}} diff --git a/src/components/common/TextField/TextField.tsx b/src/components/common/TextField/TextField.tsx index 085a43e..b6a9f7b 100644 --- a/src/components/common/TextField/TextField.tsx +++ b/src/components/common/TextField/TextField.tsx @@ -1,11 +1,12 @@ import { composeTailwindRenderProps } from "@/components/utils"; import { Eye, EyeOff } from "lucide-react"; -import { useState } from "react"; +import { forwardRef, useState } from "react"; import { TextField as AriaTextField, type TextFieldProps as AriaTextFieldProps, type ValidationResult, } from "react-aria-components"; +import { twMerge } from "tailwind-merge"; import { Button } from "../Button"; import { FieldDescription, @@ -22,52 +23,67 @@ export interface TextFieldProps extends AriaTextFieldProps { prefix?: React.ReactNode; suffix?: React.ReactNode; errorMessage?: string | ((validation: ValidationResult) => string); + size?: "medium" | "large"; } -export function TextField({ - label, - description, - prefix, - suffix, - errorMessage, - ...props -}: TextFieldProps) { - const [isPasswordVisible, setIsPasswordVisible] = useState(false); +export const TextField = forwardRef( + function TextField( + { + label, + description, + prefix, + suffix, + errorMessage, + type, + size = "medium", + ...props + }, + ref, + ) { + const [isPasswordVisible, setIsPasswordVisible] = useState(false); - return ( - - {label && } - - {prefix} - - {suffix} - {props.type === "password" && ( - -