promptslide 0.2.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.
- package/dist/index.d.ts +377 -0
- package/dist/index.js +963 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/commands/build.mjs +73 -0
- package/src/commands/create.mjs +197 -0
- package/src/commands/preview.mjs +22 -0
- package/src/commands/studio.mjs +27 -0
- package/src/core/animated.tsx +153 -0
- package/src/core/animation-config.ts +98 -0
- package/src/core/animation-context.tsx +54 -0
- package/src/core/index.ts +73 -0
- package/src/core/layouts/shared-footer.tsx +43 -0
- package/src/core/morph.tsx +153 -0
- package/src/core/slide-deck.tsx +430 -0
- package/src/core/slide-error-boundary.tsx +50 -0
- package/src/core/theme-context.tsx +48 -0
- package/src/core/transitions.ts +200 -0
- package/src/core/types.ts +136 -0
- package/src/core/use-slide-navigation.ts +142 -0
- package/src/core/utils.ts +8 -0
- package/src/index.mjs +70 -0
- package/src/utils/ansi.mjs +5 -0
- package/src/utils/colors.mjs +44 -0
- package/src/utils/prompts.mjs +50 -0
- package/src/utils/tsconfig.mjs +35 -0
- package/src/vite/config.mjs +40 -0
- package/src/vite/plugin.mjs +66 -0
- package/templates/default/AGENTS.md +453 -0
- package/templates/default/README.md +35 -0
- package/templates/default/package.json +26 -0
- package/templates/default/public/logo.svg +7 -0
- package/templates/default/src/App.tsx +11 -0
- package/templates/default/src/deck-config.ts +8 -0
- package/templates/default/src/globals.css +157 -0
- package/templates/default/src/layouts/slide-layout-centered.tsx +59 -0
- package/templates/default/src/slides/slide-example.tsx +53 -0
- package/templates/default/src/slides/slide-title.tsx +27 -0
- package/templates/default/src/theme.ts +8 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slide transition variants for the slide deck framework.
|
|
3
|
+
* These define how slides enter and exit during navigation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Variants } from "framer-motion"
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
EASE_MORPH,
|
|
10
|
+
MORPH_DURATION,
|
|
11
|
+
SLIDE_DISTANCE,
|
|
12
|
+
SLIDE_TRANSITION,
|
|
13
|
+
SLIDE_TRANSITION_DURATION
|
|
14
|
+
} from "./animation-config"
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// TYPES
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
export type SlideTransitionType =
|
|
21
|
+
| "fade"
|
|
22
|
+
| "slide-left"
|
|
23
|
+
| "slide-right"
|
|
24
|
+
| "slide-up"
|
|
25
|
+
| "slide-down"
|
|
26
|
+
| "zoom"
|
|
27
|
+
| "zoom-fade"
|
|
28
|
+
| "morph"
|
|
29
|
+
| "none"
|
|
30
|
+
|
|
31
|
+
export interface SlideTransitionConfig {
|
|
32
|
+
/** The transition type */
|
|
33
|
+
type: SlideTransitionType
|
|
34
|
+
/** Optional custom duration (overrides default) */
|
|
35
|
+
duration?: number
|
|
36
|
+
/** Whether to use directional transitions based on navigation direction */
|
|
37
|
+
directional?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// STATIC VARIANTS (non-directional)
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
const fadeVariants: Variants = {
|
|
45
|
+
enter: { opacity: 0 },
|
|
46
|
+
center: { opacity: 1 },
|
|
47
|
+
exit: { opacity: 0 }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const slideLeftVariants: Variants = {
|
|
51
|
+
enter: { x: SLIDE_DISTANCE, opacity: 0 },
|
|
52
|
+
center: { x: 0, opacity: 1 },
|
|
53
|
+
exit: { x: -SLIDE_DISTANCE, opacity: 0 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const slideRightVariants: Variants = {
|
|
57
|
+
enter: { x: -SLIDE_DISTANCE, opacity: 0 },
|
|
58
|
+
center: { x: 0, opacity: 1 },
|
|
59
|
+
exit: { x: SLIDE_DISTANCE, opacity: 0 }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const slideUpVariants: Variants = {
|
|
63
|
+
enter: { y: SLIDE_DISTANCE, opacity: 0 },
|
|
64
|
+
center: { y: 0, opacity: 1 },
|
|
65
|
+
exit: { y: -SLIDE_DISTANCE, opacity: 0 }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const slideDownVariants: Variants = {
|
|
69
|
+
enter: { y: -SLIDE_DISTANCE, opacity: 0 },
|
|
70
|
+
center: { y: 0, opacity: 1 },
|
|
71
|
+
exit: { y: SLIDE_DISTANCE, opacity: 0 }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const zoomVariants: Variants = {
|
|
75
|
+
enter: { scale: 0.8, opacity: 0 },
|
|
76
|
+
center: { scale: 1, opacity: 1 },
|
|
77
|
+
exit: { scale: 1.2, opacity: 0 }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const zoomFadeVariants: Variants = {
|
|
81
|
+
enter: { scale: 0.95, opacity: 0 },
|
|
82
|
+
center: { scale: 1, opacity: 1 },
|
|
83
|
+
exit: { scale: 1.05, opacity: 0 }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const morphVariants: Variants = {
|
|
87
|
+
enter: {
|
|
88
|
+
opacity: 0,
|
|
89
|
+
zIndex: 1
|
|
90
|
+
},
|
|
91
|
+
center: {
|
|
92
|
+
opacity: 1,
|
|
93
|
+
zIndex: 1,
|
|
94
|
+
transition: {
|
|
95
|
+
opacity: { delay: 0.05, duration: 0.25 }
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
exit: {
|
|
99
|
+
opacity: 0,
|
|
100
|
+
zIndex: 0,
|
|
101
|
+
transition: {
|
|
102
|
+
opacity: { duration: MORPH_DURATION, ease: EASE_MORPH }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const noneVariants: Variants = {
|
|
108
|
+
enter: {},
|
|
109
|
+
center: {},
|
|
110
|
+
exit: {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// VARIANT REGISTRY
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
export const SLIDE_VARIANTS: Record<SlideTransitionType, Variants> = {
|
|
118
|
+
fade: fadeVariants,
|
|
119
|
+
"slide-left": slideLeftVariants,
|
|
120
|
+
"slide-right": slideRightVariants,
|
|
121
|
+
"slide-up": slideUpVariants,
|
|
122
|
+
"slide-down": slideDownVariants,
|
|
123
|
+
zoom: zoomVariants,
|
|
124
|
+
"zoom-fade": zoomFadeVariants,
|
|
125
|
+
morph: morphVariants,
|
|
126
|
+
none: noneVariants
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// DIRECTIONAL VARIANTS (based on navigation direction)
|
|
131
|
+
// =============================================================================
|
|
132
|
+
|
|
133
|
+
export function createDirectionalVariants(axis: "x" | "y" = "x"): (direction: number) => Variants {
|
|
134
|
+
return (direction: number) => ({
|
|
135
|
+
enter: {
|
|
136
|
+
[axis]: direction > 0 ? SLIDE_DISTANCE : -SLIDE_DISTANCE,
|
|
137
|
+
opacity: 0
|
|
138
|
+
},
|
|
139
|
+
center: {
|
|
140
|
+
[axis]: 0,
|
|
141
|
+
opacity: 1
|
|
142
|
+
},
|
|
143
|
+
exit: {
|
|
144
|
+
[axis]: direction < 0 ? SLIDE_DISTANCE : -SLIDE_DISTANCE,
|
|
145
|
+
opacity: 0
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Horizontal directional slide (left/right based on direction) */
|
|
151
|
+
export const directionalSlideX = createDirectionalVariants("x")
|
|
152
|
+
|
|
153
|
+
/** Vertical directional slide (up/down based on direction) */
|
|
154
|
+
export const directionalSlideY = createDirectionalVariants("y")
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// HELPER FUNCTIONS
|
|
158
|
+
// =============================================================================
|
|
159
|
+
|
|
160
|
+
export function getSlideVariants(
|
|
161
|
+
config: SlideTransitionConfig | SlideTransitionType,
|
|
162
|
+
direction: number = 1
|
|
163
|
+
): Variants {
|
|
164
|
+
const type = typeof config === "string" ? config : config.type
|
|
165
|
+
const directional = typeof config === "object" ? config.directional : false
|
|
166
|
+
|
|
167
|
+
if (directional && (type === "slide-left" || type === "slide-right")) {
|
|
168
|
+
return directionalSlideX(direction)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (directional && (type === "slide-up" || type === "slide-down")) {
|
|
172
|
+
return directionalSlideY(direction)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return SLIDE_VARIANTS[type]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getSlideTransition(config?: SlideTransitionConfig | SlideTransitionType): {
|
|
179
|
+
duration: number
|
|
180
|
+
ease: typeof SLIDE_TRANSITION.ease
|
|
181
|
+
} {
|
|
182
|
+
if (!config) return SLIDE_TRANSITION
|
|
183
|
+
|
|
184
|
+
const type = typeof config === "string" ? config : config.type
|
|
185
|
+
|
|
186
|
+
const defaultDuration = type === "morph" ? MORPH_DURATION : SLIDE_TRANSITION_DURATION
|
|
187
|
+
|
|
188
|
+
const duration = typeof config === "object" && config.duration ? config.duration : defaultDuration
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...SLIDE_TRANSITION,
|
|
192
|
+
duration
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// DEFAULT EXPORT
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
export const DEFAULT_SLIDE_TRANSITION: SlideTransitionType = "fade"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the slide deck framework.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SlideTransitionType } from "./transitions"
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// SLIDE CONFIGURATION TYPES
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props that all slide components receive.
|
|
13
|
+
*/
|
|
14
|
+
export interface SlideProps {
|
|
15
|
+
slideNumber: number
|
|
16
|
+
totalSlides: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A React component that renders a slide.
|
|
21
|
+
*/
|
|
22
|
+
export type SlideComponent = React.ComponentType<SlideProps>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for a single slide including its animation step count.
|
|
26
|
+
* The `steps` property declares how many animation steps the slide has,
|
|
27
|
+
* eliminating the need for runtime step discovery.
|
|
28
|
+
*/
|
|
29
|
+
export interface SlideConfig {
|
|
30
|
+
/** The slide component to render */
|
|
31
|
+
component: SlideComponent
|
|
32
|
+
/** Number of animation steps (0 = no animations). This is the max step number used in <Animated step={N}> */
|
|
33
|
+
steps: number
|
|
34
|
+
/** Display name for grid view thumbnails, navigation, and accessibility */
|
|
35
|
+
title?: string
|
|
36
|
+
/** Speaker notes (not rendered on slide, shown in notes panel) */
|
|
37
|
+
notes?: string
|
|
38
|
+
/** Per-slide transition override (falls back to deck-level transition) */
|
|
39
|
+
transition?: SlideTransitionType
|
|
40
|
+
/** Section/chapter name for grouping slides in grid view */
|
|
41
|
+
section?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// NAVIGATION TYPES
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Direction of slide navigation.
|
|
50
|
+
* - 1: Forward (next slide)
|
|
51
|
+
* - -1: Backward (previous slide)
|
|
52
|
+
* - 0: Direct jump (no direction)
|
|
53
|
+
*/
|
|
54
|
+
export type NavigationDirection = -1 | 0 | 1
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// THEME CONFIGURATION TYPES
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Logo variants for different contexts.
|
|
62
|
+
*/
|
|
63
|
+
export interface ThemeLogos {
|
|
64
|
+
/** Full logo (default, used in footers) */
|
|
65
|
+
full?: string
|
|
66
|
+
/** Icon-only mark (used in compact spaces) */
|
|
67
|
+
icon?: string
|
|
68
|
+
/** Light version for dark backgrounds */
|
|
69
|
+
fullLight?: string
|
|
70
|
+
/** Light icon for dark backgrounds */
|
|
71
|
+
iconLight?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Brand color overrides using OKLCH strings.
|
|
76
|
+
* Injected as CSS custom properties at runtime.
|
|
77
|
+
* If omitted, the existing globals.css values apply.
|
|
78
|
+
*/
|
|
79
|
+
export interface ThemeColors {
|
|
80
|
+
primary?: string
|
|
81
|
+
primaryForeground?: string
|
|
82
|
+
secondary?: string
|
|
83
|
+
secondaryForeground?: string
|
|
84
|
+
accent?: string
|
|
85
|
+
accentForeground?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Corporate visual assets (paths relative to public/).
|
|
90
|
+
*/
|
|
91
|
+
export interface ThemeAssets {
|
|
92
|
+
/** Background image URL (e.g. for title slides) */
|
|
93
|
+
backgroundImage?: string
|
|
94
|
+
/** Subtle pattern overlay URL */
|
|
95
|
+
patternImage?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Typography preferences. Font names must be loaded via
|
|
100
|
+
* <link> in index.html or @font-face in globals.css.
|
|
101
|
+
*/
|
|
102
|
+
export interface ThemeFonts {
|
|
103
|
+
heading?: string
|
|
104
|
+
body?: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Full theme configuration.
|
|
109
|
+
*/
|
|
110
|
+
export interface ThemeConfig {
|
|
111
|
+
/** Company/product name (shown in footers) */
|
|
112
|
+
name: string
|
|
113
|
+
/** Optional tagline */
|
|
114
|
+
tagline?: string
|
|
115
|
+
/** Logo configuration */
|
|
116
|
+
logo?: ThemeLogos
|
|
117
|
+
/** Brand color overrides (OKLCH strings) */
|
|
118
|
+
colors?: ThemeColors
|
|
119
|
+
/** Corporate visual assets */
|
|
120
|
+
assets?: ThemeAssets
|
|
121
|
+
/** Typography preferences */
|
|
122
|
+
fonts?: ThemeFonts
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// LAYOUT BASE PROPS
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Base props shared by all layout components.
|
|
131
|
+
*/
|
|
132
|
+
export interface LayoutBaseProps extends SlideProps {
|
|
133
|
+
children?: React.ReactNode
|
|
134
|
+
hideFooter?: boolean
|
|
135
|
+
className?: string
|
|
136
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
import type { NavigationDirection, SlideConfig } from "./types"
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// TYPES
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
type NavigationStatus = "idle" | "transitioning"
|
|
10
|
+
|
|
11
|
+
type QueuedAction = "advance" | "goBack" | null
|
|
12
|
+
|
|
13
|
+
interface NavigationState {
|
|
14
|
+
status: NavigationStatus
|
|
15
|
+
direction: NavigationDirection
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UseSlideNavigationOptions {
|
|
19
|
+
slides: SlideConfig[]
|
|
20
|
+
initialSlide?: number
|
|
21
|
+
onSlideChange?: (slideIndex: number) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseSlideNavigationReturn {
|
|
25
|
+
currentSlide: number
|
|
26
|
+
animationStep: number
|
|
27
|
+
totalSteps: number
|
|
28
|
+
direction: NavigationDirection
|
|
29
|
+
isTransitioning: boolean
|
|
30
|
+
showAllAnimations: boolean
|
|
31
|
+
advance: () => void
|
|
32
|
+
goBack: () => void
|
|
33
|
+
goToSlide: (index: number) => void
|
|
34
|
+
onTransitionComplete: () => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// HOOK
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
export function useSlideNavigation({
|
|
42
|
+
slides,
|
|
43
|
+
initialSlide = 0,
|
|
44
|
+
onSlideChange
|
|
45
|
+
}: UseSlideNavigationOptions): UseSlideNavigationReturn {
|
|
46
|
+
const [currentSlide, setCurrentSlide] = useState(initialSlide)
|
|
47
|
+
const [animationStep, setAnimationStep] = useState(0)
|
|
48
|
+
|
|
49
|
+
const [navState, setNavState] = useState<NavigationState>({
|
|
50
|
+
status: "idle",
|
|
51
|
+
direction: 0
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const [queuedAction, setQueuedAction] = useState<QueuedAction>(null)
|
|
55
|
+
|
|
56
|
+
const totalSteps = slides[currentSlide]?.steps ?? 0
|
|
57
|
+
|
|
58
|
+
const onTransitionComplete = useCallback(() => {
|
|
59
|
+
setNavState(prev => {
|
|
60
|
+
if (prev.status === "transitioning") {
|
|
61
|
+
return { status: "idle", direction: 0 }
|
|
62
|
+
}
|
|
63
|
+
return prev
|
|
64
|
+
})
|
|
65
|
+
}, [])
|
|
66
|
+
|
|
67
|
+
const advance = useCallback(() => {
|
|
68
|
+
if (navState.status === "transitioning") {
|
|
69
|
+
setQueuedAction("advance")
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const currentTotalSteps = slides[currentSlide]?.steps ?? 0
|
|
74
|
+
|
|
75
|
+
if (animationStep >= currentTotalSteps) {
|
|
76
|
+
const nextSlide = (currentSlide + 1) % slides.length
|
|
77
|
+
setNavState({ status: "transitioning", direction: 1 })
|
|
78
|
+
setAnimationStep(0)
|
|
79
|
+
setCurrentSlide(nextSlide)
|
|
80
|
+
onSlideChange?.(nextSlide)
|
|
81
|
+
} else {
|
|
82
|
+
setAnimationStep(prev => prev + 1)
|
|
83
|
+
}
|
|
84
|
+
}, [navState.status, animationStep, currentSlide, slides, onSlideChange])
|
|
85
|
+
|
|
86
|
+
const goBack = useCallback(() => {
|
|
87
|
+
if (navState.status === "transitioning") {
|
|
88
|
+
setQueuedAction("goBack")
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (animationStep <= 0) {
|
|
93
|
+
const prevSlide = (currentSlide - 1 + slides.length) % slides.length
|
|
94
|
+
const prevSlideSteps = slides[prevSlide]?.steps ?? 0
|
|
95
|
+
setNavState({ status: "transitioning", direction: -1 })
|
|
96
|
+
setAnimationStep(prevSlideSteps)
|
|
97
|
+
setCurrentSlide(prevSlide)
|
|
98
|
+
onSlideChange?.(prevSlide)
|
|
99
|
+
} else {
|
|
100
|
+
setAnimationStep(prev => prev - 1)
|
|
101
|
+
}
|
|
102
|
+
}, [navState.status, animationStep, currentSlide, slides, onSlideChange])
|
|
103
|
+
|
|
104
|
+
const goToSlide = useCallback(
|
|
105
|
+
(index: number) => {
|
|
106
|
+
if (index < 0 || index >= slides.length || index === currentSlide) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const direction = index > currentSlide ? 1 : -1
|
|
111
|
+
setNavState({ status: "transitioning", direction })
|
|
112
|
+
setAnimationStep(0)
|
|
113
|
+
setCurrentSlide(index)
|
|
114
|
+
onSlideChange?.(index)
|
|
115
|
+
},
|
|
116
|
+
[currentSlide, slides.length, onSlideChange]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (navState.status === "idle" && queuedAction !== null) {
|
|
121
|
+
setQueuedAction(null)
|
|
122
|
+
if (queuedAction === "advance") {
|
|
123
|
+
advance()
|
|
124
|
+
} else if (queuedAction === "goBack") {
|
|
125
|
+
goBack()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, [navState.status, queuedAction, advance, goBack])
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
currentSlide,
|
|
132
|
+
animationStep,
|
|
133
|
+
totalSteps,
|
|
134
|
+
direction: navState.direction,
|
|
135
|
+
isTransitioning: navState.status !== "idle",
|
|
136
|
+
showAllAnimations: navState.direction === -1 && navState.status === "transitioning",
|
|
137
|
+
advance,
|
|
138
|
+
goBack,
|
|
139
|
+
goToSlide,
|
|
140
|
+
onTransitionComplete
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs"
|
|
4
|
+
import { join, dirname } from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
|
|
7
|
+
import { bold, dim, red } from "./utils/ansi.mjs"
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
10
|
+
const __dirname = dirname(__filename)
|
|
11
|
+
|
|
12
|
+
function getVersion() {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"))
|
|
14
|
+
return pkg.version
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function printHelp() {
|
|
18
|
+
console.log()
|
|
19
|
+
console.log(` ${bold("promptslide")} ${dim(`v${getVersion()}`)}`)
|
|
20
|
+
console.log()
|
|
21
|
+
console.log(` ${bold("Commands:")}`)
|
|
22
|
+
console.log(` create ${dim("<dir>")} Scaffold a new slide deck project`)
|
|
23
|
+
console.log(` studio Start the development studio`)
|
|
24
|
+
console.log(` build Build for production`)
|
|
25
|
+
console.log(` preview Preview the production build`)
|
|
26
|
+
console.log()
|
|
27
|
+
console.log(` ${bold("Options:")}`)
|
|
28
|
+
console.log(` --help, -h Show this help message`)
|
|
29
|
+
console.log(` --version, -v Show version number`)
|
|
30
|
+
console.log()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const command = process.argv[2]
|
|
34
|
+
const args = process.argv.slice(3)
|
|
35
|
+
|
|
36
|
+
switch (command) {
|
|
37
|
+
case "create": {
|
|
38
|
+
const { create } = await import("./commands/create.mjs")
|
|
39
|
+
await create(args)
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
case "studio": {
|
|
43
|
+
const { studio } = await import("./commands/studio.mjs")
|
|
44
|
+
await studio(args)
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
case "build": {
|
|
48
|
+
const { build } = await import("./commands/build.mjs")
|
|
49
|
+
await build(args)
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
case "preview": {
|
|
53
|
+
const { preview } = await import("./commands/preview.mjs")
|
|
54
|
+
await preview(args)
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
case "--help":
|
|
58
|
+
case "-h":
|
|
59
|
+
case undefined:
|
|
60
|
+
printHelp()
|
|
61
|
+
break
|
|
62
|
+
case "--version":
|
|
63
|
+
case "-v":
|
|
64
|
+
console.log(getVersion())
|
|
65
|
+
break
|
|
66
|
+
default:
|
|
67
|
+
console.error(` ${red("Error:")} Unknown command "${command}"`)
|
|
68
|
+
printHelp()
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function hexToOklch(hex) {
|
|
2
|
+
hex = hex.replace("#", "")
|
|
3
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255
|
|
4
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255
|
|
5
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255
|
|
6
|
+
|
|
7
|
+
// sRGB → linear RGB
|
|
8
|
+
const toLinear = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4))
|
|
9
|
+
const lr = toLinear(r)
|
|
10
|
+
const lg = toLinear(g)
|
|
11
|
+
const lb = toLinear(b)
|
|
12
|
+
|
|
13
|
+
// Linear RGB → LMS
|
|
14
|
+
const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb
|
|
15
|
+
const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb
|
|
16
|
+
const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb
|
|
17
|
+
|
|
18
|
+
// LMS → OKLAB
|
|
19
|
+
const l_ = Math.cbrt(l)
|
|
20
|
+
const m_ = Math.cbrt(m)
|
|
21
|
+
const s_ = Math.cbrt(s)
|
|
22
|
+
const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_
|
|
23
|
+
const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_
|
|
24
|
+
const bv = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
|
|
25
|
+
|
|
26
|
+
// OKLAB → OKLCH
|
|
27
|
+
const C = Math.sqrt(a * a + bv * bv)
|
|
28
|
+
let H = Math.atan2(bv, a) * (180 / Math.PI)
|
|
29
|
+
if (H < 0) H += 360
|
|
30
|
+
|
|
31
|
+
const round = (n, d = 3) => +n.toFixed(d)
|
|
32
|
+
return `oklch(${round(L)} ${round(C)} ${round(H)})`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function hexToOklchDark(hex) {
|
|
36
|
+
const oklch = hexToOklch(hex)
|
|
37
|
+
const match = oklch.match(/oklch\(([\d.]+) ([\d.]+) ([\d.]+)\)/)
|
|
38
|
+
const L = Math.min(1, parseFloat(match[1]) + 0.05)
|
|
39
|
+
return `oklch(${+L.toFixed(3)} ${match[2]} ${match[3]})`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isValidHex(hex) {
|
|
43
|
+
return /^#?[0-9a-fA-F]{6}$/.test(hex)
|
|
44
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createInterface } from "node:readline"
|
|
2
|
+
|
|
3
|
+
import { dim } from "./ansi.mjs"
|
|
4
|
+
|
|
5
|
+
let _rl = null
|
|
6
|
+
let _closed = false
|
|
7
|
+
|
|
8
|
+
function getRL() {
|
|
9
|
+
if (!_rl && !_closed) {
|
|
10
|
+
_rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
11
|
+
_rl.on("close", () => {
|
|
12
|
+
_rl = null
|
|
13
|
+
_closed = true
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
return _rl
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function closePrompts() {
|
|
20
|
+
if (_rl) {
|
|
21
|
+
_rl.close()
|
|
22
|
+
_rl = null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function prompt(question, defaultValue) {
|
|
27
|
+
const rl = getRL()
|
|
28
|
+
if (!rl) return Promise.resolve(defaultValue || "")
|
|
29
|
+
return new Promise(resolve => {
|
|
30
|
+
const suffix = defaultValue ? ` ${dim(`(${defaultValue})`)} ` : " "
|
|
31
|
+
rl.question(` ${question}${suffix}`, answer => {
|
|
32
|
+
resolve(answer.trim() || defaultValue || "")
|
|
33
|
+
})
|
|
34
|
+
rl.once("close", () => resolve(defaultValue || ""))
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function confirm(question, defaultYes = true) {
|
|
39
|
+
const rl = getRL()
|
|
40
|
+
if (!rl) return Promise.resolve(defaultYes)
|
|
41
|
+
return new Promise(resolve => {
|
|
42
|
+
const hint = defaultYes ? "Y/n" : "y/N"
|
|
43
|
+
rl.question(` ${question} ${dim(`(${hint})`)} `, answer => {
|
|
44
|
+
const a = answer.trim().toLowerCase()
|
|
45
|
+
if (!a) return resolve(defaultYes)
|
|
46
|
+
resolve(a === "y" || a === "yes")
|
|
47
|
+
})
|
|
48
|
+
rl.once("close", () => resolve(defaultYes))
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
export function ensureTsConfig(cwd) {
|
|
5
|
+
const tsconfigPath = join(cwd, "tsconfig.json")
|
|
6
|
+
|
|
7
|
+
const tsconfig = {
|
|
8
|
+
compilerOptions: {
|
|
9
|
+
target: "ES2020",
|
|
10
|
+
useDefineForClassFields: true,
|
|
11
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
12
|
+
module: "ESNext",
|
|
13
|
+
skipLibCheck: true,
|
|
14
|
+
moduleResolution: "bundler",
|
|
15
|
+
allowImportingTsExtensions: true,
|
|
16
|
+
isolatedModules: true,
|
|
17
|
+
moduleDetection: "force",
|
|
18
|
+
noEmit: true,
|
|
19
|
+
jsx: "react-jsx",
|
|
20
|
+
strict: true,
|
|
21
|
+
noUnusedLocals: true,
|
|
22
|
+
noUnusedParameters: true,
|
|
23
|
+
noFallthroughCasesInSwitch: true,
|
|
24
|
+
noUncheckedIndexedAccess: true,
|
|
25
|
+
baseUrl: ".",
|
|
26
|
+
paths: {
|
|
27
|
+
"@/*": ["./src/*"],
|
|
28
|
+
"promptslide": ["./node_modules/promptslide/src/core/index.ts"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
include: ["src"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n")
|
|
35
|
+
}
|