@xsolla/xui-gradient-picker 0.204.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/GradientPicker.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../../../foundation/primitives-native/src/index.tsx","../../src/GradientStopMarker.tsx","../../src/utils.ts","../../src/icons.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text, isWeb } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme } from \"@xsolla/xui-core\";\nimport { Input } from \"@xsolla/xui-input\";\nimport { InputColor } from \"@xsolla/xui-input-color\";\nimport { Select } from \"@xsolla/xui-select\";\nimport { IconButton } from \"@xsolla/xui-button\";\nimport { ColorPicker } from \"@xsolla/xui-color-picker\";\nimport { GradientStopMarker } from \"./GradientStopMarker\";\nimport { AngleGlyph, MinusGlyph, PercentGlyph, PlusGlyph } from \"./icons\";\nimport {\n DEFAULT_GRADIENT,\n addStopAt,\n clampOpacity,\n clampPosition,\n normalizeHex,\n removeStop,\n sortStops,\n supportsAngle,\n toCssGradient,\n toPreviewCssGradient,\n updateStop,\n} from \"./utils\";\nimport type {\n GradientPickerProps,\n GradientStop,\n GradientType,\n GradientValue,\n} from \"./types\";\n\nconst DEFAULT_TYPE_LABELS: Record<GradientType, string> = {\n linear: \"Linear gradient\",\n radial: \"Radial gradient\",\n angular: \"Angular gradient\",\n};\n\nconst ALL_TYPES: GradientType[] = [\"linear\", \"radial\", \"angular\"];\n\n/**\n * Vertical distance (px) a stop must be dragged away from the track before it\n * is dropped, per the Figma behaviour note: \"a stop can be removed by dragging\n * it off the track (up or down beyond a threshold)\".\n */\nconst DRAG_OFF_THRESHOLD = 32;\n\nconst TRACK_HEIGHT = 16;\n\n/**\n * Horizontal padding around the track so a marker parked at 0% / 100% — which\n * is centred on the track edge — is not clipped by the panel padding.\n */\nconst MARKER_INSET = 9;\n\n/**\n * Combines a stop's hex + opacity into the `#RRGGBBAA` string that ColorPicker\n * and InputColor expect when the alpha channel is enabled.\n */\nconst toHexa = (color: string, opacity: number): string => {\n const alpha = Math.round((clampOpacity(opacity) / 100) * 255)\n .toString(16)\n .padStart(2, \"0\")\n .toUpperCase();\n return `${normalizeHex(color)}${alpha}`;\n};\n\n/**\n * Splits a `#RRGGBB` / `#RRGGBBAA` string back into a stop's hex + opacity\n * percent. The alpha pair is omitted by ColorPicker when fully opaque.\n */\nconst fromHexa = (value: string): { color: string; opacity: number } => {\n const raw = String(value ?? \"\")\n .trim()\n .replace(/^#/, \"\");\n if (/^[0-9a-fA-F]{8}$/.test(raw)) {\n return {\n color: normalizeHex(raw.slice(0, 6)),\n opacity: clampOpacity((parseInt(raw.slice(6, 8), 16) / 255) * 100),\n };\n }\n return { color: normalizeHex(raw), opacity: 100 };\n};\n\n/**\n * A compound control for defining a CSS gradient.\n *\n * Combines a live preview track carrying draggable stop markers with a per-stop\n * row editor (position, colour, opacity) and an integrated `ColorPicker` for\n * the active stop. Supports linear, radial and angular (CSS `conic`) gradients\n * and 1–5 colour stops.\n *\n * Every interaction updates the preview immediately — there is no confirm step —\n * and the gradient is emitted both as structured data and as a ready-to-apply\n * CSS string.\n *\n * @example\n * ```tsx\n * const [gradient, setGradient] = useState<GradientValue>();\n *\n * <GradientPicker\n * value={gradient}\n * onChange={({ value, css }) => {\n * setGradient(value);\n * setBackground(css); // e.g. \"linear-gradient(0deg, #D9D9D9 0%, #22A8C3 100%)\"\n * }}\n * />\n * ```\n */\nexport const GradientPicker: React.FC<GradientPickerProps> = ({\n value: propValue,\n defaultValue,\n onChange,\n gradientTypes = ALL_TYPES,\n typeLabels,\n minStops = 1,\n maxStops = 5,\n disabled = false,\n typeAriaLabel = \"Gradient type\",\n angleAriaLabel = \"Gradient angle\",\n stopsLabel = \"Stops\",\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n // Token bags are widened the same way Select does — the shipped theme carries\n // more leaves than the published type.\n const contentColors = theme.colors.content as any;\n\n /**\n * Every themeable child has to receive the override explicitly. Without this\n * the panel resolves `themeMode` but Select / Input / IconButton fall back to\n * the provider theme, so a light-mode picker renders white-on-white text and\n * invisible +/− glyphs.\n */\n const themeProps = { themeMode, themeProductContext };\n\n const isControlled = propValue !== undefined;\n const [innerValue, setInnerValue] = useState<GradientValue>(\n () => propValue ?? defaultValue ?? DEFAULT_GRADIENT\n );\n const value = isControlled ? (propValue as GradientValue) : innerValue;\n\n const [activeStopId, setActiveStopId] = useState<string | null>(null);\n const [draggingStopId, setDraggingStopId] = useState<string | null>(null);\n\n const trackRef = useRef<any>(null);\n const rootRef = useRef<any>(null);\n\n const stops = sortStops(value.stops);\n const activeStop = stops.find((stop) => stop.id === activeStopId) ?? null;\n const canAdd = !disabled && stops.length < maxStops;\n const canRemove = !disabled && stops.length > minStops;\n\n const emit = useCallback(\n (next: GradientValue) => {\n if (!isControlled) setInnerValue(next);\n onChange?.({ value: next, css: toCssGradient(next) });\n },\n [isControlled, onChange]\n );\n\n const patchStops = useCallback(\n (nextStops: GradientStop[]) => {\n if (nextStops === value.stops) return;\n emit({ ...value, stops: nextStops });\n },\n [emit, value]\n );\n\n /**\n * Returns focus to a stop marker — required when the ColorPicker closes\n * (\"focus must return to the stop marker\").\n */\n const focusMarker = useCallback((stopId: string) => {\n if (!isWeb) return;\n const node = rootRef.current?.querySelector?.(\n `[data-testid=\"gradient-stop-${stopId}\"]`\n ) as HTMLElement | null;\n node?.focus?.();\n }, []);\n\n const closeColorPicker = useCallback(() => {\n const previous = activeStopId;\n setActiveStopId(null);\n if (previous) focusMarker(previous);\n }, [activeStopId, focusMarker]);\n\n // Escape closes the ColorPicker and deactivates the stop.\n useEffect(() => {\n if (!isWeb || !activeStopId) return undefined;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") closeColorPicker();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [activeStopId, closeColorPicker]);\n\n const positionFromClientX = useCallback((clientX: number): number => {\n const rect = trackRef.current?.getBoundingClientRect?.();\n if (!rect || !rect.width) return 0;\n return clampPosition(((clientX - rect.left) / rect.width) * 100);\n }, []);\n\n /**\n * Adds a stop and activates it, so the ColorPicker opens on the new stop.\n * No-ops silently once `maxStops` is reached.\n */\n const addStop = (position: number) => {\n const before = value.stops;\n const after = addStopAt(before, position, maxStops);\n if (after === before) return;\n patchStops(after);\n const added = after.find(\n (stop) => !before.some((existing) => existing.id === stop.id)\n );\n if (added) setActiveStopId(added.id);\n };\n\n const handleTrackPress = (event: React.MouseEvent) => {\n if (!canAdd) return;\n addStop(positionFromClientX(event.clientX));\n };\n\n /**\n * Keyboard-accessible alternative to clicking the track: drops the new stop\n * in the widest gap so it never lands exactly on top of an existing one.\n */\n const handleAddStopPress = () => {\n if (!canAdd) return;\n const sorted = sortStops(value.stops);\n let position = 50;\n let widest = -1;\n for (let index = 0; index < sorted.length - 1; index += 1) {\n const gap = sorted[index + 1].position - sorted[index].position;\n if (gap > widest) {\n widest = gap;\n position = sorted[index].position + gap / 2;\n }\n }\n addStop(position);\n };\n\n const handleRemoveStop = (stopId: string) => {\n const after = removeStop(value.stops, stopId, minStops);\n if (after === value.stops) return;\n if (activeStopId === stopId) setActiveStopId(null);\n patchStops(after);\n };\n\n const handleDragStart = (stop: GradientStop) => (event: React.MouseEvent) => {\n if (disabled) return;\n event.preventDefault();\n event.stopPropagation();\n\n const startY = event.clientY;\n setDraggingStopId(stop.id);\n setActiveStopId(stop.id);\n\n let latest = value.stops;\n let removed = false;\n\n const onMouseMove = (moveEvent: MouseEvent) => {\n if (removed) return;\n\n if (Math.abs(moveEvent.clientY - startY) > DRAG_OFF_THRESHOLD) {\n const after = removeStop(latest, stop.id, minStops);\n if (after !== latest) {\n latest = after;\n removed = true;\n patchStops(after);\n }\n return;\n }\n\n latest = updateStop(latest, stop.id, {\n position: positionFromClientX(moveEvent.clientX),\n });\n patchStops(latest);\n };\n\n const onMouseUp = () => {\n setDraggingStopId(null);\n if (removed) setActiveStopId(null);\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n };\n\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n };\n\n const handleMarkerKeyDown =\n (stop: GradientStop) => (event: React.KeyboardEvent) => {\n if (disabled) return;\n\n const step = event.shiftKey ? 10 : 1;\n let handled = true;\n\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n patchStops(\n updateStop(value.stops, stop.id, { position: stop.position + step })\n );\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n patchStops(\n updateStop(value.stops, stop.id, { position: stop.position - step })\n );\n break;\n case \"Home\":\n patchStops(updateStop(value.stops, stop.id, { position: 0 }));\n break;\n case \"End\":\n patchStops(updateStop(value.stops, stop.id, { position: 100 }));\n break;\n case \"Enter\":\n case \" \":\n setActiveStopId((current) => (current === stop.id ? null : stop.id));\n break;\n case \"Delete\":\n case \"Backspace\":\n handleRemoveStop(stop.id);\n break;\n default:\n handled = false;\n }\n\n if (handled) event.preventDefault();\n };\n\n const typeOptions = gradientTypes.map((type) => ({\n label: typeLabels?.[type] ?? DEFAULT_TYPE_LABELS[type],\n value: type,\n }));\n\n const showAngle = supportsAngle(value.type);\n\n return (\n <Box\n ref={rootRef}\n testID={testID}\n data-testid={testID}\n backgroundColor={theme.colors.layer.float}\n borderRadius={theme.shape.contextMenu.lg.borderRadius}\n padding={16}\n gap={12}\n width={344}\n style={{ boxShadow: \"0px 4px 16px 0px rgba(7, 7, 8, 0.1)\" }}\n >\n {/* Header — gradient type + (linear / angular only) rotation.\n The group wrapper carries the accessible name because SelectProps has\n no `aria-label` today; see the \"Known gaps\" note in the API doc. */}\n <Box flexDirection=\"row\" gap={8} alignItems=\"center\">\n <Box flex={1} role=\"group\" aria-label={typeAriaLabel}>\n <Select\n {...themeProps}\n size=\"sm\"\n options={typeOptions}\n value={value.type}\n disabled={disabled}\n onChange={(next: string) =>\n emit({ ...value, type: next as GradientType })\n }\n />\n </Box>\n\n {showAngle && (\n <Box width={88}>\n <Input\n {...themeProps}\n size=\"sm\"\n inputMode=\"numeric\"\n disabled={disabled}\n aria-label={angleAriaLabel}\n iconLeft={<AngleGlyph />}\n value={`${Math.round(value.angle)}`}\n onChange={(event: React.ChangeEvent<HTMLInputElement>) => {\n const parsed = parseInt(event.target.value, 10);\n emit({\n ...value,\n angle: Number.isNaN(parsed)\n ? 0\n : ((parsed % 360) + 360) % 360,\n });\n }}\n />\n </Box>\n )}\n </Box>\n\n {/* Preview track.\n The gradient fill is decorative and carries `aria-hidden` — the\n numeric position fields and each marker's `aria-valuetext` carry the\n same information for users who cannot perceive the colour ramp.\n The markers must be SIBLINGS of that fill, never children: anything\n inside an `aria-hidden` subtree is pruned from the accessibility\n tree, which would hide every `role=\"slider\"` handle from assistive\n technology. The bare positioning box below exists to give the markers\n a percentage origin that matches the fill exactly. */}\n <Box\n height={TRACK_HEIGHT}\n justifyContent=\"center\"\n paddingHorizontal={MARKER_INSET}\n >\n <Box\n ref={trackRef}\n width=\"100%\"\n height={TRACK_HEIGHT}\n position=\"relative\"\n cursor={canAdd ? \"copy\" : \"default\"}\n onMouseDown={isWeb && canAdd ? handleTrackPress : undefined}\n >\n <Box\n testID=\"gradient-track\"\n data-testid=\"gradient-track\"\n aria-hidden={true}\n position=\"absolute\"\n width=\"100%\"\n height={TRACK_HEIGHT}\n borderRadius={999}\n style={{\n top: 0,\n left: 0,\n backgroundImage: toPreviewCssGradient(value),\n }}\n />\n {stops.map((stop, index) => (\n <GradientStopMarker\n key={stop.id}\n stop={stop}\n index={index + 1}\n disabled={disabled}\n active={activeStopId === stop.id || draggingStopId === stop.id}\n onDragStart={handleDragStart(stop)}\n onKeyDown={handleMarkerKeyDown(stop)}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n ))}\n </Box>\n </Box>\n\n {/* Stop list header */}\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"space-between\"\n >\n <Text color={contentColors.primary} fontSize={14}>\n {stopsLabel}\n </Text>\n <IconButton\n {...themeProps}\n size=\"sm\"\n variant=\"tertiary\"\n tone=\"mono\"\n disabled={!canAdd}\n onPress={handleAddStopPress}\n icon={<PlusGlyph />}\n aria-label=\"Add stop\"\n />\n </Box>\n\n {/* Stop rows */}\n <Box gap={8}>\n {stops.map((stop, index) => (\n <Box\n key={stop.id}\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={8}\n testID={`gradient-stop-row-${stop.id}`}\n data-testid={`gradient-stop-row-${stop.id}`}\n >\n <Box width={72}>\n <Input\n {...themeProps}\n size=\"sm\"\n inputMode=\"numeric\"\n disabled={disabled}\n aria-label={`Stop ${index + 1} position percent`}\n iconRight={<PercentGlyph />}\n value={`${stop.position}`}\n onChange={(event: React.ChangeEvent<HTMLInputElement>) => {\n const parsed = parseInt(event.target.value, 10);\n if (Number.isNaN(parsed)) return;\n patchStops(\n updateStop(value.stops, stop.id, { position: parsed })\n );\n }}\n />\n </Box>\n\n <Box flex={1}>\n <InputColor\n size=\"sm\"\n type=\"color-hex\"\n transparency\n aria-label={`Stop ${index + 1} colour`}\n testID={`gradient-stop-color-${stop.id}`}\n value={toHexa(stop.color, stop.opacity)}\n onChange={(next: string) => {\n const { color, opacity } = fromHexa(next);\n patchStops(\n updateStop(value.stops, stop.id, { color, opacity })\n );\n }}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n </Box>\n\n <IconButton\n {...themeProps}\n size=\"sm\"\n variant=\"tertiary\"\n tone=\"mono\"\n disabled={!canRemove}\n onPress={() => handleRemoveStop(stop.id)}\n icon={<MinusGlyph />}\n aria-label={`Remove stop ${index + 1}`}\n />\n </Box>\n ))}\n </Box>\n\n {/* Active-stop editor. Rendered inline below the stop list; a floating\n popover with viewport flipping is a follow-up (see the API doc). */}\n {activeStop && !disabled && (\n <ColorPicker\n alpha\n colorFormat=\"hex\"\n value={toHexa(activeStop.color, activeStop.opacity)}\n onChange={({\n valueInInitialFormat,\n }: {\n valueInInitialFormat: string;\n }) => {\n const { color, opacity } = fromHexa(valueInInitialFormat);\n patchStops(\n updateStop(value.stops, activeStop.id, { color, opacity })\n );\n }}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n )}\n </Box>\n );\n};\n\nGradientPicker.displayName = \"GradientPicker\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n AccessibilityRole,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleToAccessibilityRole: Record<string, AccessibilityRole> = {\n button: \"button\",\n link: \"link\",\n img: \"image\",\n image: \"image\",\n summary: \"summary\",\n switch: \"switch\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n heading: \"header\",\n text: \"text\",\n search: \"search\",\n alert: \"alert\",\n none: \"none\",\n presentation: \"none\",\n progressbar: \"progressbar\",\n scrollbar: \"scrollbar\",\n adjustable: \"adjustable\",\n tab: \"tab\",\n menu: \"menu\",\n menuitem: \"menuitem\",\n list: \"list\",\n timer: \"timer\",\n};\n\n/** RN ViewStyle.gap is typed as number (Yoga gap), not DimensionValue. */\nconst toGapValue = (gap: number | string | undefined): number | undefined => {\n if (gap === undefined) return undefined;\n if (typeof gap === \"number\") return gap;\n const pxMatch = /^(\\d+(?:\\.\\d+)?)px$/.exec(gap);\n if (pxMatch) return Number(pxMatch[1]);\n const asNumber = Number(gap);\n return Number.isFinite(asNumber) ? asNumber : undefined;\n};\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n flexShrink,\n gap,\n overflow,\n opacity,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n disabled,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n opacity: opacity ?? (disabled ? 0.5 : undefined),\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n flexShrink,\n gap: toGapValue(gap),\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Map web a11y props; drop the rest of the web-only attributes before\n // spreading onto RN hosts (unknown props warn at runtime).\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": ariaDisabled,\n \"aria-live\": ariaLive,\n \"aria-busy\": ariaBusy,\n className,\n \"data-testid\": _dataTestId,\n cursor: _cursor,\n type: _type,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n const accessibilityRole =\n (typeof role === \"string\" && roleToAccessibilityRole[role]) ||\n (as === \"button\" ? \"button\" : undefined);\n const isDisabled = Boolean(disabled || ariaDisabled);\n const accessibilityState = {\n disabled: isDisabled || undefined,\n busy: Boolean(ariaBusy) || undefined,\n };\n const accessibilityLiveRegion =\n ariaLive === \"polite\" || ariaLive === \"assertive\" ? ariaLive : undefined;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : alt}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n // Prefer Pressable whenever the node is interactive or explicitly a button,\n // so disabled / aria-disabled controls stay in the a11y tree as buttons.\n if (onPress || as === \"button\") {\n return (\n <Pressable\n onPress={!isDisabled && onPress ? onPress : undefined}\n disabled={isDisabled}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={\n typeof ariaLabel === \"string\" ? ariaLabel : undefined\n }\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : undefined}\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport {\n Text as RNText,\n TextStyle,\n AccessibilityRole,\n StyleSheet,\n} from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nconst parseNumericValue = (\n value: string | number | undefined\n): number | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign,\n lineHeight,\n numberOfLines,\n id,\n role,\n testID,\n \"data-testid\": dataTestId,\n style: styleProp,\n ...props\n}) => {\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n if (\n resolvedFontFamily === \"Pilat Wide\" ||\n resolvedFontFamily === \"Pilat Wide Bold\" ||\n resolvedFontFamily === \"Aktiv Grotesk\"\n ) {\n resolvedFontFamily = undefined;\n }\n\n const incomingStyle = StyleSheet.flatten(styleProp) as TextStyle | undefined;\n\n const baseStyle: TextStyle = {\n color: color ?? incomingStyle?.color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n textAlign: textAlign ?? incomingStyle?.textAlign,\n lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),\n marginTop: parseNumericValue(\n incomingStyle?.marginTop as number | string | undefined\n ),\n marginBottom: parseNumericValue(\n incomingStyle?.marginBottom as number | string | undefined\n ),\n };\n\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText\n style={baseStyle}\n numberOfLines={numberOfLines}\n testID={dataTestId || testID || id}\n accessibilityRole={accessibilityRole}\n >\n {children}\n </RNText>\n );\n};\n","export * from \"./Box\";\nexport * from \"./Text\";\nexport * from \"./Spinner\";\nexport * from \"./Icon\";\nexport * from \"./Divider\";\nexport * from \"./Input\";\nexport * from \"./TextArea\";\nexport * from \"./LinearGradient\";\n\nexport const isWeb = false;\nexport const isNative = true;\n","import React from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, isWeb } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, type ThemeOverrideProps } from \"@xsolla/xui-core\";\nimport { MAX_POSITION, MIN_POSITION, stopToCssColor } from \"./utils\";\nimport type { GradientStop } from \"./types\";\n\nexport interface GradientStopMarkerProps extends ThemeOverrideProps {\n /**\n * The stop this marker represents.\n */\n stop: GradientStop;\n /**\n * 1-based index used to build the accessible name (`\"Stop 2 position\"`).\n */\n index: number;\n /**\n * Whether this stop is the one currently being edited in the ColorPicker.\n */\n active?: boolean;\n /**\n * Whether the whole picker is disabled.\n */\n disabled?: boolean;\n /**\n * Pointer-down on the marker — the parent owns the drag because it owns the\n * track geometry.\n */\n onDragStart?: (event: React.MouseEvent) => void;\n /**\n * Keyboard interaction on the marker. Handled by the parent so that arrow\n * keys, Home/End, Enter/Space and Delete/Backspace share one implementation.\n */\n onKeyDown?: (event: React.KeyboardEvent) => void;\n}\n\n/**\n * A single draggable handle on the gradient preview track.\n *\n * Exposed as `role=\"slider\"` over the `0`–`100` position range so the stop can\n * be moved with the keyboard alone, and so its numeric position is announced —\n * the visual gradient must never be the only carrier of that information.\n */\nexport const GradientStopMarker: React.FC<GradientStopMarkerProps> = ({\n stop,\n index,\n active = false,\n disabled = false,\n onDragStart,\n onKeyDown,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n\n const size = active ? 18 : 16;\n\n return (\n <Box\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-label={`Stop ${index} position`}\n aria-valuenow={stop.position}\n aria-valuemin={MIN_POSITION}\n aria-valuemax={MAX_POSITION}\n aria-valuetext={`${stop.position}%`}\n aria-orientation=\"horizontal\"\n aria-disabled={disabled || undefined}\n testID={`gradient-stop-${stop.id}`}\n data-testid={`gradient-stop-${stop.id}`}\n position=\"absolute\"\n left={`${stop.position}%`}\n width={size}\n height={size}\n borderRadius={999}\n zIndex={active ? 3 : 2}\n cursor={disabled ? \"not-allowed\" : \"grab\"}\n onMouseDown={isWeb && !disabled ? onDragStart : undefined}\n onKeyDown={disabled ? undefined : onKeyDown}\n style={{\n top: \"50%\",\n transform: \"translate(-50%, -50%)\",\n boxSizing: \"border-box\",\n borderStyle: \"solid\",\n borderWidth: 3,\n borderColor: theme.colors.background.primary,\n backgroundColor: stopToCssColor(stop),\n boxShadow: active\n ? \"0px 0px 0px 2px rgba(7, 7, 8, 0.25)\"\n : \"0px 1px 2px rgba(7, 7, 8, 0.2), 0px 1px 3px 1px rgba(7, 7, 8, 0.1)\",\n transition: \"width 0.1s ease, height 0.1s ease, box-shadow 0.1s ease\",\n outline: \"none\",\n }}\n />\n );\n};\n\nGradientStopMarker.displayName = \"GradientStopMarker\";\n","import type { GradientStop, GradientValue } from \"./types\";\n\n/**\n * Lowest stop position, in percent.\n */\nexport const MIN_POSITION = 0;\n\n/**\n * Highest stop position, in percent.\n */\nexport const MAX_POSITION = 100;\n\n/**\n * Clamps a stop position into `0`–`100` and rounds it to a whole percent.\n */\nexport const clampPosition = (position: number): number => {\n if (!Number.isFinite(position)) return MIN_POSITION;\n return Math.min(MAX_POSITION, Math.max(MIN_POSITION, Math.round(position)));\n};\n\n/**\n * Clamps a stop opacity into `0`–`100` and rounds it to a whole percent.\n */\nexport const clampOpacity = (opacity: number): number => {\n if (!Number.isFinite(opacity)) return 100;\n return Math.min(100, Math.max(0, Math.round(opacity)));\n};\n\n/**\n * Normalises any accepted hex form (`#abc`, `abc`, `#AABBCC`) to upper-case\n * `#RRGGBB`. Unparseable input falls back to `#000000` so serialisation never\n * emits an invalid CSS colour.\n */\nexport const normalizeHex = (hex: string): string => {\n const raw = String(hex ?? \"\")\n .trim()\n .replace(/^#/, \"\");\n\n if (/^[0-9a-fA-F]{3}$/.test(raw)) {\n return `#${raw\n .split(\"\")\n .map((char) => char + char)\n .join(\"\")}`.toUpperCase();\n }\n\n if (/^[0-9a-fA-F]{6}$/.test(raw)) return `#${raw}`.toUpperCase();\n\n // 8-digit hex: drop the alpha pair, opacity lives on the stop.\n if (/^[0-9a-fA-F]{8}$/.test(raw)) return `#${raw.slice(0, 6)}`.toUpperCase();\n\n return \"#000000\";\n};\n\n/**\n * Converts a hex colour to its `r` / `g` / `b` channels (`0`–`255`).\n */\nexport const hexToRgb = (hex: string): { r: number; g: number; b: number } => {\n const normalized = normalizeHex(hex).slice(1);\n return {\n r: parseInt(normalized.slice(0, 2), 16),\n g: parseInt(normalized.slice(2, 4), 16),\n b: parseInt(normalized.slice(4, 6), 16),\n };\n};\n\n/**\n * Converts `r` / `g` / `b` channels back to an upper-case `#RRGGBB` string.\n */\nexport const rgbToHex = (r: number, g: number, b: number): string => {\n const toChannel = (channel: number) =>\n Math.min(255, Math.max(0, Math.round(channel)))\n .toString(16)\n .padStart(2, \"0\");\n return `#${toChannel(r)}${toChannel(g)}${toChannel(b)}`.toUpperCase();\n};\n\n/**\n * Returns the stops ordered by position without mutating the input array.\n * Ties keep their original relative order, so two stops parked on the same\n * percent do not flicker while one of them is being dragged.\n */\nexport const sortStops = (stops: GradientStop[]): GradientStop[] =>\n stops\n .map((stop, index) => ({ stop, index }))\n .sort((a, b) =>\n a.stop.position === b.stop.position\n ? a.index - b.index\n : a.stop.position - b.stop.position\n )\n .map(({ stop }) => stop);\n\n/**\n * Serialises one stop to a CSS colour — hex while fully opaque, `rgba()` once\n * opacity drops below 100%.\n */\nexport const stopToCssColor = (stop: GradientStop): string => {\n const opacity = clampOpacity(stop.opacity);\n if (opacity >= 100) return normalizeHex(stop.color);\n const { r, g, b } = hexToRgb(stop.color);\n return `rgba(${r}, ${g}, ${b}, ${Number((opacity / 100).toFixed(2))})`;\n};\n\n/**\n * Serialises the stop list to the `<color> <position>%` sequence shared by every\n * CSS gradient function. A single stop is emitted twice (`0%` and `100%`)\n * because CSS gradients require at least two colour stops.\n */\nexport const stopsToCssStopList = (stops: GradientStop[]): string => {\n if (stops.length === 0) return \"transparent 0%, transparent 100%\";\n\n if (stops.length === 1) {\n const color = stopToCssColor(stops[0]);\n return `${color} 0%, ${color} 100%`;\n }\n\n return sortStops(stops)\n .map((stop) => `${stopToCssColor(stop)} ${clampPosition(stop.position)}%`)\n .join(\", \");\n};\n\n/**\n * Serialises the gradient to a CSS-compatible string usable as a `background`\n * or `background-image` value.\n */\nexport const toCssGradient = (value: GradientValue): string => {\n const stopList = stopsToCssStopList(value.stops);\n const angle = Number.isFinite(value.angle) ? Math.round(value.angle) : 0;\n\n switch (value.type) {\n case \"radial\":\n return `radial-gradient(circle, ${stopList})`;\n case \"angular\":\n return `conic-gradient(from ${angle}deg, ${stopList})`;\n case \"linear\":\n default:\n return `linear-gradient(${angle}deg, ${stopList})`;\n }\n};\n\n/**\n * The gradient rendered inside the horizontal preview track. The track always\n * reads left-to-right regardless of gradient type, so the user can see stop\n * ordering while dragging — the type-specific rendering is what `toCssGradient`\n * produces for the consuming surface.\n */\nexport const toPreviewCssGradient = (value: GradientValue): string =>\n `linear-gradient(90deg, ${stopsToCssStopList(value.stops)})`;\n\n/**\n * Whether the given gradient type exposes an angle control. Radial gradients\n * have no rotation, so the angle field is hidden for them.\n */\nexport const supportsAngle = (value: GradientValue[\"type\"]): boolean =>\n value === \"linear\" || value === \"angular\";\n\n/**\n * Samples the gradient at `position` (`0`–`100`) and returns the interpolated\n * colour + opacity. Used to give a newly added stop the colour the gradient\n * already had at that point, so adding a stop never visibly changes the ramp.\n */\nexport const interpolateColorAt = (\n stops: GradientStop[],\n position: number\n): { color: string; opacity: number } => {\n if (stops.length === 0) return { color: \"#000000\", opacity: 100 };\n\n const sorted = sortStops(stops);\n const target = clampPosition(position);\n\n const first = sorted[0];\n if (target <= first.position) {\n return {\n color: normalizeHex(first.color),\n opacity: clampOpacity(first.opacity),\n };\n }\n\n const last = sorted[sorted.length - 1];\n if (target >= last.position) {\n return {\n color: normalizeHex(last.color),\n opacity: clampOpacity(last.opacity),\n };\n }\n\n for (let index = 0; index < sorted.length - 1; index += 1) {\n const from = sorted[index];\n const to = sorted[index + 1];\n if (target < from.position || target > to.position) continue;\n\n const span = to.position - from.position;\n const ratio = span === 0 ? 0 : (target - from.position) / span;\n\n const fromRgb = hexToRgb(from.color);\n const toRgb = hexToRgb(to.color);\n\n return {\n color: rgbToHex(\n fromRgb.r + (toRgb.r - fromRgb.r) * ratio,\n fromRgb.g + (toRgb.g - fromRgb.g) * ratio,\n fromRgb.b + (toRgb.b - fromRgb.b) * ratio\n ),\n opacity: clampOpacity(\n clampOpacity(from.opacity) +\n (clampOpacity(to.opacity) - clampOpacity(from.opacity)) * ratio\n ),\n };\n }\n\n return {\n color: normalizeHex(last.color),\n opacity: clampOpacity(last.opacity),\n };\n};\n\n/**\n * Produces the next `stop-N` id that does not collide with an existing one.\n * Deterministic (no randomness) so stories, tests and SSR stay stable.\n */\nexport const createStopId = (stops: GradientStop[]): string => {\n const highest = stops.reduce((max, stop) => {\n const match = /^stop-(\\d+)$/.exec(stop.id);\n return match ? Math.max(max, Number(match[1])) : max;\n }, 0);\n return `stop-${highest + 1}`;\n};\n\n/**\n * Adds a stop at `position`, taking its colour from the gradient at that point.\n * Returns the original array untouched when `maxStops` is already reached, so\n * callers can compare by reference to detect a no-op.\n */\nexport const addStopAt = (\n stops: GradientStop[],\n position: number,\n maxStops: number\n): GradientStop[] => {\n if (stops.length >= maxStops) return stops;\n\n const target = clampPosition(position);\n const { color, opacity } = interpolateColorAt(stops, target);\n\n return sortStops([\n ...stops,\n { id: createStopId(stops), color, position: target, opacity },\n ]);\n};\n\n/**\n * Removes a stop by id. Returns the original array when the removal would drop\n * below `minStops`, or when the id is unknown.\n */\nexport const removeStop = (\n stops: GradientStop[],\n id: string,\n minStops: number\n): GradientStop[] => {\n if (stops.length <= minStops) return stops;\n const next = stops.filter((stop) => stop.id !== id);\n return next.length === stops.length ? stops : next;\n};\n\n/**\n * Applies a partial update to a single stop, clamping position and opacity and\n * normalising the hex colour.\n */\nexport const updateStop = (\n stops: GradientStop[],\n id: string,\n patch: Partial<Omit<GradientStop, \"id\">>\n): GradientStop[] =>\n stops.map((stop) => {\n if (stop.id !== id) return stop;\n return {\n ...stop,\n ...patch,\n color: patch.color === undefined ? stop.color : normalizeHex(patch.color),\n position:\n patch.position === undefined\n ? stop.position\n : clampPosition(patch.position),\n opacity:\n patch.opacity === undefined\n ? stop.opacity\n : clampOpacity(patch.opacity),\n };\n });\n\n/**\n * The default gradient — the two-stop `#D9D9D9 → #22A8C3` linear ramp from the\n * Figma component.\n */\nexport const DEFAULT_GRADIENT: GradientValue = {\n type: \"linear\",\n angle: 0,\n stops: [\n { id: \"stop-1\", color: \"#D9D9D9\", position: 0, opacity: 100 },\n { id: \"stop-2\", color: \"#22A8C3\", position: 100, opacity: 100 },\n ],\n};\n","import React from \"react\";\n\ninterface GlyphProps {\n /**\n * Rendered edge length in px. Injected by `IconButton` / `Icon` from the\n * resolved size token, so callers normally omit it.\n */\n size?: number;\n /**\n * Stroke colour. Injected by `IconButton` / `Icon` from the resolved content\n * colour.\n */\n color?: string;\n}\n\n/**\n * \"Add stop\" glyph.\n */\nexport const PlusGlyph: React.FC<GlyphProps> = ({\n size = 18,\n color = \"currentColor\",\n}) => (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n);\n\n/**\n * \"Remove stop\" glyph.\n */\nexport const MinusGlyph: React.FC<GlyphProps> = ({\n size = 18,\n color = \"currentColor\",\n}) => (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M5 12h14\" />\n </svg>\n);\n\n/**\n * Angle glyph shown inside the rotation field.\n */\nexport const AngleGlyph: React.FC<GlyphProps> = ({\n size = 16,\n color = \"currentColor\",\n}) => (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M4 20h16L4 4z\" />\n </svg>\n);\n\n/**\n * Percent suffix glyph used inside the numeric position field.\n */\nexport const PercentGlyph: React.FC<GlyphProps> = ({\n size = 16,\n color = \"currentColor\",\n}) => (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle cx=\"6.5\" cy=\"6.5\" r=\"2.5\" />\n <circle cx=\"17.5\" cy=\"17.5\" r=\"2.5\" />\n <path d=\"M19 5 5 19\" />\n </svg>\n);\n"],"mappings":"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","names":["jsx","useResolvedTheme","jsx","jsx","jsx","jsxs","useResolvedTheme"]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@xsolla/xui-gradient-picker",
3
+ "version": "0.204.0",
4
+ "main": "./web/index.js",
5
+ "module": "./web/index.mjs",
6
+ "types": "./web/index.d.ts",
7
+ "scripts": {
8
+ "build": "yarn build:web && yarn build:native",
9
+ "build:web": "PLATFORM=web tsup",
10
+ "build:native": "PLATFORM=native tsup",
11
+ "test": "vitest",
12
+ "test:run": "vitest run",
13
+ "test:coverage": "vitest run --coverage"
14
+ },
15
+ "dependencies": {
16
+ "@xsolla/xui-button": "0.204.0",
17
+ "@xsolla/xui-color-picker": "0.204.0",
18
+ "@xsolla/xui-core": "0.204.0",
19
+ "@xsolla/xui-input": "0.204.0",
20
+ "@xsolla/xui-input-color": "0.204.0",
21
+ "@xsolla/xui-primitives-core": "0.204.0",
22
+ "@xsolla/xui-select": "0.204.0"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=16.8.0",
26
+ "styled-components": ">=4"
27
+ },
28
+ "devDependencies": {
29
+ "@testing-library/jest-dom": "^6.9.1",
30
+ "@testing-library/react": "^14.1.2",
31
+ "@vitest/coverage-v8": "^4.0.18",
32
+ "jsdom": "^24.0.0",
33
+ "react": "^18.0.0",
34
+ "react-dom": "^18.0.0",
35
+ "tsup": "^8.0.0",
36
+ "vitest": "^4.0.18"
37
+ },
38
+ "license": "MIT",
39
+ "sideEffects": false,
40
+ "react-native": "./native/index.js",
41
+ "exports": {
42
+ ".": {
43
+ "react-native": {
44
+ "types": "./native/index.d.ts",
45
+ "import": "./native/index.mjs",
46
+ "require": "./native/index.js"
47
+ },
48
+ "import": {
49
+ "types": "./web/index.d.ts",
50
+ "default": "./web/index.mjs"
51
+ },
52
+ "require": {
53
+ "types": "./web/index.d.ts",
54
+ "default": "./web/index.js"
55
+ },
56
+ "default": {
57
+ "types": "./web/index.d.ts",
58
+ "default": "./web/index.js"
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,243 @@
1
+ import React from 'react';
2
+ import { ThemeOverrideProps } from '@xsolla/xui-core';
3
+
4
+ /**
5
+ * Supported gradient geometries.
6
+ *
7
+ * `angular` maps to the CSS `conic-gradient()` function. Some products call
8
+ * this "Conic" — use `typeLabels` to relabel it without changing the value.
9
+ */
10
+ type GradientType = "linear" | "radial" | "angular";
11
+ /**
12
+ * A single colour stop on the gradient track.
13
+ */
14
+ interface GradientStop {
15
+ /**
16
+ * Stable identity for the stop. Used as the React key and as the handle for
17
+ * update / remove operations, so it must survive reordering.
18
+ */
19
+ id: string;
20
+ /**
21
+ * Stop colour as a `#RRGGBB` hex string. Opacity is tracked separately in
22
+ * `opacity` so the hex field stays human-editable.
23
+ */
24
+ color: string;
25
+ /**
26
+ * Position along the track, `0`–`100` (percent).
27
+ */
28
+ position: number;
29
+ /**
30
+ * Stop opacity, `0`–`100` (percent).
31
+ */
32
+ opacity: number;
33
+ }
34
+ /**
35
+ * The full gradient definition rendered by the picker.
36
+ */
37
+ interface GradientValue {
38
+ /**
39
+ * Gradient geometry.
40
+ */
41
+ type: GradientType;
42
+ /**
43
+ * Rotation in degrees. Only meaningful for `linear` and `angular` — a radial
44
+ * gradient has no angle, so the angle field is hidden for it.
45
+ */
46
+ angle: number;
47
+ /**
48
+ * Colour stops. Order is not significant: the component sorts by `position`
49
+ * when serialising, so stops may swap as the user drags them past each other.
50
+ */
51
+ stops: GradientStop[];
52
+ }
53
+ /**
54
+ * Payload emitted on every gradient change.
55
+ */
56
+ interface GradientPickerChangeEvent {
57
+ /**
58
+ * The new gradient definition.
59
+ */
60
+ value: GradientValue;
61
+ /**
62
+ * The same gradient serialised as a CSS-compatible string, ready to drop into
63
+ * a `background` / `background-image` declaration.
64
+ */
65
+ css: string;
66
+ }
67
+ interface GradientPickerProps extends ThemeOverrideProps {
68
+ /**
69
+ * Controlled gradient value. Pair with `onChange`.
70
+ */
71
+ value?: GradientValue;
72
+ /**
73
+ * Initial gradient value for uncontrolled usage.
74
+ */
75
+ defaultValue?: GradientValue;
76
+ /**
77
+ * Called on every interaction — stop drag, colour edit, opacity edit,
78
+ * gradient-type switch, angle change, add / remove stop.
79
+ */
80
+ onChange?: (event: GradientPickerChangeEvent) => void;
81
+ /**
82
+ * Gradient types offered in the type selector, in display order.
83
+ * @default ["linear", "radial", "angular"]
84
+ */
85
+ gradientTypes?: GradientType[];
86
+ /**
87
+ * Overrides for the gradient-type option labels — e.g. `{ angular: "Conic gradient" }`
88
+ * when the product uses the CSS term.
89
+ */
90
+ typeLabels?: Partial<Record<GradientType, string>>;
91
+ /**
92
+ * Smallest number of stops the user may reduce the gradient to. The remove
93
+ * control is disabled once this floor is reached.
94
+ * @default 1
95
+ */
96
+ minStops?: number;
97
+ /**
98
+ * Largest number of stops the user may add. The add control is disabled once
99
+ * this ceiling is reached.
100
+ * @default 5
101
+ */
102
+ maxStops?: number;
103
+ /**
104
+ * Disables every control and blocks track interaction.
105
+ */
106
+ disabled?: boolean;
107
+ /**
108
+ * Accessible label for the gradient-type selector.
109
+ * @default "Gradient type"
110
+ */
111
+ typeAriaLabel?: string;
112
+ /**
113
+ * Accessible label for the angle field.
114
+ * @default "Gradient angle"
115
+ */
116
+ angleAriaLabel?: string;
117
+ /**
118
+ * Heading rendered above the stop list.
119
+ * @default "Stops"
120
+ */
121
+ stopsLabel?: string;
122
+ testID?: string;
123
+ }
124
+
125
+ /**
126
+ * A compound control for defining a CSS gradient.
127
+ *
128
+ * Combines a live preview track carrying draggable stop markers with a per-stop
129
+ * row editor (position, colour, opacity) and an integrated `ColorPicker` for
130
+ * the active stop. Supports linear, radial and angular (CSS `conic`) gradients
131
+ * and 1–5 colour stops.
132
+ *
133
+ * Every interaction updates the preview immediately — there is no confirm step —
134
+ * and the gradient is emitted both as structured data and as a ready-to-apply
135
+ * CSS string.
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * const [gradient, setGradient] = useState<GradientValue>();
140
+ *
141
+ * <GradientPicker
142
+ * value={gradient}
143
+ * onChange={({ value, css }) => {
144
+ * setGradient(value);
145
+ * setBackground(css); // e.g. "linear-gradient(0deg, #D9D9D9 0%, #22A8C3 100%)"
146
+ * }}
147
+ * />
148
+ * ```
149
+ */
150
+ declare const GradientPicker: React.FC<GradientPickerProps>;
151
+
152
+ interface GradientStopMarkerProps extends ThemeOverrideProps {
153
+ /**
154
+ * The stop this marker represents.
155
+ */
156
+ stop: GradientStop;
157
+ /**
158
+ * 1-based index used to build the accessible name (`"Stop 2 position"`).
159
+ */
160
+ index: number;
161
+ /**
162
+ * Whether this stop is the one currently being edited in the ColorPicker.
163
+ */
164
+ active?: boolean;
165
+ /**
166
+ * Whether the whole picker is disabled.
167
+ */
168
+ disabled?: boolean;
169
+ /**
170
+ * Pointer-down on the marker — the parent owns the drag because it owns the
171
+ * track geometry.
172
+ */
173
+ onDragStart?: (event: React.MouseEvent) => void;
174
+ /**
175
+ * Keyboard interaction on the marker. Handled by the parent so that arrow
176
+ * keys, Home/End, Enter/Space and Delete/Backspace share one implementation.
177
+ */
178
+ onKeyDown?: (event: React.KeyboardEvent) => void;
179
+ }
180
+ /**
181
+ * A single draggable handle on the gradient preview track.
182
+ *
183
+ * Exposed as `role="slider"` over the `0`–`100` position range so the stop can
184
+ * be moved with the keyboard alone, and so its numeric position is announced —
185
+ * the visual gradient must never be the only carrier of that information.
186
+ */
187
+ declare const GradientStopMarker: React.FC<GradientStopMarkerProps>;
188
+
189
+ /**
190
+ * Returns the stops ordered by position without mutating the input array.
191
+ * Ties keep their original relative order, so two stops parked on the same
192
+ * percent do not flicker while one of them is being dragged.
193
+ */
194
+ declare const sortStops: (stops: GradientStop[]) => GradientStop[];
195
+ /**
196
+ * Serialises the gradient to a CSS-compatible string usable as a `background`
197
+ * or `background-image` value.
198
+ */
199
+ declare const toCssGradient: (value: GradientValue) => string;
200
+ /**
201
+ * The gradient rendered inside the horizontal preview track. The track always
202
+ * reads left-to-right regardless of gradient type, so the user can see stop
203
+ * ordering while dragging — the type-specific rendering is what `toCssGradient`
204
+ * produces for the consuming surface.
205
+ */
206
+ declare const toPreviewCssGradient: (value: GradientValue) => string;
207
+ /**
208
+ * Whether the given gradient type exposes an angle control. Radial gradients
209
+ * have no rotation, so the angle field is hidden for them.
210
+ */
211
+ declare const supportsAngle: (value: GradientValue["type"]) => boolean;
212
+ /**
213
+ * Samples the gradient at `position` (`0`–`100`) and returns the interpolated
214
+ * colour + opacity. Used to give a newly added stop the colour the gradient
215
+ * already had at that point, so adding a stop never visibly changes the ramp.
216
+ */
217
+ declare const interpolateColorAt: (stops: GradientStop[], position: number) => {
218
+ color: string;
219
+ opacity: number;
220
+ };
221
+ /**
222
+ * Adds a stop at `position`, taking its colour from the gradient at that point.
223
+ * Returns the original array untouched when `maxStops` is already reached, so
224
+ * callers can compare by reference to detect a no-op.
225
+ */
226
+ declare const addStopAt: (stops: GradientStop[], position: number, maxStops: number) => GradientStop[];
227
+ /**
228
+ * Removes a stop by id. Returns the original array when the removal would drop
229
+ * below `minStops`, or when the id is unknown.
230
+ */
231
+ declare const removeStop: (stops: GradientStop[], id: string, minStops: number) => GradientStop[];
232
+ /**
233
+ * Applies a partial update to a single stop, clamping position and opacity and
234
+ * normalising the hex colour.
235
+ */
236
+ declare const updateStop: (stops: GradientStop[], id: string, patch: Partial<Omit<GradientStop, "id">>) => GradientStop[];
237
+ /**
238
+ * The default gradient — the two-stop `#D9D9D9 → #22A8C3` linear ramp from the
239
+ * Figma component.
240
+ */
241
+ declare const DEFAULT_GRADIENT: GradientValue;
242
+
243
+ export { DEFAULT_GRADIENT, GradientPicker, type GradientPickerChangeEvent, type GradientPickerProps, type GradientStop, GradientStopMarker, type GradientStopMarkerProps, type GradientType, type GradientValue, addStopAt, interpolateColorAt, removeStop, sortStops, supportsAngle, toCssGradient, toPreviewCssGradient, updateStop };