create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vexcms",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Scaffold a new VexCMS project",
5
5
  "keywords": [
6
6
  "cms",
@@ -31,9 +31,17 @@ NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
31
31
  NEXT_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site
32
32
  ```
33
33
 
34
- Then, in the [Convex Dashboard](https://dashboard.convex.dev) → Settings →
35
- Environment Variables, add `BETTER_AUTH_SECRET` (same value as `.env.local`)
36
- and `SITE_URL` (`http://localhost:3010` for local dev).
34
+ Better Auth runs inside your Convex deployment, not in the Next.js server —
35
+ it does **not** read `.env.local`. Set the same two values on the
36
+ deployment itself:
37
+
38
+ ```bash
39
+ npx convex env set SITE_URL http://localhost:3010
40
+ npx convex env set BETTER_AUTH_SECRET <the BETTER_AUTH_SECRET value from .env.local>
41
+ ```
42
+
43
+ Skipping this step is the most common cause of a `403` on your first sign-in
44
+ attempt.
37
45
 
38
46
  ### 4. Run the dev servers
39
47
 
@@ -8,16 +8,16 @@
8
8
  "allowSyntheticDefaultImports": true,
9
9
  "target": "ESNext",
10
10
  "lib": ["ES2023", "dom", "dom.iterable"],
11
+ "types": ["node"],
11
12
  "forceConsistentCasingInFileNames": true,
12
13
  "module": "ESNext",
13
14
  "isolatedModules": true,
14
15
  "noEmit": true,
15
- "baseUrl": ".",
16
16
  "paths": {
17
17
  "~/*": ["../src/*"],
18
18
  "@convex/*": ["./*"]
19
19
  }
20
20
  },
21
- "include": ["./**/*"],
21
+ "include": ["./**/*", "../src/vex.types.ts"],
22
22
  "exclude": ["./_generated"]
23
23
  }
@@ -24,18 +24,17 @@
24
24
  "@t3-oss/env-nextjs": "0.13.11",
25
25
  "@tanstack/react-form": "1.33.1",
26
26
  "@tanstack/react-query": "5.101.2",
27
- "@vexcms/better-auth": "~0.1.0-alpha.2",
28
- "@vexcms/core": "~0.1.0-alpha.2",
29
- "@vexcms/file-storage-convex": "~0.1.0-alpha.2",
30
- "@vexcms/next": "~0.1.0-alpha.2",
31
- "@vexcms/react": "~0.1.0-alpha.2",
27
+ "@vexcms/better-auth": "~0.1.0-alpha.3",
28
+ "@vexcms/core": "~0.1.0-alpha.3",
29
+ "@vexcms/file-storage-convex": "~0.1.0-alpha.3",
30
+ "@vexcms/next": "~0.1.0-alpha.3",
31
+ "@vexcms/react": "~0.1.0-alpha.3",
32
32
  "better-auth": "1.6.23",
33
33
  "class-variance-authority": "0.7.1",
34
34
  "clsx": "2.1.1",
35
35
  "convex": "1.44.0",
36
36
  "convex-helpers": "0.1.120",
37
37
  "lucide-react": "0.577.0",
38
- "motion": "13.1.1",
39
38
  "next": "16.3.3",
40
39
  "nuqs": "2.9.0",
41
40
  "react": "19.2.7",
@@ -53,7 +52,7 @@
53
52
  "@types/node": "20.19.43",
54
53
  "@types/react": "19.2.17",
55
54
  "@types/react-dom": "19.2.3",
56
- "@vexcms/cli": "~0.1.0-alpha.2",
55
+ "@vexcms/cli": "~0.1.0-alpha.3",
57
56
  "babel-plugin-react-compiler": "1.0.0",
58
57
  "eslint": "9.39.5",
59
58
  "eslint-config-next": "15.5.9",
@@ -1,138 +1,62 @@
1
- "use client"
1
+ import React, { type ComponentPropsWithRef, type CSSProperties } from "react"
2
2
 
3
- import { motion, type Variants } from "motion/react"
4
- import React, { type ComponentPropsWithRef } from "react"
3
+ import { cn } from "~/lib/utils"
5
4
 
6
- export type PresetType =
7
- | "blur"
8
- | "blur-slide"
9
- | "bounce"
10
- | "fade"
11
- | "flip"
12
- | "rotate"
13
- | "scale"
14
- | "slide"
15
- | "swing"
16
- | "zoom"
5
+ import styles from "./reveal.module.css"
17
6
 
