diff --git a/features/links-page/screens/BioScreen.tsx b/features/links-page/screens/BioScreen.tsx
index bd90dc19..481d4fb5 100644
--- a/features/links-page/screens/BioScreen.tsx
+++ b/features/links-page/screens/BioScreen.tsx
@@ -16,7 +16,7 @@ import { View, Text, Image } from 'aetherspace/primitives'
// SEO
import { H1 } from 'aetherspace/html-elements'
// Components
-import { AetherIcon } from 'aetherspace/components'
+import { AetherIcon, AetherModal } from 'aetherspace/components'
import BioLink from '../components/BioLink'
// Utils
import { isEmpty } from 'aetherspace/utils'
@@ -210,6 +210,21 @@ export const BioScreen = (props: BioScreenProps) => {
+
+
+
+ Free template repo
+
+ {' / '}
+
+ Hire me.
+
+
+
)
}
diff --git a/packages/@aetherspace/components/AetherModal/AetherModal.tsx b/packages/@aetherspace/components/AetherModal/AetherModal.tsx
new file mode 100644
index 00000000..d84cf18d
--- /dev/null
+++ b/packages/@aetherspace/components/AetherModal/AetherModal.tsx
@@ -0,0 +1,71 @@
+import { useState } from 'react'
+import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated'
+// Schemas
+import { z, aetherSchema, AetherProps } from '../../schemas'
+// Primitives
+import { Pressable, View } from '../../primitives'
+
+/* --- Schemas --------------------------------------------------------------------------------- */
+
+export const AetherModalProps = aetherSchema('AetherModalProps', {
+ backdropClasses: z.string().optional(),
+ backdropColor: z.string().color().default('#333333'),
+ modalClasses: z.string().optional(),
+ modalColor: z.string().color().default('#FFFFFF'),
+})
+
+export type TAetherModalProps = AetherProps & {
+ children: React.ReactNode
+}
+
+/* --- -------------------------------------------------------------------------- */
+
+export const AetherModal = (props: TAetherModalProps) => {
+ // Props
+ const { children, backdropClasses, backdropColor, modalClasses, modalColor } =
+ AetherModalProps.applyDefaults(props) as TAetherModalProps
+
+ // State
+ const [isOpen, setOpen] = useState(false)
+
+ // -- Handlers --
+
+ const toggleSheet = () => setOpen(!isOpen)
+
+ // -- Render --
+
+ if (!isOpen) return null
+
+ return (
+ <>
+
+
+ {children}
+
+ >
+ )
+}
+
+/* --- Styles ---------------------------------------------------------------------------------- */
+
+const AnimatedContainer = Animated.createAnimatedComponent(View)
+
+/* --- Docs ------------------------------------------------------------------------------------ */
+
+export const getDocumentationProps = AetherModalProps.introspect()
diff --git a/packages/@aetherspace/components/AetherModal/index.ts b/packages/@aetherspace/components/AetherModal/index.ts
new file mode 100644
index 00000000..fa24bedf
--- /dev/null
+++ b/packages/@aetherspace/components/AetherModal/index.ts
@@ -0,0 +1 @@
+export * from './AetherModal'
diff --git a/packages/@aetherspace/components/index.ts b/packages/@aetherspace/components/index.ts
index 339eed9f..051bb497 100644
--- a/packages/@aetherspace/components/index.ts
+++ b/packages/@aetherspace/components/index.ts
@@ -1 +1,2 @@
-export * from './AetherIcon'
+export { AetherIcon } from './AetherIcon'
+export { AetherModal } from './AetherModal'
diff --git a/packages/@aetherspace/document.tsx b/packages/@aetherspace/document.tsx
index 9c3b5674..fbced566 100644
--- a/packages/@aetherspace/document.tsx
+++ b/packages/@aetherspace/document.tsx
@@ -75,6 +75,10 @@ export const getInitialProps = async (ctx: DocumentContext) => {
/* --- ----------------------------------------------------------------------------- */
class Document extends NextDocument {
+ async componentDidMount(): Promise {
+ await import('raf/polyfill')
+ }
+
render() {
return (
diff --git a/packages/@aetherspace/package.json b/packages/@aetherspace/package.json
index 6d6fe017..ed25ae24 100644
--- a/packages/@aetherspace/package.json
+++ b/packages/@aetherspace/package.json
@@ -45,6 +45,7 @@
"axios": "^1.4.0",
"graphql": "^16.7.1",
"graphql-request": "^6.1.0",
+ "raf": "^3.4.1",
"swr": "^2.2.0",
"twrnc": "^3.6.1",
"zod": "~3.20.6"