diff --git a/.vscode/settings.json b/.vscode/settings.json index 001c9f0..fabfe19 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,21 @@ }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", ".*Styles*"], + "tailwindCSS.experimental.classRegex": [ + "(?:enter|leave)(?:From|To)?=\\s*(?:\"|'|{`)([^(?:\"|'|`})]*)", + ["clsx\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], + ["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], + ["cva\\(([^;]*)[\\);]", "[`'\"`]([^'\"`;]*)[`'\"`]"], + ["(?:twMerge|twJoin)\\(([^;]*)[\\);]", "[`'\"`]([^'\"`;]*)[`'\"`]"], + [ + "(tw`(?:(?:(?:[^`]*\\$\\{[^]*?\\})[^`]*)+|[^`]*`))", + "((?:(?<=`)(?:[^\"'`]*)(?=\\${|`))|(?:(?<=\\})(?:[^\"'`]*)(?=\\${))|(?:(?<=\\})(?:[^\"'`]*)(?=`))|(?:(?<=')(?:[^\"'`]*)(?='))|(?:(?<=\")(?:[^\"'`]*)(?=\"))|(?:(?<=`)(?:[^\"'`]*)(?=`)))" + ], + [ + "(?:\\b(?:const|let|var)\\s+)?[\\w$_]*(?:[Ss]tyles|[Cc]lasses|[Cc]lassnames)[\\w\\d]*\\s*=\\s*{([\\s\\S]*?)}", + "\\s?[\\w].*:\\s*?[\"'`]([^\"'`]*).*?,?\\s?" + ] + ] } diff --git a/app/components/application/app-theme-switcher.tsx b/app/components/application/app-theme-switcher.tsx index 92da1dc..3a80645 100644 --- a/app/components/application/app-theme-switcher.tsx +++ b/app/components/application/app-theme-switcher.tsx @@ -15,13 +15,16 @@ export function AppThemeSwitcher() { return (
- - - + + + + Toggle theme + + } + /> + - -
- -
-
- Utilities - Webapp -
- - + render={ + +
+ +
+
+ Utilities + Webapp +
+ + } + /> ); diff --git a/app/components/navigation/nav-main.tsx b/app/components/navigation/nav-main.tsx index 2a20ada..3095454 100644 --- a/app/components/navigation/nav-main.tsx +++ b/app/components/navigation/nav-main.tsx @@ -8,7 +8,7 @@ import { PencilRulerIcon, RefreshCcwDotIcon, } from 'lucide-react'; -import { Activity } from 'react'; +import { Activity, useEffect, useState } from 'react'; import { NavLink, useLocation } from 'react-router'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/collapsible'; import { @@ -78,6 +78,31 @@ export function NavMain() { }, ]; + const [openGroups, setOpenGroups] = useState>(() => + Object.fromEntries(LINKS.map((item) => [item.title, Boolean(item.isActive)])), + ); + + useEffect(() => { + setOpenGroups((prev) => { + const next = { ...prev }; + let hasChanges = false; + + for (const item of LINKS) { + if (next[item.title] === undefined) { + next[item.title] = Boolean(item.isActive); + hasChanges = true; + } + + if (item.isActive && !next[item.title]) { + next[item.title] = true; + hasChanges = true; + } + } + + return hasChanges ? next : prev; + }); + }, [LINKS]); + return ( @@ -86,64 +111,69 @@ export function NavMain() { aria-current={location.pathname === '/' ? 'page' : undefined} {...(location.pathname.endsWith('/') && { 'data-current': '' })} className="cursor-pointer data-current:bg-sidebar-accent data-current:text-sidebar-accent-foreground" - asChild - > - - - Home - - + render={ + + + Home + + } + /> + - - - About - - + render={ + + + About + + } + /> {LINKS.map((item) => ( - - - {item.icon && } - - + + {item.icon && } + + } + /> - {item.title} + + {item.title} + - + {item.items?.map((subItem) => { const isActive = location.pathname.endsWith(subItem.url); return ( - - {subItem.title} - - + render={ + + {subItem.title} + + } + /> ); })} @@ -154,47 +184,64 @@ export function NavMain() { {LINKS.map((item) => ( - - - - - {item.icon && } - {item.title} - - - - - - {item.items?.map((subItem) => { - const isActive = location.pathname.endsWith(subItem.url); - - return ( - - - - {subItem.title} - - - - ); - })} - - - - + + setOpenGroups((prev) => ({ + ...prev, + [item.title]: open, + })) + } + className="group/collapsible" + render={ + + + {item.icon && } + {item.title} + + + } + /> + + + + {item.items?.map((subItem) => { + const isActive = location.pathname.endsWith(subItem.url); + + return ( + + + {subItem.title} + + } + /> + + ); + })} + + + + } + /> ))} diff --git a/app/components/pages/calculators/date/date-add-subtract.tsx b/app/components/pages/calculators/date/date-add-subtract.tsx index b27a75e..415bca1 100644 --- a/app/components/pages/calculators/date/date-add-subtract.tsx +++ b/app/components/pages/calculators/date/date-add-subtract.tsx @@ -1,6 +1,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { format } from 'date-fns'; import { CalendarIcon, CircleMinusIcon, CirclePlusIcon } from 'lucide-react'; +import { floor, random } from 'mathjs'; import { useState } from 'react'; import { Controller, Resolver, useForm } from 'react-hook-form'; import { z } from 'zod/v4'; @@ -113,18 +114,26 @@ export default function DateAddSubtract() { Select a Date - - - + + {field.value ? ( + format(field.value, 'PPP') + ) : ( + Pick a date + )} + + + } + /> + Action - - + - + Add @@ -178,7 +187,7 @@ export default function DateAddSubtract() { name={input as keyof Omit} render={({ field, fieldState }) => { const isInvalid = !!form.formState.errors[input as keyof DateAddSubtractType]; - const randomPlaceholder = Math.floor(Math.random() * 100).toString(); + const randomPlaceholder = floor(random(100)).toString(); const { value, ...rest } = field; diff --git a/app/components/pages/calculators/date/date-difference.tsx b/app/components/pages/calculators/date/date-difference.tsx index 3b61055..26ac3f0 100644 --- a/app/components/pages/calculators/date/date-difference.tsx +++ b/app/components/pages/calculators/date/date-difference.tsx @@ -189,22 +189,29 @@ export function DateDifference() {
- - - + + {field.value ? ( + format(field.value, 'PPP') + ) : ( + Pick a date + )} + + + } + /> + (null); const resultRef = useRef(null); - const formStyles = cn`mx-auto my-0 grid max-w-7xl grid-cols-[1fr_auto] overflow-hidden rounded-md`; - const areaStyles = cn`m-0 resize-none border-0 bg-accent p-4 font-mono text-lg leading-6 text-balance text-accent-foreground outline-none focus:ring-0 focus-visible:ring-0`; + const formStyles = cn('mx-auto my-0 grid max-w-7xl grid-cols-[1fr_auto] overflow-hidden rounded-md'); + const areaStyles = cn( + 'm-0 resize-none border-0 bg-secondary p-4 font-mono text-lg leading-6 text-balance text-accent outline-none focus:ring-0 focus-visible:ring-0', + ); function handleCalculation() { if (!textAreaRef.current || !resultRef.current) return; diff --git a/app/components/pages/calculators/percentage/is-percent-of-what-calc.tsx b/app/components/pages/calculators/percentage/is-percent-of-what-calc.tsx index d67ae53..ecc508d 100644 --- a/app/components/pages/calculators/percentage/is-percent-of-what-calc.tsx +++ b/app/components/pages/calculators/percentage/is-percent-of-what-calc.tsx @@ -3,6 +3,7 @@ import { Button } from '~/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card'; import { Field, FieldLabel } from '~/components/ui/field'; import { Input } from '~/components/ui/input'; +import { cn } from '~/lib/utils'; export default function IsPercentOfWhatCalc() { const [value, setValue] = useState(''); @@ -123,7 +124,7 @@ export default function IsPercentOfWhatCalc() { aria-readonly="true" aria-label="Calculation result" aria-describedby={`${componentName}-formula`} - className="font-bold" + className={cn(result !== null && 'font-bold')} value={result === null ? '' : String(Number(result.toFixed(2)))} readOnly /> diff --git a/app/components/pages/calculators/percentage/is-what-percent-of-calc.tsx b/app/components/pages/calculators/percentage/is-what-percent-of-calc.tsx index f4d2d07..3e75396 100644 --- a/app/components/pages/calculators/percentage/is-what-percent-of-calc.tsx +++ b/app/components/pages/calculators/percentage/is-what-percent-of-calc.tsx @@ -3,6 +3,7 @@ import { Button } from '~/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card'; import { Field, FieldLabel } from '~/components/ui/field'; import { Input } from '~/components/ui/input'; +import { cn } from '~/lib/utils'; export default function IsWhatPercentOfCalc() { const [percentValue, setPercentValue] = useState(''); @@ -118,8 +119,8 @@ export default function IsWhatPercentOfCalc() { aria-readonly="true" aria-label="Calculation result" aria-describedby={`${componentName}-formula`} - className="font-bold" - defaultValue={result ?? ''} + className={cn(result !== null && 'font-bold')} + value={result ?? ''} readOnly /> diff --git a/app/components/pages/calculators/percentage/percent-increase-decrease-calc.tsx b/app/components/pages/calculators/percentage/percent-increase-decrease-calc.tsx index 6194c6b..42b6e77 100644 --- a/app/components/pages/calculators/percentage/percent-increase-decrease-calc.tsx +++ b/app/components/pages/calculators/percentage/percent-increase-decrease-calc.tsx @@ -4,6 +4,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } import { Field, FieldGroup, FieldLabel } from '~/components/ui/field'; import { Input } from '~/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; +import { cn } from '~/lib/utils'; const Action = { INCREASE: 'Increase', @@ -150,7 +151,7 @@ export default function PercentIncreaseDecreaseCalc() { aria-label="Select action" > - + @@ -172,7 +173,7 @@ export default function PercentIncreaseDecreaseCalc() { aria-readonly="true" aria-label="Calculation result" aria-describedby={`${componentName}-formula`} - className="font-bold" + className={cn(result !== null && 'font-bold')} value={result === null ? '' : String(Number(result.toFixed(2)))} readOnly /> diff --git a/app/components/pages/calculators/percentage/percent-of-calc.tsx b/app/components/pages/calculators/percentage/percent-of-calc.tsx index b14c806..185986e 100644 --- a/app/components/pages/calculators/percentage/percent-of-calc.tsx +++ b/app/components/pages/calculators/percentage/percent-of-calc.tsx @@ -3,6 +3,7 @@ import { Button } from '~/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card'; import { Field, FieldLabel } from '~/components/ui/field'; import { Input } from '~/components/ui/input'; +import { cn } from '~/lib/utils'; export default function PercentOfCalc() { const [percentValue, setPercentValue] = useState(''); @@ -118,8 +119,8 @@ export default function PercentOfCalc() { aria-readonly="true" aria-label="Calculation result" aria-describedby={`${componentName}-formula`} - className="font-bold" - defaultValue={result ?? ''} + className={cn(result !== null && 'font-bold')} + value={result ?? ''} readOnly /> diff --git a/app/components/pages/calculators/percentage/value-increase-decrease-percent.tsx b/app/components/pages/calculators/percentage/value-increase-decrease-percent.tsx index 70591cb..1e80fd6 100644 --- a/app/components/pages/calculators/percentage/value-increase-decrease-percent.tsx +++ b/app/components/pages/calculators/percentage/value-increase-decrease-percent.tsx @@ -4,6 +4,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } import { Field, FieldGroup, FieldLabel } from '~/components/ui/field'; import { Input } from '~/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; +import { cn } from '~/lib/utils'; const Action = { INCREASE: 'increase', @@ -135,7 +136,7 @@ export default function ValueIncreaseDecreasePercent() { aria-label="Select action" > - + @@ -176,7 +177,7 @@ export default function ValueIncreaseDecreasePercent() { aria-readonly="true" aria-label="Calculation result" aria-describedby={`${componentName}-formula`} - className="font-bold" + className={cn(result !== null && 'font-bold')} value={result === null ? '' : String(Number(result.toFixed(2)))} readOnly /> diff --git a/app/components/pages/programming/base64-to-image.tsx b/app/components/pages/programming/base64-to-image.tsx index 3192949..018de9c 100644 --- a/app/components/pages/programming/base64-to-image.tsx +++ b/app/components/pages/programming/base64-to-image.tsx @@ -86,11 +86,11 @@ export default function Base64ToImage() { Download Format {fieldState.error && } - - + - + {['png', 'jpeg', 'webp'].map((format) => ( {format.toUpperCase()} @@ -136,12 +136,13 @@ export default function Base64ToImage() { variant="outline" aria-label="Download Image" disabled={!result} - asChild={!!result} - > - - Download - - + nativeButton={false} + render={ + + Download + + } + />
diff --git a/app/components/pages/utilities/canvas/canvas-text-controls.tsx b/app/components/pages/utilities/canvas/canvas-text-controls.tsx index cb67a93..1cfb83d 100644 --- a/app/components/pages/utilities/canvas/canvas-text-controls.tsx +++ b/app/components/pages/utilities/canvas/canvas-text-controls.tsx @@ -188,11 +188,13 @@ export default function CanvasTextControls({ - - - - - + + + + } + />

Text content

@@ -219,8 +221,8 @@ export default function CanvasTextControls({ {/* FONT FAMILY */} field.onChange(val === undefined ? undefined : Number(val))} - defaultValue={undefined} - > + @@ -180,7 +175,13 @@ export default function TextToSpeech() { />
- + diff --git a/app/components/pages/utilities/url-slug-generator.tsx b/app/components/pages/utilities/url-slug-generator.tsx index 4e471a4..dc3948f 100644 --- a/app/components/pages/utilities/url-slug-generator.tsx +++ b/app/components/pages/utilities/url-slug-generator.tsx @@ -103,8 +103,6 @@ export default function UrlSlugGenerator() { async (data: SlugifyType) => { const { text, separator, lowercase, removeSpecialChars, removeNumbers } = data; - console.log(data); - if (!savedCharMap.current || !savedLocale.current) { const [resCM, resLol] = await Promise.all([ fetch('/slug-settings/charmap.json'), @@ -154,6 +152,7 @@ export default function UrlSlugGenerator() { : reduced; const slug = afterSpecials + .trim() .replaceAll(/\s+/g, separator) .replaceAll(new RegExp(`\\${separator}+`, 'g'), separator) .trim(); @@ -206,8 +205,8 @@ export default function UrlSlugGenerator() {
Separator diff --git a/app/components/ui/action-bar.tsx b/app/components/ui/action-bar.tsx index e9a4105..2c26055 100644 --- a/app/components/ui/action-bar.tsx +++ b/app/components/ui/action-bar.tsx @@ -1,7 +1,9 @@ -import { Direction as DirectionPrimitive, Slot as SlotPrimitive } from 'radix-ui'; +import { mergeProps } from '@base-ui/react/merge-props'; +import { useRender } from '@base-ui/react/use-render'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Button } from '~/components/ui/button'; +import { useDirection } from '~/components/ui/direction'; import { useAsRef } from '~/hooks/use-as-ref'; import { useIsomorphicLayoutEffect } from '~/hooks/use-isomorphic-layout-effect'; import { useComposedRefs } from '~/lib/compose-refs'; @@ -19,13 +21,10 @@ const EVENT_OPTIONS = { bubbles: false, cancelable: true }; type Direction = 'ltr' | 'rtl'; type Orientation = 'horizontal' | 'vertical'; -interface DivProps extends React.ComponentProps<'div'> { - asChild?: boolean; -} +interface DivProps extends useRender.ComponentProps<'div'>, React.ComponentProps<'div'> {} type RootElement = React.ComponentRef; type ItemElement = React.ComponentRef; -type CloseElement = React.ComponentRef; function focusFirst(candidates: React.RefObject[], preventScroll = false) { const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement; @@ -122,7 +121,7 @@ function ActionBar(props: Readonly) { className, style, ref, - asChild, + render, ...rootProps } = props; @@ -136,7 +135,8 @@ function ActionBar(props: Readonly) { onOpenChange, }); - const dir = DirectionPrimitive.useDirection(dirProp); + const contextDir = useDirection(); + const dir = dirProp ?? contextDir; React.useLayoutEffect(() => { setMounted(true); @@ -172,65 +172,74 @@ function ActionBar(props: Readonly) { const portalContainer = portalContainerProp ?? (mounted ? globalThis.document?.body : null); - if (!portalContainer || !open) return null; + const element = useRender({ + defaultTagName: 'div', + props: mergeProps<'div'>( + { + role: 'toolbar' as const, + 'aria-orientation': orientation, + dir, + ref: composedRef, + className: cn( + 'fixed z-50 rounded-lg border bg-card shadow-lg outline-none', + 'animate-in duration-250 fade-in-0 zoom-in-95 [animation-timing-function:cubic-bezier(0.16,1,0.3,1)]', + 'data-[side=bottom]:slide-in-from-bottom-4 data-[side=top]:slide-in-from-top-4', + 'motion-reduce:animate-none motion-reduce:transition-none', + orientation === 'horizontal' + ? 'flex flex-row items-center gap-2 px-2 py-1.5' + : 'flex flex-col items-start gap-2 px-1.5 py-2', + className, + ), + style: { + [side]: `${sideOffset}px`, + ...(align === 'center' && { + left: '50%', + translate: '-50% 0', + }), + ...(align === 'start' && { left: `${alignOffset}px` }), + ...(align === 'end' && { right: `${alignOffset}px` }), + ...style, + }, + }, + rootProps, + ), + render, + state: { + slot: 'action-bar', + side, + align, + orientation, + }, + }); - const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div'; + if (!portalContainer || !open) return null; return ( - {ReactDOM.createPortal( - , - portalContainer, - )} + {ReactDOM.createPortal(element, portalContainer)} ); } -function ActionBarSelection(props: Readonly) { - const { className, asChild, ...selectionProps } = props; - - const SelectionPrimitive = asChild ? SlotPrimitive.Slot : 'div'; +function ActionBarSelection(props: DivProps) { + const { className, render, ...selectionProps } = props; - return ( - - ); + return useRender({ + defaultTagName: 'div', + props: mergeProps<'div'>( + { + className: cn( + 'flex items-center gap-1 rounded-sm border px-2 py-1 text-sm font-medium tabular-nums', + className, + ), + }, + selectionProps, + ), + render, + state: { + slot: 'action-bar-selection', + }, + }); } function ActionBarGroup(props: Readonly) { @@ -239,7 +248,7 @@ function ActionBarGroup(props: Readonly) { onFocus: onFocusProp, onMouseDown: onMouseDownProp, className, - asChild, + render, ref, ...groupProps } = props; @@ -364,29 +373,33 @@ function ActionBarGroup(props: Readonly) { ], ); - const GroupPrimitive = asChild ? SlotPrimitive.Slot : 'div'; - - return ( - - ( + { + role: 'group' as const, + dir, + tabIndex: isTabbingBackOut || focusableItemCount === 0 ? -1 : 0, + ref: composedRef, + className: cn( 'flex gap-2 outline-none', orientation === 'horizontal' ? 'items-center' : 'w-full flex-col items-start', className, - )} - onBlur={onBlur} - onFocus={onFocus} - onMouseDown={onMouseDown} - /> - - ); + ), + onBlur, + onFocus, + onMouseDown, + }, + groupProps, + ), + render, + state: { + slot: 'action-bar-group', + orientation, + }, + }); + + return {element}; } interface ActionBarItemProps extends Omit, 'onSelect'> { @@ -435,8 +448,8 @@ function ActionBarItem(props: Readonly) { }; }, [focusContext, itemId, disabled]); - const onClick = React.useCallback( - (event: React.MouseEvent) => { + const onClick: ActionBarItemProps['onClick'] = React.useCallback( + (event) => { onClickProp?.(event); if (event.defaultPrevented) return; @@ -448,7 +461,7 @@ function ActionBarItem(props: Readonly) { cancelable: true, }); - item.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { + item.addEventListener(ITEM_SELECT, (evt) => onSelect?.(evt), { once: true, }); @@ -461,8 +474,8 @@ function ActionBarItem(props: Readonly) { [onClickProp, onOpenChange, onSelect], ); - const onFocus = React.useCallback( - (event: React.FocusEvent) => { + const onFocus: ActionBarItemProps['onFocus'] = React.useCallback( + (event) => { onFocusProp?.(event); if (event.defaultPrevented) return; @@ -472,8 +485,8 @@ function ActionBarItem(props: Readonly) { [onFocusProp, focusContext, itemId], ); - const onKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { + const onKeyDown: ActionBarItemProps['onKeyDown'] = React.useCallback( + (event) => { onKeyDownProp?.(event); if (event.defaultPrevented) return; @@ -520,8 +533,8 @@ function ActionBarItem(props: Readonly) { [onKeyDownProp, focusContext, dir, orientation, loop], ); - const onMouseDown = React.useCallback( - (event: React.MouseEvent) => { + const onMouseDown: ActionBarItemProps['onMouseDown'] = React.useCallback( + (event) => { onMouseDownProp?.(event); if (event.defaultPrevented) return; @@ -555,17 +568,13 @@ function ActionBarItem(props: Readonly) { ); } -interface ActionBarCloseProps extends React.ComponentProps<'button'> { - asChild?: boolean; -} - -function ActionBarClose(props: Readonly) { - const { asChild, className, onClick, ...closeProps } = props; +interface ActionBarCloseProps extends useRender.ComponentProps<'button'>, React.ComponentProps<'button'> {} +function ActionBarClose({ render, className, onClick, ...props }: ActionBarCloseProps) { const { onOpenChange } = useActionBarContext(CLOSE_NAME); - const onCloseClick = React.useCallback( - (event: React.MouseEvent) => { + const onCloseClick: ActionBarCloseProps['onClick'] = React.useCallback( + (event) => { onClick?.(event); if (event.defaultPrevented) return; @@ -574,48 +583,57 @@ function ActionBarClose(props: Readonly) { [onOpenChange, onClick], ); - const ClosePrimitive = asChild ? SlotPrimitive.Slot : 'button'; - - return ( - - ); + return useRender({ + defaultTagName: 'button', + props: mergeProps<'button'>( + { + type: 'button' as const, + className: cn( + "rounded-xs opacity-70 outline-none hover:opacity-100 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", + className, + ), + onClick: onCloseClick, + }, + props, + ), + render, + state: { + slot: 'action-bar-close', + }, + }); } -interface ActionBarSeparatorProps extends Readonly { +interface ActionBarSeparatorProps extends DivProps { orientation?: Orientation; } -function ActionBarSeparator(props: Readonly) { - const { orientation: orientationProp, asChild, className, ...separatorProps } = props; +function ActionBarSeparator(props: ActionBarSeparatorProps) { + const { orientation: orientationProp, render, className, ...separatorProps } = props; const context = useActionBarContext(SEPARATOR_NAME); const orientation = orientationProp ?? context.orientation; - const SeparatorPrimitive = asChild ? SlotPrimitive.Slot : 'div'; - - return ( -
); } -function DialogTitle({ className, ...props }: React.ComponentProps) { +function DialogTitle({ className, ...props }: Readonly) { return ( ) { +function DialogDescription({ className, ...props }: Readonly) { return ( ) { - return ; +function DropdownMenu({ ...props }: Readonly) { + return ; } -function DropdownMenuPortal({ ...props }: React.ComponentProps) { - return ; +function DropdownMenuPortal({ ...props }: Readonly) { + return ; } -function DropdownMenuTrigger({ ...props }: React.ComponentProps) { - return ; +function DropdownMenuTrigger({ ...props }: Readonly) { + return ; } function DropdownMenuContent({ - className, align = 'start', + alignOffset = 0, + side = 'bottom', sideOffset = 4, + className, ...props -}: React.ComponentProps) { +}: MenuPrimitive.Popup.Props & Pick) { return ( - - + - + alignOffset={alignOffset} + side={side} + sideOffset={sideOffset} + > + + + ); } -function DropdownMenuGroup({ ...props }: React.ComponentProps) { - return ; +function DropdownMenuGroup({ ...props }: Readonly) { + return ; +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: MenuPrimitive.GroupLabel.Props & { + inset?: boolean; +}) { + return ( + + ); } function DropdownMenuItem({ @@ -47,12 +72,12 @@ function DropdownMenuItem({ inset, variant = 'default', ...props -}: React.ComponentProps & { +}: MenuPrimitive.Item.Props & { inset?: boolean; variant?: 'default' | 'destructive'; }) { return ( - ) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: MenuPrimitive.SubmenuTrigger.Props & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + align = 'start', + alignOffset = -3, + side = 'inline-end', + sideOffset = 0, + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props -}: React.ComponentProps & { +}: MenuPrimitive.CheckboxItem.Props & { inset?: boolean; }) { return ( - - + - + {children} - + ); } -function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) { - return ; +function DropdownMenuRadioGroup({ ...props }: Readonly) { + return ; } function DropdownMenuRadioItem({ @@ -107,11 +184,11 @@ function DropdownMenuRadioItem({ children, inset, ...props -}: React.ComponentProps & { +}: MenuPrimitive.RadioItem.Props & { inset?: boolean; }) { return ( - - + - + {children} - + ); } -function DropdownMenuLabel({ - className, - inset, - ...props -}: React.ComponentProps & { - inset?: boolean; -}) { +function DropdownMenuSeparator({ className, ...props }: Readonly) { return ( - - ); -} - -function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) { - return ( - ) { - return ; -} - -function DropdownMenuSubTrigger({ - className, - inset, - children, - ...props -}: React.ComponentProps & { - inset?: boolean; -}) { - return ( - - {children} - - - ); -} - -function DropdownMenuSubContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - export { DropdownMenu, DropdownMenuCheckboxItem, diff --git a/app/components/ui/dropzone.tsx b/app/components/ui/dropzone.tsx index 13ecf09..2687618 100644 --- a/app/components/ui/dropzone.tsx +++ b/app/components/ui/dropzone.tsx @@ -8,10 +8,10 @@ import { cn } from '~/lib/utils'; interface DropzoneContextType { src?: File[]; - accept?: DropzoneOptions['accept']; - maxSize?: DropzoneOptions['maxSize']; - minSize?: DropzoneOptions['minSize']; - maxFiles?: DropzoneOptions['maxFiles']; + accept: DropzoneOptions['accept']; + maxSize: DropzoneOptions['maxSize']; + minSize: DropzoneOptions['minSize']; + maxFiles: DropzoneOptions['maxFiles']; } const renderBytes = (bytes: number) => { diff --git a/app/components/ui/empty.tsx b/app/components/ui/empty.tsx index 441309c..228c36c 100644 --- a/app/components/ui/empty.tsx +++ b/app/components/ui/empty.tsx @@ -88,4 +88,4 @@ function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) { ); } -export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle }; +export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia }; diff --git a/app/components/ui/input-group.tsx b/app/components/ui/input-group.tsx index 5e5f699..986689d 100644 --- a/app/components/ui/input-group.tsx +++ b/app/components/ui/input-group.tsx @@ -81,7 +81,10 @@ function InputGroupButton({ variant = 'ghost', size = 'xs', ...props -}: Omit, 'size'> & VariantProps) { +}: Omit, 'size' | 'type'> & + VariantProps & { + type?: 'button' | 'submit' | 'reset'; + }) { return ( + } + > + + Close )} - + ); } @@ -78,7 +79,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) { return
; } -function SheetTitle({ className, ...props }: React.ComponentProps) { +function SheetTitle({ className, ...props }: Readonly) { return ( ) { +function SheetDescription({ className, ...props }: Readonly) { return ( void; }) { - const fetcher = useFetcher(); const { isMobile } = useIsMobile(); const [openMobile, setOpenMobile] = React.useState(false); @@ -70,13 +71,7 @@ function SidebarProvider({ } // This sets the cookie to keep the sidebar state. - fetcher.submit( - { sidebar_state: openState.valueOf() }, - { - method: 'post', - action: '/sidebar', - }, - ); + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; }, [setOpenProp, open], ); @@ -95,8 +90,8 @@ function SidebarProvider({ } }; - globalThis.window.addEventListener('keydown', handleKeyDown); - return () => globalThis.window.removeEventListener('keydown', handleKeyDown); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); }, [toggleSidebar]); // We add a state so that we can do data-state="expanded" or "collapsed". @@ -265,6 +260,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) { return ( + - - + + + +