{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-bubble",
  "type": "registry:component",
  "title": "Bubble",
  "description": "The message surface, with variants and reactions",
  "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-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/components/src/bubble.tsx",
      "target": "@ui/gpuixui/components/bubble.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { DimensionValue, Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport {\n  BUBBLE_ACTIONS,\n  BUBBLE_COLUMN,\n  BUBBLE_CONTENT,\n  BUBBLE_GROUP,\n  BUBBLE_REACTIONS,\n  BUBBLE_ROOT,\n  BUBBLE_VARIANTS,\n  cn,\n} from \"../tw/index.js\";\nimport { createContext, useContext, useState, type ReactNode } from \"react\";\nimport { accessibleName } from \"../primitives/core/index.js\";\nimport { Button, type ButtonProps } from \"./internal/button.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 { textOf } from \"./internal/text.js\";\nimport { alpha, controlHeight, useTheme } from \"./internal/theme.js\";\nimport {\n  bubbleAlignOf,\n  bubbleCorners,\n  bubbleGroupOf,\n  bubbleVariantOf,\n  reactionInset,\n  type BubbleAlign,\n  type BubbleGroupPosition,\n  type BubbleVariant,\n} from \"./bubble/logic.js\";\n\nexport type { BubbleAlign, BubbleGroupPosition, BubbleVariant };\n\ninterface BubbleContextValue {\n  readonly variant: BubbleVariant;\n  readonly align: BubbleAlign;\n  readonly group: BubbleGroupPosition;\n  readonly hovered: boolean;\n  readonly setHovered: (hovered: boolean) => void;\n}\n\nconst BubbleContext = createContext<BubbleContextValue | undefined>(undefined);\n\nconst useBubble = (): BubbleContextValue => {\n  const value = useContext(BubbleContext);\n  if (value === undefined) {\n    throw new Error(\"Bubble parts must be used inside Bubble\");\n  }\n  return value;\n};\n\nexport interface BubbleProps extends Omit<Props, \"children\"> {\n  readonly variant?: BubbleVariant;\n\n  readonly align?: BubbleAlign;\n\n  readonly group?: BubbleGroupPosition;\n\n  readonly maxWidth?: DimensionValue;\n\n  readonly avatar?: ReactNode;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Bubble = ({\n  variant: variantProp = \"default\",\n  align: alignProp = \"start\",\n  group: groupProp = \"single\",\n  maxWidth,\n  avatar,\n  className,\n  children,\n  style,\n  onMouseEnter,\n  onMouseLeave,\n  ...rest\n}: BubbleProps) => {\n  const theme = useTheme();\n  const variant = bubbleVariantOf(variantProp);\n  const align = bubbleAlignOf(alignProp);\n  const group = bubbleGroupOf(groupProp);\n  const [hovered, setHovered] = useState(false);\n  assertParts(bubbleRoot, children);\n\n  return (\n    <BubbleContext.Provider\n      value={{ variant, align, group, hovered, setHovered }}\n    >\n      <div\n        {...rest}\n        onMouseEnter={(event) => {\n          setHovered(true);\n          onMouseEnter?.(event);\n        }}\n        onMouseLeave={(event) => {\n          setHovered(false);\n          onMouseLeave?.(event);\n        }}\n        style={mergeStyles(\n          mergeStyles(recipeStyle(cn(BUBBLE_ROOT, className), theme), {\n            flexDirection: \"row\",\n            alignSelf: align === \"end\" ? \"flex-end\" : \"flex-start\",\n            maxWidth: maxWidth ?? (variant === \"ghost\" ? \"100%\" : \"80%\"),\n            ...(variant === \"ghost\" ? { flexGrow: 1 } : {}),\n          }),\n          style,\n        )}\n      >\n        {align === \"start\" ? avatar : null}\n        <div\n          style={mergeStyles(recipeStyle(BUBBLE_COLUMN, theme), {\n            alignItems: align === \"end\" ? \"flex-end\" : \"flex-start\",\n          })}\n        >\n          {children}\n        </div>\n        {align === \"end\" ? avatar : null}\n      </div>\n    </BubbleContext.Provider>\n  );\n};\n\nexport interface BubbleContentProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const BubbleContent = ({\n  className,\n  children,\n  style,\n  onMouseEnter,\n  onMouseLeave,\n  ...rest\n}: BubbleContentProps) => {\n  const theme = useTheme();\n  const { variant, align, group, setHovered } = useBubble();\n  const corners = bubbleCorners(\n    align,\n    group,\n    theme.radius.xl,\n    theme.radius.sm,\n    theme.radius.sm,\n  );\n  const ghost = variant === \"ghost\";\n\n  return (\n    <div\n      {...rest}\n      onMouseEnter={(event) => {\n        setHovered(true);\n        onMouseEnter?.(event);\n      }}\n      onMouseLeave={(event) => {\n        setHovered(false);\n        onMouseLeave?.(event);\n      }}\n      style={mergeStyles(\n        mergeStyles(\n          recipeStyle(\n            cn(BUBBLE_CONTENT, BUBBLE_VARIANTS[variant], className),\n            theme,\n          ),\n          {\n            ...corners,\n            ...(variant === \"tinted\"\n              ? { backgroundColor: alpha(theme.colors.primary, 0.14) }\n              : {}),\n            ...(ghost\n              ? {\n                  paddingTop: 0,\n                  paddingBottom: 0,\n                  paddingLeft: 0,\n                  paddingRight: 0,\n                  borderTopLeftRadius: 0,\n                  borderTopRightRadius: 0,\n                  borderBottomLeftRadius: 0,\n                  borderBottomRightRadius: 0,\n                }\n              : {}),\n          },\n        ),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface BubbleActionsProps extends Omit<Props, \"children\"> {\n  readonly label?: string;\n\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const BubbleActions = ({\n  label = \"Message actions\",\n  className,\n  children,\n  style,\n  ...rest\n}: BubbleActionsProps) => {\n  const theme = useTheme();\n  const { hovered, setHovered } = useBubble();\n  const name = accessibleName(textOf(label)) ?? \"Message actions\";\n  const [held, setHeld] = useState(false);\n  const revealed = hovered || held;\n\n  return (\n    <BubbleFocusContext.Provider value={setHeld}>\n      <div\n        role=\"group\"\n        aria-label={name}\n        {...rest}\n        style={mergeStyles(\n          mergeStyles(recipeStyle(cn(BUBBLE_ACTIONS, className), theme), {\n            height: controlHeight(theme, \"sm\"),\n            opacity: revealed ? 1 : 0,\n\n            ...(revealed ? {} : { pointerEvents: \"none\" as const }),\n          }),\n          style,\n        )}\n        onMouseEnter={() => setHovered(true)}\n        onMouseLeave={() => {\n          setHovered(false);\n          setHeld(false);\n        }}\n      >\n        {children}\n      </div>\n    </BubbleFocusContext.Provider>\n  );\n};\n\nconst BubbleFocusContext = createContext<\n  ((focused: boolean) => void) | undefined\n>(undefined);\n\nexport interface BubbleActionProps extends Omit<\n  ButtonProps,\n  \"variant\" | \"size\"\n> {\n  readonly label: string;\n}\n\nexport const BubbleAction = ({\n  label,\n  onFocus,\n  onBlur,\n  ...rest\n}: BubbleActionProps) => {\n  const report = useContext(BubbleFocusContext);\n  if (report === undefined) {\n    throw new Error(\"BubbleAction must be used inside BubbleActions\");\n  }\n  return (\n    <Button\n      {...rest}\n      variant=\"ghost\"\n      size=\"sm\"\n      onFocus={(event) => {\n        report(true);\n        onFocus?.(event);\n      }}\n      onBlur={(event) => {\n        report(false);\n        onBlur?.(event);\n      }}\n    >\n      {label}\n    </Button>\n  );\n};\n\nexport interface BubbleReactionsProps extends Omit<Props, \"children\"> {\n  readonly label: string;\n\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const BubbleReactions = ({\n  label,\n  className,\n  children,\n  style,\n  ...rest\n}: BubbleReactionsProps) => {\n  const theme = useTheme();\n  const name = accessibleName(textOf(label)) ?? \"Reactions\";\n  const recipe = recipeStyle(cn(BUBBLE_REACTIONS, className), theme);\n  const height =\n    (recipe.lineHeight ?? 0) +\n    (recipe.paddingTop ?? 0) +\n    (recipe.paddingBottom ?? 0);\n\n  return (\n    <div\n      role=\"group\"\n      aria-label={name}\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipe, {\n          alignSelf: \"flex-end\",\n          marginTop: reactionInset(height, theme.spacing.xs),\n          marginRight: theme.spacing.lg,\n          boxShadow: {\n            offsetX: 0,\n            offsetY: 0,\n            blurRadius: 0,\n            spreadRadius: theme.borderWidth.strong,\n            color: theme.colors.background,\n          },\n        }),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface BubbleGroupProps extends Omit<Props, \"children\"> {\n  readonly gap?: number;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const BubbleGroup = ({\n  gap,\n  className,\n  children,\n  style,\n  ...rest\n}: BubbleGroupProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(BUBBLE_GROUP, className), theme), {\n          gap: gap ?? theme.spacing.md,\n          alignItems: \"flex-start\",\n        }),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nconst bubbleFamily: ReadonlyArray<PartComponent> = [\n  Bubble,\n  BubbleContent,\n  BubbleActions,\n  BubbleAction,\n  BubbleReactions,\n  BubbleGroup,\n];\n\nconst bubbleRoot: PartsContract = {\n  parent: \"Bubble\",\n  family: bubbleFamily,\n  accepts: [BubbleContent, BubbleActions, BubbleReactions],\n  hint: \"Put BubbleContent, BubbleActions and BubbleReactions directly inside it.\",\n};\n\nexport const bubbleMeta = {\n  name: \"Bubble\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"the reaction pill hangs a quarter of its height over the surface's edge; shadcn's three-quarter overlap needs parent-relative absolute positioning, which `position: absolute` does not provide in 0.11.0 (it resolves against the mount parent), so the hanging part also lays out below the edge instead of over the next row\",\n    \"the surface paints the theme's `xl` corner (12px in the default ladder, 16–18px on the platform skins); shadcn computes `--radius * 1.4` (14px with its default radius), and the library's ladder has no such step\",\n    \"the type is the recipe's `text-sm` (14px/20px); shadcn sets `leading-relaxed` (22.75px) and the type scale has no relaxed step\",\n    \"the avatar is a prop slot, not a part, because the runtime cannot wrap a caller's children in the bubble's column selectively\",\n    \"no `render`/polymorphic content: `BubbleContent` is always a div, because the runtime has no anchor host\",\n    \"the action strip reserves its height while hidden, so revealing it does not move the bubble; a focus event belongs to the focused element, so a caller-supplied control that is not a `BubbleAction` does not reveal the strip on keyboard focus\",\n  ],\n  docs: \"docs/bubble.mdx\",\n  example: \"examples/bubble.example.tsx\",\n  testFile: \"src/behaviour/bubble.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/bubble/logic.ts",
      "target": "@ui/gpuixui/components/bubble/logic.ts",
      "type": "registry:lib",
      "content": "import * as Schema from \"effect/Schema\";\n\nconst BubbleVariants = Schema.Literals([\n  \"default\",\n  \"secondary\",\n  \"muted\",\n  \"tinted\",\n  \"outline\",\n  \"ghost\",\n  \"destructive\",\n]);\n\nexport type BubbleVariant = typeof BubbleVariants.Type;\n\nconst BubbleAlignments = Schema.Literals([\"start\", \"end\"]);\n\nexport type BubbleAlign = typeof BubbleAlignments.Type;\n\nconst BubbleGroups = Schema.Literals([\"single\", \"first\", \"middle\", \"last\"]);\n\nexport type BubbleGroupPosition = typeof BubbleGroups.Type;\n\nexport const bubbleVariantOf = (value: unknown): BubbleVariant =>\n  Schema.is(BubbleVariants)(value) ? value : \"default\";\n\nexport const bubbleAlignOf = (value: unknown): BubbleAlign =>\n  Schema.is(BubbleAlignments)(value) ? value : \"start\";\n\nexport const bubbleGroupOf = (value: unknown): BubbleGroupPosition =>\n  Schema.is(BubbleGroups)(value) ? value : \"single\";\n\nexport interface BubbleCorners {\n  readonly borderTopLeftRadius: number;\n  readonly borderTopRightRadius: number;\n  readonly borderBottomLeftRadius: number;\n  readonly borderBottomRightRadius: number;\n}\n\nexport const bubbleCorners = (\n  align: BubbleAlign,\n  group: BubbleGroupPosition,\n  radius: number,\n  join: number,\n  tail: number,\n): BubbleCorners => {\n  const top = group === \"middle\" || group === \"last\" ? join : radius;\n  const bottom = group === \"first\" || group === \"middle\" ? join : radius;\n  const sender = group === \"last\" ? tail : bottom;\n  return {\n    borderTopLeftRadius: top,\n    borderTopRightRadius: top,\n    borderBottomLeftRadius: align === \"end\" ? bottom : sender,\n    borderBottomRightRadius: align === \"end\" ? sender : bottom,\n  };\n};\n\nexport const reactionInset = (rowHeight: number, gap: number): number =>\n  -(rowHeight / 4 + gap);\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/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/text.ts",
      "target": "@ui/gpuixui/components/internal/text.ts",
      "type": "registry:lib",
      "content": "import { pipe } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport * as Str from \"effect/String\";\n\nexport const textOf = (value: unknown): string =>\n  typeof value === \"string\" ? value : \"\";\n\nexport const nameOf = (value: unknown): string | undefined =>\n  pipe(\n    Option.some(textOf(value)),\n    Option.map(Str.trim),\n    Option.filter(Str.isNonEmpty),\n    Option.getOrUndefined,\n  );\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": "[Bubble](https://gpuixui.sudokw.com/components/bubble) — 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/bubble",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}