{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-alert",
  "type": "registry:component",
  "title": "Alert",
  "description": "A persistent, in-flow message about the current state",
  "author": "sudo-kw <https://github.com/sudo-kw>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "effect@4.1.2",
    "react@^19.2.4"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-icons.json",
    "https://gpuixui.sudokw.com/gpuix-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/components/src/alert.tsx",
      "target": "@ui/gpuixui/components/alert.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport {\n  ALERT_BASE,\n  ALERT_CONTENT,\n  ALERT_DESCRIPTION,\n  ALERT_ICON,\n  ALERT_TITLE,\n  ALERT_TONES,\n  cn,\n} from \"../tw/index.js\";\nimport { createContext, useContext } from \"react\";\nimport { IconButton } from \"./internal/button.js\";\nimport { activationHandlers } from \"./internal/key.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  keyboardRing,\n  messageLink,\n  messageSelection,\n  targetSize,\n  tone,\n  toneOf,\n  useTheme,\n} from \"./internal/theme.js\";\n\nexport type AlertTone = \"info\" | \"success\" | \"warning\" | \"danger\";\n\ninterface AlertContextValue {\n  readonly tone: AlertTone;\n\n  readonly foreground: string;\n}\n\nconst AlertContext = createContext<AlertContextValue | undefined>(undefined);\n\nconst useAlert = (): AlertContextValue => {\n  const value = useContext(AlertContext);\n  if (value === undefined) {\n    throw new Error(\"Alert parts must be used inside Alert\");\n  }\n  return value;\n};\n\nexport interface AlertProps extends Omit<Props, \"children\"> {\n  readonly tone?: AlertTone;\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Alert = ({\n  tone: requestedTone = \"info\",\n  className,\n  children,\n  style,\n  ...rest\n}: AlertProps) => {\n  const theme = useTheme();\n  const value = toneOf(requestedTone);\n  const { fg } = tone(theme, value);\n  assertParts(alertRoot, children);\n\n  return (\n    <AlertContext.Provider value={{ tone: value, foreground: fg }}>\n      <div\n        role=\"alert\"\n        {...rest}\n        style={mergeStyles(\n          mergeStyles(\n            recipeStyle(cn(ALERT_BASE, ALERT_TONES[value], className), theme),\n\n            { selectionColor: messageSelection(theme, value) },\n          ),\n          style,\n        )}\n      >\n        {children}\n      </div>\n    </AlertContext.Provider>\n  );\n};\n\nexport interface AlertIconProps extends Props {\n  readonly className?: string;\n}\n\nexport const AlertIcon = ({ className, style, ...rest }: AlertIconProps) => {\n  const theme = useTheme();\n  const { foreground } = useAlert();\n\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(ALERT_ICON, className), theme), {\n          color: foreground,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface AlertContentProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const AlertContent = ({\n  className,\n  children,\n  style,\n  ...rest\n}: AlertContentProps) => {\n  const theme = useTheme();\n  assertParts(alertContent, children);\n\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(ALERT_CONTENT, className), theme),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface AlertTitleProps extends Props {\n  readonly className?: string;\n}\n\nexport const AlertTitle = ({ className, style, ...rest }: AlertTitleProps) => {\n  const theme = useTheme();\n  const { foreground } = useAlert();\n\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(ALERT_TITLE, className), theme), {\n          color: foreground,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface AlertDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const AlertDescription = ({\n  className,\n  style,\n  ...rest\n}: AlertDescriptionProps) => {\n  const theme = useTheme();\n\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(ALERT_DESCRIPTION, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface AlertDismissProps extends Omit<Props, \"children\"> {\n  readonly onDismiss: () => void;\n\n  readonly label?: string;\n}\n\nexport const AlertDismiss = ({\n  onDismiss,\n  label = \"Dismiss\",\n  ...rest\n}: AlertDismissProps) => {\n  const theme = useTheme();\n  return (\n    <IconButton\n      {...rest}\n      size=\"sm\"\n      label={label}\n      testId=\"alert-dismiss\"\n      onClick={onDismiss}\n    >\n      <Icon name=\"x\" size=\"sm\" color={theme.colors.foreground} />\n    </IconButton>\n  );\n};\n\nexport interface AlertLinkProps extends Props {\n  readonly onSelect?: () => void;\n  readonly className?: string;\n}\n\nexport const AlertLink = ({\n  onSelect,\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  onFocus,\n  onBlur,\n  className,\n  style,\n  children,\n  ...rest\n}: AlertLinkProps) => {\n  const theme = useTheme();\n  const { tone: value } = useAlert();\n\n  return (\n    <div\n      role=\"link\"\n      tabIndex={0}\n      {...rest}\n      onClick={(event) => {\n        onClick?.(event);\n        onSelect?.();\n      }}\n      {...activationHandlers(() => onSelect?.(), { onKeyDown, onKeyUp })}\n      onFocus={(event) => onFocus?.(event)}\n      onBlur={(event) => onBlur?.(event)}\n      style={mergeStyles(\n        mergeStyles(\n          recipeStyle(\n            cn(\n              ALERT_DESCRIPTION,\n              \"flex flex-row items-center self-start\",\n              className,\n            ),\n            theme,\n          ),\n          {\n            height: targetSize.minimum,\n            color: messageLink(theme, value),\n            hover: { textDecoration: \"underline\" },\n            cursor: \"pointer\",\n            ...keyboardRing(theme),\n          },\n        ),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nconst alertParts: ReadonlyArray<PartComponent> = [\n  Alert,\n  AlertIcon,\n  AlertContent,\n  AlertTitle,\n  AlertDescription,\n  AlertDismiss,\n  AlertLink,\n];\n\nconst alertRoot: PartsContract = {\n  parent: \"Alert\",\n  family: alertParts,\n  accepts: [\n    AlertIcon,\n    AlertContent,\n    AlertTitle,\n    AlertDescription,\n    AlertDismiss,\n    AlertLink,\n  ],\n  hint: \"Put the icon, content and dismiss control directly inside Alert.\",\n};\n\nconst alertContent: PartsContract = {\n  parent: \"AlertContent\",\n  family: alertParts,\n  accepts: [AlertTitle, AlertDescription, AlertLink],\n  hint: \"Put AlertTitle, AlertDescription and AlertLink directly inside AlertContent.\",\n};\n\nexport const alertMeta = {\n  name: \"Alerts\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"a message that appears is not announced by a live region: 0.11.0 has no aria-live, so only the root's alert role reaches assistive technology\",\n  ],\n  docs: \"docs/alert.mdx\",\n  example: \"examples/alert.example.tsx\",\n  testFile: \"src/behaviour/alert.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/internal/button.tsx",
      "target": "@ui/gpuixui/components/internal/button.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport type { ReactNode } from \"react\";\nimport {\n  Button as PrimitiveButton,\n  IconButton as PrimitiveIconButton,\n  actionOnColor as primitiveActionOnColor,\n  type ButtonVariant as PrimitiveVariant,\n} from \"../../primitives/button.js\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Schema from \"effect/Schema\";\nimport type { Theme } from \"./theme.js\";\n\nconst ButtonVariants = Schema.Literals([\n  \"primary\",\n  \"secondary\",\n  \"ghost\",\n  \"danger\",\n  \"link\",\n]);\n\nexport type ButtonVariant = typeof ButtonVariants.Type;\n\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ButtonProps extends Props {\n  readonly variant?: ButtonVariant;\n\n  readonly size?: ButtonSize;\n\n  readonly disabled?: boolean;\n\n  readonly loading?: boolean;\n\n  readonly loadingLabel?: string;\n\n  readonly reducedMotion?: boolean;\n\n  readonly leading?: ReactNode;\n\n  readonly trailing?: ReactNode;\n}\n\nexport interface IconButtonProps extends Props {\n  readonly label: string;\n\n  readonly size?: ButtonSize;\n  readonly disabled?: boolean;\n}\n\ninterface Look {\n  readonly variant: PrimitiveVariant;\n  readonly className: string;\n}\n\nconst LOOKS: Readonly<Record<ButtonVariant, Look>> = {\n  primary: { variant: \"primary\", className: \"\" },\n  secondary: { variant: \"secondary\", className: \"border border-strong\" },\n  ghost: { variant: \"ghost\", className: \"\" },\n  danger: { variant: \"destructive\", className: \"\" },\n\n  link: { variant: \"link\", className: \"underline underline-offset-4\" },\n};\n\nconst lookOf = (variant: unknown): Look =>\n  LOOKS[Schema.is(ButtonVariants)(variant) ? variant : \"secondary\"];\n\nexport const actionOnColor = (theme: Theme, variant: ButtonVariant): string =>\n  primitiveActionOnColor(theme, lookOf(variant).variant);\n\nexport const Button = ({\n  variant = \"secondary\",\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  \"aria-label\": ariaLabel,\n  ...rest\n}: ButtonProps) => {\n  const look = lookOf(variant);\n  return (\n    <PrimitiveButton\n      {...rest}\n      variant={look.variant}\n      size={size}\n      disabled={disabled}\n      label={ariaLabel}\n      className={cn(look.className)}\n      onActivate={onClick}\n      style={style}\n    />\n  );\n};\n\nexport const IconButton = ({\n  label,\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  children,\n  ...rest\n}: IconButtonProps) => (\n  <PrimitiveIconButton\n    {...rest}\n    icon={children}\n    variant=\"ghost\"\n    size={size}\n    disabled={disabled}\n    label={label}\n    className=\"text-muted-foreground hover:text-foreground\"\n    onActivate={onClick}\n    style={style}\n  />\n);\n"
    },
    {
      "path": "packages/components/src/internal/key.ts",
      "target": "@ui/gpuixui/components/internal/key.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload, KeyEvent, Props } from \"@gpuix/react\";\nimport * as Option from \"effect/Option\";\n\nexport type KeyPhase = \"down\" | \"up\";\n\nexport const isActivation = (event: EventPayload, phase: KeyPhase): boolean => {\n  if (event.isHeld === true) return false;\n  if (event.key === \"enter\") return phase === \"down\";\n  return event.key === \"space\" && phase === \"up\";\n};\n\nexport const swallowsDefault = (\n  event: EventPayload,\n  phase: KeyPhase,\n): boolean => event.key === \"space\" && phase === \"down\";\n\nexport const activationHandlers = (\n  activate: (event: KeyEvent) => void,\n  observe: Pick<Props, \"onKeyDown\" | \"onKeyUp\"> = {},\n): Pick<Props, \"onKeyDown\" | \"onKeyUp\"> => ({\n  onKeyDown: (event: KeyEvent) => {\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    if (isActivation(event, \"down\")) activate(event);\n    observe.onKeyDown?.(event);\n  },\n  onKeyUp: (event: KeyEvent) => {\n    if (isActivation(event, \"up\")) activate(event);\n    observe.onKeyUp?.(event);\n  },\n});\n\nexport const isEnter = (event: EventPayload): boolean => event.key === \"enter\";\n\nexport const isDown = (event: EventPayload): boolean =>\n  event.key === \"down\" || (event.key === \"n\" && event.modifiers?.ctrl === true);\n\nexport const isUp = (event: EventPayload): boolean =>\n  event.key === \"up\" || (event.key === \"p\" && event.modifiers?.ctrl === true);\n\nexport const isRight = (event: EventPayload): boolean => event.key === \"right\";\n\nexport const isLeft = (event: EventPayload): boolean => event.key === \"left\";\n\nexport const isFirst = (event: EventPayload): boolean => event.key === \"home\";\n\nexport const isLast = (event: EventPayload): boolean => event.key === \"end\";\n\nexport const isPageUp = (event: EventPayload): boolean =>\n  event.key === \"pageup\";\n\nexport const isPageDown = (event: EventPayload): boolean =>\n  event.key === \"pagedown\";\n\nexport const isDismiss = (event: EventPayload): boolean =>\n  event.key === \"escape\";\n\nexport const isTab = (event: EventPayload): boolean => event.key === \"tab\";\n\nexport const isSelectAll = (event: EventPayload): boolean =>\n  event.key === \"a\" &&\n  (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n\nexport const isMenuKey = (event: EventPayload): boolean =>\n  (event.key === \"f10\" && event.modifiers?.shift === true) ||\n  event.key === \"contextmenu\";\n\nexport const axisDirection = (\n  event: EventPayload,\n  orientation: \"horizontal\" | \"vertical\",\n): Option.Option<1 | -1> => {\n  if (event.key === \"right\") return Option.some(1);\n  if (event.key === \"left\") return Option.some(-1);\n  if (orientation === \"vertical\") {\n    if (isDown(event)) return Option.some(1);\n    if (isUp(event)) return Option.some(-1);\n  }\n  return Option.none();\n};\n"
    },
    {
      "path": "packages/components/src/internal/parts.ts",
      "target": "@ui/gpuixui/components/internal/parts.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as P from \"effect/Predicate\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\n\nexport type PartComponent = (props: never) => ReactNode;\n\nexport type PartChildren =\n  | ReactElement<unknown>\n  | boolean\n  | null\n  | undefined\n  | ReadonlyArray<PartChildren>;\n\nexport interface PartsContract {\n  readonly parent: string;\n\n  readonly family: ReadonlyArray<PartComponent>;\n\n  readonly accepts: ReadonlyArray<PartComponent>;\n\n  readonly hint?: string;\n}\n\nconst componentName = (type: unknown): string =>\n  P.isString(type)\n    ? type\n    : P.isFunction(type)\n      ? type.name || \"anonymous\"\n      : \"element\";\n\nconst fragmentChildren = (element: ReactElement<unknown>): ReactNode =>\n  hasChildren(element.props) ? element.props.children : undefined;\n\nconst hasChildren = (\n  value: unknown,\n): value is { readonly children?: ReactNode } =>\n  P.isObject(value) && \"children\" in value;\n\nconst leaves = (children: ReactNode): ReadonlyArray<unknown> =>\n  Arr.flatMap(Children.toArray(children), (child) =>\n    isValidElement<unknown>(child) && child.type === Fragment\n      ? leaves(fragmentChildren(child))\n      : [child],\n  );\n\nexport const assertParts = (\n  contract: PartsContract,\n  children: ReactNode,\n): void => {\n  Arr.forEach(leaves(children), (leaf) => {\n    if (!isValidElement(leaf)) return;\n    if (Arr.some(contract.accepts, (part) => part === leaf.type)) return;\n    if (!Arr.some(contract.family, (part) => part === leaf.type)) return;\n\n    throw new Error(\n      `${componentName(leaf.type)} cannot be used inside ${contract.parent}.` +\n        (contract.hint === undefined ? \"\" : ` ${contract.hint}`),\n    );\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/theme.ts",
      "target": "@ui/gpuixui/components/internal/theme.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  brandTextOn,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  pointerWash,\n  selectionOn,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n  type BoxShadow,\n  type ElevationLevel,\n  type FindHighlightSource,\n  type NativeTextFill,\n  type PlatformName,\n  type PointerWashSurface,\n  type StatusTone,\n  type SyntaxColors,\n  type SyntaxRole,\n  type TextRole,\n  type Theme,\n  type ThemeColors,\n} from \"../../tokens/index.js\";\n\nexport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n};\nexport type {\n  BoxShadow,\n  ElevationLevel,\n  FindHighlightSource,\n  NativeTextFill,\n  PlatformName,\n  StatusTone,\n  SyntaxColors,\n  SyntaxRole,\n  TextRole,\n  Theme,\n};\nexport { ThemeProvider, useTheme } from \"../../tokens/react.js\";\n\nexport const keyboardRing = (theme: Theme, invalid = false): StyleDesc =>\n  controlFocusRing(theme, { invalid }) ?? {};\n\nexport interface ControlRingState {\n  readonly disabled?: boolean;\n\n  readonly invalid?: boolean;\n}\n\nexport const controlRing = (\n  theme: Theme,\n  state: ControlRingState = {},\n): StyleDesc => controlFocusRing(theme, state) ?? {};\n\nexport const pressWash = (theme: Theme, replaced: PointerWashSurface): string =>\n  pointerWash(theme, \"active\", replaced) ?? theme.colors.active;\n\nexport const selectionWash = (theme: Theme): StyleDesc => ({\n  selectionColor: theme.colors.selection,\n});\n\nexport const keepContent: StyleDesc = { keyboardFocusDim: false };\n\nexport const disabledFade = (\n  disabled: boolean,\n): { readonly opacity: number } | undefined =>\n  disabled ? { opacity: disabledOpacity } : undefined;\n\nconst Tones = Schema.Literals([\"info\", \"success\", \"warning\", \"danger\"]);\n\nexport type Tone = typeof Tones.Type;\n\nexport const toneOf = (value: unknown): Tone =>\n  Schema.is(Tones)(value) ? value : \"info\";\n\nexport interface ToneStyle {\n  readonly fg: string;\n  readonly bg: string;\n  readonly border: string;\n}\n\nconst TOKEN_TONE: Readonly<Record<Tone, StatusTone>> = {\n  info: \"info\",\n  success: \"success\",\n  warning: \"warning\",\n  danger: \"destructive\",\n};\n\nexport const tone = (theme: Theme, value: Tone): ToneStyle => {\n  const { background, border, foreground } = statusTone(\n    theme,\n    TOKEN_TONE[value],\n  );\n  return { bg: background, border, fg: foreground };\n};\n\nconst softRoles: Readonly<Record<Tone, keyof ThemeColors>> = {\n  info: \"infoSoft\",\n  success: \"successSoft\",\n  warning: \"warningSoft\",\n  danger: \"destructiveSoft\",\n};\n\nexport const messageLink = (theme: Theme, value: Tone): string =>\n  brandTextOn(theme, softRoles[value]);\n\nexport const messageSelection = (theme: Theme, value: Tone): string =>\n  selectionOn(theme, softRoles[value]);\n\nexport const toneSurface = (theme: Theme, value: Tone): StyleDesc => {\n  const { bg, border, fg } = tone(theme, value);\n  return {\n    backgroundColor: bg,\n    color: fg,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: border,\n  };\n};\n"
    }
  ],
  "docs": "[Alert](https://gpuixui.sudokw.com/components/alert) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/components/alert",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}