Permission Selector
dominik-portfolio/permission-selectorFreeComponent
A composable segmented permission control with an animated sliding pill — read/write levels and deny/neutral/allow toggles.
Install it
npx shadcn@latest add https://dominikkoch.dev/r/permission-selector.jsonSource
1"use client";23import { domMax, LazyMotion, m } from "motion/react";4import {5 createContext,6 type ReactNode,7 useContext,8 useId,9 useState,10} from "react";11import { cn } from "@/lib/utils";1213type PermissionTone = "neutral" | "success" | "danger" | "warning";1415const SPRING = { type: "spring", bounce: 0.2, duration: 0.4 } as const;1617const TONE_TEXT: Record<PermissionTone, string> = {18 neutral: "text-foreground",19 success: "text-emerald-600 dark:text-emerald-400",20 danger: "text-destructive",21 warning: "text-amber-600 dark:text-amber-500",22};2324const TONE_PILL: Record<PermissionTone, string> = {25 neutral: "bg-background ring-border",26 success: "bg-emerald-500/10 ring-emerald-500/30",27 danger: "bg-destructive/10 ring-destructive/30",28 warning: "bg-amber-500/10 ring-amber-500/30",29};3031interface PermissionRowContextValue {32 value: string | undefined;33 select: (value: string) => void;34 layoutId: string;35 disabled: boolean;36}3738const PermissionRowContext = createContext<PermissionRowContextValue | null>(39 null40);4142function usePermissionRow() {43 const context = useContext(PermissionRowContext);44 if (!context) {45 throw new Error("PermissionOption must be used within a PermissionRow");46 }47 return context;48}4950interface PermissionSelectorProps {51 children: ReactNode;52 label?: string;53 className?: string;54}5556function PermissionSelector({57 children,58 label = "Permissions",59 className,60}: PermissionSelectorProps) {61 return (62 <fieldset63 className={cn(64 "w-full divide-y overflow-hidden rounded-xl border bg-background",65 className66 )}67 >68 <legend className="sr-only">{label}</legend>69 {children}70 </fieldset>71 );72}7374interface PermissionRowProps {75 children: ReactNode;76 label: ReactNode;77 description?: ReactNode;78 value?: string;79 defaultValue?: string;80 onValueChange?: (value: string) => void;81 disabled?: boolean;82 className?: string;83}8485function PermissionRow({86 children,87 label,88 description,89 value,90 defaultValue,91 onValueChange,92 disabled = false,93 className,94}: PermissionRowProps) {95 const layoutId = useId();96 const [internalValue, setInternalValue] = useState(defaultValue);97 const activeValue = value ?? internalValue;9899 const select = (next: string) => {100 setInternalValue(next);101 onValueChange?.(next);102 };103104 const context: PermissionRowContextValue = {105 value: activeValue,106 select,107 layoutId,108 disabled,109 };110111 return (112// … truncated
What it pulls in
npm packages
Files it writes
More from dominik-portfolio
All 4 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Braille Loaderbraille-loader | dominik-portfolio | Components | Free | no deps · 2 files | |
| Command Tabscommand-tabs | dominik-portfolio | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | dominik-portfolio | Components | Free | no deps |
