@flikk/ui 1.0.0-beta.20 → 1.0.0-beta.21

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.
@@ -1,6 +1,6 @@
1
1
  const tooltipTheme = {
2
2
  // Base style for the tooltip - using fixed positioning for portal rendering
3
- baseStyle: "fixed z-[99] rounded-[var(--tooltip-radius)] py-1 px-2 text-sm text-[var(--color-text-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 pointer-events-none bg-white/90 backdrop-blur-md outline outline-[var(--color-border)] shadow-lg border-t-[1px] border-white " +
3
+ baseStyle: "fixed z-[99] rounded-[var(--tooltip-radius)] py-1 px-2 text-sm text-[var(--color-text-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 pointer-events-none bg-white/90 backdrop-blur-md outline outline-[var(--color-border)] shadow-lg " +
4
4
  "dark:bg-[var(--color-neutral-800)]/90 dark:outline-[var(--color-border)]/80 dark:border-[var(--color-border)]",
5
5
  };
6
6
 
@@ -12,17 +12,36 @@ const ANIMATION_PRESETS = {
12
12
  slideLeft: { opacity: 0, x: -24 },
13
13
  slideRight: { opacity: 0, x: 24 },
14
14
  };
15
+ const RESOLVED_VALUES = {
16
+ opacity: 1,
17
+ y: 0,
18
+ x: 0,
19
+ scale: 1,
20
+ };
21
+ // Pre-compute visible states per preset (only keys that the preset actually animates)
22
+ const VISIBLE_STATES = Object.fromEntries(Object.entries(ANIMATION_PRESETS).map(([key, initial]) => {
23
+ const visible = {};
24
+ for (const k of Object.keys(initial)) {
25
+ visible[k] = RESOLVED_VALUES[k];
26
+ }
27
+ return [key, visible];
28
+ }));
29
+ // Pre-compute exit variants (same as hidden states)
30
+ const EXIT_VARIANTS = Object.fromEntries(Object.entries(ANIMATION_PRESETS).map(([key, value]) => [key, { ...value }]));
31
+ // Pre-compute full variant sets for default transition (zero-allocation fast path)
32
+ const DEFAULT_VARIANTS = Object.fromEntries(Object.entries(ANIMATION_PRESETS).map(([key, initial]) => [
33
+ key,
34
+ {
35
+ hidden: { ...initial },
36
+ visible: { ...VISIBLE_STATES[key], transition: defaultSpringTransition },
37
+ },
38
+ ]));
15
39
  function getAnimationVariants(preset, customTransition) {
16
- const initial = ANIMATION_PRESETS[preset];
40
+ if (!customTransition)
41
+ return DEFAULT_VARIANTS[preset];
17
42
  return {
18
- hidden: { ...initial },
19
- visible: {
20
- opacity: 1,
21
- y: 0,
22
- x: 0,
23
- scale: 1,
24
- transition: customTransition !== null && customTransition !== void 0 ? customTransition : defaultSpringTransition,
25
- },
43
+ hidden: { ...ANIMATION_PRESETS[preset] },
44
+ visible: { ...VISIBLE_STATES[preset], transition: customTransition },
26
45
  };
27
46
  }
28
47
  function getGroupVariants(stagger, delay) {
@@ -43,7 +62,7 @@ function getGroupVariants(stagger, delay) {
43
62
  };
44
63
  }
45
64
  function getExitVariant(preset) {
46
- return { ...ANIMATION_PRESETS[preset] };
65
+ return EXIT_VARIANTS[preset];
47
66
  }
48
67
 
49
68
  export { ANIMATION_PRESETS, defaultSpringTransition, getAnimationVariants, getExitVariant, getGroupVariants };
@@ -1,6 +1,6 @@
1
1
  import type { AnimatedContextValue, AnimatedGroupProps } from './Animated.types';
2
2
  export declare const useAnimatedContext: () => AnimatedContextValue | undefined;
3
3
  export declare const Animated: {
4
- Group: ({ stagger, delay, as, children, className, ...props }: AnimatedGroupProps) => import("react/jsx-runtime").JSX.Element;
5
- Item: ({ animation, transition, as, delay, className, children, ...props }: import("./Animated.types").AnimatedItemProps) => import("react/jsx-runtime").JSX.Element;
4
+ Group: ({ stagger, delay, as, children, className, style, ...props }: AnimatedGroupProps) => import("react/jsx-runtime").JSX.Element;
5
+ Item: ({ animation, transition, as, delay, className, children, style, ...props }: import("./Animated.types").AnimatedItemProps) => import("react/jsx-runtime").JSX.Element;
6
6
  };
@@ -9,14 +9,14 @@ const AnimatedContext = createContext(undefined);
9
9
  const useAnimatedContext = () => {
10
10
  return useContext(AnimatedContext);
11
11
  };
