2026-01-22 16:23:11 +00:00
|
|
|
import * as React from 'react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
2026-02-09 22:31:52 +00:00
|
|
|
import { useId, useState } from 'react';
|
|
|
|
|
import { Eye, EyeOff } from 'lucide-react';
|
2026-01-22 16:23:11 +00:00
|
|
|
|
|
|
|
|
export interface FloatingInputProps
|
|
|
|
|
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
|
|
|
label: string;
|
|
|
|
|
error?: string;
|
|
|
|
|
icon?: React.ReactNode;
|
2026-02-09 22:31:52 +00:00
|
|
|
showPasswordToggle?: boolean;
|
2026-01-22 16:23:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FloatingInput = React.forwardRef<HTMLInputElement, FloatingInputProps>(
|
2026-02-09 22:31:52 +00:00
|
|
|
({ className, label, error, icon, type, id, showPasswordToggle, ...props }, ref) => {
|
2026-01-22 16:23:11 +00:00
|
|
|
const generatedId = useId();
|
|
|
|
|
const inputId = id || generatedId;
|
2026-02-09 22:31:52 +00:00
|
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
|
const inputType = type === 'password' && showPassword ? 'text' : type;
|
|
|
|
|
const hasToggle = type === 'password' && showPasswordToggle;
|
2026-01-22 16:23:11 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative group w-full mb-5">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<input
|
2026-02-09 22:31:52 +00:00
|
|
|
type={inputType}
|
2026-01-22 16:23:11 +00:00
|
|
|
id={inputId}
|
|
|
|
|
className={cn(
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
"block px-4 pb-2.5 pt-5 w-full text-sm text-white bg-kodo-graphite/40 rounded-xl border appearance-none focus:outline-none focus:ring-0 peer transition-all duration-[var(--duration-fast)]",
|
2026-02-09 22:31:52 +00:00
|
|
|
// Focus glow
|
|
|
|
|
"focus:shadow-[0_0_0_3px_oklch(var(--primary)/0.15),0_0_12px_oklch(var(--primary)/0.1)]",
|
2026-01-22 16:23:11 +00:00
|
|
|
// Borders & Colors
|
|
|
|
|
error
|
|
|
|
|
? "border-kodo-red focus:border-kodo-red"
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
: "border-white/10 hover:border-white/20 focus:border-primary",
|
2026-01-22 16:23:11 +00:00
|
|
|
// Glassmorphism
|
|
|
|
|
"backdrop-blur-sm",
|
|
|
|
|
icon ? "pl-11" : "",
|
2026-02-09 22:31:52 +00:00
|
|
|
hasToggle ? "pr-10" : "",
|
2026-01-22 16:23:11 +00:00
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
placeholder=" "
|
|
|
|
|
ref={ref}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Icon */}
|
|
|
|
|
{icon && (
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-kodo-text-dim peer-focus:text-primary transition-colors duration-[var(--duration-fast)] pointer-events-none">
|
2026-01-22 16:23:11 +00:00
|
|
|
{icon}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-02-09 22:31:52 +00:00
|
|
|
{/* Password visibility toggle */}
|
|
|
|
|
{hasToggle && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
|
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-opacity transition-colors duration-[var(--duration-fast)]"
|
|
|
|
|
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
>
|
|
|
|
|
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-01-22 16:23:11 +00:00
|
|
|
{/* Floating Label */}
|
|
|
|
|
<label
|
|
|
|
|
htmlFor={inputId}
|
|
|
|
|
className={cn(
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
"absolute text-sm duration-[var(--duration-fast)] transform -translate-y-3 scale-75 top-4 z-10 origin-[0] peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-3 pointer-events-none transition-transform",
|
2026-01-22 16:23:11 +00:00
|
|
|
icon ? "left-11 peer-focus:left-11 peer-placeholder-shown:left-11" : "left-4 peer-focus:left-4 peer-placeholder-shown:left-4",
|
|
|
|
|
error
|
2026-02-08 23:14:40 +00:00
|
|
|
? "text-destructive"
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
: "text-kodo-text-dim peer-focus:text-primary group-hover:text-white/70"
|
2026-01-22 16:23:11 +00:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{label}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Error Message */}
|
|
|
|
|
{error && (
|
2026-02-09 22:31:52 +00:00
|
|
|
<p className="mt-1 text-xs text-destructive animate-shake">
|
2026-01-22 16:23:11 +00:00
|
|
|
{error}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
FloatingInput.displayName = 'FloatingInput';
|
|
|
|
|
|
|
|
|
|
export { FloatingInput };
|