A do anything be anything component inspired by apples dynamic island.
1"use client"
2
3import { createContext, useContext } from "react"
4import {
5 ArrowUpLeftSquareIcon,
6 Loader,
7 Mail,
8 MessageCircle,
9 MousePointerClickIcon,
10 User,
11 Waves,
12} from "lucide-react"
13import { motion, useReducedMotion } from "motion/react"
14
15import { Badge } from "@/components/ui/badge"
16import { Button } from "@/components/ui/button"
17import {
18 DynamicContainer,
19 DynamicDescription,
20 DynamicDiv,
21 DynamicIsland,
22 DynamicIslandProvider,
23 DynamicTitle,
24 SizePresets,
25 useDynamicIslandSize,
26 useScheduledAnimations,
27} from "@/components/ui/dynamic-island"
28
29const DynamicAction = () => {
30 const { state: blobState, setSize } = useDynamicIslandSize()
31
32 const blobStates: SizePresets[] = [
33 "compact",
34 "large",
35 "tall",
36 "long",
37 "medium",
38 ]
39
40 const cycleBlobStates = () => {
41 const currentIndex = blobStates.indexOf(blobState.size)
42 const nextIndex = (currentIndex + 1) % blobStates.length
43 setSize(blobStates[nextIndex])
44 }
45
46 useScheduledAnimations([
47 { size: "compact", delay: 1000 },
48 { size: "large", delay: 1200 },
49 { size: "tall", delay: 1600 },
50 { size: "long", delay: 1800 },
51 { size: "medium", delay: 2200 },
52 ])
53
54 // Provide dynamic detail in such a beautiful small place :)
55 const renderCompactState = () => (
56 <DynamicContainer className="flex items-center justify-center h-full w-full">
57 <div className="relative w-full flex items-center">
58 <DynamicDescription className="absolute left-4 my-auto text-lg font-medium tracking-tighter text-white ">
59 <MessageCircle className=" h-5 w-5 fill-cyan-400 text-cyan-400" />
60 </DynamicDescription>
61
62 <DynamicDescription className="absolute text-white right-4 my-auto text-lg font-bold tracking-tighter ">
63 newcult.co
64 </DynamicDescription>
65 </div>
66 </DynamicContainer>
67 )
68
69 // Great for call to action, popping up in users face :)
70 const renderLargeState = () => (
71 <DynamicContainer className="flex items-center justify-center h-full w-full">
72 <div className="relative flex w-full items-center justify-between gap-6 px-4">
73 <Loader className="animate-spin h-12 w-12 text-yellow-300" />
74
75 <DynamicTitle className="my-auto text-2xl font-black tracking-tighter text-white ">
76 loading
77 </DynamicTitle>
78 </div>
79 </DynamicContainer>
80 )
81
82 // Great for user onboarding, forms, etc
83 const renderTallState = () => (
84 <DynamicContainer className=" flex flex-col mt-6 w-full items-start gap-1 px-8 font-semibold">
85 <DynamicDescription className="bg-cyan-300 rounded-2xl tracking-tight leading-5 p-2">
86 The Cult of Pythagoras
87 </DynamicDescription>
88 <DynamicDescription className="bg-cyan-300 rounded-2xl tracking-tight leading-5 p-2 text-left">
89 Music of the Spheres, an idea that celestial bodies produce a form of
90 music through their movements
91 </DynamicDescription>
92
93 <DynamicTitle className=" text-4xl font-black tracking-tighter text-cyan-100 ">
94 any cool cults?
95 </DynamicTitle>
96 </DynamicContainer>
97 )
98
99 const renderLongState = () => (
100 <DynamicContainer className="flex items-center justify-center h-full w-full">
101 <DynamicDiv className="relative flex w-full items-center justify-between gap-6 px-4">
102 <div>
103 <Waves className=" text-cyan-400 h-8 w-8" />
104 </div>
105
106 <DynamicTitle className="my-auto text-xl font-black tracking-tighter text-white ">
107 Supercalifragilisticexpialid
108 </DynamicTitle>
109 </DynamicDiv>
110 </DynamicContainer>
111 )
112
113 const renderMediumState = () => (
114 <DynamicContainer className="flex flex-col justify-between px-2 pt-4 text-left text-white h-full">
115 <DynamicTitle className="text-2xl pl-3 font-black tracking-tighter">
116 Reincarnation, welcome back
117 </DynamicTitle>
118 <DynamicDescription className="leading-5 text-neutral-500 pl-3">
119 Good for small tasks or call outs
120 </DynamicDescription>
121
122 <DynamicDiv className="flex flex-col mt-auto space-y-1 mb-2 bg-neutral-700 p-2 rounded-b-2xl">
123 <Button>
124 <Mail className="mr-2 h-4 w-4 fill-cyan-400 text-neutral-900" /> Login
125 with email
126 </Button>
127
128 <Button className="mt-1 ">
129 <User className="mr-2 h-4 w-4 fill-cyan-400 text-cyan-400" /> Join the
130 cult now
131 </Button>
132 </DynamicDiv>
133 </DynamicContainer>
134 )
135
136 // Render function for other states
137 const renderOtherStates = () => (
138 <div className="flex items-center justify-center h-full w-full">
139 <div>
140 <ArrowUpLeftSquareIcon className="text-white" />
141 </div>
142 <p className="text-white">cycle states</p>
143 </div>
144 )
145
146 // Main render logic based on size
147 function renderState() {
148 switch (blobState.size) {
149 case "compact":
150 return renderCompactState()
151 case "large":
152 return renderLargeState()
153 case "tall":
154 return renderTallState()
155 case "medium":
156 return renderMediumState()
157 case "long":
158 return renderLongState()
159 // Optionally add cases for other states as necessary
160 default:
161 return renderOtherStates()
162 }
163 }
164
165 return (
166 <div className=" h-full">
167 <div className="flex flex-col gap-4 h-full">
168 <div className="absolute top-12 left-1">
169 {/* {!blobState.isAnimating ? ( */}
170 <Button
171 onClick={cycleBlobStates}
172 disabled={blobState.isAnimating}
173 className="mt-4 p-2 border rounded-md max-w-[200px] "
174 >
175 Click
176 <MousePointerClickIcon className="ml-2 h-4 w-4" />
177 </Button>
178 {/* ) : null} */}
179 </div>
180 <div className="absolute top-1 right-2">
181 <div>
182 <Badge variant="outline">prev - {blobState.previousSize}</Badge>
183 <Badge variant="outline">cur -{blobState.size}</Badge>
184 </div>
185 </div>
186
187 <DynamicIsland id="dynamic-blob">{renderState()}</DynamicIsland>
188 </div>
189 </div>
190 )
191}
192
193export function DynamicIslandDemo() {
194 return (
195 <DynamicIslandProvider initialSize={"default"}>
196 <div>
197 <DynamicAction />
198 </div>
199 </DynamicIslandProvider>
200 )
201}
202
203const FadeInStaggerContext = createContext(false)
204
205const viewport = { once: true, margin: "0px 0px -200px" }
206
207export function FadeIn(props: any) {
208 let shouldReduceMotion = useReducedMotion()
209 let isInStaggerGroup = useContext(FadeInStaggerContext)
210
211 return (
212 <motion.div
213 variants={{
214 hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 24 },
215 visible: { opacity: 1, y: 0 },
216 }}
217 transition={{ duration: 0.5 }}
218 {...(isInStaggerGroup
219 ? {}
220 : {
221 initial: "hidden",
222 whileInView: "visible",
223 viewport,
224 })}
225 {...props}
226 />
227 )
228}
229
230export function FadeInStagger({ faster = false, ...props }) {
231 return (
232 <FadeInStaggerContext.Provider value={true}>
233 <motion.div
234 initial="hidden"
235 whileInView="visible"
236 viewport={viewport}
237 transition={{ staggerChildren: faster ? 0.12 : 0.2 }}
238 {...props}
239 />
240 </FadeInStaggerContext.Provider>
241 )
242}