18
7
  export type AnimatedGroupProps = ComponentPropsWithRef<"div"> & {
19
8
  as?: React.ElementType
20
- asChild?: React.ElementType
21
- className?: string
22
- preset?: PresetType
23
- variants?: {
24
- container?: Variants
25
- item?: Variants
26
- }
9
+ /** Seconds before the first child starts animating. */
10
+ delay?: number
11
+ /** Seconds between each child's start. */
12
+ stagger?: number
27
13
  }
28
14
 
29
- const defaultContainerVariants: Variants = {
30
- visible: {
31
- transition: {
32
- staggerChildren: 0.1,
33
- },
34
- },
35
- }
36
-
37
- const defaultItemVariants: Variants = {
38
- hidden: { opacity: 0 },
39
- visible: { opacity: 1 },
40
- }
41
-
42
- const presetVariants: Record<PresetType, Variants> = {
43
- "blur-slide": {
44
- hidden: { filter: "blur(4px)", y: 20 },
45
- visible: { filter: "blur(0px)", y: 0 },
46
- },
47
- slide: {
48
- hidden: { y: 20 },
49
- visible: { y: 0 },
50
- },
51
- blur: {
52
- hidden: { filter: "blur(4px)" },
53
- visible: { filter: "blur(0px)" },
54
- },
55
- bounce: {
56
- hidden: { y: -50 },
57
- visible: {
58
- transition: { type: "spring", damping: 10, stiffness: 400 },
59
- y: 0,
60
- },
61
- },
62
- fade: {},
63
- flip: {
64
- hidden: { rotateX: -90 },
65
- visible: {
66
- rotateX: 0,
67
- transition: { type: "spring", damping: 20, stiffness: 300 },
68
- },
69
- },
70
- rotate: {
71
- hidden: { rotate: -180 },
72
- visible: {
73
- rotate: 0,
74
- transition: { type: "spring", damping: 15, stiffness: 200 },
75
- },
76
- },
77
- scale: {
78
- hidden: { scale: 0.8 },
79
- visible: { scale: 1 },
80
- },
81
- swing: {
82
- hidden: { rotate: -10 },
83
- visible: {
84
- rotate: 0,
85
- transition: { type: "spring", damping: 8, stiffness: 300 },
86
- },
87
- },
88
- zoom: {
89
- hidden: { scale: 0.5 },
90
- visible: {
91
- scale: 1,
92
- transition: { type: "spring", damping: 20, stiffness: 300 },
93
- },
94
- },
95
- }
96
-
97
- const addDefaultVariants = (variants: Variants) => ({
98
- hidden: { ...defaultItemVariants.hidden, ...variants.hidden },
99
- visible: { ...defaultItemVariants.visible, ...variants.visible },
100
- })
101
-
102
- function AnimatedGroup({
103
- as = "div",
104
- asChild = "div",
15
+ /**
16
+ * Staggers a blur + fade + rise entrance across its direct children using a
17
+ * pure-CSS keyframe (see `reveal.module.css`) — no animation-library
18
+ * dependency. Each child is wrapped in a div carrying the animation with an
19
+ * incremented `animation-delay`.
20
+ */
21
+ export function AnimatedGroup({
22
+ as: Tag = "div",
105
23
  children,
106
24
  className,
107
- preset,
108
- variants,
25
+ delay = 0,
26
+ stagger = 0.05,
109
27
  ...divProps
110
28
  }: AnimatedGroupProps) {
111
- const selectedVariants = {
112
- container: addDefaultVariants(defaultContainerVariants),
113
- item: addDefaultVariants(preset ? presetVariants[preset] : {}),
114
- }
115
- const containerVariants = variants?.container ?? selectedVariants.container
116
- const itemVariants = variants?.item ?? selectedVariants.item
117
-
118
- const MotionComponent = motion.create(as)
119
- const MotionChild = motion.create(asChild)
120
-
121
29
  return (
122
- <MotionComponent
123
- animate="visible"
124
- className={className}
125
- initial="hidden"
126
- variants={containerVariants}
127
- {...divProps}
128
- >
30
+ <Tag className={className} {...divProps}>
129
31
  {React.Children.map(children, (child, index) => (
130
- <MotionChild key={index} variants={itemVariants}>
32
+ <div
33
+ className={styles.item}
34
+ style={{ "--reveal-delay": `${delay + index * stagger}s` } as CSSProperties}
35
+ >
131
36
  {child}
132
- </MotionChild>
37
+ </div>
133
38
  ))}
134
- </MotionComponent>
39
+ </Tag>
135
40
  )
136
41
  }
137
42
 
138
- export { AnimatedGroup }
43
+ /**
44
+ * Applies the same reveal animation to a single element without adding a
45
+ * wrapper div — for callers that only need one animated child.
46
+ */
47
+ export function AnimatedItem({
48
+ children,
49
+ className,
50
+ delay = 0,
51
+ ...divProps
52
+ }: ComponentPropsWithRef<"div"> & { delay?: number }) {
53
+ return (
54
+ <div
55
+ className={cn(styles.item, className)}
56
+ style={{ "--reveal-delay": `${delay}s` } as CSSProperties}
57
+ {...divProps}
58
+ >
59
+ {children}
60
+ </div>
61
+ )
62
+ }
@@ -0,0 +1,32 @@
1
+ /*
2
+ * Shared "reveal" animation for AnimatedGroup and TextEffect — the
3
+ * blur + fade + rise entrance the hero uses, implemented in pure CSS so the
4
+ * template carries no animation-library dependency.
5
+ *
6
+ * `both` fill-mode keeps elements hidden until their delay elapses, which is
7
+ * what makes the stagger read as a sequence. Delay per element arrives via
8
+ * the `--reveal-delay` custom property.
9
+ */
10
+ @keyframes reveal {
11
+ from {
12
+ opacity: 0;
13
+ filter: blur(12px);
14
+ transform: translateY(12px);
15
+ }
16
+ to {
17
+ opacity: 1;
18
+ filter: blur(0);
19
+ transform: translateY(0);
20
+ }
21
+ }
22
+
23
+ .item {
24
+ animation: reveal 1.1s cubic-bezier(0.25, 1, 0.5, 1) both;
25
+ animation-delay: var(--reveal-delay, 0s);
26
+ }
27
+
28
+ @media (prefers-reduced-motion: reduce) {
29
+ .item {
30
+ animation: none;
31
+ }
32
+ }
@@ -1,292 +1,66 @@
1
- "use client"
2
-
3
- import {
4
- AnimatePresence,
5
- motion,
6
- type TargetAndTransition,
7
- type Transition,
8
- type Variant,
9
- type Variants,
10
- } from "motion/react"
11
- import React from "react"
1
+ import React, { type CSSProperties } from "react"
12
2
 
13
3
  import { cn } from "~/lib/utils"
14
4
 
15
- export type PerType = "char" | "line" | "word"
16
- export type PresetType = "blur" | "fade" | "fade-in-blur" | "scale" | "slide"
5
+ import styles from "./reveal.module.css"
6
+
7
+ export type PerType = "line" | "word"
17
8
 
18
9
  export type TextEffectProps = {
19
10
  as?: keyof React.JSX.IntrinsicElements
20
11
  children: string
21
12
  className?: string
22
- containerTransition?: Transition
13
+ /** Seconds before the first segment starts animating. */
23
14
  delay?: number
24
- onAnimationComplete?: () => void
25
- onAnimationStart?: () => void
15
+ /** Split granularity for the stagger. Lines split on `\n`. */
26
16
  per?: PerType
27
- preset?: PresetType
28
- segmentTransition?: Transition
29
- segmentWrapperClassName?: string
30
- speedReveal?: number
31
- speedSegment?: number
32
- style?: React.CSSProperties
33
- trigger?: boolean
34
- variants?: {
35
- container?: Variants
36
- item?: Variants
37
- }
17
+ /** Seconds between each segment's start. */
18
+ stagger?: number
38
19
  }
39
20
 
40
- const defaultStaggerTimes: Record<PerType, number> = {
41
- char: 0.03,
42
- line: 0.1,
43
- word: 0.05,
44
- }
45
-
46
- const defaultContainerVariants: Variants = {
47
- hidden: { opacity: 0 },
48
- exit: {
49
- transition: { staggerChildren: 0.05, staggerDirection: -1 },
50
- },
51
- visible: {
52
- opacity: 1,
53
- transition: {
54
- staggerChildren: 0.05,
55
- },
56
- },
57
- }
58
-
59
- const defaultItemVariants: Variants = {
60
- hidden: { opacity: 0 },
61
- exit: { opacity: 0 },
62
- visible: {
63
- opacity: 1,
64
- },
65
- }
66
-
67
- const presetVariants: Record<
68
- PresetType,
69
- { container: Variants; item: Variants }
70
- > = {
71
- slide: {
72
- container: defaultContainerVariants,
73
- item: {
74
- hidden: { opacity: 0, y: 20 },
75
- exit: { opacity: 0, y: 20 },
76
- visible: { opacity: 1, y: 0 },
77
- },
78
- },
79
- blur: {
80
- container: defaultContainerVariants,
81
- item: {
82
- hidden: { filter: "blur(12px)", opacity: 0 },
83
- exit: { filter: "blur(12px)", opacity: 0 },
84
- visible: { filter: "blur(0px)", opacity: 1 },
85
- },
86
- },
87
- fade: {
88
- container: defaultContainerVariants,
89
- item: {
90
- hidden: { opacity: 0 },
91
- exit: { opacity: 0 },
92
- visible: { opacity: 1 },
93
- },
94
- },
95
- "fade-in-blur": {
96
- container: defaultContainerVariants,
97
- item: {
98
- hidden: { filter: "blur(12px)", opacity: 0, y: 20 },
99
- exit: { filter: "blur(12px)", opacity: 0, y: 20 },
100
- visible: { filter: "blur(0px)", opacity: 1, y: 0 },
101
- },
102
- },
103
- scale: {
104
- container: defaultContainerVariants,
105
- item: {
106
- hidden: { opacity: 0, scale: 0 },
107
- exit: { opacity: 0, scale: 0 },
108
- visible: { opacity: 1, scale: 1 },
109
- },
110
- },
111
- }
112
-
113
- const AnimationComponent: React.FC<{
114
- per: "char" | "line" | "word"
115
- segment: string
116
- segmentWrapperClassName?: string
117
- variants: Variants
118
- }> = React.memo(({ per, segment, segmentWrapperClassName, variants }) => {
119
- const content =
120
- per === "line" ? (
121
- <motion.span className="block" variants={variants}>
122
- {segment}
123
- </motion.span>
124
- ) : per === "word" ? (
125
- <motion.span
126
- aria-hidden="true"
127
- className="inline-block whitespace-pre"
128
- variants={variants}
129
- >
130
- {segment}
131
- </motion.span>
132
- ) : (
133
- <motion.span className="inline-block whitespace-pre">
134
- {segment.split("").map((char, charIndex) => (
135
- <motion.span
136
- aria-hidden="true"
137
- className="inline-block whitespace-pre"
138
- key={`char-${charIndex}`}
139
- variants={variants}
140
- >
141
- {char}
142
- </motion.span>
143
- ))}
144
- </motion.span>
145
- )
146
-
147
- if (!segmentWrapperClassName) {
148
- return content
149
- }
150
-
151
- const defaultWrapperClassName = per === "line" ? "block" : "inline-block"
152
-
153
- return (
154
- <span className={cn(defaultWrapperClassName, segmentWrapperClassName)}>
155
- {content}
156
- </span>
157
- )
158
- })
159
-
160
- AnimationComponent.displayName = "AnimationComponent"
161
-
162
- const splitText = (text: string, per: "char" | "line" | "word") => {
163
- if (per === "line") return text.split("\n")
164
- return text.split(/(\s+)/)
165
- }
166
-
167
- const hasTransition = (
168
- variant: Variant,
169
- ): variant is TargetAndTransition & { transition?: Transition } => {
170
- return (
171
- typeof variant === "object" && variant !== null && "transition" in variant
172
- )
173
- }
174
-
175
- const createVariantsWithTransition = (
176
- baseVariants: Variants,
177
- transition?: Transition & { exit?: Transition },
178
- ): Variants => {
179
- if (!transition) return baseVariants
180
-
181
- const { ...mainTransition } = transition
182
-
183
- return {
184
- ...baseVariants,
185
- exit: {
186
- ...baseVariants.exit,
187
- transition: {
188
- ...(hasTransition(baseVariants.exit)
189
- ? baseVariants.exit.transition
190
- : {}),
191
- ...mainTransition,
192
- staggerDirection: -1,
193
- },
194
- },
195
- visible: {
196
- ...baseVariants.visible,
197
- transition: {
198
- ...(hasTransition(baseVariants.visible)
199
- ? baseVariants.visible.transition
200
- : {}),
201
- ...mainTransition,
202
- },
203
- },
204
- }
21
+ const defaultStagger: Record<PerType, number> = {
22
+ line: 0.15,
23
+ word: 0.04,
205
24
  }
206
25
 
26
+ /**
27
+ * Staggered blur + fade + rise text entrance, split per word or per line —
28
+ * pure CSS (see `reveal.module.css`), no animation-library dependency.
29
+ * Word segments keep their trailing space inside the animated span so text
30
+ * still wraps naturally.
31
+ */
207
32
  export function TextEffect({
208
33
  as = "p",
209
34
  children,
210
35
  className,
211
- containerTransition,
212
36
  delay = 0,
213
- onAnimationComplete,
214
- onAnimationStart,
215
37
  per = "word",
216
- preset = "fade",
217
- segmentTransition,
218
- segmentWrapperClassName,
219
- speedReveal = 1,
220
- speedSegment = 1,
221
- style,
222
- trigger = true,
223
- variants,
38
+ stagger,
224
39
  }: TextEffectProps) {
225
- const segments = splitText(children, per)
226
- const MotionTag = motion[as as keyof typeof motion] as typeof motion.div
227
-
228
- const baseVariants = preset
229
- ? presetVariants[preset]
230
- : { container: defaultContainerVariants, item: defaultItemVariants }
231
-
232
- const stagger = defaultStaggerTimes[per] / speedReveal
233
-
234
- const baseDuration = 0.3 / speedSegment
235
-
236
- const customStagger = hasTransition(variants?.container?.visible ?? {})
237
- ? (variants?.container?.visible as TargetAndTransition).transition
238
- ?.staggerChildren
239
- : undefined
240
-
241
- const customDelay = hasTransition(variants?.container?.visible ?? {})
242
- ? (variants?.container?.visible as TargetAndTransition).transition
243
- ?.delayChildren
244
- : undefined
245
-
246
- const computedVariants = {
247
- container: createVariantsWithTransition(
248
- variants?.container ?? baseVariants.container,
249
- {
250
- delayChildren: customDelay ?? delay,
251
- staggerChildren: customStagger ?? stagger,
252
- ...containerTransition,
253
- exit: {
254
- staggerChildren: customStagger ?? stagger,
255
- staggerDirection: -1,
256
- },
257
- },
258
- ),
259
- item: createVariantsWithTransition(variants?.item ?? baseVariants.item, {
260
- duration: baseDuration,
261
- ...segmentTransition,
262
- }),
263
- }
40
+ const Tag = as as React.ElementType
41
+ const segments =
42
+ per === "line" ? children.split("\n") : children.split(/(\s+)/).filter(Boolean)
43
+ const step = stagger ?? defaultStagger[per]
264
44
 
45
+ let animatedIndex = 0
265
46
  return (
266
- <AnimatePresence mode="popLayout">
267
- {trigger && (
268
- <MotionTag
269
- animate="visible"
270
- className={className}
271
- exit="exit"
272
- initial="hidden"
273
- onAnimationComplete={onAnimationComplete}
274
- onAnimationStart={onAnimationStart}
275
- style={style}
276
- variants={computedVariants.container}
277
- >
278
- {per !== "line" ? <span className="sr-only">{children}</span> : null}
279
- {segments.map((segment, index) => (
280
- <AnimationComponent
281
- key={`${per}-${index}-${segment}`}
282
- per={per}
283
- segment={segment}
284
- segmentWrapperClassName={segmentWrapperClassName}
285
- variants={computedVariants.item}
286
- />
287
- ))}
288
- </MotionTag>
289
- )}
290
- </AnimatePresence>
47
+ <Tag className={className}>
48
+ {segments.map((segment, index) => {
49
+ if (per === "word" && /^\s+$/.test(segment)) {
50
+ return <React.Fragment key={index}>{segment}</React.Fragment>
51
+ }
52
+ const segmentDelay = delay + animatedIndex * step
53
+ animatedIndex += 1
54
+ return (
55
+ <span
56
+ className={cn(styles.item, per === "line" ? "block" : "inline-block")}
57
+ key={index}
58
+ style={{ "--reveal-delay": `${segmentDelay}s` } as CSSProperties}
59
+ >
60
+ {segment}
61
+ </span>
62
+ )
63
+ })}
64
+ </Tag>
291
65
  )
292
66
  }