12
- const AnimatedGroup = ({ stagger = 0.1, delay = 0, as = 'div', children, className, ...props }) => {
12
+ const AnimatedGroup = ({ stagger = 0.1, delay = 0, as = 'div', children, className, style, ...props }) => {
13
13
  const shouldReduceMotion = useReducedMotion();
14
+ const groupVariants = useMemo(() => getGroupVariants(stagger, delay), [stagger, delay]);
15
+ const MotionComponent = useMemo(() => motion[as], [as]);
14
16
  if (shouldReduceMotion) {
15
17
  const Tag = as;
16
18
  return (jsx(Tag, { className: className, ...props, children: children }));
17
19
  }
18
- const groupVariants = useMemo(() => getGroupVariants(stagger, delay), [stagger, delay]);
19
- const MotionComponent = motion[as];
20
20
  const motionProps = {
21
21
  initial: 'hidden',
22
22
  animate: 'visible',
@@ -24,6 +24,7 @@ const AnimatedGroup = ({ stagger = 0.1, delay = 0, as = 'div', children, classNa
24
24
  variants: groupVariants,
25
25
  className: cn(className),
26
26
  ...props,
27
+ style: { willChange: 'transform, opacity', ...style },
27
28
  };
28
29
  return (jsx(AnimatedContext.Provider, { value: { isInsideGroup: true, stagger, delay }, children: jsx(AnimatePresence, { children: jsx(MotionComponent, { ...motionProps, children: children }) }) }));
29
30
  };
@@ -1,2 +1,2 @@
1
1
  import type { AnimatedItemProps } from './Animated.types';
2
- export declare const AnimatedItem: ({ animation, transition, as, delay, className, children, ...props }: AnimatedItemProps) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const AnimatedItem: ({ animation, transition, as, delay, className, children, style, ...props }: AnimatedItemProps) => import("react/jsx-runtime").JSX.Element;
@@ -5,20 +5,22 @@ import { cn } from '../../../utils/cn.js';
5
5
  import { useAnimatedContext } from './Animated.js';
6
6
  import { getAnimationVariants, getExitVariant } from './Animated.animations.js';
7
7
 
8
- const AnimatedItem = ({ animation = 'fadeUp', transition, as = 'div', delay, className, children, ...props }) => {
8
+ const AnimatedItem = ({ animation = 'fadeUp', transition, as = 'div', delay, className, children, style, ...props }) => {
9
9
  const shouldReduceMotion = useReducedMotion();
10
10
  const groupContext = useAnimatedContext();
11
+ const variants = useMemo(() => getAnimationVariants(animation, transition), [animation, transition]);
12
+ const exitState = useMemo(() => getExitVariant(animation), [animation]);
13
+ const MotionComponent = useMemo(() => motion[as], [as]);
11
14
  if (shouldReduceMotion) {
12
15
  const Tag = as;
13
16
  return (jsx(Tag, { className: className, ...props, children: children }));
14
17
  }
15
- const variants = useMemo(() => getAnimationVariants(animation, transition), [animation, transition]);
16
- const exitState = useMemo(() => getExitVariant(animation), [animation]);
17
18
  const motionProps = {
18
19
  variants,
19
20
  exit: exitState,
20
21
  className: cn(className),
21
22
  ...props,
23
+ style: { willChange: 'transform, opacity', ...style },
22
24
  };
23
25
  if (!groupContext) {
24
26
  motionProps.initial = 'hidden';
@@ -32,7 +34,6 @@ const AnimatedItem = ({ animation = 'fadeUp', transition, as = 'div', delay, cla
32
34
  };
33
35
  }
34
36
  }
35
- const MotionComponent = motion[as];
36
37
  return (
37
38
  // @ts-expect-error -- polymorphic motion element type mismatch is safe at runtime
38
39
  jsx(MotionComponent, { ...motionProps, children: children }));
@@ -15,10 +15,6 @@ const checkboxTheme = {
15
15
  "checked:border-[var(--color-primary)] checked:bg-[var(--color-primary)] " +
16
16
  // Checked state - dark
17
17
  " " +
18
- // Checked outline - light
19
- "checked:outline-2 checked:outline-[var(--color-primary-100)] checked:outline-offset-1 " +
20
- // Checked outline - dark
21
- "dark:checked:outline-[var(--color-primary-900)] " +
22
18
  // Focus visible - light
23
19
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary-600)] " +
24
20
  // Focus visible - dark
@@ -6,6 +6,8 @@ const radioDotVariants = {
6
6
  unchecked: {
7
7
  scale: 0,
8
8
  opacity: 0,
9
+ x: "-50%",
10
+ y: "-50%",
9
11
  transition: {
10
12
  duration: 0.15,
11
13
  ease: "easeOut",
@@ -14,6 +16,8 @@ const radioDotVariants = {
14
16
  checked: {
15
17
  scale: 1,
16
18
  opacity: 1,
19
+ x: "-50%",
20
+ y: "-50%",
17
21
  transition: {
18
22
  type: "spring",
19
23
  stiffness: 400,
@@ -26,8 +30,8 @@ const radioDotVariants = {
26
30
  * Reduced motion variants — instant opacity toggle, no scale
27
31
  */
28
32
  const reducedMotionDotVariants = {
29
- unchecked: { opacity: 0 },
30
- checked: { opacity: 1 },
33
+ unchecked: { opacity: 0, x: "-50%", y: "-50%" },
34
+ checked: { opacity: 1, x: "-50%", y: "-50%" },
31
35
  };
32
36
 
33
37
  export { radioDotVariants, reducedMotionDotVariants };
@@ -43,7 +43,7 @@ const RadioComponent = React__default.forwardRef(({ id: idProp, name: nameProp,
43
43
  const inputProps = isControlled
44
44
  ? { checked }
45
45
  : { defaultChecked };
46
- return (jsxs("div", { className: cn(radioTheme.baseStyle, className), ...restProps, children: [jsxs("div", { className: cn(radioTheme.inputContainerStyle, radioTheme.sizes[size]), children: [jsx("input", { ref: ref, type: "radio", id: id, name: name, value: value, ...inputProps, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn("peer", radioTheme.inputStyle, radioTheme.sizes[size]) }), jsx(motion.div, { className: cn(radioTheme.dotStyle, isDisabled && "saturate-0 opacity-80", size === "sm" ? "size-2.5" : "size-3"), initial: false, animate: isChecked ? "checked" : "unchecked", variants: shouldReduceMotion ? reducedMotionDotVariants : radioDotVariants })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : label), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: radioTheme.descriptionStyle, children: description })) : description)] }))] }));
46
+ return (jsxs("div", { className: cn(radioTheme.baseStyle, className), ...restProps, children: [jsxs("div", { className: cn(radioTheme.inputContainerStyle, radioTheme.sizes[size]), children: [jsx("input", { ref: ref, type: "radio", id: id, name: name, value: value, ...inputProps, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn("peer", radioTheme.inputStyle, radioTheme.sizes[size]) }), jsx(motion.div, { className: cn(radioTheme.dotStyle, radioTheme.dotSizes[size], isDisabled && "saturate-0 opacity-80"), initial: false, animate: isChecked ? "checked" : "unchecked", variants: shouldReduceMotion ? reducedMotionDotVariants : radioDotVariants })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : label), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: radioTheme.descriptionStyle, children: description })) : description)] }))] }));
47
47
  });
