diff --git a/src/client/index.tsx b/src/client/index.tsx
index 6439b94..41e8a67 100644
--- a/src/client/index.tsx
+++ b/src/client/index.tsx
@@ -2,6 +2,8 @@ import { render as renderApp } from 'hono/jsx/dom'
import { define, Styles } from 'forge'
import type { App, AppState } from '../shared/types'
import { theme } from './themes'
+import { Modal, initModal } from './tags/modal'
+import { openEmojiPicker } from './tags/emoji-picker'
// UI state (survives re-renders)
let selectedApp: string | null = localStorage.getItem('selectedApp')
@@ -335,11 +337,21 @@ const toggleSidebar = () => {
render()
}
+const OpenEmojiPicker = define('OpenEmojiPicker', {
+ cursor: 'pointer',
+
+ render({ props, parts: { Root } }) {
+ return openEmojiPicker((emoji) => {
+ console.log('Selected:', emoji)
+ })}>{props.children}
+ }
+})
+
const AppDetail = ({ app }: { app: App }) => (
<>
- {app.icon ?? }
+ {app.icon ?? }
{app.name}
@@ -481,6 +493,7 @@ const Dashboard = () => {
Select an app to view details
)}
+
)
}
@@ -489,6 +502,9 @@ const render = () => {
renderApp(, document.getElementById('app')!)
}
+// Initialize modal with render function
+initModal(render)
+
// Set theme based on system preference
const setTheme = () => {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
diff --git a/src/client/tags/emoji-picker.tsx b/src/client/tags/emoji-picker.tsx
new file mode 100644
index 0000000..06d34bf
--- /dev/null
+++ b/src/client/tags/emoji-picker.tsx
@@ -0,0 +1,141 @@
+import { define } from 'forge'
+import { theme } from '../themes'
+import { openModal, closeModal, rerenderModal } from './modal'
+
+type Category = 'People' | 'Gestures' | 'Animals' | 'Food' | 'Activities' | 'Travel' | 'Objects' | 'Symbols' | 'Nature'
+
+const CATEGORY_ICONS: Record = {
+ 'People': '๐',
+ 'Gestures': '๐',
+ 'Animals': '๐ถ',
+ 'Food': '๐',
+ 'Activities': 'โฝ',
+ 'Travel': '๐',
+ 'Objects': '๐ก',
+ 'Symbols': 'โค๏ธ',
+ 'Nature': '๐ธ',
+}
+
+const EMOJI_CATEGORIES: Record = {
+ 'People': ['๐', '๐', '๐', '๐', '๐', '๐
', '๐คฃ', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฅฐ', '๐', '๐คฉ', '๐', '๐', '๐', '๐', '๐ฅฒ', '๐', '๐', '๐', '๐คช', '๐', '๐ค', '๐ค', '๐คญ', '๐คซ', '๐ค', '๐ค', '๐คจ', '๐', '๐', '๐ถ', '๐', '๐', '๐', '๐ฌ', '๐คฅ', '๐', '๐', '๐ช', '๐คค', '๐ด', '๐ท', '๐ค', '๐ค', '๐คข', '๐คฎ', '๐คง', '๐ฅต', '๐ฅถ', '๐ฅด', '๐ต', '๐คฏ', '๐ค ', '๐ฅณ', '๐ฅธ', '๐', '๐ค', '๐ง', '๐', '๐', '๐', '๐ฎ', '๐ฏ', '๐ฒ', '๐ณ', '๐ฅบ', '๐ฆ', '๐ง', '๐จ', '๐ฐ', '๐ฅ', '๐ข', '๐ญ', '๐ฑ', '๐', '๐ฃ', '๐', '๐', '๐ฉ', '๐ซ', '๐ฅฑ', '๐ค', '๐ก', '๐ ', '๐คฌ', '๐', '๐ฟ', '๐', 'โ ๏ธ', '๐ฉ', '๐คก', '๐น', '๐บ', '๐ป', '๐ฝ', '๐พ', '๐ค'],
+ 'Gestures': ['๐', '๐ค', '๐๏ธ', 'โ', '๐', '๐', '๐ค', '๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', 'โ', '๐', '๐ค', '๐ค', '๐', '๐', '๐', '๐คฒ', '๐ค', '๐', 'โ๏ธ', '๐
', '๐คณ', '๐ช', '๐ฆพ', '๐ฆฟ', '๐ฆต', '๐ฆถ', '๐', '๐ฆป', '๐', '๐ง ', '๐ซ', '๐ซ', '๐ฆท', '๐ฆด', '๐', '๐๏ธ', '๐
', '๐'],
+ 'Animals': ['๐ถ', '๐ฑ', '๐ญ', '๐น', '๐ฐ', '๐ฆ', '๐ป', '๐ผ', '๐ปโโ๏ธ', '๐จ', '๐ฏ', '๐ฆ', '๐ฎ', '๐ท', '๐ธ', '๐ต', '๐', '๐', '๐', '๐', '๐', '๐ง', '๐ฆ', '๐ค', '๐ฃ', '๐ฅ', '๐ฆ', '๐ฆ
', '๐ฆ', '๐ฆ', '๐บ', '๐', '๐ด', '๐ฆ', '๐', '๐ชฑ', '๐', '๐ฆ', '๐', '๐', '๐', '๐ชฐ', '๐ชฒ', '๐ชณ', '๐ฆ', '๐ฆ', '๐ท๏ธ', '๐ฆ', '๐ข', '๐', '๐ฆ', '๐ฆ', '๐ฆ', '๐', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ก', '๐ ', '๐', '๐ฌ', '๐ณ', '๐', '๐ฆ', '๐', '๐
', '๐', '๐ฆ', '๐ฆ', '๐ฆง', '๐ฆฃ', '๐', '๐ฆ', '๐ฆ', '๐ช', '๐ซ', '๐ฆ', '๐ฆ', '๐ฆฌ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฆ', '๐', '๐ฆ', '๐', '๐ฉ', '๐ฆฎ', '๐โ๐ฆบ', '๐', '๐โโฌ', '๐ชถ', '๐', '๐ฆ', '๐ฆค', '๐ฆ', '๐ฆ', '๐ฆข', '๐ฆฉ', '๐๏ธ', '๐', '๐ฆ', '๐ฆจ', '๐ฆก', '๐ฆซ', '๐ฆฆ', '๐ฆฅ', '๐', '๐', '๐ฟ๏ธ', '๐ฆ'],
+ 'Food': ['๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ซ', '๐', '๐', '๐', '๐ฅญ', '๐', '๐ฅฅ', '๐ฅ', '๐
', '๐', '๐ฅ', '๐ฅฆ', '๐ฅฌ', '๐ฅ', '๐ถ๏ธ', '๐ซ', '๐ฝ', '๐ฅ', '๐ซ', '๐ง', '๐ง
', '๐ฅ', '๐ ', '๐ฅ', '๐ฅฏ', '๐', '๐ฅ', '๐ฅจ', '๐ง', '๐ฅ', '๐ณ', '๐ง', '๐ฅ', '๐ง', '๐ฅ', '๐ฅฉ', '๐', '๐', '๐ฆด', '๐ญ', '๐', '๐', '๐', '๐ซ', '๐ฅช', '๐ฅ', '๐ง', '๐ฎ', '๐ฏ', '๐ซ', '๐ฅ', '๐ฅ', '๐ซ', '๐ฅซ', '๐', '๐', '๐ฒ', '๐', '๐ฃ', '๐ฑ', '๐ฅ', '๐ฆช', '๐ค', '๐', '๐', '๐', '๐ฅ', '๐ฅ ', '๐ฅฎ', '๐ข', '๐ก', '๐ง', '๐จ', '๐ฆ', '๐ฅง', '๐ง', '๐ฐ', '๐', '๐ฎ', '๐ญ', '๐ฌ', '๐ซ', '๐ฟ', '๐ฉ', '๐ช', '๐ฐ', '๐ฅ', '๐ฏ', '๐ฅ', '๐ผ', '๐ซ', 'โ', '๐ต', '๐ง', '๐ฅค', '๐ง', '๐ถ', '๐บ', '๐ป', '๐ฅ', '๐ท', '๐ฅ', '๐ธ', '๐น', '๐ง', '๐พ', '๐ง'],
+ 'Activities': ['โฝ', '๐', '๐', 'โพ', '๐ฅ', '๐พ', '๐', '๐', '๐ฅ', '๐ฑ', '๐ช', '๐', '๐ธ', '๐', '๐', '๐ฅ', '๐', '๐ช', '๐ฅ
', 'โณ', '๐ช', '๐น', '๐ฃ', '๐คฟ', '๐ฅ', '๐ฅ', '๐ฝ', '๐น', '๐ผ', '๐ท', 'โธ๏ธ', '๐ฅ', '๐ฟ', 'โท๏ธ', '๐', '๐ช', '๐๏ธ', '๐คผ', '๐คธ', 'โน๏ธ', '๐คบ', '๐คพ', '๐๏ธ', '๐', '๐ง', '๐', '๐', '๐คฝ', '๐ฃ', '๐ง', '๐ต', '๐ด', '๐', '๐ฅ', '๐ฅ', '๐ฅ', '๐
', '๐๏ธ', '๐ต๏ธ', '๐๏ธ', '๐ซ', '๐๏ธ', '๐ช', '๐ญ', '๐จ', '๐ฌ', '๐ค', '๐ง', '๐ผ', '๐น', '๐ฅ', '๐ช', '๐ท', '๐บ', '๐ช', '๐ธ', '๐ช', '๐ป', '๐ฒ', 'โ๏ธ', '๐ฏ', '๐ณ', '๐ฎ', '๐ฐ', '๐งฉ'],
+ 'Travel': ['๐', '๐', '๐', '๐', '๐', '๐๏ธ', '๐', '๐', '๐', '๐', '๐ป', '๐', '๐', '๐', '๐ฆฏ', '๐ฆฝ', '๐ฆผ', '๐ด', '๐ฒ', '๐ต', '๐๏ธ', '๐บ', '๐จ', '๐', '๐', '๐', '๐', '๐ก', '๐ ', '๐', '๐', '๐', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐ซ', '๐ฌ', '๐ฉ๏ธ', '๐บ', '๐ฐ๏ธ', '๐', '๐ธ', '๐', '๐ถ', 'โต', '๐ค', '๐ฅ๏ธ', '๐ณ๏ธ', 'โด๏ธ', '๐ข', 'โ', '๐ช', 'โฝ', '๐ง', '๐ฆ', '๐ฅ', '๐', '๐บ๏ธ', '๐ฟ', '๐ฝ', '๐ผ', '๐ฐ', '๐ฏ', '๐๏ธ', '๐ก', '๐ข', '๐ ', 'โฒ', 'โฑ๏ธ', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐', 'โฐ๏ธ', '๐๏ธ', '๐ป', '๐๏ธ', 'โบ', '๐', '๐ ', '๐ก', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐ญ', '๐ข', '๐ฌ', '๐ฃ', '๐ค', '๐ฅ', '๐ฆ', '๐จ', '๐ช', '๐ซ', '๐ฉ', '๐', '๐๏ธ', 'โช', '๐', '๐', '๐', '๐', 'โฉ๏ธ'],
+ 'Objects': ['โ', '๐ฑ', '๐ฒ', '๐ป', 'โจ๏ธ', '๐ฅ๏ธ', '๐จ๏ธ', '๐ฑ๏ธ', '๐ฒ๏ธ', '๐น๏ธ', '๐๏ธ', '๐ฝ', '๐พ', '๐ฟ', '๐', '๐ผ', '๐ท', '๐ธ', '๐น', '๐ฅ', '๐ฝ๏ธ', '๐๏ธ', '๐', 'โ๏ธ', '๐', '๐ ', '๐บ', '๐ป', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐งญ', 'โฑ๏ธ', 'โฒ๏ธ', 'โฐ', '๐ฐ๏ธ', 'โ', 'โณ', '๐ก', '๐', '๐', '๐ก', '๐ฆ', '๐ฏ๏ธ', '๐ช', '๐งฏ', '๐ข๏ธ', '๐ธ', '๐ต', '๐ด', '๐ถ', '๐ท', '๐ช', '๐ฐ', '๐ณ', '๐', 'โ๏ธ', '๐ช', '๐งฐ', '๐ช', '๐ง', '๐จ', 'โ๏ธ', '๐ ๏ธ', 'โ๏ธ', '๐ช', '๐ฉ', 'โ๏ธ', '๐ชค', '๐งฑ', 'โ๏ธ', '๐งฒ', '๐ซ', '๐ฃ', '๐งจ', '๐ช', '๐ช', '๐ก๏ธ', 'โ๏ธ', '๐ก๏ธ', '๐ฌ', 'โฐ๏ธ', '๐ชฆ', 'โฑ๏ธ', '๐บ', '๐ฎ', '๐ฟ', '๐งฟ', '๐', 'โ๏ธ', '๐ญ', '๐ฌ', '๐ณ๏ธ', '๐ฉน', '๐ฉบ', '๐', '๐', '๐ฉธ', '๐งฌ', '๐ฆ ', '๐งซ', '๐งช'],
+ 'Symbols': ['โค๏ธ', '๐งก', '๐', '๐', '๐', '๐', '๐ค', '๐ค', '๐ค', '๐', 'โฃ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โฎ๏ธ', 'โ๏ธ', 'โช๏ธ', '๐๏ธ', 'โธ๏ธ', 'โก๏ธ', '๐ฏ', '๐', 'โฏ๏ธ', 'โฆ๏ธ', '๐', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ', '๐', 'โ๏ธ', '๐', 'โข๏ธ', 'โฃ๏ธ', '๐ด', '๐ณ', '๐ถ', '๐', '๐ธ', '๐บ', '๐ท๏ธ', 'โด๏ธ', '๐', '๐ฎ', '๐', 'ใ๏ธ', 'ใ๏ธ', '๐ด', '๐ต', '๐น', '๐ฒ', '๐
ฐ๏ธ', '๐
ฑ๏ธ', '๐', '๐', '๐
พ๏ธ', '๐', 'โ', 'โญ', '๐', 'โ', '๐', '๐ซ', '๐ฏ', '๐ข', 'โจ๏ธ', '๐ท', '๐ฏ', '๐ณ', '๐ฑ', '๐', '๐ต', '๐ญ', 'โ', 'โ', 'โ', 'โ', 'โผ๏ธ', 'โ๏ธ', '๐
', '๐', 'ใฝ๏ธ', 'โ ๏ธ', '๐ธ', '๐ฑ', 'โ๏ธ', '๐ฐ', 'โป๏ธ', 'โ
', '๐ฏ', '๐น', 'โ๏ธ', 'โณ๏ธ', 'โ', '๐', '๐ ', 'โ๏ธ', '๐', '๐ค', '๐ง', '๐พ', 'โฟ', '๐
ฟ๏ธ', '๐', '๐ณ', '๐๏ธ', '๐', '๐', '๐', '๐
', '๐น', '๐บ', '๐ผ', 'โง๏ธ', '๐ป', '๐ฎ', '๐ฆ', '๐ถ', '๐', '๐ฃ', 'โน๏ธ', '๐ค', '๐ก', '๐ ', '๐', '๐', '๐', '๐', '๐', '๐', '0๏ธโฃ', '1๏ธโฃ', '2๏ธโฃ', '3๏ธโฃ', '4๏ธโฃ', '5๏ธโฃ', '6๏ธโฃ', '7๏ธโฃ', '8๏ธโฃ', '9๏ธโฃ', '๐', '๐ข', '#๏ธโฃ', '*๏ธโฃ', 'โ๏ธ', 'โถ๏ธ', 'โธ๏ธ', 'โฏ๏ธ', 'โน๏ธ', 'โบ๏ธ', 'โญ๏ธ', 'โฎ๏ธ', 'โฉ', 'โช', 'โซ', 'โฌ', 'โ๏ธ', '๐ผ', '๐ฝ', 'โก๏ธ', 'โฌ
๏ธ', 'โฌ๏ธ', 'โฌ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โช๏ธ', 'โฉ๏ธ', 'โคด๏ธ', 'โคต๏ธ', '๐', '๐', '๐', '๐', '๐', '๐ต', '๐ถ', 'โ', 'โ', 'โ', 'โ๏ธ', 'โพ๏ธ', '๐ฒ', '๐ฑ', 'โข๏ธ', 'ยฉ๏ธ', 'ยฎ๏ธ', 'ใฐ๏ธ', 'โฐ', 'โฟ', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', 'โ๏ธ', '๐', '๐ด', '๐ ', '๐ก', '๐ข', '๐ต', '๐ฃ', 'โซ', 'โช', '๐ค', '๐บ', '๐ป', '๐ธ', '๐น', '๐ถ', '๐ท', '๐ณ', '๐ฒ', 'โช๏ธ', 'โซ๏ธ', 'โพ', 'โฝ', 'โผ๏ธ', 'โป๏ธ', '๐ฅ', '๐ง', '๐จ', '๐ฉ', '๐ฆ', '๐ช', 'โฌ', 'โฌ', '๐ซ', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฃ', '๐ข', '๐๏ธโ๐จ๏ธ', '๐ฌ', '๐ญ', '๐ฏ๏ธ', 'โ ๏ธ', 'โฃ๏ธ', 'โฅ๏ธ', 'โฆ๏ธ', '๐', '๐ด', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ ', '๐ก', '๐ข', '๐ฃ', '๐ค', '๐ฅ', '๐ฆ', '๐ง'],
+ 'Nature': ['๐ธ', '๐ฎ', '๐ต๏ธ', '๐น', '๐ฅ', '๐บ', '๐ป', '๐ผ', '๐ท', '๐ฑ', '๐ชด', '๐ฒ', '๐ณ', '๐ด', '๐ต', '๐พ', '๐ฟ', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐ฐ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐', '๐', '๐', '๐', '๐ชจ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', '๐ช', 'โญ', '๐', '๐ ', '๐', 'โ๏ธ', 'โ
', 'โ๏ธ', '๐ค๏ธ', '๐ฅ๏ธ', '๐ฆ๏ธ', '๐ง๏ธ', '๐จ๏ธ', '๐ฉ๏ธ', '๐ช๏ธ', '๐ซ๏ธ', '๐ฌ๏ธ', '๐', '๐', '๐', 'โ๏ธ', 'โ', 'โฑ๏ธ', 'โก', 'โ๏ธ', 'โ๏ธ', 'โ', 'โ๏ธ', '๐ฅ', '๐ง', '๐'],
+}
+
+let selectedCategory: Category = 'People'
+let onSelectCallback: ((emoji: string) => void) | null = null
+
+const Container = define('EmojiPickerContainer', {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 12,
+})
+
+const EmojiGrid = define('EmojiGrid', {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(10, 1fr)',
+ gap: 4,
+ overflow: 'auto',
+ alignContent: 'start',
+ height: 280,
+})
+
+const TabBar = define('EmojiTabBar', {
+ display: 'flex',
+ justifyContent: 'center',
+ gap: 4,
+ paddingTop: 12,
+ borderTop: `1px solid ${theme('colors-border')}`,
+ marginTop: 'auto',
+})
+
+const TabButton = define('EmojiTabButton', {
+ base: 'button',
+ padding: '8px 10px',
+ background: 'none',
+ border: 'none',
+ borderRadius: theme('radius-md'),
+ cursor: 'pointer',
+ fontSize: 18,
+ lineHeight: 1,
+ opacity: 0.5,
+ selectors: {
+ '&:hover': { opacity: 0.8, background: theme('colors-bgHover') },
+ },
+ variants: {
+ active: { opacity: 1, background: theme('colors-bgSelected') },
+ },
+})
+
+const EmojiButton = define('EmojiButton', {
+ base: 'button',
+ padding: 6,
+ background: 'none',
+ border: 'none',
+ borderRadius: theme('radius-md'),
+ cursor: 'pointer',
+ fontSize: 22,
+ lineHeight: 1,
+ selectors: {
+ '&:hover': { background: theme('colors-bgHover') },
+ },
+})
+
+const getEmojis = (): string[] => {
+ return EMOJI_CATEGORIES[selectedCategory]
+}
+
+const selectCategory = (category: Category) => {
+ selectedCategory = category
+ rerenderModal()
+}
+
+const handleSelect = (emoji: string) => {
+ onSelectCallback?.(emoji)
+ closeModal()
+}
+
+const CATEGORIES: Category[] = ['People', 'Gestures', 'Animals', 'Food', 'Activities', 'Travel', 'Objects', 'Symbols', 'Nature']
+
+const EmojiPickerContent = define('EmojiPickerContent', {
+ render() {
+ const emojis = getEmojis()
+
+ return (
+
+
+ {emojis.map((emoji, i) => (
+ handleSelect(emoji)}>
+ {emoji}
+
+ ))}
+
+
+
+ {CATEGORIES.map(cat => (
+ selectCategory(cat)}
+ title={cat}
+ >
+ {CATEGORY_ICONS[cat]}
+
+ ))}
+
+
+ )
+ }
+})
+
+export const openEmojiPicker = (onSelect: (emoji: string) => void) => {
+ selectedCategory = 'People'
+ onSelectCallback = onSelect
+ openModal('Choose Emoji', () => )
+}
diff --git a/src/client/tags/modal.tsx b/src/client/tags/modal.tsx
new file mode 100644
index 0000000..d709e09
--- /dev/null
+++ b/src/client/tags/modal.tsx
@@ -0,0 +1,105 @@
+import type { Child } from 'hono/jsx'
+import { define } from 'forge'
+import { theme } from '../themes'
+
+let modalTitle: string | null = null
+let modalContent: (() => Child) | null = null
+let renderFn: (() => void) | null = null
+
+export const initModal = (render: () => void) => {
+ renderFn = render
+}
+
+export const openModal = (title: string, content: () => Child) => {
+ modalTitle = title
+ modalContent = content
+ renderFn?.()
+}
+
+export const closeModal = () => {
+ modalTitle = null
+ modalContent = null
+ renderFn?.()
+}
+
+export const rerenderModal = () => {
+ renderFn?.()
+}
+
+// ESC key handler
+document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && modalContent) {
+ closeModal()
+ }
+})
+
+const ModalBackdrop = define('ModalBackdrop', {
+ position: 'fixed',
+ inset: 0,
+ background: 'rgba(0, 0, 0, 0.5)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ zIndex: 1000,
+})
+
+const ModalBox = define('ModalBox', {
+ background: theme('colors-bg'),
+ borderRadius: theme('radius-md'),
+ border: `1px solid ${theme('colors-border')}`,
+ boxShadow: '0 4px 24px rgba(0, 0, 0, 0.2)',
+ maxWidth: 500,
+ width: '90%',
+ maxHeight: '80vh',
+ overflow: 'auto',
+})
+
+const ModalHeader = define('ModalHeader', {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: '16px 20px',
+ borderBottom: `1px solid ${theme('colors-border')}`,
+})
+
+const ModalTitle = define('ModalTitle', {
+ fontSize: 16,
+ fontWeight: 600,
+ margin: 0,
+})
+
+const ModalCloseButton = define('ModalCloseButton', {
+ base: 'button',
+ background: 'none',
+ border: 'none',
+ cursor: 'pointer',
+ padding: 4,
+ fontSize: 18,
+ color: theme('colors-textMuted'),
+ lineHeight: 1,
+ selectors: {
+ '&:hover': { color: theme('colors-text') },
+ },
+})
+
+const ModalBody = define('ModalBody', {
+ padding: 20,
+})
+
+export const Modal = () => {
+ if (!modalContent) return null
+
+ return (
+
+ e.stopPropagation()}>
+
+ {modalTitle}
+ ร
+
+
+ {modalContent()}
+
+
+
+ )
+}