refactor: remove pagination and toast components along with related styles and types

- Deleted TablePagination component and its associated styles and types.
- Removed pagination-related exports from index files.
- Eliminated sonner toast implementation including its context, container, and configuration.
- Cleaned up unused form and table components, including FieldGroup, ValidationSummary, Body, EmptyState, Header, and their respective types.
This commit is contained in:
2026-01-02 22:28:11 +00:00
parent 9dab4999c0
commit feac579bef
283 changed files with 0 additions and 15692 deletions
@@ -1,18 +0,0 @@
'use client'
import { useRouter } from 'next/navigation'
import { Level1 } from '@/components/Level1'
import type { AppLevel } from '@/lib/level-types'
import { getLevelPath } from '@/lib/navigation/get-level-path'
export function Level1Client() {
const router = useRouter()
const handleNavigate = (level: number) => {
const normalizedLevel = Math.min(6, Math.max(1, level)) as AppLevel
router.push(getLevelPath(normalizedLevel))
}
return <Level1 onNavigate={handleNavigate} />
}
@@ -1,15 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import LevelsClient from './LevelsClient'
describe('LevelsClient', () => {
it('renders permission levels and promotes to the next tier', () => {
render(<LevelsClient />)
expect(screen.getByText(/Level 1 · Public/)).toBeTruthy()
const promoteButton = screen.getByRole('button', { name: /Promote to/ })
fireEvent.click(promoteButton)
expect(screen.getByText(/Upgraded to User/)).toBeTruthy()
})
})
@@ -1,75 +0,0 @@
'use client'
import { useMemo, useState } from 'react'
import { Container, Stack, Typography } from '@/fakemui'
import { LevelDetails } from './components/LevelDetails'
import { LevelsGrid } from './components/LevelsGrid'
import { PERMISSION_LEVELS } from './levels-data'
export default function LevelsClient() {
const [selectedLevelId, setSelectedLevelId] = useState(PERMISSION_LEVELS[0].id)
const [note, setNote] = useState('')
const selectedLevel = useMemo(
() => PERMISSION_LEVELS.find(level => level.id === selectedLevelId) ?? PERMISSION_LEVELS[0],
[selectedLevelId]
)
const nextLevel = useMemo(
() => PERMISSION_LEVELS.find(level => level.id === selectedLevelId + 1) ?? null,
[selectedLevelId]
)
const maxCapabilityCount = useMemo(
() => Math.max(...PERMISSION_LEVELS.map(level => level.capabilities.length)),
[]
)
const handleSelect = (levelId: number) => {
setSelectedLevelId(levelId)
setNote(
`Selected ${PERMISSION_LEVELS.find(l => l.id === levelId)?.title ?? 'unknown'} privileges.`
)
}
const handlePromote = () => {
if (!nextLevel) {
setNote('You already command the cosmos. No further promotions available.')
return
}
setSelectedLevelId(nextLevel.id)
setNote(`Upgraded to ${nextLevel.title}.`)
}
return (
<Container maxWidth="lg" sx={{ py: 8 }}>
<Stack spacing={4}>
<Stack spacing={1}>
<Typography variant="h3" component="h1">
The Six Permission Levels
</Typography>
<Typography color="text.secondary">
Level up through Public, User, Moderator, Admin, God, and Super God to unlock the right
controls for your role.
</Typography>
</Stack>
<LevelsGrid
levels={PERMISSION_LEVELS}
onSelect={handleSelect}
selectedLevelId={selectedLevelId}
/>
<LevelDetails
selectedLevel={selectedLevel}
nextLevel={nextLevel}
maxCapabilityCount={maxCapabilityCount}
note={note}
onPromote={handlePromote}
/>
</Stack>
</Container>
)
}
@@ -1,90 +0,0 @@
import {
Alert,
Box,
Button,
Chip,
Divider,
LinearProgress,
Paper,
Stack,
Typography,
} from '@/fakemui'
import type { PermissionLevel } from '../levels-data'
import { highlightColor } from '../utils/highlightColor'
type LevelDetailsProps = {
selectedLevel: PermissionLevel
nextLevel: PermissionLevel | null
maxCapabilityCount: number
note: string
onPromote: () => void
}
export const LevelDetails = ({
selectedLevel,
nextLevel,
maxCapabilityCount,
note,
onPromote,
}: LevelDetailsProps) => (
<Paper
sx={{
p: 4,
border: theme => `1px dashed ${theme.palette.divider}`,
bgcolor: 'background.paper',
}}
>
<Stack spacing={2}>
<Stack direction="row" alignItems="center" spacing={1}>
<Typography variant="h5">Selected level details</Typography>
<Chip label={selectedLevel.badge} size="small" color="secondary" />
</Stack>
<Typography variant="body1" color="text.secondary">
{selectedLevel.description}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{selectedLevel.capabilities.map(capability => (
<Chip
key={capability}
label={capability}
size="small"
sx={{ borderColor: highlightColor(selectedLevel) }}
/>
))}
</Stack>
<Stack spacing={1}>
<LinearProgress
variant="determinate"
value={(selectedLevel.capabilities.length / maxCapabilityCount) * 100}
sx={{ height: 10, borderRadius: 2 }}
/>
<Typography variant="body2" color="text.secondary">
{selectedLevel.capabilities.length} of {maxCapabilityCount} capability tiers unlocked
</Typography>
</Stack>
<Divider />
<Box>
<Typography variant="subtitle2" gutterBottom>
Next move
</Typography>
{nextLevel ? (
<Typography variant="body2" color="text.secondary">
Promote into <strong>{nextLevel.title}</strong> to unlock{' '}
{nextLevel.capabilities.length} controls.
</Typography>
) : (
<Typography variant="body2" color="text.secondary">
Super God reigns supreme. You already own every privilege.
</Typography>
)}
</Box>
<Box>
<Button variant="contained" onClick={onPromote}>
{nextLevel ? `Promote to ${nextLevel.title}` : 'Hold the crown'}
</Button>
</Box>
{note && <Alert severity="info">{note}</Alert>}
</Stack>
</Paper>
)
@@ -1,50 +0,0 @@
import { Box, Chip, Grid, Paper, Stack, Typography } from '@/fakemui'
import type { PermissionLevel } from '../levels-data'
type LevelsGridProps = {
levels: PermissionLevel[]
selectedLevelId: number
onSelect: (levelId: number) => void
}
export const LevelsGrid = ({ levels, selectedLevelId, onSelect }: LevelsGridProps) => (
<Grid container spacing={3}>
{levels.map(level => (
<Grid item xs={12} md={6} lg={4} key={level.id} component="div">
<Paper
onClick={() => onSelect(level.id)}
sx={{
border: theme =>
`2px solid ${selectedLevelId === level.id ? theme.palette.primary.main : theme.palette.divider}`,
p: 3,
cursor: 'pointer',
position: 'relative',
'&:hover': {
borderColor: 'primary.main',
},
}}
elevation={selectedLevelId === level.id ? 6 : 1}
>
<Box sx={{ position: 'absolute', top: 16, right: 16 }}>
<Chip label={level.badge} />
</Box>
<Typography variant="h6">
Level {level.id} · {level.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{level.tagline}
</Typography>
<Typography variant="body2" sx={{ mb: 2 }}>
{level.description}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{level.capabilities.slice(0, 3).map(capability => (
<Chip key={capability} label={capability} size="small" variant="outlined" />
))}
</Stack>
</Paper>
</Grid>
))}
</Grid>
)
@@ -1,100 +0,0 @@
export type PermissionLevel = {
id: number
key: string
title: string
description: string
badge: string
capabilities: string[]
tagline: string
}
export const PERMISSION_LEVELS: PermissionLevel[] = [
{
id: 1,
key: 'public',
title: 'Public',
badge: '🌍',
description: 'Read-only access to marketing, help, and showcase pages without signing in.',
tagline: 'Open browsing with zero authentication.',
capabilities: [
'Access the landing experience',
'Follow feature stories',
'Preview public dashboards',
],
},
{
id: 2,
key: 'user',
title: 'User',
badge: '🧑‍💻',
description:
'Personalized workspace for building content, saving dashboards, and collaborating.',
tagline: 'Everyday contributors and team members.',
capabilities: [
'Edit personal settings',
'Manage own content',
'Launch saved dashboards',
'Join shared workflows',
],
},
{
id: 3,
key: 'moderator',
title: 'Moderator',
badge: '🛡️',
description:
'Protect the community by triaging flags, reviewing reports, and shaping shared spaces.',
tagline: 'Guardians of behavior and tone.',
capabilities: [
'Moderate discussions',
'Resolve user flags',
'Review incident reports',
'Hide or restore content',
],
},
{
id: 4,
key: 'admin',
title: 'Admin',
badge: '🧰',
description:
'Tenant administrators who manage users, billing, policies, and broader content sets.',
tagline: 'Operational control for the tenant layer.',
capabilities: [
'Manage user accounts',
'Adjust tenant settings',
'Approve packages',
'Oversee moderation queue',
],
},
{
id: 5,
key: 'god',
title: 'God',
badge: '🧙‍♂️',
description:
'Blueprint builders who orchestrate workflows, seed packages, and shape the system architecture.',
tagline: 'Power users with advanced scripting rights.',
capabilities: [
'Author workflows',
'Compose the builder UI',
'Define multi-tenant templates',
'Seed packages',
],
},
{
id: 6,
key: 'supergod',
title: 'Super God',
badge: '👑',
description:
'Full sovereignty over every tenant, infrastructure, and override path in the universe.',
tagline: 'Ultimate authority for platform-level change.',
capabilities: [
'Assign god roles',
'Transfer ownership',
'Burn and restore tenants',
'Run system-wide audits',
],
},
]
-12
View File
@@ -1,12 +0,0 @@
import type { Metadata } from 'next'
import LevelsClient from './LevelsClient'
export const metadata: Metadata = {
title: 'Permission Levels',
description: 'Explore the five permission tiers that govern MetaBuilder.',
}
export default function LevelsPage() {
return <LevelsClient />
}
@@ -1,7 +0,0 @@
import type { PermissionLevel } from '../levels-data'
export const highlightColor = (level: PermissionLevel) => {
if (level.id === 6) return 'warning.main'
if (level.id === 5) return 'primary.main'
return 'divider'
}
@@ -1,115 +0,0 @@
# Atoms
Atoms are the smallest, indivisible UI elements in the MetaBuilder component library. Built on Material UI.
## Components
### Controls
| Component | Description | MUI Base |
|-----------|-------------|----------|
| `Button` | Primary action button with variants | `MuiButton` |
| `Checkbox` | Boolean toggle with optional label | `MuiCheckbox` |
| `Switch` | Toggle switch with optional label | `MuiSwitch` |
| `Radio` | Radio button with optional label | `MuiRadio` |
### Inputs
| Component | Description | MUI Base |
|-----------|-------------|----------|
| `Input` | Text input field | `InputBase` |
| `TextArea` | Multi-line text input | `TextareaAutosize` |
| `Select` | Dropdown selection | `MuiSelect` |
### Display
| Component | Description | MUI Base |
|-----------|-------------|----------|
| `Label` | Form field label | `Typography` |
| `Badge` | Status indicator chip | `Chip` |
| `Avatar` | User/entity image with fallback | `MuiAvatar` |
| `IconButton` | Icon-only button | `MuiIconButton` |
| `Icon` | Icon wrapper for fakemui icons | `@/fakemui/icons` |
| `Link` | Navigation link with Next.js integration | `MuiLink` + `NextLink` |
| `Text` | Typography with weight/alignment options | `Typography` |
### Feedback
| Component | Description | MUI Base |
|-----------|-------------|----------|
| `Skeleton` | Loading placeholder | `MuiSkeleton` |
| `Separator` | Visual divider | `Divider` |
| `Progress` | Progress indicator | `LinearProgress` |
| `Tooltip` | Hover information | `MuiTooltip` |
| `Spinner` | Loading spinner | `CircularProgress` |
## Usage
```typescript
import {
Button, Input, TextArea, Select, Radio,
Label, Badge, Icon, Link, Text
} from '@/components/atoms'
function MyComponent() {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Label required>Username</Label>
<Input placeholder="Enter username" />
<Label>Bio</Label>
<TextArea placeholder="Tell us about yourself" minRows={4} />
<Label>Country</Label>
<Select
options={[
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
]}
placeholder="Select country"
/>
<Radio label="Subscribe to newsletter" />
<Text variant="body2" muted>
By submitting, you agree to our <Link href="/terms">Terms</Link>
</Text>
<Button variant="contained" startIcon={<Icon name="Save" />}>
Save Profile
</Button>
<Badge variant="success" label="Active" />
</Box>
)
}
```
## Design Principles
1.**Single responsibility** - Each atom does one thing well
2.**MUI-powered** - Built on Material UI for consistency
3.**Theme-aware** - Respects light/dark mode via `sx` prop
4.**Accessible** - ARIA attributes and keyboard navigation
5.**Composable** - Combine to build molecules
6.**DON'T** import molecules or organisms
7.**DON'T** add business logic
8.**DON'T** use Tailwind classes (use MUI `sx` prop instead)
## Styling
All atoms use the MUI `sx` prop for styling, which provides:
- Theme-aware values (colors, spacing, typography)
- Responsive breakpoints
- Dark/light mode support
- Type-safe CSS properties
```tsx
<Button
sx={{
mt: 2, // theme.spacing(2)
bgcolor: 'primary.main',
'&:hover': {
bgcolor: 'primary.dark'
}
}}
>
Styled Button
</Button>
```
@@ -1,81 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Button as FakemuiButton } from '@/fakemui'
import type { ButtonProps as FakemuiButtonProps } from '@/fakemui/fakemui/inputs/Button'
/** Button visual style variants */
export type ButtonVariant = 'contained' | 'outlined' | 'text' | 'destructive' | 'ghost'
/** Button size options */
export type ButtonSize = 'small' | 'medium' | 'large' | 'icon'
/**
* Props for the Button component
* Wrapper around fakemui Button to maintain API compatibility
*/
export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'variant' | 'size'> {
/** Visual style variant of the button */
variant?: ButtonVariant
/** Size of the button */
size?: ButtonSize
/** Whether to show a loading spinner */
loading?: boolean
/** Compatibility prop - ignored */
asChild?: boolean
/** Start icon element */
startIcon?: React.ReactNode
/** End icon element */
endIcon?: React.ReactNode
/** Full width button */
fullWidth?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'contained', size = 'medium', loading, disabled, children, sx, startIcon, endIcon, fullWidth, className, ...props }, ref) => {
// Map MUI variants to fakemui variants
const fakemuiVariant: FakemuiButtonProps['variant'] =
variant === 'contained' ? 'primary' :
variant === 'outlined' ? 'outline' :
variant === 'text' ? 'text' :
variant === 'destructive' ? 'danger' :
variant === 'ghost' ? 'ghost' :
'default'
// Map MUI sizes to fakemui sizes
const fakemuiSize: FakemuiButtonProps['size'] =
size === 'small' ? 'sm' :
size === 'large' ? 'lg' :
'md'
const isIcon = size === 'icon'
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiButton
ref={ref}
variant={fakemuiVariant}
size={fakemuiSize}
icon={isIcon}
loading={loading}
disabled={disabled}
fullWidth={fullWidth}
startIcon={startIcon}
endIcon={endIcon}
className={combinedClassName}
{...props}
>
{children}
</FakemuiButton>
)
}
)
Button.displayName = 'Button'
export { Button }
@@ -1,40 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Checkbox as FakemuiCheckbox } from '@/fakemui'
/**
* Props for the Checkbox component
* Wrapper around fakemui Checkbox to maintain API compatibility
*/
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
/** Optional label text to display next to the checkbox */
label?: React.ReactNode
/** Whether the checkbox is in an error state (MUI compatibility) */
error?: boolean
/** MUI color prop (ignored for compatibility) */
color?: string
/** MUI size prop (ignored for compatibility) */
size?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(({ label, error, color, size, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className, error ? 'checkbox--error' : ''].filter(Boolean).join(' ')
return (
<FakemuiCheckbox
ref={ref}
label={label}
className={combinedClassName}
{...props}
/>
)
})
Checkbox.displayName = 'Checkbox'
export { Checkbox }
@@ -1,31 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Radio } from './Radio'
describe('Radio', () => {
it.each([
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
{ label: undefined, value: 'c' },
])('renders with props %o', ({ label, value }) => {
render(<Radio label={label} value={value} />)
const radio = screen.getByRole('radio')
expect(radio).not.toBeNull()
if (label) {
expect(screen.getByText(label)).not.toBeNull()
}
})
it('renders checked state', () => {
render(<Radio label="Selected" checked onChange={() => {}} />)
const radio = screen.getByRole('radio') as HTMLInputElement
expect(radio.checked).toBe(true)
})
it('renders disabled state', () => {
render(<Radio label="Disabled" disabled />)
const radio = screen.getByRole('radio') as HTMLInputElement
expect(radio.disabled).toBe(true)
})
})
@@ -1,38 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Radio as FakemuiRadio } from '@/fakemui'
/**
* Props for the Radio component
* Wrapper around fakemui Radio to maintain API compatibility
*/
export interface RadioProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
/** Optional label text to display next to the radio */
label?: React.ReactNode
/** MUI color prop (ignored for compatibility) */
color?: string
/** MUI size prop (ignored for compatibility) */
size?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Radio = forwardRef<HTMLInputElement, RadioProps>(({ label, color, size, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiRadio
ref={ref}
label={label}
className={combinedClassName}
{...props}
/>
)
})
Radio.displayName = 'Radio'
export { Radio }
@@ -1,38 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Switch as FakemuiSwitch } from '@/fakemui'
/**
* Props for the Switch component
* Wrapper around fakemui Switch to maintain API compatibility
*/
export interface SwitchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
/** Optional label text to display next to the switch */
label?: React.ReactNode
/** MUI color prop (ignored for compatibility) */
color?: string
/** MUI size prop (ignored for compatibility) */
size?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Switch = forwardRef<HTMLInputElement, SwitchProps>(({ label, color, size, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiSwitch
ref={ref}
label={label}
className={combinedClassName}
{...props}
/>
)
})
Switch.displayName = 'Switch'
export { Switch }
@@ -1,62 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Avatar as FakemuiAvatar, AvatarGroup as FakemuiAvatarGroup } from '@/fakemui'
/** Avatar size options */
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
/**
* Props for the Avatar component
* Wrapper around fakemui Avatar to maintain API compatibility
*/
export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
/** Size of the avatar */
size?: AvatarSize
/** Fallback text to display when no image is provided */
fallback?: string
/** Image source */
src?: string
/** Alt text for image */
alt?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Avatar = forwardRef<HTMLDivElement, AvatarProps>(
({ size = 'md', fallback, children, sx, src, alt, className, ...props }, ref) => {
// Map size to fakemui size props
const sizeProps = {
sm: size === 'xs' || size === 'sm',
md: size === 'md',
lg: size === 'lg',
xl: size === 'xl',
}
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiAvatar
ref={ref}
src={src}
alt={alt}
className={combinedClassName}
{...sizeProps}
{...props}
>
{children || fallback}
</FakemuiAvatar>
)
}
)
Avatar.displayName = 'Avatar'
// Re-export AvatarGroup and create compatibility components
const AvatarGroup = FakemuiAvatarGroup
const AvatarFallback = ({ children }: { children: React.ReactNode }) => <>{children}</>
const AvatarImage = Avatar
export { Avatar, AvatarFallback, AvatarGroup, AvatarImage }
@@ -1,63 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Chip } from '@/fakemui'
/** Badge visual style variants */
export type BadgeVariant =
| 'default'
| 'secondary'
| 'destructive'
| 'outline'
| 'success'
| 'warning'
/**
* Props for the Badge component
* Uses fakemui Chip component for badge functionality
*/
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
/** Visual style variant of the badge */
variant?: BadgeVariant
/** Label text */
label?: string | React.ReactNode
/** Size of the badge */
size?: 'small' | 'medium'
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** Whether badge is clickable */
clickable?: boolean
/** Delete handler */
onDelete?: () => void
}
const Badge = forwardRef<HTMLDivElement, BadgeProps>(
({ variant = 'default', size = 'small', label, children, sx, className, ...props }, ref) => {
// Map variant to color class
const variantClass =
variant === 'destructive' ? 'chip--error' :
variant === 'success' ? 'chip--success' :
variant === 'warning' ? 'chip--warning' :
variant === 'secondary' ? 'chip--secondary' :
variant === 'outline' ? 'chip--outline' :
''
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className, variantClass].filter(Boolean).join(' ')
return (
<Chip
ref={ref}
label={label || children}
size={size}
className={combinedClassName}
{...props}
/>
)
}
)
Badge.displayName = 'Badge'
export { Badge }
@@ -1,26 +0,0 @@
import { render } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Icon, type IconName } from './Icon'
describe('Icon', () => {
it.each([
{ name: 'Home', size: 'small' },
{ name: 'Settings', size: 'medium' },
{ name: 'Trash', size: 'large' },
{ name: 'Plus', size: 'inherit' },
] as const)('renders icon $name with size $size', ({ name, size }) => {
const { container } = render(<Icon name={name} size={size} />)
expect(container.querySelector('svg')).not.toBeNull()
})
it('applies custom sx styles', () => {
const { container } = render(<Icon name="Home" sx={{ color: 'blue' }} />)
expect(container.querySelector('svg')).not.toBeNull()
})
it('returns null for unknown icon', () => {
const { container } = render(<Icon name={'UnknownIcon' as IconName} />)
expect(container.querySelector('svg')).toBeNull()
})
})
@@ -1,51 +0,0 @@
'use client'
import { CSSProperties, forwardRef } from 'react'
import * as FakeMuiIcons from '@/fakemui/icons'
// Create a type from the available fakemui icons
export type IconName = keyof typeof FakeMuiIcons
export type IconSize = 'small' | 'medium' | 'large' | 'inherit'
export interface IconProps {
name: IconName
size?: IconSize
sx?: CSSProperties & Record<string, unknown>
className?: string
style?: CSSProperties
}
const sizeMap = {
small: 20,
medium: 24,
large: 32,
inherit: undefined,
}
const Icon = forwardRef<SVGSVGElement, IconProps>(
({ name, size = 'medium', sx, className, style, ...props }, ref) => {
const IconComponent = FakeMuiIcons[name]
if (!IconComponent || typeof IconComponent !== 'function') {
console.warn(`Icon "${name}" not found in @/fakemui/icons`)
return null
}
const sizeValue = sizeMap[size]
const combinedStyle = { ...style, ...sx }
return (
<IconComponent
size={sizeValue}
style={combinedStyle}
className={className}
{...props}
/>
)
}
)
Icon.displayName = 'Icon'
export { Icon }
@@ -1,57 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { IconButton as FakemuiIconButton } from '@/fakemui'
/** IconButton size options */
export type IconButtonSize = 'small' | 'medium' | 'large'
/**
* Props for the IconButton component
* Wrapper around fakemui IconButton to maintain API compatibility
*/
export interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Visual style variant of the icon button */
variant?: 'default' | 'outlined' | 'contained'
/** Size of the icon button */
size?: IconButtonSize
/** MUI color prop (ignored for compatibility) */
color?: string
/** MUI edge prop (ignored for compatibility) */
edge?: string | false
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
({ variant = 'default', size = 'medium', color, edge, sx, className, ...props }, ref) => {
// Map size to fakemui size props
const sizeProps = {
sm: size === 'small',
lg: size === 'large',
}
// Map variant to className
const variantClass =
variant === 'outlined' ? 'icon-btn--outlined' :
variant === 'contained' ? 'icon-btn--contained' :
''
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className, variantClass].filter(Boolean).join(' ')
return (
<FakemuiIconButton
ref={ref}
className={combinedClassName}
{...sizeProps}
{...props}
/>
)
}
)
IconButton.displayName = 'IconButton'
export { IconButton }
@@ -1,40 +0,0 @@
'use client'
import { forwardRef, LabelHTMLAttributes } from 'react'
import { Label as FakemuiLabel } from '@/fakemui'
/**
* Props for the Label component
* Wrapper around fakemui Label to maintain API compatibility
*/
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
/** Whether to display a required indicator (*) */
required?: boolean
/** Whether to style the label as an error state */
error?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Label = forwardRef<HTMLLabelElement, LabelProps>(
({ children, required, error, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [
className,
sx?.className,
error ? 'label--error' : '',
].filter(Boolean).join(' ')
return (
<label ref={ref} className={`label ${combinedClassName}`} {...props}>
{children}
{required && <span className="label__required"> *</span>}
</label>
)
}
)
Label.displayName = 'Label'
export { Label }
@@ -1,38 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Link } from './Link'
describe('Link', () => {
it.each([
{ href: '/about', external: false, children: 'About Us' },
{ href: '/contact', external: false, children: 'Contact' },
{ href: 'https://example.com', external: true, children: 'External Link' },
])('renders link with props %o', ({ href, external, children }) => {
render(
<Link href={href} external={external}>
{children}
</Link>
)
const link = screen.getByText(children)
expect(link).not.toBeNull()
expect(link.getAttribute('href')).not.toBeNull()
})
it('renders external link with correct attributes', () => {
render(
<Link href="https://example.com" external>
External
</Link>
)
const link = screen.getByText('External')
expect(link.getAttribute('target')).toBe('_blank')
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
})
it('renders internal link without target', () => {
render(<Link href="/internal">Internal</Link>)
const link = screen.getByText('Internal')
expect(link.getAttribute('target')).toBeNull()
})
})
@@ -1,64 +0,0 @@
'use client'
import NextLink, { LinkProps as NextLinkProps } from 'next/link'
import { forwardRef } from 'react'
import { Link as FakemuiLink } from '@/fakemui'
/**
* Props for the Link component
* Wrapper around fakemui Link with Next.js integration
*/
export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
/** Link href (Next.js or external URL) */
href: NextLinkProps['href']
/** Whether this is an external link */
external?: boolean
/** Link underline style */
underline?: 'none' | 'hover' | 'always'
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI component prop (ignored for compatibility) */
component?: any
}
const Link = forwardRef<HTMLAnchorElement, LinkProps>(
({ href, external, children, underline = 'hover', sx, className, component, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
if (external) {
return (
<FakemuiLink
ref={ref}
href={href as string}
target="_blank"
rel="noopener noreferrer"
underline={underline}
className={combinedClassName}
{...props}
>
{children}
</FakemuiLink>
)
}
// For internal links, wrap fakemui Link with Next.js Link
return (
<NextLink href={href} passHref legacyBehavior>
<FakemuiLink
ref={ref}
underline={underline}
className={combinedClassName}
{...props}
>
{children}
</FakemuiLink>
</NextLink>
)
}
)
Link.displayName = 'Link'
export { Link }
@@ -1,40 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Text } from './Text'
describe('Text', () => {
it.each([
{ variant: 'h1', weight: 'bold', children: 'Heading 1' },
{ variant: 'h2', weight: 'semibold', children: 'Heading 2' },
{ variant: 'body1', weight: 'regular', children: 'Body text' },
{ variant: 'body2', weight: 'light', children: 'Small text' },
{ variant: 'caption', weight: 'medium', children: 'Caption' },
] as const)('renders with variant=$variant weight=$weight', ({ variant, weight, children }) => {
render(
<Text variant={variant} weight={weight}>
{children}
</Text>
)
expect(screen.getByText(children)).not.toBeNull()
})
it.each([
{ align: 'left', children: 'Left aligned' },
{ align: 'center', children: 'Centered' },
{ align: 'right', children: 'Right aligned' },
] as const)('renders with align=$align', ({ align, children }) => {
render(<Text align={align}>{children}</Text>)
expect(screen.getByText(children)).not.toBeNull()
})
it('renders muted text', () => {
render(<Text muted>Muted text</Text>)
expect(screen.getByText('Muted text')).not.toBeNull()
})
it('renders truncated text', () => {
render(<Text truncate>Very long text that should truncate</Text>)
expect(screen.getByText('Very long text that should truncate')).not.toBeNull()
})
})
@@ -1,103 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Text as FakemuiText, Typography } from '@/fakemui'
export type TextVariant =
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6'
| 'body1'
| 'body2'
| 'subtitle1'
| 'subtitle2'
| 'caption'
| 'overline'
export type TextWeight = 'light' | 'regular' | 'medium' | 'semibold' | 'bold'
export type TextAlign = 'left' | 'center' | 'right' | 'justify'
/**
* Props for the Text component
* Wrapper around fakemui Text/Typography to maintain API compatibility
*/
export interface TextProps extends React.HTMLAttributes<HTMLElement> {
/** Typography variant (MUI compatibility) */
variant?: TextVariant
/** Font weight */
weight?: TextWeight
/** Text alignment */
align?: TextAlign
/** Muted/secondary text style */
muted?: boolean
/** Truncate text with ellipsis */
truncate?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI component prop - specify HTML element */
component?: React.ElementType
}
const weightMap = {
light: 'font-light',
regular: 'font-normal',
medium: 'font-medium',
semibold: 'font-semibold',
bold: 'font-bold',
}
const Text = forwardRef<HTMLElement, TextProps>(
(
{ variant = 'body1', weight = 'regular', align = 'left', muted, truncate, sx, className, component, ...props },
ref
) => {
// For heading variants, use Typography
if (variant && ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(variant)) {
const combinedClassName = [
className,
sx?.className,
weightMap[weight],
muted ? 'text-secondary' : '',
truncate ? 'truncate' : '',
`text-${align}`,
].filter(Boolean).join(' ')
return (
<Typography
ref={ref as any}
variant={variant}
className={combinedClassName}
as={component}
{...props}
/>
)
}
// For body/caption variants, use fakemui Text
const combinedClassName = [
className,
sx?.className,
weightMap[weight],
`text-${align}`,
].filter(Boolean).join(' ')
return (
<FakemuiText
ref={ref as any}
secondary={muted}
truncate={truncate}
className={combinedClassName}
as={component || 'span'}
{...props}
/>
)
}
)
Text.displayName = 'Text'
export { Text }
@@ -1,79 +0,0 @@
'use client'
import React, { Component, ReactNode } from 'react'
import { Alert, Button, Typography } from '@/fakemui'
import { logError, LogLevel } from '@/lib/errors/log-error'
interface Props {
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
}
interface State {
hasError: boolean
error?: Error
}
/**
* Error Boundary component to catch React rendering errors
* Logs errors and displays a fallback UI
*/
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
// Log error with component stack
logError(
error,
{
component: 'ErrorBoundary',
componentStack: errorInfo.componentStack,
},
LogLevel.ERROR
)
// Call custom error handler if provided
this.props.onError?.(error, errorInfo)
}
handleReset = (): void => {
this.setState({ hasError: false, error: undefined })
}
render(): ReactNode {
if (this.state.hasError) {
// Use custom fallback if provided
if (this.props.fallback) {
return this.props.fallback
}
// Default error UI
return (
<div style={{ padding: '1.5rem' }}>
<Alert severity="error" style={{ marginBottom: '1rem' }}>
<Typography variant="h6">
Something went wrong
</Typography>
<Typography variant="body2" style={{ marginBottom: '1rem' }}>
{this.state.error?.message || 'An unexpected error occurred'}
</Typography>
<Button variant="primary" onClick={this.handleReset} size="sm">
Try Again
</Button>
</Alert>
</div>
)
}
return this.props.children
}
}
@@ -1,60 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { CircularProgress,LinearProgress } from '@/fakemui'
/**
* Props for the Progress component
* Wrapper around fakemui LinearProgress to maintain API compatibility
*/
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
/** Progress value (0-100) */
value?: number
/** Whether to display a percentage label next to the progress bar */
showLabel?: boolean
/** Variant of the progress bar */
variant?: 'determinate' | 'indeterminate'
/** Color of the progress bar */
color?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Progress = forwardRef<HTMLDivElement, ProgressProps>(
({ value, showLabel, variant, color, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
if (showLabel && value !== undefined) {
return (
<div ref={ref} className={`progress-with-label ${combinedClassName}`} style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div style={{ flex: 1 }}>
<LinearProgress
value={value}
className={combinedClassName}
{...props}
/>
</div>
<span className="progress-label text-secondary" style={{ minWidth: '40px' }}>
{Math.round(value)}%
</span>
</div>
)
}
return (
<LinearProgress
ref={ref}
value={value}
className={combinedClassName}
{...props}
/>
)
}
)
Progress.displayName = 'Progress'
// Also export CircularProgress for convenience
export { CircularProgress, Progress }
@@ -1,39 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Divider } from '@/fakemui'
/**
* Props for the Separator component
* Wrapper around fakemui Divider to maintain API compatibility
*/
export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
/** Orientation of the separator */
orientation?: 'horizontal' | 'vertical'
/** Whether the separator is decorative (for accessibility) */
decorative?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Separator = forwardRef<HTMLHRElement, SeparatorProps>(
({ orientation = 'horizontal', decorative, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<Divider
ref={ref}
orientation={orientation}
role={decorative ? 'presentation' : 'separator'}
className={combinedClassName}
{...props}
/>
)
}
)
Separator.displayName = 'Separator'
export { Separator }
@@ -1,52 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Skeleton as FakemuiSkeleton } from '@/fakemui'
import type { SkeletonProps as FakemuiSkeletonProps } from '@/fakemui/fakemui/feedback/Skeleton'
/**
* Props for the Skeleton component
* Wrapper around fakemui Skeleton to maintain API compatibility
*/
export interface SkeletonProps extends React.HTMLAttributes<HTMLSpanElement> {
/** Shape variant */
variant?: 'text' | 'rectangular' | 'circular' | 'rounded'
/** Animation type */
animation?: 'pulse' | 'wave' | false
/** Width of the skeleton */
width?: string | number
/** Height of the skeleton */
height?: string | number
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Skeleton = forwardRef<HTMLSpanElement, SkeletonProps>(
({ variant = 'rounded', animation = 'wave', width, height, sx, className, ...props }, ref) => {
// Map MUI variant to fakemui variant
const fakemuiVariant = variant === 'rounded' ? 'rectangular' : variant
// Map MUI animation to fakemui animation
const fakemuiAnimation = animation === 'wave' ? 'pulse' : animation
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiSkeleton
ref={ref}
variant={fakemuiVariant}
animation={fakemuiAnimation}
width={width}
height={height}
className={combinedClassName}
{...props}
/>
)
}
)
Skeleton.displayName = 'Skeleton'
export { Skeleton }
@@ -1,62 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { CircularProgress } from '@/fakemui'
/** Spinner size options */
export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg'
/**
* Props for the Spinner component
* Wrapper around fakemui CircularProgress to maintain API compatibility
*/
export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
/** Size of the spinner (xs: 16px, sm: 20px, md: 24px, lg: 40px) or a custom number */
size?: SpinnerSize | number
/** Whether to center the spinner in its container */
centered?: boolean
/** Color of the spinner */
color?: string
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const sizeMap: Record<SpinnerSize, number> = {
xs: 16,
sm: 20,
md: 24,
lg: 40,
}
const Spinner = forwardRef<HTMLDivElement, SpinnerProps>(
({ size = 'md', centered, color, sx, className, ...props }, ref) => {
const dimension = typeof size === 'number' ? size : sizeMap[size]
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
const spinner = (
<CircularProgress
ref={ref}
size={dimension}
className={combinedClassName}
{...props}
/>
)
if (centered) {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '1rem' }}>
{spinner}
</div>
)
}
return spinner
}
)
Spinner.displayName = 'Spinner'
export { Spinner }
@@ -1,75 +0,0 @@
'use client'
import { forwardRef, ReactElement, ReactNode } from 'react'
import { Tooltip as FakemuiTooltip } from '@/fakemui'
/**
* Props for the Tooltip component
* Wrapper around fakemui Tooltip to maintain API compatibility
*/
export interface TooltipProps {
/** The element that triggers the tooltip */
children: ReactElement
/** Title or main content of the tooltip */
title?: ReactNode
/** Alias for title - main content of the tooltip */
content?: ReactNode
/** Position of the tooltip relative to its trigger */
side?: 'top' | 'right' | 'bottom' | 'left'
/** Delay in milliseconds before showing the tooltip */
delayDuration?: number
/** Whether to display an arrow pointing to the trigger element */
arrow?: boolean
/** Controlled open state */
open?: boolean
/** Callback when tooltip is opened */
onOpen?: () => void
/** Callback when tooltip is closed */
onClose?: () => void
/** MUI placement prop (mapped to side) */
placement?: 'top' | 'right' | 'bottom' | 'left'
/** MUI enterDelay prop (mapped to delayDuration) */
enterDelay?: number
}
const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
(
{
children,
content,
title,
side,
placement,
delayDuration,
enterDelay,
arrow = true,
open,
onOpen,
onClose,
...props
},
ref
) => {
return (
<FakemuiTooltip
title={content || title || ''}
placement={side || placement || 'top'}
arrow={arrow}
open={open}
{...props}
>
{children}
</FakemuiTooltip>
)
}
)
Tooltip.displayName = 'Tooltip'
// Compatibility exports
const TooltipTrigger = ({ children }: { children: ReactElement }) => children
const TooltipContent = ({ children }: { children: React.ReactNode }) => <>{children}</>
const TooltipProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
@@ -1,48 +0,0 @@
// Atoms - Basic building blocks
// These are the smallest, indivisible UI components that form the foundation
// Controls
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './controls/Button'
export { Checkbox, type CheckboxProps } from './controls/Checkbox'
export { Radio, type RadioProps } from './controls/Radio'
export { Switch, type SwitchProps } from './controls/Switch'
// Inputs
export { Input, type InputProps } from './inputs/Input'
export { Select, type SelectOption, type SelectProps } from './inputs/Select'
export { TextArea, type TextAreaProps } from './inputs/TextArea'
// Display
export {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
type AvatarProps,
type AvatarSize,
} from './display/Avatar'
export { Badge, type BadgeProps, type BadgeVariant } from './display/Badge'
export { Icon, type IconName, type IconProps, type IconSize } from './display/Icon'
export { IconButton, type IconButtonProps, type IconButtonSize } from './display/IconButton'
export { Label, type LabelProps } from './display/Label'
export { Link, type LinkProps } from './display/Link'
export {
Text,
type TextAlign,
type TextProps,
type TextVariant,
type TextWeight,
} from './display/Text'
// Feedback
export { CircularProgress, Progress, type ProgressProps } from './feedback/Progress'
export { Separator, type SeparatorProps } from './feedback/Separator'
export { Skeleton, type SkeletonProps } from './feedback/Skeleton'
export { Spinner, type SpinnerProps, type SpinnerSize } from './feedback/Spinner'
export {
Tooltip,
TooltipContent,
type TooltipProps,
TooltipProvider,
TooltipTrigger,
} from './feedback/Tooltip'
@@ -1,49 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Input as FakemuiInput } from '@/fakemui'
import type { InputProps as FakemuiInputProps } from '@/fakemui/fakemui/inputs/Input'
/**
* Props for the Input component
* Wrapper around fakemui Input to maintain API compatibility
*/
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
/** Whether the input is in an error state */
error?: boolean
/** Whether the input should take up the full width of its container */
fullWidth?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI inputRef - forwarded as ref */
inputRef?: React.Ref<HTMLInputElement>
/** Start adornment element */
startAdornment?: React.ReactNode
/** End adornment element */
endAdornment?: React.ReactNode
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ type, error, fullWidth = true, sx, inputRef, startAdornment, endAdornment, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
return (
<FakemuiInput
ref={inputRef || ref}
type={type}
error={error}
fullWidth={fullWidth}
startAdornment={startAdornment}
endAdornment={endAdornment}
className={combinedClassName}
{...props}
/>
)
}
)
Input.displayName = 'Input'
export { Input }
@@ -1,39 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Select } from './Select'
const mockOptions = [
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' },
{ value: 'opt3', label: 'Option 3', disabled: true },
]
describe('Select', () => {
it.each([
{ options: mockOptions, placeholder: 'Choose option', fullWidth: true },
{ options: mockOptions, placeholder: undefined, fullWidth: false },
])('renders with props %o', props => {
render(<Select {...props} value="" onChange={() => {}} />)
const select = screen.getByRole('combobox')
expect(select).not.toBeNull()
})
it('shows placeholder when provided', () => {
render(<Select options={mockOptions} placeholder="Select..." value="" onChange={() => {}} />)
const select = screen.getByRole('combobox')
expect(select).not.toBeNull()
})
it('renders all options', () => {
render(<Select options={mockOptions} value="" onChange={() => {}} />)
const select = screen.getByRole('combobox')
expect(select).not.toBeNull()
})
it('shows error state', () => {
render(<Select options={mockOptions} error value="" onChange={() => {}} />)
const select = screen.getByRole('combobox')
expect(select).not.toBeNull()
})
})
@@ -1,61 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Select as FakemuiSelect } from '@/fakemui'
export interface SelectOption {
value: string | number
label: string
disabled?: boolean
}
/**
* Props for the Select component
* Wrapper around fakemui Select to maintain API compatibility
*/
export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'children'> {
/** Array of select options */
options: SelectOption[]
/** Placeholder text */
placeholder?: string
/** Error state */
error?: boolean
/** Full width */
fullWidth?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI displayEmpty prop (ignored for compatibility) */
displayEmpty?: boolean
}
const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ options, error, fullWidth = true, placeholder, sx, className, displayEmpty, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className, error ? 'select--error' : ''].filter(Boolean).join(' ')
return (
<FakemuiSelect
ref={ref}
fullWidth={fullWidth}
className={combinedClassName}
{...props}
>
{placeholder && (
<option value="" disabled>
{placeholder}
</option>
)}
{options.map(option => (
<option key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</option>
))}
</FakemuiSelect>
)
}
)
Select.displayName = 'Select'
export { Select }
@@ -1,29 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { TextArea } from './TextArea'
describe('TextArea', () => {
it.each([
{ placeholder: 'Enter text', fullWidth: true },
{ placeholder: 'Comment here', fullWidth: false },
{ placeholder: 'Description', minRows: 5 },
])('renders with props %o', props => {
render(<TextArea {...props} />)
const textarea = screen.getByPlaceholderText(props.placeholder)
expect(textarea).not.toBeNull()
})
it('shows error state', () => {
render(<TextArea error placeholder="Enter text" />)
const textarea = screen.getByPlaceholderText('Enter text')
expect(textarea).not.toBeNull()
})
it('accepts placeholder prop', () => {
const placeholder = 'Type here'
render(<TextArea placeholder={placeholder} />)
const textarea = screen.getByPlaceholderText(placeholder) as HTMLTextAreaElement
expect(textarea.placeholder).toBe(placeholder)
})
})
@@ -1,44 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Textarea } from '@/fakemui'
/**
* Props for the TextArea component
* Wrapper around fakemui Textarea to maintain API compatibility
*/
export interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
/** Whether the textarea is in an error state */
error?: boolean
/** Whether the textarea should take up the full width of its container */
fullWidth?: boolean
/** Minimum number of rows */
minRows?: number
/** Maximum number of rows */
maxRows?: number
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ error, fullWidth = true, minRows = 3, maxRows, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className, error ? 'textarea--error' : ''].filter(Boolean).join(' ')
return (
<Textarea
ref={ref}
rows={minRows}
error={error}
fullWidth={fullWidth}
className={combinedClassName}
{...props}
/>
)
}
)
TextArea.displayName = 'TextArea'
export { TextArea }
@@ -1,112 +0,0 @@
# Molecules
Molecules are simple groups of atoms that function together as a cohesive unit. Built on Material UI.
## Components
| Component | Description | Atoms Used |
|-----------|-------------|------------|
| `Card` | Container with header/content/footer | Box, Typography |
| `Dialog` | Modal dialog with transitions | MuiDialog |
| `Alert` | Feedback message with severity | MuiAlert |
| `Tabs` | Tab navigation | MuiTabs, MuiTab |
| `Accordion` | Collapsible sections | MuiAccordion |
| `Select` | Dropdown selection | MuiSelect |
| `DropdownMenu` | Context/action menu | Menu, MenuItem |
| `FormField` | Label + input + error | Label, Input |
| `SearchInput` | Input with search icon | TextField |
| `PasswordField` | Password input with visibility toggle | TextField, IconButton |
| `EmailField` | Email input with icon | TextField, InputAdornment |
| `NumberField` | Number input with constraints | TextField |
| `SearchBar` | Search input with clear and filter buttons | TextField, IconButton |
| `Popover` | Floating content panel | MuiPopover |
### Application Molecules
- `AppHeader` - Application header with logo and navigation
- `AppFooter` - Application footer with links
- `ProfileCard` - User profile display card
- `SecurityWarningDialog` - Security warning modal
- `PasswordChangeDialog` - Password change form modal
## Usage
```typescript
import {
Card, CardHeader, CardContent,
Dialog, Alert,
PasswordField, EmailField, NumberField, SearchBar
} from '@/components/molecules'
function MyPage() {
return (
<Box>
<Card>
<CardHeader title="Title" description="Subtitle" />
<CardContent>Content here</CardContent>
</Card>
<Alert variant="success" title="Success!">
Operation completed.
</Alert>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>Modal content</DialogContent>
</Dialog>
<PasswordField
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<EmailField
label="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
showIcon
/>
<NumberField
label="Age"
min={0}
max={120}
value={age}
onChange={(e) => setAge(e.target.value)}
/>
<SearchBar
value={searchQuery}
onChange={setSearchQuery}
onClear={() => setSearchQuery('')}
showFilterButton
onFilterClick={handleOpenFilters}
/>
</Box>
)
}
```
## Rules
1.**DO** compose molecules from atoms
2.**DO** keep molecules focused on a single purpose
3.**DO** use MUI `sx` prop for styling
4.**DO** support both controlled and uncontrolled modes
5.**DON'T** import organisms
6.**DON'T** add complex business logic
7.**DON'T** use Tailwind classes (use MUI `sx` prop)
## Styling
```tsx
<Card sx={{ maxWidth: 400, mx: 'auto' }}>
<CardHeader
title="User Profile"
sx={{ bgcolor: 'primary.light' }}
/>
<CardContent>
<Typography>Content with theme colors</Typography>
</CardContent>
</Card>
```
@@ -1,100 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Accordion as FakeMuiAccordion,
AccordionDetails,
AccordionSummary,
Typography,
} from '@/fakemui'
import { ExpandMore } from '@/fakemui/icons'
// Accordion (single item)
export interface AccordionProps {
children: ReactNode
expanded?: boolean
disabled?: boolean
type?: 'single' | 'multiple'
collapsible?: boolean
value?: string
className?: string
}
const Accordion = forwardRef<HTMLDivElement, AccordionProps>(
({ children, type = 'single', collapsible, expanded, disabled, className, ...props }, ref) => {
return (
<FakeMuiAccordion
ref={ref}
expanded={expanded}
disabled={disabled}
className={`accordion ${className || ''}`}
{...props}
>
{children}
</FakeMuiAccordion>
)
}
)
Accordion.displayName = 'Accordion'
// AccordionItem (for shadcn compat - wraps Accordion)
interface AccordionItemProps {
children: ReactNode
value: string
className?: string
}
const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(
({ children, value, className, ...props }, ref) => {
return (
<Accordion ref={ref} className={className} {...props}>
{children}
</Accordion>
)
}
)
AccordionItem.displayName = 'AccordionItem'
// AccordionTrigger
interface AccordionTriggerProps {
children: ReactNode
className?: string
}
const AccordionTrigger = forwardRef<HTMLButtonElement, AccordionTriggerProps>(
({ children, className, ...props }, ref) => {
return (
<AccordionSummary
ref={ref}
expandIcon={<ExpandMore />}
className={`accordion-trigger ${className || ''}`}
{...props}
>
<Typography variant="body1" className="font-medium">
{children}
</Typography>
</AccordionSummary>
)
}
)
AccordionTrigger.displayName = 'AccordionTrigger'
// AccordionContent
interface AccordionContentProps {
children: ReactNode
className?: string
}
const AccordionContent = forwardRef<HTMLDivElement, AccordionContentProps>(
({ children, className, ...props }, ref) => {
return (
<AccordionDetails ref={ref} className={`accordion-content ${className || ''}`} {...props}>
{children}
</AccordionDetails>
)
}
)
AccordionContent.displayName = 'AccordionContent'
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }
@@ -1,73 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Alert as FakeMuiAlert, AlertTitle as FakeMuiAlertTitle, IconButton } from '@/fakemui'
import { Close } from '@/fakemui/icons'
export type AlertVariant = 'default' | 'destructive' | 'success' | 'warning' | 'info'
export interface AlertProps {
variant?: AlertVariant
title?: ReactNode
dismissible?: boolean
onDismiss?: () => void
children?: ReactNode
className?: string
}
const variantMap: Record<AlertVariant, 'info' | 'error' | 'success' | 'warning'> = {
default: 'info',
destructive: 'error',
success: 'success',
warning: 'warning',
info: 'info',
}
const Alert = forwardRef<HTMLDivElement, AlertProps>(
(
{ variant = 'default', title, dismissible, onDismiss, children, className, ...props },
ref
) => {
const severity = variantMap[variant]
return (
<FakeMuiAlert
ref={ref}
severity={severity}
variant="outlined"
className={`alert ${className || ''}`}
action={
dismissible && onDismiss ? (
<IconButton aria-label="close" onClick={onDismiss}>
<Close size={16} />
</IconButton>
) : undefined
}
{...props}
>
{title && <FakeMuiAlertTitle>{title}</FakeMuiAlertTitle>}
{children}
</FakeMuiAlert>
)
}
)
Alert.displayName = 'Alert'
// AlertTitle (for direct use)
const AlertTitle = FakeMuiAlertTitle
// AlertDescription (shadcn compat)
const AlertDescription = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<div ref={ref} className={`alert-description ${className || ''}`} {...props}>
{children}
</div>
)
}
)
AlertDescription.displayName = 'AlertDescription'
export { Alert, AlertDescription, AlertTitle }
@@ -1,117 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Box,
Card as FakeMuiCard,
CardActions,
CardContent as FakeMuiCardContent,
CardHeader as FakeMuiCardHeader,
Typography,
} from '@/fakemui'
// Card
export interface CardProps {
children?: ReactNode
noPadding?: boolean
className?: string
}
const Card = forwardRef<HTMLDivElement, CardProps>(({ noPadding, className, ...props }, ref) => {
return (
<FakeMuiCard
ref={ref}
className={`card ${noPadding ? 'card--no-padding' : ''} ${className || ''}`}
{...props}
/>
)
})
Card.displayName = 'Card'
// CardHeader
export interface CardHeaderProps {
title?: ReactNode
subheader?: ReactNode
action?: ReactNode
avatar?: ReactNode
description?: ReactNode
className?: string
}
const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
({ description, subheader, ...props }, ref) => {
return (
<FakeMuiCardHeader
ref={ref}
subheader={description || subheader}
{...props}
/>
)
}
)
CardHeader.displayName = 'CardHeader'
// CardTitle (for shadcn compatibility)
interface CardTitleProps {
children: ReactNode
className?: string
}
const CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(({ children, className, ...props }, ref) => {
return (
<Typography ref={ref} variant="h6" className={`card-title ${className || ''}`} {...props}>
{children}
</Typography>
)
})
CardTitle.displayName = 'CardTitle'
// CardDescription
interface CardDescriptionProps {
children: ReactNode
className?: string
}
const CardDescription = forwardRef<HTMLParagraphElement, CardDescriptionProps>(
({ children, className, ...props }, ref) => {
return (
<Typography ref={ref} variant="body2" className={`card-description ${className || ''}`} {...props}>
{children}
</Typography>
)
}
)
CardDescription.displayName = 'CardDescription'
// CardContent
const CardContent = forwardRef<HTMLDivElement, { children?: ReactNode; className?: string }>(
({ className, ...props }, ref) => {
return <FakeMuiCardContent ref={ref} className={`card-content ${className || ''}`} {...props} />
}
)
CardContent.displayName = 'CardContent'
// CardFooter / CardActions
const CardFooter = forwardRef<HTMLDivElement, { children?: ReactNode; className?: string }>(
({ className, ...props }, ref) => {
return (
<CardActions ref={ref} className={`card-footer ${className || ''}`} {...props} />
)
}
)
CardFooter.displayName = 'CardFooter'
// CardAction (for header actions - shadcn compat)
const CardAction = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`card-action ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
CardAction.displayName = 'CardAction'
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
@@ -1,66 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { EmailField } from './EmailField'
describe('EmailField', () => {
it.each([
{ label: 'Email', placeholder: 'you@example.com', showIcon: true },
{ label: 'Your Email', placeholder: 'Enter email', showIcon: false },
{ label: 'Work Email', placeholder: undefined, showIcon: true },
])(
'renders with label "$label", placeholder "$placeholder", showIcon $showIcon',
({ label, placeholder, showIcon }) => {
render(<EmailField label={label} placeholder={placeholder} showIcon={showIcon} />)
expect(screen.getByLabelText(label)).toBeTruthy()
if (placeholder) {
expect(screen.getByPlaceholderText(placeholder)).toBeTruthy()
}
}
)
it('renders with email icon by default', () => {
const { container } = render(<EmailField />)
// Icon is rendered via MUI Icon component
expect(container.querySelector('svg')).toBeTruthy()
})
it('does not render icon when showIcon is false', () => {
const { container } = render(<EmailField showIcon={false} />)
// No icon should be present
expect(container.querySelector('svg')).toBeNull()
})
it.each([
{ error: 'Invalid email', helperText: undefined },
{ error: undefined, helperText: 'Enter a valid email address' },
{ error: 'Required field', helperText: 'Please provide your email' },
])('displays error "$error" or helperText "$helperText"', ({ error, helperText }) => {
render(<EmailField error={error} helperText={helperText} />)
const displayText = error || helperText
if (displayText) {
expect(screen.getByText(displayText)).toBeTruthy()
}
})
it('calls onChange when value changes', () => {
const handleChange = vi.fn()
render(<EmailField onChange={handleChange} />)
const input = screen.getByLabelText('Email')
fireEvent.change(input, { target: { value: 'test@example.com' } })
expect(handleChange).toHaveBeenCalled()
})
it('has type="email" attribute', () => {
render(<EmailField />)
const input = screen.getByLabelText('Email') as HTMLInputElement
expect(input.type).toBe('email')
})
})
@@ -1,73 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { TextField } from '@/fakemui'
import { Email } from '@/fakemui/icons'
export interface EmailFieldProps {
label?: string
name?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
error?: string
helperText?: string
required?: boolean
placeholder?: string
fullWidth?: boolean
disabled?: boolean
autoComplete?: string
showIcon?: boolean
className?: string
}
const EmailField = forwardRef<HTMLInputElement, EmailFieldProps>(
(
{
label = 'Email',
name = 'email',
value,
onChange,
error,
helperText,
required = false,
placeholder = 'you@example.com',
fullWidth = true,
disabled = false,
autoComplete = 'email',
showIcon = true,
className,
...props
},
ref
) => {
return (
<TextField
ref={ref}
type="email"
label={label}
name={name}
value={value}
onChange={onChange}
error={!!error}
helperText={error || helperText}
required={required}
placeholder={placeholder}
fullWidth={fullWidth}
disabled={disabled}
autoComplete={autoComplete}
className={className}
startAdornment={
showIcon ? (
<Email size={16} style={{ color: 'rgba(0,0,0,0.54)' }} />
) : undefined
}
{...props}
/>
)
}
)
EmailField.displayName = 'EmailField'
export { EmailField }
@@ -1,64 +0,0 @@
.formField {
display: flex;
flex-direction: column;
gap: 4px;
}
.helperText {
font-size: 0.75rem;
color: var(--text-secondary, #666);
&.error {
color: var(--error-main, #d32f2f);
}
}
.searchInput {
border-radius: 4px;
}
.searchIcon {
color: rgba(0, 0, 0, 0.54);
}
.textarea {
width: auto;
min-height: 80px;
padding: 8px 12px;
font-size: 0.875rem;
font-family: inherit;
border: 1px solid var(--divider, rgba(0, 0, 0, 0.12));
border-radius: 4px;
background-color: var(--background-paper, #fff);
resize: vertical;
transition: border-color 0.2s, box-shadow 0.2s;
&:hover {
border-color: var(--text-secondary, #666);
}
&:focus {
outline: none;
border-color: var(--primary-main, #1976d2);
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.15);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.error {
border-color: var(--error-main, #d32f2f);
&:hover,
&:focus {
border-color: var(--error-main, #d32f2f);
box-shadow: 0 0 0 2px rgba(211, 47, 47, 0.15);
}
}
&.fullWidth {
width: 100%;
}
}
@@ -1,89 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, TextField } from '@/fakemui'
import { Search } from '@/fakemui/icons'
import { Label } from '../atoms/Label'
import styles from './FormField.module.scss'
// FormField - combines label and input
export interface FormFieldProps {
label?: string
name?: string
error?: string
required?: boolean
helperText?: string
children: ReactNode
className?: string
}
const FormField = forwardRef<HTMLDivElement, FormFieldProps>(
({ label, name, error, required, helperText, children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.formField} ${className || ''}`} {...props}>
{label && (
<Label htmlFor={name} required={required} error={!!error}>
{label}
</Label>
)}
{children}
{(error || helperText) && (
<span className={`${styles.helperText} ${error ? styles.error : ''}`}>
{error || helperText}
</span>
)}
</Box>
)
}
)
FormField.displayName = 'FormField'
// SearchInput - input with search icon
export interface SearchInputProps {
value?: string
onChange?: (value: string) => void
placeholder?: string
fullWidth?: boolean
className?: string
}
const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
({ value, onChange, placeholder = 'Search...', fullWidth = true, className, ...props }, ref) => {
return (
<TextField
ref={ref}
value={value}
onChange={e => onChange?.(e.target.value)}
placeholder={placeholder}
fullWidth={fullWidth}
className={`${styles.searchInput} ${className || ''}`}
startAdornment={<Search size={16} className={styles.searchIcon} />}
{...props}
/>
)
}
)
SearchInput.displayName = 'SearchInput'
// TextArea
export interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
error?: boolean
fullWidth?: boolean
}
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ error, fullWidth = true, className, ...props }, ref) => {
return (
<textarea
ref={ref}
className={`${styles.textarea} ${error ? styles.error : ''} ${fullWidth ? styles.fullWidth : ''} ${className || ''}`}
{...props}
/>
)
}
)
TextArea.displayName = 'TextArea'
export { FormField, SearchInput, TextArea }
@@ -1,12 +0,0 @@
.numberField {
// Hide number input spinners
input[type='number'] {
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
@@ -1,70 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { NumberField } from './NumberField'
describe('NumberField', () => {
it.each([
{ label: 'Number', value: undefined },
{ label: 'Age', value: 25 },
{ label: 'Quantity', value: 100 },
])('renders with label "$label" and value $value', ({ label, value }) => {
render(<NumberField label={label} value={value} />)
expect(screen.getByLabelText(label)).toBeTruthy()
if (value !== undefined) {
expect(screen.getByDisplayValue(value.toString())).toBeTruthy()
}
})
it.each([
{ min: 0, max: 100, step: 1 },
{ min: -10, max: 10, step: 0.5 },
{ min: undefined, max: undefined, step: undefined },
])('respects min $min, max $max, step $step constraints', ({ min, max, step }) => {
render(<NumberField min={min} max={max} step={step} />)
const input = screen.getByLabelText('Number') as HTMLInputElement
if (min !== undefined) {
expect(input.min).toBe(min.toString())
}
if (max !== undefined) {
expect(input.max).toBe(max.toString())
}
if (step !== undefined) {
expect(input.step).toBe(step.toString())
} else {
expect(input.step).toBe('1')
}
})
it('calls onChange when value changes', () => {
const handleChange = vi.fn()
render(<NumberField onChange={handleChange} />)
const input = screen.getByLabelText('Number')
fireEvent.change(input, { target: { value: '42' } })
expect(handleChange).toHaveBeenCalled()
})
it.each([
{ error: 'Value too high', helperText: undefined },
{ error: undefined, helperText: 'Enter a number between 0 and 100' },
])('displays error "$error" or helperText "$helperText"', ({ error, helperText }) => {
render(<NumberField error={error} helperText={helperText} />)
const displayText = error || helperText
if (displayText) {
expect(screen.getByText(displayText)).toBeTruthy()
}
})
it('has type="number" attribute', () => {
render(<NumberField />)
const input = screen.getByLabelText('Number') as HTMLInputElement
expect(input.type).toBe('number')
})
})
@@ -1,73 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { TextField } from '@/fakemui'
import styles from './NumberField.module.scss'
export interface NumberFieldProps {
label?: string
name?: string
value?: number | string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
error?: string
helperText?: string
required?: boolean
placeholder?: string
fullWidth?: boolean
disabled?: boolean
min?: number
max?: number
step?: number | string
className?: string
}
const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(
(
{
label = 'Number',
name,
value,
onChange,
error,
helperText,
required = false,
placeholder,
fullWidth = true,
disabled = false,
min,
max,
step = 1,
className,
...props
},
ref
) => {
return (
<TextField
ref={ref}
type="number"
label={label}
name={name}
value={value}
onChange={onChange}
error={!!error}
helperText={error || helperText}
required={required}
placeholder={placeholder}
fullWidth={fullWidth}
disabled={disabled}
min={min}
max={max}
step={step}
className={`${styles.numberField} ${className || ''}`}
{...props}
/>
)
}
)
NumberField.displayName = 'NumberField'
export { NumberField }
@@ -1,64 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { PasswordField } from './PasswordField'
describe('PasswordField', () => {
it.each([
{ label: 'Password', placeholder: undefined },
{ label: 'Enter Password', placeholder: 'Your password' },
{ label: 'Confirm Password', placeholder: 'Confirm your password' },
])('renders with label "$label" and placeholder "$placeholder"', ({ label, placeholder }) => {
render(<PasswordField label={label} placeholder={placeholder} />)
expect(screen.getByLabelText(label)).toBeTruthy()
if (placeholder) {
expect(screen.getByPlaceholderText(placeholder)).toBeTruthy()
}
})
it('toggles password visibility when icon button is clicked', () => {
render(<PasswordField />)
const input = screen.getByLabelText('Password') as HTMLInputElement
expect(input.type).toBe('password')
const toggleButton = screen.getByLabelText('toggle password visibility')
fireEvent.click(toggleButton)
expect(input.type).toBe('text')
fireEvent.click(toggleButton)
expect(input.type).toBe('password')
})
it.each([
{ error: 'Password is required', helperText: undefined },
{ error: undefined, helperText: 'Must be at least 8 characters' },
{ error: 'Too short', helperText: 'Should be longer' },
])('displays error "$error" or helperText "$helperText"', ({ error, helperText }) => {
render(<PasswordField error={error} helperText={helperText} />)
const displayText = error || helperText
if (displayText) {
expect(screen.getByText(displayText)).toBeTruthy()
}
})
it('calls onChange when value changes', () => {
const handleChange = vi.fn()
render(<PasswordField onChange={handleChange} />)
const input = screen.getByLabelText('Password')
fireEvent.change(input, { target: { value: 'newpassword' } })
expect(handleChange).toHaveBeenCalled()
})
it('disables toggle button when field is disabled', () => {
render(<PasswordField disabled />)
const toggleButton = screen.getByLabelText('toggle password visibility')
expect(toggleButton.hasAttribute('disabled')).toBe(true)
})
})
@@ -1,87 +0,0 @@
'use client'
import { forwardRef, useState } from 'react'
import { IconButton, TextField } from '@/fakemui'
import { Visibility, VisibilityOff } from '@/fakemui/icons'
export interface PasswordFieldProps {
label?: string
name?: string
value?: string
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
error?: string
helperText?: string
required?: boolean
placeholder?: string
fullWidth?: boolean
disabled?: boolean
autoComplete?: string
className?: string
}
const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
(
{
label = 'Password',
name = 'password',
value,
onChange,
error,
helperText,
required = false,
placeholder,
fullWidth = true,
disabled = false,
autoComplete = 'current-password',
className,
...props
},
ref
) => {
const [showPassword, setShowPassword] = useState(false)
const togglePasswordVisibility = () => {
setShowPassword(prev => !prev)
}
return (
<TextField
ref={ref}
type={showPassword ? 'text' : 'password'}
label={label}
name={name}
value={value}
onChange={onChange}
error={!!error}
helperText={error || helperText}
required={required}
placeholder={placeholder}
fullWidth={fullWidth}
disabled={disabled}
autoComplete={autoComplete}
className={className}
endAdornment={
<IconButton
aria-label="toggle password visibility"
onClick={togglePasswordVisibility}
onMouseDown={e => e.preventDefault()}
disabled={disabled}
sm
>
{showPassword ? (
<VisibilityOff size={16} />
) : (
<Visibility size={16} />
)}
</IconButton>
}
{...props}
/>
)
}
)
PasswordField.displayName = 'PasswordField'
export { PasswordField }
@@ -1,28 +0,0 @@
.searchBar {
border-radius: 8px;
background-color: var(--background-paper, #fff);
transition: box-shadow 0.2s;
&:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
&:focus-within {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
}
.searchIcon {
color: rgba(0, 0, 0, 0.54);
}
.endAdornments {
display: flex;
gap: 4px;
align-items: center;
}
.clearButton,
.filterButton {
padding: 4px;
}
@@ -1,90 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { SearchBar } from './SearchBar'
describe('SearchBar', () => {
it.each([
{ placeholder: 'Search...', value: '' },
{ placeholder: 'Find items...', value: 'test query' },
{ placeholder: 'Type to search', value: 'example' },
])('renders with placeholder "$placeholder" and value "$value"', ({ placeholder, value }) => {
render(<SearchBar placeholder={placeholder} value={value} />)
expect(screen.getByPlaceholderText(placeholder)).toBeTruthy()
if (value) {
expect(screen.getByDisplayValue(value)).toBeTruthy()
}
})
it('shows search icon by default', () => {
const { container } = render(<SearchBar />)
// Search icon is always present
expect(container.querySelector('svg')).toBeTruthy()
})
it.each([
{ showClearButton: true, value: 'test', shouldShowClear: true },
{ showClearButton: false, value: 'test', shouldShowClear: false },
{ showClearButton: true, value: '', shouldShowClear: false },
])(
'handles clear button with showClearButton=$showClearButton, value="$value"',
({ showClearButton, value, shouldShowClear }) => {
render(<SearchBar showClearButton={showClearButton} value={value} />)
const clearButton = screen.queryByLabelText('clear search')
if (shouldShowClear) {
expect(clearButton).toBeTruthy()
} else {
expect(clearButton).toBeNull()
}
}
)
it('calls onClear when clear button is clicked', () => {
const handleClear = vi.fn()
const handleChange = vi.fn()
render(<SearchBar value="test" onClear={handleClear} onChange={handleChange} />)
const clearButton = screen.getByLabelText('clear search')
fireEvent.click(clearButton)
expect(handleClear).toHaveBeenCalled()
expect(handleChange).toHaveBeenCalledWith('')
})
it.each([{ showFilterButton: true }, { showFilterButton: false }])(
'renders filter button when showFilterButton=$showFilterButton',
({ showFilterButton }) => {
render(<SearchBar showFilterButton={showFilterButton} />)
const filterButton = screen.queryByLabelText('open filters')
if (showFilterButton) {
expect(filterButton).toBeTruthy()
} else {
expect(filterButton).toBeNull()
}
}
)
it('calls onFilterClick when filter button is clicked', () => {
const handleFilterClick = vi.fn()
render(<SearchBar showFilterButton onFilterClick={handleFilterClick} />)
const filterButton = screen.getByLabelText('open filters')
fireEvent.click(filterButton)
expect(handleFilterClick).toHaveBeenCalled()
})
it('calls onChange when input value changes', () => {
const handleChange = vi.fn()
render(<SearchBar onChange={handleChange} />)
const input = screen.getByPlaceholderText('Search...')
fireEvent.change(input, { target: { value: 'new search' } })
expect(handleChange).toHaveBeenCalledWith('new search')
})
})
@@ -1,99 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, IconButton, TextField } from '@/fakemui'
import { Clear, FilterList, Search } from '@/fakemui/icons'
import styles from './SearchBar.module.scss'
export interface SearchBarProps {
value?: string
onChange?: (value: string) => void
onClear?: () => void
onFilterClick?: () => void
placeholder?: string
fullWidth?: boolean
showFilterButton?: boolean
showClearButton?: boolean
disabled?: boolean
loading?: boolean
endAdornment?: ReactNode
className?: string
}
const SearchBar = forwardRef<HTMLInputElement, SearchBarProps>(
(
{
value = '',
onChange,
onClear,
onFilterClick,
placeholder = 'Search...',
fullWidth = true,
showFilterButton = false,
showClearButton = true,
disabled = false,
loading = false,
endAdornment,
className,
...props
},
ref
) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.value)
}
const handleClear = () => {
onChange?.('')
onClear?.()
}
return (
<TextField
ref={ref}
value={value}
onChange={handleChange}
placeholder={placeholder}
fullWidth={fullWidth}
disabled={disabled}
className={`${styles.searchBar} ${className || ''}`}
startAdornment={
<Search size={16} className={styles.searchIcon} />
}
endAdornment={
<Box className={styles.endAdornments}>
{showClearButton && value && !disabled && (
<IconButton
aria-label="clear search"
onClick={handleClear}
sm
className={styles.clearButton}
>
<Clear size={16} />
</IconButton>
)}
{showFilterButton && (
<IconButton
aria-label="open filters"
onClick={onFilterClick}
disabled={disabled}
sm
className={styles.filterButton}
>
<FilterList size={16} />
</IconButton>
)}
{endAdornment}
</Box>
}
{...props}
/>
)
}
)
SearchBar.displayName = 'SearchBar'
export { SearchBar }
@@ -1,28 +0,0 @@
.selectWrapper {
display: flex;
flex-direction: column;
gap: 4px;
&.fullWidth {
width: 100%;
}
}
.selectContainer {
position: relative;
display: inline-flex;
align-items: center;
}
.select {
appearance: none;
width: 100%;
padding-right: 32px;
}
.icon {
position: absolute;
right: 8px;
pointer-events: none;
color: rgba(0, 0, 0, 0.54);
}
@@ -1,76 +0,0 @@
'use client'
import { forwardRef, ReactNode, SelectHTMLAttributes } from 'react'
import { Box, FormControl, FormHelperText, FormLabel, Select as FakeMuiSelect } from '@/fakemui'
import { KeyboardArrowDown } from '@/fakemui/icons'
import styles from './Select.module.scss'
import { SelectContent } from './SelectContent'
import { SelectGroup } from './SelectGroup'
import type { SelectItemProps } from './SelectItem'
import { SelectItem } from './SelectItem'
import { SelectLabel } from './SelectLabel'
import { SelectSeparator } from './SelectSeparator'
import { SelectTrigger } from './SelectTrigger'
import { SelectValue } from './SelectValue'
export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'onChange'> {
onValueChange?: (value: string) => void
helperText?: ReactNode
label?: ReactNode
error?: boolean
fullWidth?: boolean
}
const Select = forwardRef<HTMLSelectElement, SelectProps>(
(
{
onValueChange,
value,
defaultValue,
label,
error,
helperText,
children,
className,
fullWidth = true,
...props
},
ref
) => {
return (
<FormControl className={`${styles.selectWrapper} ${fullWidth ? styles.fullWidth : ''} ${className || ''}`}>
{label && <FormLabel>{label}</FormLabel>}
<Box className={styles.selectContainer}>
<FakeMuiSelect
ref={ref}
value={value as string}
defaultValue={defaultValue as string}
onChange={e => onValueChange?.(e.target.value)}
error={error}
className={styles.select}
{...props}
>
{children}
</FakeMuiSelect>
<KeyboardArrowDown size={16} className={styles.icon} />
</Box>
{helperText && <FormHelperText error={error}>{helperText}</FormHelperText>}
</FormControl>
)
}
)
Select.displayName = 'Select'
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
}
export type { SelectItemProps }
@@ -1,22 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
interface SelectContentProps {
children: ReactNode
className?: string
}
const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
({ children, ...props }, ref) => {
return (
<div ref={ref} {...props}>
{children}
</div>
)
}
)
SelectContent.displayName = 'SelectContent'
export { SelectContent }
@@ -1,22 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
interface SelectGroupProps {
children: ReactNode
className?: string
}
const SelectGroup = forwardRef<HTMLDivElement, SelectGroupProps>(({ children, ...props }, ref) => {
return (
<Box ref={ref} {...props}>
{children}
</Box>
)
})
SelectGroup.displayName = 'SelectGroup'
export { SelectGroup }
@@ -1,26 +0,0 @@
'use client'
import { ButtonHTMLAttributes,forwardRef } from 'react'
import { MenuItem } from '@/fakemui'
export interface SelectItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
value?: string | number
textValue?: string
selected?: boolean
disabled?: boolean
}
const SelectItem = forwardRef<HTMLButtonElement, SelectItemProps>(
({ value, children, selected, disabled, ...props }, ref) => {
return (
<MenuItem ref={ref} selected={selected} disabled={disabled} data-value={value} {...props}>
{children}
</MenuItem>
)
}
)
SelectItem.displayName = 'SelectItem'
export { SelectItem }
@@ -1,6 +0,0 @@
.selectLabel {
padding: 8px 16px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary, #666);
}
@@ -1,26 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
import styles from './SelectLabel.module.scss'
interface SelectLabelProps {
children: ReactNode
className?: string
}
const SelectLabel = forwardRef<HTMLDivElement, SelectLabelProps>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.selectLabel} ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
SelectLabel.displayName = 'SelectLabel'
export { SelectLabel }
@@ -1,4 +0,0 @@
.selectSeparator {
margin: 4px 0;
border-color: var(--divider, rgba(0, 0, 0, 0.12));
}
@@ -1,21 +0,0 @@
'use client'
import { forwardRef } from 'react'
import { Divider } from '@/fakemui'
import styles from './SelectSeparator.module.scss'
interface SelectSeparatorProps {
className?: string
}
const SelectSeparator = forwardRef<HTMLHRElement, SelectSeparatorProps>(
({ className, ...props }, ref) => {
return <Divider ref={ref} className={`${styles.selectSeparator} ${className || ''}`} {...props} />
}
)
SelectSeparator.displayName = 'SelectSeparator'
export { SelectSeparator }
@@ -1,19 +0,0 @@
.selectTrigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border: 1px solid var(--divider, rgba(0, 0, 0, 0.12));
border-radius: 4px;
cursor: pointer;
transition: border-color 0.2s;
&:hover {
border-color: var(--text-secondary, #666);
}
}
.icon {
margin-left: 4px;
color: rgba(0, 0, 0, 0.54);
}
@@ -1,28 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
import { KeyboardArrowDown } from '@/fakemui/icons'
import styles from './SelectTrigger.module.scss'
interface SelectTriggerProps {
children: ReactNode
className?: string
}
const SelectTrigger = forwardRef<HTMLDivElement, SelectTriggerProps>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.selectTrigger} ${className || ''}`} {...props}>
{children}
<KeyboardArrowDown size={16} className={styles.icon} />
</Box>
)
}
)
SelectTrigger.displayName = 'SelectTrigger'
export { SelectTrigger }
@@ -1,9 +0,0 @@
.selectValue {
&.hasValue {
color: var(--text-primary, rgba(0, 0, 0, 0.87));
}
&.placeholder {
color: var(--text-secondary, #666);
}
}
@@ -1,32 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
import styles from './SelectValue.module.scss'
interface SelectValueProps {
placeholder?: string
children?: ReactNode
className?: string
}
const SelectValue = forwardRef<HTMLSpanElement, SelectValueProps>(
({ placeholder, children, className, ...props }, ref) => {
return (
<Box
component="span"
ref={ref}
className={`${styles.selectValue} ${children ? styles.hasValue : styles.placeholder} ${className || ''}`}
{...props}
>
{children || placeholder}
</Box>
)
}
)
SelectValue.displayName = 'SelectValue'
export { SelectValue }
@@ -1,30 +0,0 @@
.tabsWrapper {
// Container for tabs
}
.tabs {
// Tabs container styling
}
.tabsList {
display: inline-flex;
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
border-radius: 4px;
padding: 4px;
}
.tabTrigger {
min-height: 36px;
padding: 8px 24px;
text-transform: none;
font-weight: 500;
border-radius: 2px;
&.tab--active {
background-color: var(--background-paper, #fff);
}
}
.tabContent {
padding-top: 16px;
}
@@ -1,97 +0,0 @@
'use client'
import { ButtonHTMLAttributes, forwardRef, HTMLAttributes } from 'react'
import { Box, Tab as FakeMuiTab, TabProps as FakeMuiTabProps,Tabs as FakeMuiTabs } from '@/fakemui'
import styles from './Tabs.module.scss'
// Tabs container
export interface TabsProps extends HTMLAttributes<HTMLDivElement> {
defaultValue?: string
value?: string
onValueChange?: (value: string) => void
variant?: 'standard' | 'scrollable' | 'fullWidth'
}
const Tabs = forwardRef<HTMLDivElement, TabsProps>(
({ defaultValue, value, onValueChange, children, className, variant, ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.tabsWrapper} ${className || ''}`} {...props}>
<FakeMuiTabs
value={value || defaultValue}
onChange={(_, newValue) => onValueChange?.(newValue)}
variant={variant}
className={styles.tabs}
>
{children}
</FakeMuiTabs>
</Box>
)
}
)
Tabs.displayName = 'Tabs'
// TabsList (wrapper for tabs - for shadcn compat)
const TabsList = forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.tabsList} ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
TabsList.displayName = 'TabsList'
// TabsTrigger (individual tab)
export interface TabsTriggerProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value'> {
value: string
label?: React.ReactNode
selected?: boolean
}
const TabsTrigger = forwardRef<HTMLButtonElement, TabsTriggerProps>(
({ value, label, children, className, selected, ...props }, ref) => {
return (
<FakeMuiTab
ref={ref}
value={value}
label={label || children}
selected={selected}
className={`${styles.tabTrigger} ${className || ''}`}
{...props}
/>
)
}
)
TabsTrigger.displayName = 'TabsTrigger'
// TabsContent (content panel)
interface TabsContentProps {
value: string
children: React.ReactNode
className?: string
forceMount?: boolean
}
const TabsContent = forwardRef<HTMLDivElement, TabsContentProps>(
({ value, children, forceMount, className, ...props }, ref) => {
return (
<Box
ref={ref}
role="tabpanel"
hidden={!forceMount}
id={`tabpanel-${value}`}
aria-labelledby={`tab-${value}`}
className={`${styles.tabContent} ${className || ''}`}
{...props}
>
{children}
</Box>
)
}
)
TabsContent.displayName = 'TabsContent'
export { Tabs, TabsContent, TabsList, TabsTrigger }
@@ -1,109 +0,0 @@
// Molecules - Simple combinations of atoms
// These combine atoms to create more complex, reusable components
// New MUI-based molecules
export {
Accordion,
AccordionContent,
AccordionItem,
type AccordionProps,
AccordionTrigger,
} from './display/Accordion'
export {
Alert,
AlertDescription,
type AlertProps,
AlertTitle,
type AlertVariant,
} from './display/Alert'
export {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
type CardHeaderProps,
type CardProps,
CardTitle,
} from './display/Card'
export { EmailField, type EmailFieldProps } from './form/EmailField'
export {
FormField,
type FormFieldProps,
SearchInput,
type SearchInputProps,
TextArea,
type TextAreaProps,
} from './form/FormField'
export { NumberField, type NumberFieldProps } from './form/NumberField'
export { PasswordField, type PasswordFieldProps } from './form/PasswordField'
export { SearchBar, type SearchBarProps } from './form/SearchBar'
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
type SelectItemProps,
SelectLabel,
type SelectProps,
SelectSeparator,
SelectTrigger,
SelectValue,
} from './form/Select'
export {
Tabs,
TabsContent,
TabsList,
type TabsProps,
TabsTrigger,
type TabsTriggerProps,
} from './form/Tabs'
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
type DialogProps,
DialogTitle,
DialogTrigger,
} from './overlay/Dialog'
export {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
type DropdownMenuContentProps,
DropdownMenuGroup,
DropdownMenuItem,
type DropdownMenuItemProps,
DropdownMenuLabel,
DropdownMenuPortal,
type DropdownMenuProps,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from './overlay/DropdownMenu'
export {
Popover,
PopoverAnchor,
PopoverContent,
type PopoverContentProps,
type PopoverProps,
PopoverTrigger,
} from './overlay/Popover'
// Legacy shared components
export { GodCredentialsBanner } from '../level1/GodCredentialsBanner'
export { ProfileCard } from '../level2/ProfileCard'
export { PasswordChangeDialog } from '../PasswordChangeDialog'
export { AppFooter } from '../shared/AppFooter'
export { AppHeader } from '../shared/AppHeader'
@@ -1,187 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Box,
DialogActions,
DialogContent as FakeMuiDialogContent,
DialogTitle as FakeMuiDialogTitle,
IconButton,
Modal,
Slide,
Typography,
} from '@/fakemui'
import { Close } from '@/fakemui/icons'
// Dialog
export interface DialogProps {
open?: boolean
onOpenChange?: (open: boolean) => void
onClose?: () => void
children?: ReactNode
className?: string
}
const Dialog = forwardRef<HTMLDivElement, DialogProps>(
({ open, onOpenChange, onClose, children, className, ...props }, ref) => {
const handleClose = () => {
onClose?.()
onOpenChange?.(false)
}
return (
<Modal open={open} onClose={handleClose} className={className} {...props}>
<Slide direction="up" in={open}>
<div ref={ref} className="dialog-panel">{children}</div>
</Slide>
</Modal>
)
}
)
Dialog.displayName = 'Dialog'
// DialogTrigger
interface DialogTriggerProps {
children: ReactNode
asChild?: boolean
onClick?: () => void
}
const DialogTrigger = forwardRef<HTMLDivElement, DialogTriggerProps>(
({ children, onClick, ...props }, ref) => {
return (
<Box
ref={ref}
onClick={onClick}
className="dialog-trigger"
{...props}
>
{children}
</Box>
)
}
)
DialogTrigger.displayName = 'DialogTrigger'
// DialogContent
interface DialogContentProps {
children: ReactNode
className?: string
onClose?: () => void
showCloseButton?: boolean
}
const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
({ children, showCloseButton = true, onClose, className, ...props }, ref) => {
return (
<FakeMuiDialogContent
ref={ref}
className={`dialog-content ${showCloseButton ? 'dialog-content--with-close' : ''} ${className || ''}`}
{...props}
>
{showCloseButton && onClose && (
<IconButton
aria-label="close"
onClick={onClose}
className="dialog-close-button"
>
<Close />
</IconButton>
)}
{children}
</FakeMuiDialogContent>
)
}
)
DialogContent.displayName = 'DialogContent'
// DialogHeader
const DialogHeader = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`dialog-header ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
DialogHeader.displayName = 'DialogHeader'
// DialogFooter
const DialogFooter = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<DialogActions ref={ref} className={`dialog-footer ${className || ''}`} {...props}>
{children}
</DialogActions>
)
}
)
DialogFooter.displayName = 'DialogFooter'
// DialogTitle
const DialogTitle = forwardRef<HTMLHeadingElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<FakeMuiDialogTitle ref={ref} className={`dialog-title ${className || ''}`} {...props}>
{children}
</FakeMuiDialogTitle>
)
}
)
DialogTitle.displayName = 'DialogTitle'
// DialogDescription
const DialogDescription = forwardRef<
HTMLParagraphElement,
{ children: ReactNode; className?: string }
>(({ children, className, ...props }, ref) => {
return (
<Typography variant="body2" className={`dialog-description ${className || ''}`} {...props}>
<span ref={ref}>{children}</span>
</Typography>
)
})
DialogDescription.displayName = 'DialogDescription'
// DialogClose
const DialogClose = forwardRef<
HTMLButtonElement,
{ children?: ReactNode; onClick?: () => void; asChild?: boolean }
>(({ children, onClick, ...props }, ref) => {
if (children) {
return (
<Box
onClick={onClick}
className="dialog-close"
>
{children}
</Box>
)
}
return (
<IconButton ref={ref} aria-label="close" onClick={onClick} {...props}>
<Close />
</IconButton>
)
})
DialogClose.displayName = 'DialogClose'
// Compatibility exports
const DialogPortal = ({ children }: { children: ReactNode }) => <>{children}</>
const DialogOverlay = forwardRef<HTMLDivElement>((props, ref) => <div ref={ref} {...props} />)
DialogOverlay.displayName = 'DialogOverlay'
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
@@ -1,265 +0,0 @@
'use client'
import { forwardRef, MouseEvent as ReactMouseEvent, ReactNode } from 'react'
import { Box, Divider, ListItemIcon, ListItemText, Menu, MenuItem, Typography } from '@/fakemui'
import { Check, ChevronRight } from '@/fakemui/icons'
// DropdownMenu (uses FakeMUI Menu under the hood)
export interface DropdownMenuProps {
children: ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}
const DropdownMenu = ({ children, open, onOpenChange }: DropdownMenuProps) => {
return <>{children}</>
}
DropdownMenu.displayName = 'DropdownMenu'
// DropdownMenuTrigger
interface DropdownMenuTriggerProps {
children: ReactNode
asChild?: boolean
onClick?: (event: ReactMouseEvent<HTMLElement>) => void
}
const DropdownMenuTrigger = forwardRef<HTMLDivElement, DropdownMenuTriggerProps>(
({ children, onClick, ...props }, ref) => {
return (
<Box
ref={ref}
onClick={onClick}
className="dropdown-menu-trigger"
{...props}
>
{children}
</Box>
)
}
)
DropdownMenuTrigger.displayName = 'DropdownMenuTrigger'
// DropdownMenuContent
export interface DropdownMenuContentProps {
children?: ReactNode
align?: 'start' | 'center' | 'end'
side?: 'top' | 'right' | 'bottom' | 'left'
sideOffset?: number
anchorEl?: HTMLElement | null
onClose?: () => void
className?: string
}
const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
({ children, align = 'start', side = 'bottom', anchorEl, onClose, className, ...props }, ref) => {
return (
<Menu
ref={ref}
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={onClose}
className={`dropdown-menu-content ${className || ''}`}
{...props}
>
{children}
</Menu>
)
}
)
DropdownMenuContent.displayName = 'DropdownMenuContent'
// DropdownMenuItem
export interface DropdownMenuItemProps {
children?: ReactNode
inset?: boolean
icon?: ReactNode
shortcut?: string
onClick?: () => void
disabled?: boolean
className?: string
}
const DropdownMenuItem = forwardRef<HTMLButtonElement, DropdownMenuItemProps>(
({ children, inset, icon, shortcut, className, ...props }, ref) => {
return (
<MenuItem
ref={ref}
className={`dropdown-menu-item ${inset ? 'dropdown-menu-item--inset' : ''} ${className || ''}`}
{...props}
>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText>{children}</ListItemText>
{shortcut && (
<Typography variant="caption" className="dropdown-menu-shortcut">
{shortcut}
</Typography>
)}
</MenuItem>
)
}
)
DropdownMenuItem.displayName = 'DropdownMenuItem'
// DropdownMenuCheckboxItem
interface DropdownMenuCheckboxItemProps {
children?: ReactNode
checked?: boolean
onCheckedChange?: (checked: boolean) => void
className?: string
}
const DropdownMenuCheckboxItem = forwardRef<HTMLButtonElement, DropdownMenuCheckboxItemProps>(
({ children, checked, onCheckedChange, className, ...props }, ref) => {
return (
<MenuItem
ref={ref}
onClick={() => onCheckedChange?.(!checked)}
className={`dropdown-menu-checkbox-item ${className || ''}`}
{...props}
>
<ListItemIcon>
{checked && <Check size={16} />}
</ListItemIcon>
<ListItemText>{children}</ListItemText>
</MenuItem>
)
}
)
DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem'
// DropdownMenuLabel
const DropdownMenuLabel = forwardRef<
HTMLDivElement,
{ children: ReactNode; inset?: boolean; className?: string }
>(({ children, inset, className, ...props }, ref) => {
return (
<Box
ref={ref}
className={`dropdown-menu-label ${inset ? 'dropdown-menu-label--inset' : ''} ${className || ''}`}
{...props}
>
{children}
</Box>
)
})
DropdownMenuLabel.displayName = 'DropdownMenuLabel'
// DropdownMenuSeparator
const DropdownMenuSeparator = forwardRef<HTMLHRElement, { className?: string }>((props, ref) => {
return <Divider ref={ref} className="dropdown-menu-separator" {...props} />
})
DropdownMenuSeparator.displayName = 'DropdownMenuSeparator'
// DropdownMenuShortcut
const DropdownMenuShortcut = ({ children }: { children: ReactNode }) => {
return (
<Typography variant="caption" className="dropdown-menu-shortcut">
{children}
</Typography>
)
}
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
// DropdownMenuGroup
const DropdownMenuGroup = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`dropdown-menu-group ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
DropdownMenuGroup.displayName = 'DropdownMenuGroup'
// DropdownMenuSub (submenu)
const DropdownMenuSub = ({ children }: { children: ReactNode }) => <>{children}</>
DropdownMenuSub.displayName = 'DropdownMenuSub'
// DropdownMenuSubTrigger
const DropdownMenuSubTrigger = forwardRef<
HTMLButtonElement,
{ children: ReactNode; inset?: boolean; className?: string }
>(({ children, inset, className, ...props }, ref) => {
return (
<MenuItem
ref={ref}
className={`dropdown-menu-sub-trigger ${inset ? 'dropdown-menu-sub-trigger--inset' : ''} ${className || ''}`}
{...props}
>
<ListItemText>{children}</ListItemText>
<ChevronRight size={16} />
</MenuItem>
)
})
DropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger'
// DropdownMenuSubContent
const DropdownMenuSubContent = forwardRef<
HTMLDivElement,
{ children: ReactNode; className?: string }
>(({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`dropdown-menu-sub-content ${className || ''}`} {...props}>
{children}
</Box>
)
})
DropdownMenuSubContent.displayName = 'DropdownMenuSubContent'
// DropdownMenuPortal
const DropdownMenuPortal = ({ children }: { children: ReactNode }) => <>{children}</>
DropdownMenuPortal.displayName = 'DropdownMenuPortal'
// DropdownMenuRadioGroup
const DropdownMenuRadioGroup = forwardRef<
HTMLDivElement,
{
children: ReactNode
value?: string
onValueChange?: (value: string) => void
className?: string
}
>(({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`dropdown-menu-radio-group ${className || ''}`} {...props}>
{children}
</Box>
)
})
DropdownMenuRadioGroup.displayName = 'DropdownMenuRadioGroup'
// DropdownMenuRadioItem
const DropdownMenuRadioItem = forwardRef<
HTMLButtonElement,
{ children: ReactNode; value: string; className?: string }
>(({ children, value, className, ...props }, ref) => {
return (
<MenuItem ref={ref} className={`dropdown-menu-radio-item ${className || ''}`} {...props}>
<ListItemIcon>
<Box className="dropdown-menu-radio-dot" />
</ListItemIcon>
<ListItemText>{children}</ListItemText>
</MenuItem>
)
})
DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem'
export {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
}
@@ -1,96 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, Popover as FakeMuiPopover } from '@/fakemui'
// Popover container
export interface PopoverProps {
children: ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}
const Popover = ({ children, open, onOpenChange }: PopoverProps) => {
return <>{children}</>
}
Popover.displayName = 'Popover'
// PopoverTrigger
interface PopoverTriggerProps {
children: ReactNode
asChild?: boolean
onClick?: (event: React.MouseEvent<HTMLElement>) => void
}
const PopoverTrigger = forwardRef<HTMLDivElement, PopoverTriggerProps>(
({ children, onClick, ...props }, ref) => {
return (
<Box
ref={ref}
onClick={onClick}
className="popover-trigger"
{...props}
>
{children}
</Box>
)
}
)
PopoverTrigger.displayName = 'PopoverTrigger'
// PopoverContent
export interface PopoverContentProps {
children?: ReactNode
align?: 'start' | 'center' | 'end'
side?: 'top' | 'right' | 'bottom' | 'left'
sideOffset?: number
anchorEl?: HTMLElement | null
onClose?: () => void
className?: string
}
const PopoverContent = forwardRef<HTMLDivElement, PopoverContentProps>(
(
{ children, align = 'center', side = 'bottom', sideOffset = 4, anchorEl, onClose, className, ...props },
ref
) => {
const anchorOrigin = {
vertical: side === 'top' ? 'top' : 'bottom',
horizontal: align === 'end' ? 'right' : align === 'center' ? 'center' : 'left',
} as const
return (
<FakeMuiPopover
ref={ref}
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={anchorOrigin}
transformOrigin={{
vertical: side === 'top' ? 'bottom' : 'top',
horizontal: anchorOrigin.horizontal,
}}
className={`popover-content ${className || ''}`}
{...props}
>
{children}
</FakeMuiPopover>
)
}
)
PopoverContent.displayName = 'PopoverContent'
// PopoverAnchor
const PopoverAnchor = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, ...props }, ref) => {
return (
<Box ref={ref} className="popover-anchor" {...props}>
{children}
</Box>
)
}
)
PopoverAnchor.displayName = 'PopoverAnchor'
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
@@ -1,111 +0,0 @@
# Organisms
Complex UI sections that combine atoms and molecules into complete features. Built on Material UI.
## Layout Components
| Component | Description |
|-----------|-------------|
| `Table` | Data table with header/body/footer |
| `Command` | Command palette (cmdk-style) |
| `Sheet` | Side panel drawer |
| `Sidebar` | Navigation sidebar with groups |
| `NavigationMenu` | Top navigation with dropdowns |
| `Form` | Form with react-hook-form integration |
## Feature Components
### Core Builders
- `SchemaEditor` - Full schema editing interface
- `ComponentCatalog` - Component browsing and selection
- `PropertyInspector` - Component property editor
- `Builder` - Visual page builder with drag-and-drop
- `Canvas` - Rendering canvas for visual builder
### Configuration Managers
- `CssClassBuilder` - Visual CSS class selection
- `ThemeEditor` - Theme customization interface
- `SMTPConfigEditor` - SMTP configuration form
### Code Editors
- `CodeEditor` - Monaco-based code editor
- `LuaEditor` - Lua script editor
- `JsonEditor` - JSON editing with validation
- `NerdModeIDE` - Full-featured IDE panel
### Data Management
- `DatabaseManager` - Database management
- `UserManagement` - User CRUD interface
- `PackageManager` - Package management
- `AuditLogViewer` - Audit log display
### Security Components
- `SecurityWarningDialog` - Security scan results dialog with severity classification
## Usage
```typescript
import {
Table, TableHeader, TableBody, TableRow, TableCell,
Sidebar, SidebarHeader, SidebarContent,
UserManagement
} from '@/components/organisms'
function AdminPanel() {
return (
<Box sx={{ display: 'flex', height: '100vh' }}>
<Sidebar width={280}>
<SidebarHeader>Admin</SidebarHeader>
<SidebarContent>
<SidebarGroup label="Users">
<SidebarMenuItem icon={<PeopleIcon />}>Users</SidebarMenuItem>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<Box sx={{ flex: 1, p: 3 }}>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map(user => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Box>
)
}
```
## Rules
1.**DO** compose organisms from molecules and atoms
2.**DO** include business logic when needed
3.**DO** handle data fetching and state management
4.**DO** use MUI `sx` prop for styling
5.**DON'T** use Tailwind classes
6.**DON'T** create organisms that are too granular (use molecules)
## Testing
```typescript
describe('UserManagement', () => {
it('allows creating new users', async () => {
render(<UserManagement />)
await userEvent.click(screen.getByRole('button', { name: /add user/i }))
await userEvent.type(screen.getByLabelText(/username/i), 'newuser')
await userEvent.click(screen.getByRole('button', { name: /save/i }))
expect(screen.getByText('newuser')).toBeInTheDocument()
})
})
```
@@ -1,143 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Controller,
ControllerProps,
FieldValues,
FormProvider,
useForm,
useFormContext,
UseFormReturn,
} from 'react-hook-form'
import {
Box,
FormControl,
FormGroup,
FormHelperText,
FormLabel,
} from '@/fakemui'
// Form wrapper with react-hook-form
interface FormProps<T extends FieldValues> {
form: UseFormReturn<T>
onSubmit: (data: T) => void
children: ReactNode
className?: string
}
function Form<T extends FieldValues>({ form, onSubmit, children, className, ...props }: FormProps<T>) {
return (
<FormProvider {...form}>
<Box
component="form"
onSubmit={form.handleSubmit(onSubmit)}
className={`form-wrapper ${className || ''}`}
{...props}
>
{children}
</Box>
</FormProvider>
)
}
Form.displayName = 'Form'
// FormField (wrapper for Controller)
interface FormFieldProps<T extends FieldValues> extends Omit<ControllerProps<T>, 'render'> {
render: ControllerProps<T>['render']
}
function FormField<T extends FieldValues>({ name, control, render, ...props }: FormFieldProps<T>) {
return <Controller name={name} control={control} render={render} {...props} />
}
FormField.displayName = 'FormField'
// FormItem
const FormItem = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`form-item ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
FormItem.displayName = 'FormItem'
// FormLabel
const FormLabelComponent = forwardRef<
HTMLLabelElement,
{ children: ReactNode; required?: boolean; error?: boolean; className?: string }
>(({ children, required, error, className, ...props }, ref) => {
return (
<FormLabel
ref={ref}
required={required}
error={error}
className={`form-label ${className || ''}`}
{...props}
>
{children}
</FormLabel>
)
})
FormLabelComponent.displayName = 'FormLabel'
// FormControl (wrapper)
const FormControlComponent = forwardRef<
HTMLDivElement,
{ children: ReactNode; fullWidth?: boolean; error?: boolean; className?: string }
>(({ children, fullWidth = true, error, className, ...props }, ref) => {
return (
<FormControl ref={ref} fullWidth={fullWidth} error={error} className={className} {...props}>
{children}
</FormControl>
)
})
FormControlComponent.displayName = 'FormControl'
// FormDescription
const FormDescription = forwardRef<
HTMLParagraphElement,
{ children: ReactNode; className?: string }
>(({ children, ...props }, ref) => {
return (
<FormHelperText ref={ref} {...props}>
{children}
</FormHelperText>
)
})
FormDescription.displayName = 'FormDescription'
// FormMessage (error message)
const FormMessage = forwardRef<HTMLParagraphElement, { children?: ReactNode; className?: string }>(
({ children, ...props }, ref) => {
if (!children) return null
return (
<FormHelperText ref={ref} error {...props}>
{children}
</FormHelperText>
)
}
)
FormMessage.displayName = 'FormMessage'
// useFormField hook (to get form context in nested components)
const useFormField = () => {
const context = useFormContext()
return context
}
export {
Form,
FormControlComponent as FormControl,
FormDescription,
FormField,
FormItem,
FormLabelComponent as FormLabel,
FormMessage,
FormProvider,
useForm,
useFormField,
}
@@ -1,18 +0,0 @@
// Table barrel export - maintains backward compatibility after splitting into smaller organisms
// Components split into separate files to keep each under 150 LOC
export {
TableCaption,
TableCell,
type TableCellProps,
TableHead,
type TableHeadProps,
TableRow,
type TableRowProps,
} from './TableCell'
export {
TableCore as Table,
TableBody,
TableFooter,
TableHeader,
type TableProps,
} from './TableCore'
@@ -1,98 +0,0 @@
'use client'
import { ComponentProps,forwardRef, ReactNode } from 'react'
import {
TableCell as FakemuiTableCell,
TableRow as FakemuiTableRow,
} from '@/fakemui'
// TableRow
export interface TableRowProps {
children: ReactNode
className?: string
selected?: boolean
hover?: boolean
onClick?: () => void
}
const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(
({ children, selected, hover = true, className, ...props }, ref) => {
const classes = [
'table-row',
selected && 'table-row--selected',
hover && 'table-row--hover',
className,
]
.filter(Boolean)
.join(' ')
return (
<FakemuiTableRow ref={ref} className={classes} {...props}>
{children}
</FakemuiTableRow>
)
}
)
TableRow.displayName = 'TableRow'
// TableHead (cell in header)
export interface TableHeadProps extends ComponentProps<typeof FakemuiTableCell> {
className?: string
}
const TableHead = forwardRef<HTMLTableCellElement, TableHeadProps>(
({ children, className, ...props }, ref) => {
return (
<FakemuiTableCell
ref={ref}
component="th"
className={`table-head-cell ${className || ''}`}
{...props}
>
{children}
</FakemuiTableCell>
)
}
)
TableHead.displayName = 'TableHead'
// TableCell
export interface TableCellProps extends ComponentProps<typeof FakemuiTableCell> {
className?: string
}
const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
({ children, ...props }, ref) => {
return (
<FakemuiTableCell ref={ref} {...props}>
{children}
</FakemuiTableCell>
)
}
)
TableCell.displayName = 'TableCell'
// TableCaption
const TableCaption = forwardRef<
HTMLTableCaptionElement,
{ children: ReactNode; className?: string }
>(({ children, ...props }, ref) => {
return (
<caption
ref={ref}
style={{
captionSide: 'bottom',
padding: '12px',
fontSize: '0.875rem',
color: 'var(--text-secondary)',
}}
{...props}
>
{children}
</caption>
)
})
TableCaption.displayName = 'TableCaption'
export { TableCaption, TableCell, TableHead, TableRow }
@@ -1,74 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Paper,
Table,
TableBody as FakemuiTableBody,
TableContainer,
TableFooter as FakemuiTableFooter,
TableHead as FakemuiTableHead,
} from '@/fakemui'
// Table wrapper with container
export interface TableProps {
children: ReactNode
className?: string
stickyHeader?: boolean
}
const TableCore = forwardRef<HTMLTableElement, TableProps>(
({ children, stickyHeader, className, ...props }, ref) => {
return (
<TableContainer>
<Paper className={`table-wrapper ${className || ''}`}>
<Table ref={ref} stickyHeader={stickyHeader} size="small" {...props}>
{children}
</Table>
</Paper>
</TableContainer>
)
}
)
TableCore.displayName = 'TableCore'
// TableHeader
const TableHeader = forwardRef<
HTMLTableSectionElement,
{ children: ReactNode; className?: string }
>(({ children, ...props }, ref) => {
return (
<FakemuiTableHead ref={ref} {...props}>
{children}
</FakemuiTableHead>
)
})
TableHeader.displayName = 'TableHeader'
// TableBody
const TableBody = forwardRef<HTMLTableSectionElement, { children: ReactNode; className?: string }>(
({ children, ...props }, ref) => {
return (
<FakemuiTableBody ref={ref} {...props}>
{children}
</FakemuiTableBody>
)
}
)
TableBody.displayName = 'TableBody'
// TableFooter
const TableFooter = forwardRef<
HTMLTableSectionElement,
{ children: ReactNode; className?: string }
>(({ children, ...props }, ref) => {
return (
<FakemuiTableFooter ref={ref} {...props}>
{children}
</FakemuiTableFooter>
)
})
TableFooter.displayName = 'TableFooter'
export { TableBody, TableCore, TableFooter, TableHeader }
@@ -1,136 +0,0 @@
// Organisms - Complex UI sections
// These are larger components that combine atoms and molecules
// New MUI-based organisms - Data
export {
Table,
TableBody,
TableCaption,
TableCell,
type TableCellProps,
TableFooter,
TableHead,
TableHeader,
type TableHeadProps,
type TableProps,
TableRow,
type TableRowProps,
} from './data/Table'
// New MUI-based organisms - Overlay/Dialogs
export {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandLoading,
type CommandProps,
CommandSeparator,
CommandShortcut,
} from './overlay/Command'
export {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetOverlay,
SheetPortal,
type SheetProps,
SheetTitle,
SheetTrigger,
} from './overlay/Sheet'
// New MUI-based organisms - Navigation
export {
Form,
FormControl,
FormDescription,
FormField as FormFieldController,
FormItem,
FormLabel,
FormMessage,
FormProvider,
useForm,
useFormField,
} from './data/Form'
export {
NavigationMenu,
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
type NavigationMenuProps,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
NavigationMenuViewport,
} from './navigation/NavigationMenu'
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
type SidebarProps,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
} from './navigation/Sidebar'
// Legacy feature components
export { AuditLogViewer } from '../AuditLogViewer'
export { Builder } from '../Builder'
export { Canvas } from '../Canvas'
export { CodeEditor } from '../CodeEditor'
export { ComponentCatalog } from '../ComponentCatalog'
export { ComponentConfigDialog } from '../ComponentConfigDialog'
export { ComponentHierarchyEditor } from '../ComponentHierarchyEditor'
export { CssClassBuilder } from '../CssClassBuilder'
export { CssClassManager } from '../CssClassManager'
export { DatabaseManager } from '../DatabaseManager'
export { DropdownConfigManager } from '../DropdownConfigManager'
export { FieldRenderer } from '../FieldRenderer'
export { GenericPage } from '../GenericPage'
export { GitHubActionsFetcher } from '../GitHubActionsFetcher'
export { GodCredentialsSettings } from '../GodCredentialsSettings'
export { JsonEditor } from '../JsonEditor'
export { ContactSection } from '../level1/ContactSection'
export { FeaturesSection } from '../level1/FeaturesSection'
export { HeroSection } from '../level1/HeroSection'
export { NavigationBar } from '../level1/NavigationBar'
export { CommentsList } from '../level2/CommentsList'
export { Login } from '../Login'
export { LuaEditor } from '../LuaEditor'
export { LuaSnippetLibrary } from '../LuaSnippetLibrary'
export { ModelListView } from '../ModelListView'
export { NerdModeIDE } from '../NerdModeIDE'
export { PackageImportExport } from '../PackageImportExport'
export { PackageManager } from '../PackageManager'
export { PageRoutesManager } from '../PageRoutesManager'
export { PropertyInspector } from '../PropertyInspector'
export { QuickGuide } from '../QuickGuide'
export { RecordForm } from '../RecordForm'
export { RenderComponent } from '../RenderComponent'
export { SchemaEditor } from '../SchemaEditor'
export { SchemaEditorLevel4 } from '../SchemaEditorLevel4'
export { ScreenshotAnalyzer } from '../ScreenshotAnalyzer'
export { SMTPConfigEditor } from '../SMTPConfigEditor'
export { ThemeEditor } from '../ThemeEditor'
export { UnifiedLogin } from '../UnifiedLogin'
export { UserManagement } from '../UserManagement'
export { WorkflowEditor } from '../WorkflowEditor'
// Security components
export { SecurityWarningDialog } from './security/SecurityWarningDialog'
@@ -1,15 +0,0 @@
// NavigationMenu barrel export - maintains backward compatibility after splitting into smaller organisms
// Components split into separate files to keep each under 150 LOC
export {
NavigationMenuCore as NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
type NavigationMenuProps,
} from './NavigationMenuCore'
export {
NavigationMenuIndicator,
NavigationMenuLink,
navigationMenuTriggerStyle,
NavigationMenuViewport,
} from './NavigationMenuLink'
export { NavigationMenuContent, NavigationMenuTrigger } from './NavigationMenuTrigger'
@@ -1,31 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.navigationMenu {
display: flex;
gap: 0.25rem;
&.horizontal {
flex-direction: row;
align-items: center;
}
&.vertical {
flex-direction: column;
align-items: stretch;
}
}
.navigationMenuList {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.25rem;
list-style: none;
margin: 0;
padding: 0;
}
.navigationMenuItem {
position: relative;
}
@@ -1,75 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
import styles from './NavigationMenuCore.module.scss'
// NavigationMenu container
export interface NavigationMenuProps {
children: ReactNode
className?: string
orientation?: 'horizontal' | 'vertical'
}
const NavigationMenuCore = forwardRef<HTMLDivElement, NavigationMenuProps>(
({ children, className = '', orientation = 'horizontal', ...props }, ref) => {
const orientationClass = orientation === 'horizontal'
? styles.horizontal
: styles.vertical
return (
<Box
ref={ref}
className={`${styles.navigationMenu} ${orientationClass} ${className}`}
{...props}
>
{children}
</Box>
)
}
)
NavigationMenuCore.displayName = 'NavigationMenuCore'
// NavigationMenuList
const NavigationMenuList = forwardRef<
HTMLUListElement,
{ children: ReactNode; className?: string }
>(({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
component="ul"
className={`${styles.navigationMenuList} ${className}`}
{...props}
>
{children}
</Box>
)
})
NavigationMenuList.displayName = 'NavigationMenuList'
// NavigationMenuItem
interface NavigationMenuItemProps {
children: ReactNode
className?: string
}
const NavigationMenuItem = forwardRef<HTMLLIElement, NavigationMenuItemProps>(
({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
component="li"
className={`${styles.navigationMenuItem} ${className}`}
{...props}
>
{children}
</Box>
)
}
)
NavigationMenuItem.displayName = 'NavigationMenuItem'
export { NavigationMenuCore, NavigationMenuItem, NavigationMenuList }
@@ -1,62 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.navigationMenuLink {
display: inline-flex;
align-items: center;
justify-content: center;
text-transform: none;
font-weight: 500;
min-width: auto;
padding: 0.5rem 1rem;
color: var(--text-primary, inherit);
text-decoration: none;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
}
&.active {
color: var(--primary-main, #1976d2);
}
}
.navigationMenuIndicator {
position: absolute;
bottom: 0;
height: 2px;
background-color: var(--primary-main, #1976d2);
transition: all 0.2s;
}
.navigationMenuViewport {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: var(--background-paper, #fff);
border-radius: 4px;
box-shadow: var(--shadow-3, 0 4px 20px rgba(0, 0, 0, 0.15));
overflow: hidden;
}
.triggerStyle {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 4px;
transition: all 0.2s;
&:hover {
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
}
&:focus {
background-color: var(--action-focus, rgba(0, 0, 0, 0.12));
}
}
@@ -1,91 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, Button } from '@/fakemui'
import styles from './NavigationMenuLink.module.scss'
// NavigationMenuLink
interface NavigationMenuLinkProps {
children: ReactNode
href?: string
active?: boolean
onClick?: () => void
className?: string
}
const NavigationMenuLink = forwardRef<HTMLAnchorElement, NavigationMenuLinkProps>(
({ children, href, active, onClick, className = '', ...props }, ref) => {
const activeClass = active ? styles.active : ''
// Use anchor element if href is provided
if (href) {
return (
<a
ref={ref}
href={href}
onClick={onClick}
className={`${styles.navigationMenuLink} ${activeClass} ${className}`}
{...props}
>
{children}
</a>
)
}
return (
<Button
ref={ref as React.Ref<HTMLButtonElement>}
onClick={onClick}
variant="ghost"
className={`${styles.navigationMenuLink} ${activeClass} ${className}`}
{...props}
>
{children}
</Button>
)
}
)
NavigationMenuLink.displayName = 'NavigationMenuLink'
// NavigationMenuIndicator
const NavigationMenuIndicator = forwardRef<HTMLDivElement, { className?: string }>(
({ className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.navigationMenuIndicator} ${className}`}
{...props}
/>
)
}
)
NavigationMenuIndicator.displayName = 'NavigationMenuIndicator'
// NavigationMenuViewport
const NavigationMenuViewport = forwardRef<
HTMLDivElement,
{ children?: ReactNode; className?: string }
>(({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.navigationMenuViewport} ${className}`}
{...props}
>
{children}
</Box>
)
})
NavigationMenuViewport.displayName = 'NavigationMenuViewport'
// Helper: navigationMenuTriggerStyle (returns className for consistent styling)
const navigationMenuTriggerStyle = () => styles.triggerStyle
export {
NavigationMenuIndicator,
NavigationMenuLink,
navigationMenuTriggerStyle,
NavigationMenuViewport,
}
@@ -1,19 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.navigationMenuTrigger {
text-transform: none;
font-weight: 500;
color: var(--text-primary, inherit);
&:hover {
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
}
}
.navigationMenuContent {
margin-top: 0.5rem;
min-width: 200px;
border-radius: 4px;
box-shadow: var(--shadow-3, 0 4px 20px rgba(0, 0, 0, 0.15));
}
@@ -1,61 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Button, Menu } from '@/fakemui'
import { KeyboardArrowDown } from '@/fakemui/icons'
import styles from './NavigationMenuTrigger.module.scss'
// NavigationMenuTrigger
interface NavigationMenuTriggerProps {
children: ReactNode
className?: string
onClick?: (event: React.MouseEvent<HTMLElement>) => void
}
const NavigationMenuTrigger = forwardRef<HTMLButtonElement, NavigationMenuTriggerProps>(
({ children, className = '', onClick, ...props }, ref) => {
return (
<Button
ref={ref}
onClick={onClick}
variant="ghost"
endIcon={<KeyboardArrowDown />}
className={`${styles.navigationMenuTrigger} ${className}`}
{...props}
>
{children}
</Button>
)
}
)
NavigationMenuTrigger.displayName = 'NavigationMenuTrigger'
// NavigationMenuContent
interface NavigationMenuContentProps {
children: ReactNode
className?: string
anchorEl?: HTMLElement | null
open?: boolean
onClose?: () => void
}
const NavigationMenuContent = forwardRef<HTMLDivElement, NavigationMenuContentProps>(
({ children, className = '', anchorEl: _anchorEl, open, onClose, ...props }, ref) => {
return (
<Menu
ref={ref}
open={open}
onClose={onClose}
className={`${styles.navigationMenuContent} ${className}`}
{...props}
>
{children}
</Menu>
)
}
)
NavigationMenuContent.displayName = 'NavigationMenuContent'
export { NavigationMenuContent, NavigationMenuTrigger }
@@ -1,7 +0,0 @@
// Sidebar barrel export - maintains backward compatibility after splitting into smaller organisms
// Components split into separate files to keep each under 150 LOC
export { SidebarCore as Sidebar, type SidebarProps } from './SidebarCore'
export { SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger } from './SidebarExtras'
export { SidebarGroup, SidebarGroupContent, SidebarGroupLabel } from './SidebarGroup'
export { SidebarContent, SidebarFooter, SidebarHeader, SidebarInset } from './SidebarLayout'
export { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from './SidebarMenu'
@@ -1,32 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.sidebarDrawer {
:global(.drawer__content) {
width: var(--sidebar-width, 280px);
}
}
.sidebarCore {
flex-shrink: 0;
width: var(--sidebar-width, 280px);
transition: width 0.2s ease-in-out;
overflow: hidden;
&.open {
width: var(--sidebar-width, 280px);
}
&.collapsed {
width: var(--sidebar-width, 64px);
}
}
.sidebarInner {
width: var(--sidebar-inner-width, 280px);
height: 100%;
background-color: var(--background-paper, #fff);
border-right: 1px solid var(--divider, rgba(0, 0, 0, 0.12));
display: flex;
flex-direction: column;
}
@@ -1,72 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, Drawer, useMediaQueryDown } from '@/fakemui'
import styles from './SidebarCore.module.scss'
// Sidebar container
export interface SidebarProps {
children: ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
width?: number
collapsedWidth?: number
variant?: 'permanent' | 'persistent' | 'temporary'
side?: 'left' | 'right'
}
const SidebarCore = forwardRef<HTMLDivElement, SidebarProps>(
(
{
children,
open = true,
onOpenChange,
width = 280,
collapsedWidth = 64,
variant = 'permanent',
side = 'left',
...props
},
ref
) => {
const isMobile = useMediaQueryDown('md')
if (isMobile || variant === 'temporary') {
return (
<Drawer
ref={ref}
anchor={side}
open={open}
onClose={() => onOpenChange?.(false)}
className={styles.sidebarDrawer}
style={{ '--sidebar-width': `${width}px` } as React.CSSProperties}
{...props}
>
{children}
</Drawer>
)
}
const sidebarWidth = open ? width : collapsedWidth
const openClass = open ? styles.open : styles.collapsed
return (
<Box
ref={ref}
component="nav"
className={`${styles.sidebarCore} ${openClass}`}
style={{ '--sidebar-width': `${sidebarWidth}px`, '--sidebar-inner-width': `${width}px` } as React.CSSProperties}
{...props}
>
<Box className={styles.sidebarInner}>
{children}
</Box>
</Box>
)
}
)
SidebarCore.displayName = 'SidebarCore'
export { SidebarCore }
@@ -1,24 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.sidebarSeparator {
margin: 0.5rem 0;
}
.sidebarTrigger {
// Uses IconButton styles
}
.sidebarRail {
position: absolute;
right: -4px;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
&:hover {
background-color: var(--primary-main, #1976d2);
opacity: 0.2;
}
}
@@ -1,53 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, Divider, IconButton } from '@/fakemui'
import { Menu } from '@/fakemui/icons'
import styles from './SidebarExtras.module.scss'
// SidebarSeparator
const SidebarSeparator = forwardRef<HTMLHRElement, { className?: string }>(
({ className = '', ...props }, ref) => {
return <Divider ref={ref} className={`${styles.sidebarSeparator} ${className}`} {...props} />
}
)
SidebarSeparator.displayName = 'SidebarSeparator'
// SidebarTrigger
interface SidebarTriggerProps {
onClick?: () => void
className?: string
}
const SidebarTrigger = forwardRef<HTMLButtonElement, SidebarTriggerProps>(
({ onClick, className = '', ...props }, ref) => {
return (
<IconButton ref={ref} onClick={onClick} sm className={`${styles.sidebarTrigger} ${className}`} {...props}>
<Menu />
</IconButton>
)
}
)
SidebarTrigger.displayName = 'SidebarTrigger'
// SidebarRail
const SidebarRail = forwardRef<HTMLDivElement, { className?: string }>(
({ className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.sidebarRail} ${className}`}
{...props}
/>
)
}
)
SidebarRail.displayName = 'SidebarRail'
// SidebarProvider
const SidebarProvider = ({ children }: { children: ReactNode }) => <>{children}</>
SidebarProvider.displayName = 'SidebarProvider'
export { SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger }
@@ -1,41 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.sidebarGroup {
margin-bottom: 0.5rem;
}
.sidebarGroupHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
&.collapsible {
cursor: pointer;
&:hover {
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
}
}
}
.sidebarGroupLabel {
font-size: 0.7rem;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-secondary, rgba(0, 0, 0, 0.6));
}
.sidebarGroupLabelStandalone {
display: block;
padding: 0.5rem 1rem;
font-size: 0.7rem;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-secondary, rgba(0, 0, 0, 0.6));
}
.sidebarGroupList {
padding: 0;
}
@@ -1,88 +0,0 @@
'use client'
import { forwardRef, ReactNode, useState } from 'react'
import { Box, Collapse, List, Typography } from '@/fakemui'
import { ExpandLess, ExpandMore } from '@/fakemui/icons'
import styles from './SidebarGroup.module.scss'
// SidebarGroup
interface SidebarGroupProps {
children: ReactNode
label?: string
collapsible?: boolean
defaultOpen?: boolean
className?: string
}
const SidebarGroup = forwardRef<HTMLDivElement, SidebarGroupProps>(
({ children, label, collapsible, defaultOpen = true, className = '', ...props }, ref) => {
const [open, setOpen] = useState(defaultOpen)
return (
<Box ref={ref} className={`${styles.sidebarGroup} ${className}`} {...props}>
{label && (
<Box
onClick={collapsible ? () => setOpen(!open) : undefined}
className={`${styles.sidebarGroupHeader} ${collapsible ? styles.collapsible : ''}`}
>
<Typography
variant="overline"
className={styles.sidebarGroupLabel}
>
{label}
</Typography>
{collapsible &&
(open ? <ExpandLess size={16} /> : <ExpandMore size={16} />)}
</Box>
)}
{collapsible ? (
<Collapse in={open}>
<List dense className={styles.sidebarGroupList}>
{children}
</List>
</Collapse>
) : (
<List dense className={styles.sidebarGroupList}>
{children}
</List>
)}
</Box>
)
}
)
SidebarGroup.displayName = 'SidebarGroup'
// SidebarGroupLabel
const SidebarGroupLabel = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Typography
ref={ref}
variant="overline"
className={`${styles.sidebarGroupLabelStandalone} ${className}`}
{...props}
>
{children}
</Typography>
)
}
)
SidebarGroupLabel.displayName = 'SidebarGroupLabel'
// SidebarGroupContent
const SidebarGroupContent = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box ref={ref} className={className} {...props}>
<List dense className={styles.sidebarGroupList}>
{children}
</List>
</Box>
)
}
)
SidebarGroupContent.displayName = 'SidebarGroupContent'
export { SidebarGroup, SidebarGroupContent, SidebarGroupLabel }
@@ -1,26 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.sidebarHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid var(--divider, rgba(0, 0, 0, 0.12));
}
.sidebarContent {
flex: 1;
overflow: auto;
padding: 0.5rem 0;
}
.sidebarFooter {
padding: 1rem;
border-top: 1px solid var(--divider, rgba(0, 0, 0, 0.12));
}
.sidebarInset {
flex: 1;
overflow: auto;
}
@@ -1,69 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box } from '@/fakemui'
import styles from './SidebarLayout.module.scss'
// SidebarHeader
const SidebarHeader = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.sidebarHeader} ${className}`}
{...props}
>
{children}
</Box>
)
}
)
SidebarHeader.displayName = 'SidebarHeader'
// SidebarContent
const SidebarContent = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.sidebarContent} ${className}`}
{...props}
>
{children}
</Box>
)
}
)
SidebarContent.displayName = 'SidebarContent'
// SidebarFooter
const SidebarFooter = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box
ref={ref}
className={`${styles.sidebarFooter} ${className}`}
{...props}
>
{children}
</Box>
)
}
)
SidebarFooter.displayName = 'SidebarFooter'
// SidebarInset
const SidebarInset = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box ref={ref} className={`${styles.sidebarInset} ${className}`} {...props}>
{children}
</Box>
)
}
)
SidebarInset.displayName = 'SidebarInset'
export { SidebarContent, SidebarFooter, SidebarHeader, SidebarInset }
@@ -1,66 +0,0 @@
@use '@/styles/variables' as *;
@use '@/styles/mixins' as *;
.sidebarMenuList {
padding: 0;
}
.sidebarMenuListItem {
padding: 0;
}
.sidebarMenuItemWrapper {
width: 100%;
}
.sidebarMenuButton {
display: flex;
align-items: center;
width: calc(100% - 0.5rem);
margin: 0 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 4px;
text-decoration: none;
color: inherit;
background: none;
border: none;
cursor: pointer;
font-size: 0.875rem;
text-align: left;
transition: background-color 0.2s;
&:hover {
background-color: var(--action-hover, rgba(0, 0, 0, 0.04));
}
&.active,
&:global(.list-item-button--selected) {
background-color: var(--primary-main, #1976d2);
color: var(--primary-contrast-text, #fff);
.sidebarMenuIcon {
color: var(--primary-contrast-text, #fff);
}
&:hover {
background-color: var(--primary-dark, #1565c0);
}
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
.sidebarMenuIcon {
min-width: 36px;
display: flex;
align-items: center;
color: inherit;
}
.sidebarMenuText {
font-size: 0.875rem;
}
@@ -1,82 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@/fakemui'
import styles from './SidebarMenu.module.scss'
// SidebarMenu (alias for List)
const SidebarMenu = forwardRef<HTMLDivElement, { children: ReactNode; className?: string }>(
({ children, className = '', ...props }, ref) => {
return (
<Box ref={ref} className={className} {...props}>
<List dense className={styles.sidebarMenuList}>
{children}
</List>
</Box>
)
}
)
SidebarMenu.displayName = 'SidebarMenu'
// SidebarMenuItem
interface SidebarMenuItemProps {
children?: ReactNode
icon?: ReactNode
label?: string
href?: string
active?: boolean
disabled?: boolean
onClick?: () => void
className?: string
}
const SidebarMenuItem = forwardRef<HTMLDivElement, SidebarMenuItemProps>(
({ children, icon, label, href, active, disabled, onClick, className = '', ...props }, ref) => {
const content = children || label
const activeClass = active ? styles.active : ''
const buttonContent = (
<>
{icon && <ListItemIcon className={styles.sidebarMenuIcon}>{icon}</ListItemIcon>}
<ListItemText primary={content} className={styles.sidebarMenuText} />
</>
)
return (
<ListItem className={styles.sidebarMenuListItem}>
<Box ref={ref} className={`${styles.sidebarMenuItemWrapper} ${className}`} {...props}>
{href ? (
<a
href={href}
onClick={onClick}
className={`${styles.sidebarMenuButton} ${activeClass} ${disabled ? styles.disabled : ''}`}
aria-disabled={disabled}
>
{buttonContent}
</a>
) : (
<ListItemButton
selected={active}
disabled={disabled}
onClick={onClick}
className={`${styles.sidebarMenuButton} ${activeClass}`}
>
{buttonContent}
</ListItemButton>
)}
</Box>
</ListItem>
)
}
)
SidebarMenuItem.displayName = 'SidebarMenuItem'
// SidebarMenuButton (alias for ListItemButton)
const SidebarMenuButton = forwardRef<HTMLDivElement, SidebarMenuItemProps>((props, ref) => (
<SidebarMenuItem ref={ref} {...props} />
))
SidebarMenuButton.displayName = 'SidebarMenuButton'
export { SidebarMenu, SidebarMenuButton, SidebarMenuItem }
@@ -1,6 +0,0 @@
// Command barrel export - maintains backward compatibility after splitting into smaller organisms
// Components split into separate files to keep each under 150 LOC
export { CommandCore as Command, type CommandProps } from './CommandCore'
export { CommandDialog, CommandInput } from './CommandDialog'
export { CommandItem, CommandLoading, CommandSeparator, CommandShortcut } from './CommandItem'
export { CommandEmpty, CommandGroup, CommandList } from './CommandList'
@@ -1,33 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Paper } from '@/fakemui'
// Command container (like cmdk)
export interface CommandProps {
children: ReactNode
className?: string
onValueChange?: (value: string) => void
value?: string
filter?: (value: string, search: string) => number
shouldFilter?: boolean
}
const CommandCore = forwardRef<HTMLDivElement, CommandProps>(
({ children, onValueChange, value, shouldFilter = true, className, ...props }, ref) => {
return (
<Paper
ref={ref}
variant="outlined"
className={`command-container ${className || ''}`}
{...props}
>
{children}
</Paper>
)
}
)
CommandCore.displayName = 'CommandCore'
export { CommandCore }
@@ -1,56 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, Input } from '@/fakemui'
import { Search } from '@/fakemui/icons'
// CommandDialog
interface CommandDialogProps {
children: ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}
const CommandDialog = ({ children, open, onOpenChange }: CommandDialogProps) => {
if (!open) return null
return (
<Box className="command-dialog-overlay" onClick={() => onOpenChange?.(false)}>
<Box onClick={e => e.stopPropagation()} className="command-dialog-content">
{children}
</Box>
</Box>
)
}
CommandDialog.displayName = 'CommandDialog'
// CommandInput
interface CommandInputProps {
placeholder?: string
value?: string
onValueChange?: (value: string) => void
className?: string
}
const CommandInput = forwardRef<HTMLInputElement, CommandInputProps>(
({ placeholder = 'Search...', value, onValueChange, className, ...props }, ref) => {
return (
<Box className={`command-input-wrapper ${className || ''}`}>
<Input
ref={ref}
fullWidth
size="sm"
placeholder={placeholder}
value={value}
onChange={e => onValueChange?.(e.target.value)}
startAdornment={<Search size={16} className="command-input-icon" />}
className="command-input"
{...props}
/>
</Box>
)
}
)
CommandInput.displayName = 'CommandInput'
export { CommandDialog, CommandInput }
@@ -1,79 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import {
Box,
CircularProgress,
Divider,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Typography,
} from '@/fakemui'
// CommandItem
interface CommandItemProps {
children: ReactNode
value?: string
onSelect?: (value: string) => void
disabled?: boolean
icon?: ReactNode
shortcut?: string
className?: string
}
const CommandItem = forwardRef<HTMLLIElement, CommandItemProps>(
({ children, value, onSelect, disabled, icon, shortcut, className, ...props }, ref) => {
return (
<ListItem ref={ref} disablePadding className={className} {...props}>
<ListItemButton
disabled={disabled}
onClick={() => onSelect?.(value || '')}
className="command-item-button"
>
{icon && <ListItemIcon className="command-item-icon">{icon}</ListItemIcon>}
<ListItemText primary={children} />
{shortcut && (
<Typography variant="caption" className="command-item-shortcut">
{shortcut}
</Typography>
)}
</ListItemButton>
</ListItem>
)
}
)
CommandItem.displayName = 'CommandItem'
// CommandSeparator
const CommandSeparator = forwardRef<HTMLHRElement, { className?: string }>((props, ref) => {
return <Divider ref={ref} className="command-separator" {...props} />
})
CommandSeparator.displayName = 'CommandSeparator'
// CommandShortcut
const CommandShortcut = ({ children }: { children: ReactNode }) => {
return (
<Typography variant="caption" className="command-shortcut">
{children}
</Typography>
)
}
CommandShortcut.displayName = 'CommandShortcut'
// CommandLoading
const CommandLoading = ({ children }: { children?: ReactNode }) => {
return (
<Box className="command-loading">
<CircularProgress size={16} />
<Typography variant="body2">
{children || 'Loading...'}
</Typography>
</Box>
)
}
CommandLoading.displayName = 'CommandLoading'
export { CommandItem, CommandLoading, CommandSeparator, CommandShortcut }
@@ -1,64 +0,0 @@
'use client'
import { forwardRef, ReactNode } from 'react'
import { Box, List, Typography } from '@/fakemui'
// CommandList
interface CommandListProps {
children: ReactNode
className?: string
}
const CommandList = forwardRef<HTMLDivElement, CommandListProps>(({ children, className, ...props }, ref) => {
return (
<Box ref={ref} className={`command-list ${className || ''}`} {...props}>
<List dense disablePadding>
{children}
</List>
</Box>
)
})
CommandList.displayName = 'CommandList'
// CommandEmpty
interface CommandEmptyProps {
children?: ReactNode
className?: string
}
const CommandEmpty = forwardRef<HTMLDivElement, CommandEmptyProps>(
({ children = 'No results found.', className, ...props }, ref) => {
return (
<Box ref={ref} className={`command-empty ${className || ''}`} {...props}>
{children}
</Box>
)
}
)
CommandEmpty.displayName = 'CommandEmpty'
// CommandGroup
interface CommandGroupProps {
children: ReactNode
heading?: string
className?: string
}
const CommandGroup = forwardRef<HTMLDivElement, CommandGroupProps>(
({ children, heading, className, ...props }, ref) => {
return (
<Box ref={ref} className={`command-group ${className || ''}`} {...props}>
{heading && (
<Typography variant="overline" className="command-group-heading">
{heading}
</Typography>
)}
{children}
</Box>
)
}
)
CommandGroup.displayName = 'CommandGroup'
export { CommandEmpty, CommandGroup, CommandList }

Some files were not shown because too many files have changed in this diff Show More