48
48
  RadioComponent.displayName = "Radio";
49
49
  const Radio = Object.assign(RadioComponent, { Group: RadioGroup });
@@ -11,13 +11,9 @@ const radioTheme = {
11
11
  // Default state - dark
12
12
  "dark:bg-[var(--color-background-tertiary)] " +
13
13
  // Checked state - light
14
- "checked:border-[var(--color-primary)] " +
14
+ "checked:border-[var(--color-primary)] checked:bg-white " +
15
15
  // Checked state - dark
16
- "checked:bg-[var(--color-neutral-200)] " +
17
- // Checked outline - light
18
- "checked:outline-2 checked:outline-[var(--color-primary-100)] checked:outline-offset-1 " +
19
- // Checked outline - dark
20
- "dark:checked:outline-[var(--color-primary-900)] " +
16
+ "dark:checked:bg-[var(--color-surface)] " +
21
17
  // Focus visible - light
22
18
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary-600)] " +
23
19
  // Focus visible - dark
@@ -28,10 +24,14 @@ const radioTheme = {
28
24
  // Disabled state - dark
29
25
  "dark:disabled:bg-[var(--color-neutral-700)] dark:disabled:border-[var(--color-neutral-600)] " +
30
26
  "dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)]",
31
- dotStyle: "absolute inset-0 m-auto pointer-events-none rounded-full bg-[var(--color-primary)]",
27
+ dotStyle: "absolute top-1/2 left-1/2 pointer-events-none rounded-full bg-[var(--color-primary)]",
28
+ dotSizes: {
29
+ sm: "size-2.5 mt-0.25",
30
+ md: "size-3",
31
+ },
32
32
  descriptionStyle: "text-base text-[var(--color-text-secondary)]/80 mt-0.5",
33
33
  sizes: {
34
- sm: "size-5 mt-0.25",
34
+ sm: "size-5 mt-0.5",
35
35
  md: "size-6",
36
36
  },
37
37
  // Group styles
@@ -51,6 +51,7 @@ export interface RadioTheme {
51
51
  inputContainerStyle: string;
52
52
  inputStyle: string;
53
53
  dotStyle: string;
54
+ dotSizes: Record<RadioSize, string>;
54
55
  descriptionStyle: string;
55
56
  sizes: Record<RadioSize, string>;
56
57
  groupStyle?: string;