Progress primitive wrapper
import { Progress } from '@/components/ui/progress'
...import { Progress } from '@/components/ui/progress'
export default function ProgressDemoExample() {
return <Progress value={66} max={100} aria-label="Loading progress" />
}
Installation
npx @fictjs/shadcn@latest add progressInstall the runtime dependencies:
npm install @fictjs/radix-uiCopy the Fict shadcn component into your project.
Source
components/ui/progress.tsx
src/components/ui/progress.tsx
import { Progress as ProgressPrimitive } from '@fictjs/radix-ui'
import { cn } from '@/lib/cn'
type ProgressProps = {
class?: string
indicatorClass?: string
value?: number | null
max?: number
children?: unknown
[key: string]: unknown
}
export function Progress(props: ProgressProps) {
const { class: className, indicatorClass, value = 0, max = 100, children, ...rest } = props
const percentage = value === null ? 0 : Math.min(100, Math.max(0, (value / max) * 100))
return (
<ProgressPrimitive.Root
value={value}
max={max}
class={cn('relative h-2 w-full overflow-hidden rounded-full bg-secondary', className)}
{...rest}
>
{children ?? (
<ProgressPrimitive.Indicator
class={cn('h-full w-full flex-1 bg-primary transition-transform', indicatorClass)}
style={{ transform: 'translateX(-' + String(100 - percentage) + '%)' }}
/>
)}
</ProgressPrimitive.Root>
)
}
Usage
Examples
Label
import { Progress } from '@/components/ui/progress'
...import { Progress } from '@/components/ui/progress'
export default function ProgressLabelExample() {
return <div class="grid w-full max-w-sm gap-2"><label for="upload-progress" class="flex justify-between"><span>Upload progress</span><span>66%</span></label><Progress id="upload-progress" value={66} max={100} /></div>
}
Controlled
import { Progress } from '@/components/ui/progress'
import { Slider } from '@/components/ui/slider'
...import { Progress } from '@/components/ui/progress'
import { Slider } from '@/components/ui/slider'
export default function ProgressControlledExample() {
let value = $state(50)
return <div class="grid w-full max-w-sm gap-3"><Progress value={value} max={100} /><Slider value={[value]} min={0} max={100} step={1} aria-label="Progress" onValueChange={next => { value = next[0] }} /></div>
}
Rtl
import { Progress } from '@/components/ui/progress'
...import { Progress } from '@/components/ui/progress'
const translations = {
ar: { dir: 'rtl', label: 'تقدم الرفع', value: '٦٦%' },
he: { dir: 'rtl', label: 'התקדמות העלאה', value: '66%' },
en: { dir: 'ltr', label: 'Upload progress', value: '66%' },
} as const
export default function ProgressRtlExample() {
let language = $state<keyof typeof translations>('ar')
const text = () => translations[language]
return <div class="grid gap-4"><select value={language} onChange={event => { language = event.currentTarget.value as keyof typeof translations }}><option value="ar">Arabic (العربية)</option><option value="he">Hebrew (עברית)</option><option value="en">English</option></select><div class="grid w-full max-w-sm gap-2" dir={text().dir}><label for="upload-progress-rtl" class="flex justify-between"><span>{text().label}</span><span>{text().value}</span></label><Progress id="upload-progress-rtl" value={66} max={100} /></div></div>
}