diff --git a/apps/frontend/src/assets/city-bg.png b/apps/frontend/src/assets/city-bg.png new file mode 100644 index 0000000..cabbd66 Binary files /dev/null and b/apps/frontend/src/assets/city-bg.png differ diff --git a/apps/frontend/src/assets/fcc-mark.png b/apps/frontend/src/assets/fcc-mark.png new file mode 100644 index 0000000..963b919 Binary files /dev/null and b/apps/frontend/src/assets/fcc-mark.png differ diff --git a/apps/frontend/src/components/ui/input-group.tsx b/apps/frontend/src/components/ui/input-group.tsx new file mode 100644 index 0000000..ad53e41 --- /dev/null +++ b/apps/frontend/src/components/ui/input-group.tsx @@ -0,0 +1,156 @@ +'use client'; + +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@lib/utils'; +import { Button } from '@components/ui/button'; +import { Input } from '@components/ui/input'; +import { Textarea } from '@components/ui/textarea'; + +function InputGroup({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto', + className, + )} + {...props} + /> + ); +} + +const inputGroupAddonVariants = cva( + "text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none", + { + variants: { + align: { + 'inline-start': + 'pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first', + 'inline-end': + 'pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last', + 'block-start': + 'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start', + 'block-end': + 'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start', + }, + }, + defaultVariants: { + align: 'inline-start', + }, + }, +); + +function InputGroupAddon({ + className, + align = 'inline-start', + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
{ + if ((e.target as HTMLElement).closest('button')) { + return; + } + e.currentTarget.parentElement?.querySelector('input')?.focus(); + }} + {...props} + /> + ); +} + +const inputGroupButtonVariants = cva( + 'gap-2 text-sm flex items-center shadow-none', + { + variants: { + size: { + xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5", + sm: '', + 'icon-xs': + 'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0', + 'icon-sm': 'size-8 p-0 has-[>svg]:p-0', + }, + }, + defaultVariants: { + size: 'xs', + }, + }, +); + +function InputGroupButton({ + className, + type = 'button', + variant = 'ghost', + size = 'xs', + ...props +}: Omit, 'size'> & + VariantProps) { + return ( +