UNPKG

@gfazioli/mantine-picker

Version:

A Mantine 9 iOS-style wheel picker component with 3D rotation, drag/wheel/keyboard navigation, momentum scrolling, and customizable item rendering.

271 lines (270 loc) 8.3 kB
import { MantineGradient, MantineSize, type BoxProps, type PolymorphicFactory, type StylesApiProps } from '@mantine/core'; import React from 'react'; export type PickerStylesNames = 'root' | 'container' | 'item' | 'mask' | 'highlight' | 'dividers'; export type PickerCssVariables = { root: '--picker-height' | '--picker-item-height' | '--picker-animation-duration' | '--picker-animation-easing' | '--picker-perspective' | '--picker-rotate-y' | '--picker-mask-height' | '--picker-mask-intensity'; }; type TextTruncate = 'end' | 'start' | boolean; export interface PickerBaseProps<T = string | number> { /** * Array of items to display in the picker */ data: T[]; /** * Currently selected value (controlled mode) */ value?: T; /** * Initial selected value for uncontrolled mode. Ignored when `value` is provided. */ defaultValue?: T; /** * Callback function called when value changes (called in both controlled and uncontrolled mode) */ onChange?: (value: T) => void; /** * Called when any scroll interaction begins: drag, mouse wheel, keyboard nav, or programmatic animation. */ onScrollStart?: () => void; /** * Called when all scroll interactions have settled: momentum ends, animation completes, or the wheel debounce expires. */ onScrollEnd?: () => void; /** * Trigger a short `navigator.vibrate()` pulse on supported devices whenever the selected item changes, * giving the picker a native iOS/Android feel. Pass `true` for the default 15ms duration, or a number to * set a custom duration in milliseconds. No-op on desktop or unsupported browsers. * @default false */ hapticFeedback?: boolean | number; /** * Whether to animate the scroll to the selected value on mount * @default true */ animate?: boolean; /** * Animation duration in milliseconds * @default 300 */ animationDuration?: number; /** * Easing function for animations * @default "linear" */ easingFunction?: string; /** * Whether to loop through the items when reaching the end * @default true */ loop?: boolean; /** * Height of each item in pixels * @default 40 */ itemHeight?: number; /** * Number of visible items * @default 3 */ visibleItems?: number; /** * Custom render function for items */ renderItem?: (item: T) => React.ReactNode; /** * Whether to prevent page scrolling when the mouse is over the picker * @default true */ preventPageScroll?: boolean; /** * Whether the picker is disabled * @default false */ disabled?: boolean; /** * Whether the picker is read-only (disables interaction but maintains visual appearance) * @default false */ readOnly?: boolean; /** * Minimum opacity for non-selected items * @default 0.3 */ minItemOpacity?: number; /** * Minimum scale for non-selected items * @default 0.85 */ minItemScale?: number; /** * Maximum blur amount for non-selected items (in pixels) * @default 0 */ maxBlurAmount?: number; /** * ID for the component, necessary for accessibility */ id?: string; /** * Label for the component, necessary for accessibility */ label?: string; /** * Additional description for the component, useful for accessibility */ description?: string; /** * Keyboard navigation hint */ keyboardHint?: string; /** * If true, the component can receive focus * @default true */ focusable?: boolean; /** * Mouse wheel sensitivity (higher = more sensitive) * @default 1 */ wheelSensitivity?: number; /** * Whether to include the highlight for the selected item * @default true */ withHighlight?: boolean; /** * Whether to include dividers above and below the selected item * @default true */ withDividers?: boolean; /** * Whether to include the gradient mask at the top and bottom * @default true */ withMask?: boolean; /** Controls `font-size` and `line-height`, `'md'` by default */ size?: MantineSize | (string & {}); /** Number of lines after which Text will be truncated */ lineClamp?: number; /** Side on which Text must be truncated, if `true`, text is truncated from the start */ truncate?: TextTruncate; /** Sets `line-height` to 1 for centering, `false` by default */ inline?: boolean; /** Determines whether font properties should be inherited from the parent, `false` by default */ inherit?: boolean; /** Gradient configuration, ignored when `variant` is not `gradient`, `theme.defaultGradient` by default */ gradient?: MantineGradient; /** * Enable 3D effect similar to iOS * @default true */ enable3D?: boolean; /** * Perspective value for 3D effect (in pixels) * @default 300 */ perspective?: number; /** * Maximum rotation angle for items (in degrees) * @default 60 */ maxRotation?: number; /** * Cylinder radius factor (affects the curvature of the 3D effect) * @default 4 */ cylinderRadius?: number; /** * Momentum factor for inertia effect after drag * @default 0.95 */ momentum?: number; /** * Deceleration rate for momentum scrolling * @default 0.95 */ decelerationRate?: number; /** * Height of the mask gradient at top and bottom (in percentage) * @default 55 */ maskHeight?: number; /** * Intensity of the mask gradient (in percentage) * @default 10 */ maskIntensity?: number; /** * Content to display on the left side of the picker */ leftSection?: React.ReactNode; /** * Content to display on the right side of the picker */ rightSection?: React.ReactNode; /** * Width of the left section in pixels * @default "auto" */ leftSectionWidth?: number | string; /** * Width of the right section in pixels * @default "auto" */ rightSectionWidth?: number | string; /** * Rotation angle for the items around the Y-axis (in degrees) * @default 0 */ rotateY?: number; } export interface PickerProps<T = string | number> extends BoxProps, PickerBaseProps<T>, StylesApiProps<PickerFactory> { } export type PickerFactory = PolymorphicFactory<{ props: PickerProps; defaultComponent: 'div'; defaultRef: HTMLDivElement; stylesNames: PickerStylesNames; vars: PickerCssVariables; }>; /** * Picker Component * * A component that replicates the iOS picker with smooth scrolling, 3D rotation effect, and animations. */ export declare const Picker: (<C = "div">(props: import("@mantine/core").PolymorphicComponentProps<C, PickerProps<string | number>>) => React.ReactElement) & Omit<React.FunctionComponent<(PickerProps<string | number> & { component?: any; } & Omit<any, keyof PickerProps<string | number> | "component"> & { ref?: any; renderRoot?: (props: any) => any; }) | (PickerProps<string | number> & { component: React.ElementType; renderRoot?: (props: Record<string, any>) => any; })>, never> & import("@mantine/core").ThemeExtend<{ props: PickerProps; defaultComponent: "div"; defaultRef: HTMLDivElement; stylesNames: PickerStylesNames; vars: PickerCssVariables; }> & import("@mantine/core").ComponentClasses<{ props: PickerProps; defaultComponent: "div"; defaultRef: HTMLDivElement; stylesNames: PickerStylesNames; vars: PickerCssVariables; }> & { varsResolver: import("@mantine/core").VarsResolver<{ props: PickerProps; defaultComponent: "div"; defaultRef: HTMLDivElement; stylesNames: PickerStylesNames; vars: PickerCssVariables; }>; } & import("@mantine/core").PolymorphicComponentWithProps<{ props: PickerProps; defaultComponent: "div"; defaultRef: HTMLDivElement; stylesNames: PickerStylesNames; vars: PickerCssVariables; }> & Record<string, never>; export {};