@oppulence/design-system 1.0.3 → 1.0.5

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.
Files changed (48) hide show
  1. package/hooks/use-resize-observer.ts +24 -0
  2. package/lib/ai.ts +31 -0
  3. package/package.json +19 -1
  4. package/src/components/atoms/animated-size-container.tsx +59 -0
  5. package/src/components/atoms/button.tsx +2 -0
  6. package/src/components/atoms/currency-input.tsx +16 -0
  7. package/src/components/atoms/icons.tsx +840 -0
  8. package/src/components/atoms/image.tsx +23 -0
  9. package/src/components/atoms/index.ts +10 -0
  10. package/src/components/atoms/loader.tsx +92 -0
  11. package/src/components/atoms/quantity-input.tsx +103 -0
  12. package/src/components/atoms/record-button.tsx +178 -0
  13. package/src/components/atoms/submit-button.tsx +26 -0
  14. package/src/components/atoms/text-effect.tsx +251 -0
  15. package/src/components/atoms/text-shimmer.tsx +74 -0
  16. package/src/components/molecules/actions.tsx +53 -0
  17. package/src/components/molecules/branch.tsx +192 -0
  18. package/src/components/molecules/code-block.tsx +151 -0
  19. package/src/components/molecules/form.tsx +177 -0
  20. package/src/components/molecules/index.ts +12 -0
  21. package/src/components/molecules/inline-citation.tsx +295 -0
  22. package/src/components/molecules/message.tsx +64 -0
  23. package/src/components/molecules/sources.tsx +116 -0
  24. package/src/components/molecules/suggestion.tsx +53 -0
  25. package/src/components/molecules/task.tsx +74 -0
  26. package/src/components/molecules/time-range-input.tsx +73 -0
  27. package/src/components/molecules/tool-call-indicator.tsx +42 -0
  28. package/src/components/molecules/tool.tsx +130 -0
  29. package/src/components/organisms/combobox-dropdown.tsx +171 -0
  30. package/src/components/organisms/conversation.tsx +98 -0
  31. package/src/components/organisms/date-range-picker.tsx +53 -0
  32. package/src/components/organisms/editor/extentions/bubble-menu/bubble-item.tsx +30 -0
  33. package/src/components/organisms/editor/extentions/bubble-menu/bubble-menu-button.tsx +27 -0
  34. package/src/components/organisms/editor/extentions/bubble-menu/index.tsx +63 -0
  35. package/src/components/organisms/editor/extentions/bubble-menu/link-item.tsx +104 -0
  36. package/src/components/organisms/editor/extentions/register.ts +22 -0
  37. package/src/components/organisms/editor/index.tsx +50 -0
  38. package/src/components/organisms/editor/styles.css +31 -0
  39. package/src/components/organisms/editor/utils.ts +19 -0
  40. package/src/components/organisms/index.ts +11 -0
  41. package/src/components/organisms/multiple-selector.tsx +632 -0
  42. package/src/components/organisms/prompt-input.tsx +747 -0
  43. package/src/components/organisms/reasoning.tsx +170 -0
  44. package/src/components/organisms/response.tsx +121 -0
  45. package/src/components/organisms/toast-toaster.tsx +84 -0
  46. package/src/components/organisms/toast.tsx +124 -0
  47. package/src/components/organisms/use-toast.tsx +206 -0
  48. package/src/styles/globals.css +169 -212
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps } from "react";
2
+ import type { GeneratedImage } from "../../../lib/ai";
3
+
4
+ export type ImageProps = GeneratedImage &
5
+ Omit<ComponentProps<"img">, "src" | "className"> & {
6
+ alt?: string;
7
+ };
8
+
9
+ export function Image({
10
+ base64,
11
+ mediaType,
12
+ alt,
13
+ ...props
14
+ }: ImageProps) {
15
+ return (
16
+ <img
17
+ {...props}
18
+ alt={alt}
19
+ className="h-auto max-w-full overflow-hidden rounded-md"
20
+ src={`data:${mediaType};base64,${base64}`}
21
+ />
22
+ );
23
+ }
@@ -1,21 +1,31 @@
1
1
  export * from "./aspect-ratio";
2
+ export * from "./animated-size-container";
2
3
  export * from "./avatar";
3
4
  export * from "./badge";
4
5
  export * from "./button";
5
6
  export * from "./checkbox";
6
7
  export * from "./container";
