2026-01-15 23:26:43 +00:00
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
import { Button, ButtonProps } from './button';
|
|
|
|
|
|
|
|
|
|
export interface FABProps extends Omit<ButtonProps, 'size' | 'variant'> {
|
|
|
|
|
/**
|
|
|
|
|
* Position of the FAB on the screen
|
|
|
|
|
* @default 'bottom-right'
|
|
|
|
|
*/
|
|
|
|
|
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Size of the FAB
|
|
|
|
|
* @default 'lg'
|
|
|
|
|
*/
|
|
|
|
|
size?: 'sm' | 'md' | 'lg';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether to show a label next to the FAB
|
|
|
|
|
* @default false
|
|
|
|
|
*/
|
|
|
|
|
showLabel?: boolean;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Label text (only shown if showLabel is true)
|
|
|
|
|
*/
|
|
|
|
|
label?: string;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Additional CSS classes for the container
|
|
|
|
|
*/
|
|
|
|
|
containerClassName?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const positionClasses = {
|
|
|
|
|
'bottom-right': 'bottom-6 right-6',
|
|
|
|
|
'bottom-left': 'bottom-6 left-6',
|
|
|
|
|
'top-right': 'top-6 right-6',
|
|
|
|
|
'top-left': 'top-6 left-6',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sizeClasses = {
|
|
|
|
|
sm: 'h-12 w-12 text-base',
|
|
|
|
|
md: 'h-14 w-14 text-lg',
|
|
|
|
|
lg: 'h-16 w-16 text-xl',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FAB (Floating Action Button) - Floating action button component
|
|
|
|
|
*
|
|
|
|
|
* A prominent floating button that appears fixed on the screen, typically
|
|
|
|
|
* used for primary actions like upload, create, or add.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```tsx
|
|
|
|
|
* <FAB onClick={() => handleUpload()}>
|
|
|
|
|
* <Plus className="w-6 h-6" />
|
|
|
|
|
* </FAB>
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```tsx
|
|
|
|
|
* <FAB
|
|
|
|
|
* position="bottom-right"
|
|
|
|
|
* size="lg"
|
|
|
|
|
* showLabel
|
|
|
|
|
* label="Upload Track"
|
|
|
|
|
* onClick={() => navigate('/upload')}
|
|
|
|
|
* >
|
|
|
|
|
* <Upload className="w-6 h-6" />
|
|
|
|
|
* </FAB>
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export function FAB({
|
|
|
|
|
position = 'bottom-right',
|
|
|
|
|
size = 'lg',
|
|
|
|
|
showLabel = false,
|
|
|
|
|
label,
|
|
|
|
|
className,
|
|
|
|
|
containerClassName,
|
|
|
|
|
children,
|
|
|
|
|
...buttonProps
|
|
|
|
|
}: FABProps) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
'fixed z-50 flex items-center gap-4',
|
2026-01-15 23:26:43 +00:00
|
|
|
positionClasses[position],
|
|
|
|
|
containerClassName,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{showLabel && label && (
|
2026-02-12 01:09:29 +00:00
|
|
|
<span className="text-sm font-semibold text-foreground bg-card/90 backdrop-blur-md px-4 py-2 rounded-lg border border-white/10 shadow-lg whitespace-nowrap">
|
2026-01-15 23:26:43 +00:00
|
|
|
{label}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<Button
|
consistency: remove unused button variants (neon, glass, premium, link)
- Removed neon, glass, premium, and link variants from Button component
- Replaced variant="link" in PostCard with variant="ghost" (with underline)
- Replaced variant="premium" in LibraryPage and FAB with variant="default"
- Updated COMPONENT_USAGE.md to reflect removed variants
- Remaining variants: default, destructive, outline, secondary, ghost
- Action 9.3.1.2 complete
2026-01-16 01:13:51 +00:00
|
|
|
variant="default"
|
2026-02-09 22:04:35 +00:00
|
|
|
aria-label={label || 'Action'}
|
2026-01-15 23:26:43 +00:00
|
|
|
className={cn(
|
|
|
|
|
'rounded-full aspect-square p-0',
|
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
|
|
|
'shadow-fab-glow shadow-fab-glow-hover',
|
2026-02-12 01:09:29 +00:00
|
|
|
'transition-all duration-[var(--sumi-duration-normal)]',
|
2026-01-16 11:06:00 +00:00
|
|
|
'active:opacity-80',
|
2026-01-15 23:26:43 +00:00
|
|
|
sizeClasses[size],
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
{...buttonProps}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|