8
+ export * from "./currency-input";
7
9
  export * from "./heading";
10
+ export * from "./icons";
11
+ export * from "./image";
8
12
  export * from "./input";
9
13
  export * from "./kbd";
10
14
  export * from "./label";
15
+ export * from "./loader";
11
16
  export * from "./logo";
12
17
  export * from "./progress";
18
+ export * from "./quantity-input";
19
+ export * from "./record-button";
13
20
  export * from "./separator";
14
21
  export * from "./skeleton";
15
22
  export * from "./slider";
16
23
  export * from "./spinner";
17
24
  export * from "./stack";
25
+ export * from "./submit-button";
18
26
  export * from "./switch";
19
27
  export * from "./text";
28
+ export * from "./text-effect";
29
+ export * from "./text-shimmer";
20
30
  export * from "./textarea";
21
31
  export * from "./toggle";
@@ -0,0 +1,92 @@
1
+ import type { HTMLAttributes } from "react";
2
+
3
+ type LoaderIconProps = {
4
+ size?: number;
5
+ };
6
+
7
+ const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
8
+ <svg
9
+ height={size}
10
+ strokeLinejoin="round"
11
+ style={{ color: "currentcolor" }}
12
+ viewBox="0 0 16 16"
13
+ width={size}
14
+ >
15
+ <title>Loader</title>
16
+ <g clipPath="url(#clip0_2393_1490)">
17
+ <path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
18
+ <path
19
+ d="M8 16V12"
20
+ opacity="0.5"
21
+ stroke="currentColor"
22
+ strokeWidth="1.5"
23
+ />
24
+ <path
25
+ d="M3.29773 1.52783L5.64887 4.7639"
26
+ opacity="0.9"
27
+ stroke="currentColor"
28
+ strokeWidth="1.5"
29
+ />
30
+ <path
31
+ d="M12.7023 1.52783L10.3511 4.7639"
32
+ opacity="0.1"
33
+ stroke="currentColor"
34
+ strokeWidth="1.5"
35
+ />
36
+ <path
37
+ d="M12.7023 14.472L10.3511 11.236"
38
+ opacity="0.4"
39
+ stroke="currentColor"
40
+ strokeWidth="1.5"
41
+ />
42
+ <path
43
+ d="M3.29773 14.472L5.64887 11.236"
44
+ opacity="0.6"
45
+ stroke="currentColor"
46
+ strokeWidth="1.5"
47
+ />
48
+ <path
49
+ d="M15.6085 5.52783L11.8043 6.7639"
50
+ opacity="0.2"
51
+ stroke="currentColor"
52
+ strokeWidth="1.5"
53
+ />
54
+ <path
55
+ d="M0.391602 10.472L4.19583 9.23598"
56
+ opacity="0.7"
57
+ stroke="currentColor"
58
+ strokeWidth="1.5"
59
+ />
60
+ <path
61
+ d="M15.6085 10.4722L11.8043 9.2361"
62
+ opacity="0.3"
63
+ stroke="currentColor"
64
+ strokeWidth="1.5"
65
+ />
66
+ <path
67
+ d="M0.391602 5.52783L4.19583 6.7639"
68
+ opacity="0.8"
69
+ stroke="currentColor"
70
+ strokeWidth="1.5"
71
+ />
72
+ </g>
73
+ <defs>
74
+ <clipPath id="clip0_2393_1490">
75
+ <rect fill="white" height="16" width="16" />
76
+ </clipPath>
77
+ </defs>
78
+ </svg>
79
+ );
80
+
81
+ export type LoaderProps = Omit<HTMLAttributes<HTMLDivElement>, "className"> & {
82
+ size?: number;
83
+ };
84
+
85
+ export const Loader = ({ size = 16, ...props }: LoaderProps) => (
86
+ <div
87
+ className="inline-flex animate-spin items-center justify-center"
88
+ {...props}
89
+ >
90
+ <LoaderIcon size={size} />
91
+ </div>
92
+ );
@@ -0,0 +1,103 @@
1
+ import { Minus, Plus } from "lucide-react";
2
+ import * as React from "react";
3
+
4
+ type QuantityInputProps = {
5
+ value?: number;
6
+ min?: number;
7
+ max?: number;
8
+ onChange?: (value: number) => void;
9
+ onBlur?: () => void;
10
+ onFocus?: () => void;
11
+ step?: number;
12
+ placeholder?: string;
13
+ };
14
+
15
+ export function QuantityInput({
16
+ value = 0,
17
+ min = Number.NEGATIVE_INFINITY,
18
+ max = Number.POSITIVE_INFINITY,
19
+ onChange,
20
+ onBlur,
21
+ onFocus,
22
+ step = 0.1,
23
+ placeholder = "0",
24
+ }: QuantityInputProps) {
25
+ const inputRef = React.useRef<HTMLInputElement>(null);
26
+ const [rawValue, setRawValue] = React.useState(String(value));
27
+
28
+ const handleInput: React.ChangeEventHandler<HTMLInputElement> = ({
29
+ currentTarget: el,
30
+ }) => {
31
+ const input = el.value;
32
+ setRawValue(input);
33
+
34
+ const num = Number.parseFloat(input);
35
+ if (!Number.isNaN(num) && min <= num && num <= max) {
36
+ onChange?.(num);
37
+ }
38
+ };
39
+
40
+ const handlePointerDown =
41
+ (diff: number) => (event: React.PointerEvent<HTMLButtonElement>) => {
42
+ if (event.pointerType === "mouse") {
43
+ event.preventDefault();
44
+ inputRef.current?.focus();
45
+ }
46
+ const newVal = Math.min(Math.max(value + diff, min), max);
47
+ onChange?.(newVal);
48
+ setRawValue(String(newVal));
49
+ };
50
+
51
+ return (
52
+ <div className="group flex items-stretch transition-[box-shadow] font-mono">
53
+ <button
54
+ aria-label="Decrease"
55
+ className="flex items-center pr-[.325em]"
56
+ disabled={value <= min}
57
+ onPointerDown={handlePointerDown(-1)}
58
+ type="button"
59
+ tabIndex={-1}
60
+ >
61
+ <Minus
62
+ className="size-2"
63
+ absoluteStrokeWidth
64
+ strokeWidth={3.5}
65
+ tabIndex={-1}
66
+ />
67
+ </button>
68
+ <div className="relative grid items-center justify-items-center text-center">
69
+ <input
70
+ ref={inputRef}
71
+ className="flex w-full max-w-full text-center transition-colors file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 p-0 border-0 h-6 text-xs !bg-transparent border-b border-transparent focus:border-border [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [-moz-appearance:textfield]"
72
+ style={{ fontKerning: "none" }}
73
+ type="number"
74
+ min={min}
75
+ max={max}
76
+ autoComplete="off"
77
+ step={step}
78
+ value={rawValue === "0" ? "" : rawValue}
79
+ placeholder={placeholder}
80
+ onInput={handleInput}
81
+ onBlur={onBlur}
82
+ onFocus={onFocus}
83
+ inputMode="decimal"
84
+ />
85
+ </div>
86
+ <button
87
+ aria-label="Increase"
88
+ className="flex items-center pl-[.325em]"
89
+ disabled={value >= max}
90
+ onPointerDown={handlePointerDown(1)}
91
+ type="button"
92
+ tabIndex={-1}
93
+ >
94
+ <Plus
95
+ className="size-2"
96
+ absoluteStrokeWidth
97
+ strokeWidth={3.5}
98
+ tabIndex={-1}
99
+ />
100
+ </button>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,178 @@
1
+ "use client";
2
+
3
+ import { cn } from "../../../lib/utils";
4
+ import { buttonVariants } from "./button";
5
+ import { Loader } from "./loader";
6
+
7
+ export interface RecordButtonProps {
8
+ isRecording?: boolean;
9
+ isProcessing?: boolean;
10
+ onClick?: () => void;
11
+ disabled?: boolean;
12
+ size?: number;
13
+ }
14
+
15
+ const RecordIcon = ({
16
+ size = 16,
17
+ isRecording = false,
18
+ }: {
19
+ size?: number;
20
+ isRecording?: boolean;
21
+ }) => {
22
+ return (
23
+ <svg
24
+ width={size}
25
+ height={size}
26
+ viewBox="0 0 24 24"
27
+ fill="currentColor"
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ >
30
+ <rect x="3" y="10" width="2" height="4" fill="currentColor">
31
+ {isRecording && (
32
+ <>
33
+ <animate
34
+ attributeName="height"
35
+ values="4;2;6;3;8;1;5;2;7;4"
36
+ dur="2.4s"
37
+ repeatCount="indefinite"
38
+ begin="0s"
39
+ />
40
+ <animate
41
+ attributeName="y"
42
+ values="10;11;7;10.5;6;11.5;8.5;11;6.5;10"
43
+ dur="2.4s"
44
+ repeatCount="indefinite"
45
+ begin="0s"
46
+ />
47
+ </>
48
+ )}
49
+ </rect>
50
+
51
+ <rect x="7" y="6" width="2" height="12" fill="currentColor">
52
+ {isRecording && (
53
+ <>
54
+ <animate
55
+ attributeName="height"
56
+ values="12;8;16;10;18;6;14;9;15;12"
57
+ dur="2.7s"
58
+ repeatCount="indefinite"
59
+ begin="0.45s"
60
+ />
61
+ <animate
62
+ attributeName="y"
63
+ values="6;8;2;7;1;9;5;7.5;4.5;6"
64
+ dur="2.7s"
65
+ repeatCount="indefinite"
66
+ begin="0.45s"
67
+ />
68
+ </>
69
+ )}
70
+ </rect>
71
+
72
+ <rect x="11" y="2" width="2" height="20" fill="currentColor">
73
+ {isRecording && (
74
+ <>
75
+ <animate
76
+ attributeName="height"
77
+ values="20;14;22;16;24;12;18;15;21;20"
78
+ dur="2.1s"
79
+ repeatCount="indefinite"
80
+ begin="0.9s"
81
+ />
82
+ <animate
83
+ attributeName="y"
84
+ values="2;5;1;4;0;6;3;4.5;1.5;2"
85
+ dur="2.1s"
86
+ repeatCount="indefinite"
87
+ begin="0.9s"
88
+ />
89
+ </>
90
+ )}
91
+ </rect>
92
+
93
+ <rect x="15" y="6" width="2" height="12" fill="currentColor">
94
+ {isRecording && (
95
+ <>
96
+ <animate
97
+ attributeName="height"
98
+ values="12;16;8;14;10;18;6;13;9;12"
99
+ dur="3.3s"
100
+ repeatCount="indefinite"
101
+ begin="1.35s"
102
+ />
103
+ <animate
104
+ attributeName="y"
105
+ values="6;2;8;5;7;1;9;5.5;7.5;6"
106
+ dur="3.3s"
107
+ repeatCount="indefinite"
108
+ begin="1.35s"
109
+ />
110
+ </>
111
+ )}
112
+ </rect>
113
+
114
+ <rect x="19" y="10" width="2" height="4" fill="currentColor">
115
+ {isRecording && (
116
+ <>
117
+ <animate
118
+ attributeName="height"
119
+ values="4;6;2;7;3;8;1;5;3;4"
120
+ dur="3.0s"
121
+ repeatCount="indefinite"
122
+ begin="1.8s"
123
+ />
124
+ <animate
125
+ attributeName="y"
126
+ values="10;7;11;6.5;10.5;6;11.5;8.5;10.5;10"
127
+ dur="3.0s"
128
+ repeatCount="indefinite"
129
+ begin="1.8s"
130
+ />
131
+ </>
132
+ )}
133
+ </rect>
134
+ </svg>
135
+ );
136
+ };
137
+
138
+ export function RecordButton({
139
+ isRecording = false,
140
+ isProcessing = false,
141
+ onClick,
142
+ disabled = false,
143
+ size = 16,
144
+ }: RecordButtonProps) {
145
+ const baseClassName = cn(
146
+ buttonVariants({ variant: "ghost", size: "icon-xs" }),
147
+ "size-6 mr-2 transition-all duration-300",
148
+ );
149
+
150
+ if (isProcessing) {
151
+ return (
152
+ <button
153
+ type="button"
154
+ className={cn(baseClassName, "opacity-50")}
155
+ disabled
156
+ aria-busy="true"
157
+ >
158
+ <Loader size={size} />
159
+ </button>
160
+ );
161
+ }
162
+
163
+ return (
164
+ <button
165
+ type="button"
166
+ className={cn(
167
+ baseClassName,
168
+ "text-muted-foreground hover:bg-transparent hover:text-foreground",
169
+ isRecording && "text-red-500",
170
+ disabled && "opacity-50",
171
+ )}
172
+ onClick={onClick}
173
+ disabled={disabled}
174
+ >
175
+ <RecordIcon size={size} isRecording={isRecording} />
176
+ </button>
177
+ );
178
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from "react";
2
+ import { Button, type ButtonProps } from "./button";
3
+
4
+ type SubmitButtonProps = {
5
+ children: ReactNode;
6
+ isSubmitting: boolean;
7
+ disabled?: boolean;
8
+ } &
9
+ Omit<ButtonProps, "loading">;
10
+
11
+ export function SubmitButton({
12
+ children,
13
+ isSubmitting,
14
+ disabled,
15
+ ...props
16
+ }: SubmitButtonProps) {
17
+ return (
18
+ <Button
19
+ loading={isSubmitting}
20
+ disabled={isSubmitting || disabled}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </Button>
25
+ );
26
+ }
@@ -0,0 +1,251 @@
1
+ "use client";
2
+
3
+ import { AnimatePresence, motion } from "framer-motion";
4
+ import type {
5
+ TargetAndTransition,
6
+ Transition,
7
+ Variant,
8
+ Variants,
9
+ } from "framer-motion";
10
+ import React from "react";
11
+
12
+ export type PresetType = "blur" | "fade-in-blur" | "scale" | "fade" | "slide";
13
+
14
+ export type PerType = "word" | "char" | "line";
15
+
16
+ export type TextEffectProps = {
17
+ children: string;
18
+ per?: PerType;
19
+ as?: keyof React.JSX.IntrinsicElements;
20
+ variants?: {
21
+ container?: Variants;
22
+ item?: Variants;
23
+ };
24
+ preset?: PresetType;
25
+ delay?: number;
26
+ speedReveal?: number;
27
+ speedSegment?: number;
28
+ trigger?: boolean;
29
+ onAnimationComplete?: () => void;
30
+ onAnimationStart?: () => void;
31
+ containerTransition?: Transition;
32
+ segmentTransition?: Transition;
33
+ style?: React.CSSProperties;
34
+ };
35
+
36
+ const defaultStaggerTimes: Record<PerType, number> = {
37
+ char: 0.03,
38
+ word: 0.05,
39
+ line: 0.1,
40
+ };
41
+
42
+ const defaultContainerVariants: Variants = {
43
+ hidden: { opacity: 0 },
44
+ visible: {
45
+ opacity: 1,
46
+ transition: {
47
+ staggerChildren: 0.05,
48
+ },
49
+ },
50
+ exit: {
51
+ transition: { staggerChildren: 0.05, staggerDirection: -1 },
52
+ },
53
+ };
54
+
55
+ const defaultItemVariants: Variants = {
56
+ hidden: { opacity: 0 },
57
+ visible: {
58
+ opacity: 1,
59
+ },
60
+ exit: { opacity: 0 },
61
+ };
62
+
63
+ const presetVariants: Record<
64
+ PresetType,
65
+ { container: Variants; item: Variants }
66
+ > = {
67
+ blur: {
68
+ container: defaultContainerVariants,
69
+ item: {
70
+ hidden: { opacity: 0, filter: "blur(12px)" },
71
+ visible: { opacity: 1, filter: "blur(0px)" },
72
+ exit: { opacity: 0, filter: "blur(12px)" },
73
+ },
74
+ },
75
+ "fade-in-blur": {
76
+ container: defaultContainerVariants,
77
+ item: {
78
+ hidden: { opacity: 0, y: 20, filter: "blur(12px)" },
79
+ visible: { opacity: 1, y: 0, filter: "blur(0px)" },
80
+ exit: { opacity: 0, y: 20, filter: "blur(12px)" },
81
+ },
82
+ },
83
+ scale: {
84
+ container: defaultContainerVariants,
85
+ item: {
86
+ hidden: { opacity: 0, scale: 0 },
87
+ visible: { opacity: 1, scale: 1 },
88
+ exit: { opacity: 0, scale: 0 },
89
+ },
90
+ },
91
+ fade: {
92
+ container: defaultContainerVariants,
93
+ item: {
94
+ hidden: { opacity: 0 },
95
+ visible: { opacity: 1 },
96
+ exit: { opacity: 0 },
97
+ },
98
+ },
99
+ slide: {
100
+ container: defaultContainerVariants,
101
+ item: {
102
+ hidden: { opacity: 0, y: 20 },
103
+ visible: { opacity: 1, y: 0 },
104
+ exit: { opacity: 0, y: 20 },
105
+ },
106
+ },
107
+ };
108
+
109
+ const AnimationComponent: React.FC<{
110
+ segment: string;
111
+ variants: Variants;
112
+ per: "line" | "word" | "char";
113
+ }> = React.memo(({ segment, variants, per }) => {
114
+ return per === "line" ? (
115
+ <motion.span variants={variants} className="block">
116
+ {segment}
117
+ </motion.span>
118
+ ) : per === "word" ? (
119
+ <motion.span
120
+ aria-hidden="true"
121
+ variants={variants}
122
+ className="inline-block whitespace-pre"
123
+ >
124
+ {segment}
125
+ </motion.span>
126
+ ) : (
127
+ <motion.span className="inline-block whitespace-pre">
128
+ {segment.split("").map((char, charIndex) => (
129
+ <motion.span
130
+ key={`char-${charIndex.toString()}`}
131
+ aria-hidden="true"
132
+ variants={variants}
133
+ className="inline-block whitespace-pre"
134
+ >
135
+ {char}
136
+ </motion.span>
137
+ ))}
138
+ </motion.span>
139
+ );
140
+ });
141
+
142
+ AnimationComponent.displayName = "AnimationComponent";
143
+
144
+ const splitText = (text: string, per: PerType) => {
145
+ if (per === "line") return text.split("\n");
146
+ return text.split(/(\s+)/);
147
+ };
148
+
149
+ const hasTransition = (
150
+ variant?: Variant,
151
+ ): variant is TargetAndTransition & { transition?: Transition } => {
152
+ if (!variant) return false;
153
+ return typeof variant === "object" && "transition" in variant;
154
+ };
155
+
156
+ const createVariantsWithTransition = (
157
+ baseVariants: Variants,
158
+ transition?: Transition & { exit?: Transition },
159
+ ): Variants => {
160
+ if (!transition) return baseVariants;
161
+
162
+ const { exit: _, ...mainTransition } = transition;
163
+
164
+ return {
165
+ ...baseVariants,
166
+ visible: {
167
+ ...baseVariants.visible,
168
+ transition: {
169
+ ...(hasTransition(baseVariants.visible)
170
+ ? baseVariants.visible.transition
171
+ : {}),
172
+ ...mainTransition,
173
+ },
174
+ },
175
+ exit: {
176
+ ...baseVariants.exit,
177
+ transition: {
178
+ ...(hasTransition(baseVariants.exit)
179
+ ? baseVariants.exit.transition
180
+ : {}),
181
+ ...mainTransition,
182
+ staggerDirection: -1,
183
+ },
184
+ },
185
+ };
186
+ };
187
+
188
+ export const TextEffect = ({
189
+ children,
190
+ per = "word",
191
+ as = "p",
192
+ variants,
193
+ preset,
194
+ delay = 0,
195
+ speedReveal,
196
+ speedSegment,
197
+ trigger = true,
198
+ onAnimationComplete,
199
+ onAnimationStart,
200
+ containerTransition,
201
+ segmentTransition,
202
+ style,
203
+ }: TextEffectProps) => {
204
+ const Component = motion.create(as);
205
+ const segments = splitText(children, per);
206
+
207
+ const baseContainer = preset
208
+ ? presetVariants[preset].container
209
+ : variants?.container ?? defaultContainerVariants;
210
+ const baseItem = preset
211
+ ? presetVariants[preset].item
212
+ : variants?.item ?? defaultItemVariants;
213
+
214
+ const staggerChildren = speedSegment ?? defaultStaggerTimes[per];
215
+
216
+ const containerVariants = createVariantsWithTransition(baseContainer, {
217
+ ...containerTransition,
218
+ staggerChildren,
219
+ delayChildren: delay,
220
+ });
221
+
222
+ const itemVariants = createVariantsWithTransition(baseItem, {
223
+ ...segmentTransition,
224
+ duration: speedReveal,
225
+ });
226
+
227
+ return (
228
+ <AnimatePresence>
229
+ {trigger && (
230
+ <Component
231
+ variants={containerVariants}
232
+ initial="hidden"
233
+ animate="visible"
234
+ exit="exit"
235
+ onAnimationComplete={onAnimationComplete}
236
+ onAnimationStart={onAnimationStart}
237
+ style={style}
238
+ >
239
+ {segments.map((segment, index) => (
240
+ <AnimationComponent
241
+ key={`segment-${index.toString()}`}
242
+ segment={segment}
243
+ variants={itemVariants}
244
+ per={per}
245
+ />
246
+ ))}
247
+ </Component>
248
+ )}
249
+ </AnimatePresence>
250
+ );
251
+ };