chore(input): emoji button

This commit is contained in:
molvqingtai 2023-10-15 03:14:16 +08:00
parent 9ae95efe17
commit 5fcae92532
11 changed files with 588 additions and 34 deletions

View file

@ -73,6 +73,7 @@
"react-dom": "^18.2.0",
"rimraf": "^5.0.1",
"tailwindcss": "^3.3.2",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.1.6",
"unplugin-icons": "^0.16.5",
"vite": "^4.4.3",
@ -88,6 +89,7 @@
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@tailwindcss/typography": "^0.5.9",

View file

@ -17,6 +17,9 @@ dependencies:
'@radix-ui/react-icons':
specifier: ^1.3.0
version: 1.3.0(react@18.2.0)
'@radix-ui/react-popover':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-scroll-area':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@ -175,6 +178,9 @@ devDependencies:
tailwindcss:
specifier: ^3.3.2
version: 3.3.2(ts-node@10.9.1)
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.3.2)
typescript:
specifier: ^5.1.6
version: 5.1.6
@ -1217,6 +1223,68 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@types/react': 18.2.14
react: 18.2.0
dev: false
/@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-hover-card@1.0.6(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2K3ToJuMk9wjwBOa+jdg2oPma+AmLdcEyTNsG/iC4BDVG3E0/mGCjbY8PEDSLxJcUi+nJi2QII+ec/4kWd88DA==}
peerDependencies:
@ -1254,6 +1322,56 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-id@1.0.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
react: 18.2.0
dev: false
/@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.7
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.14)(react@18.2.0)
dev: false
/@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==}
peerDependencies:
@ -1284,6 +1402,36 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.14
'@types/react-dom': 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==}
peerDependencies:
@ -1305,6 +1453,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies:
@ -2009,6 +2178,13 @@ packages:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/aria-hidden@1.2.3:
resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
engines: {node: '>=10'}
dependencies:
tslib: 2.6.0
dev: false
/array-buffer-byte-length@1.0.0:
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
dependencies:
@ -2980,6 +3156,10 @@ packages:
engines: {node: '>=6'}
dev: false
/detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false
/devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
dependencies:
@ -3978,6 +4158,11 @@ packages:
has-symbols: 1.0.3
dev: true
/get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
dev: false
/get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
@ -4413,6 +4598,12 @@ packages:
side-channel: 1.0.4
dev: true
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
loose-envify: 1.4.0
dev: false
/invert-kv@3.0.1:
resolution: {integrity: sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==}
engines: {node: '>=8'}
@ -6561,6 +6752,58 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/react-remove-scroll-bar@2.3.4(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0)
tslib: 2.6.0
dev: false
/react-remove-scroll@2.5.5(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
react-remove-scroll-bar: 2.3.4(@types/react@18.2.14)(react@18.2.0)
react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0)
tslib: 2.6.0
use-callback-ref: 1.3.0(@types/react@18.2.14)(react@18.2.0)
use-sidecar: 1.1.2(@types/react@18.2.14)(react@18.2.0)
dev: false
/react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
get-nonce: 1.0.1
invariant: 2.2.4
react: 18.2.0
tslib: 2.6.0
dev: false
/react-universal-interface@0.6.2(react@18.2.0)(tslib@2.6.0):
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
peerDependencies:
@ -7542,6 +7785,14 @@ packages:
resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
dev: false
/tailwindcss-animate@1.0.7(tailwindcss@3.3.2):
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
tailwindcss: 3.3.2(ts-node@10.9.1)
dev: true
/tailwindcss@3.3.2(ts-node@10.9.1):
resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==}
engines: {node: '>=14.0.0'}
@ -8034,6 +8285,37 @@ packages:
punycode: 2.3.0
dev: true
/use-callback-ref@1.3.0(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
react: 18.2.0
tslib: 2.6.0
dev: false
/use-sidecar@1.1.2(@types/react@18.2.14)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.14
detect-node-es: 1.1.0
react: 18.2.0
tslib: 2.6.0
dev: false
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:

View file

@ -0,0 +1,58 @@
import { SmileIcon } from 'lucide-react'
import { useState, type FC } from 'react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
import { ScrollArea } from '@/components/ui/ScrollArea'
import { Button } from '@/components/ui/Button'
import { EMOJI_LIST } from '@/constants'
import { chunk } from '@/utils'
export interface EmojiButtonProps {
onSelect?: (value: string) => void
}
const emojiGroups = chunk(EMOJI_LIST, 8)
// BUG: https://github.com/radix-ui/primitives/pull/2433
// BUG https://github.com/radix-ui/primitives/issues/1666
const EmojiButton: FC<EmojiButtonProps> = ({ onSelect }) => {
const [open, setOpen] = useState(false)
const handleSelect = (value: string) => {
onSelect?.(value)
setOpen(false)
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon">
<SmileIcon size={20} />
</Button>
</PopoverTrigger>
<PopoverContent className="z-top w-72 px-0">
<ScrollArea className="h-72 w-72 px-3">
{emojiGroups.map((group, index) => {
return (
<div key={index} className="grid grid-cols-8">
{group.map((emoji, index) => (
<Button
key={index}
size="sm"
className="text-base"
variant="ghost"
onClick={() => handleSelect(emoji)}
>
{emoji}
</Button>
))}
</div>
)
})}
</ScrollArea>
</PopoverContent>
</Popover>
)
}
EmojiButton.displayName = 'EmojiButton'
export default EmojiButton

View file

@ -23,14 +23,14 @@ const LikeButton: FC<LikeButtonProps> & { Icon: FC<LikeButtonIconProps> } = ({
onChange,
children
}) => {
const handleOnClick = (e: MouseEvent<HTMLButtonElement>) => {
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
onClick?.(e)
onChange?.(!checked, checked ? count - 1 : count + 1)
}
return (
<Button
onClick={handleOnClick}
onClick={handleClick}
variant="secondary"
className={cn(
'grid items-center overflow-hidden rounded-full leading-none transition-all',

View file

@ -0,0 +1,31 @@
import * as React from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { cn } from '@/utils/index'
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {
const shadowRoot = document.querySelector(__NAME__)!.shadowRoot! as any as HTMLElement
return (
<PopoverPrimitive.Portal container={shadowRoot}>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
></PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
)
})
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }

View file

@ -1,3 +1,169 @@
// https://www.webfx.com/tools/emoji-cheat-sheet/
export const EMOJI_LIST = [
'😀',
'😃',
'😄',
'😁',
'😆',
'😅',
'🤣',
'😂',
'🙂',
'🙃',
'🫠',
'😉',
'😊',
'😇',
'🥰',
'😍',
'🤩',
'😘',
'😗',
'😚',
'😙',
'🥲',
'😋',
'😛',
'😜',
'🤪',
'😝',
'🤑',
'🤗',
'🤭',
'🫢',
'🫣',
'🤫',
'🤔',
'🫡',
'🤐',
'🤨',
'😐',
'😶',
'🫥',
'😶‍🌫️',
'😏',
'😒',
'🙄',
'😬',
'😮‍💨',
'🤥',
'😌',
'😔',
'😪',
'🤤',
'😴',
'😷',
'🤒',
'🤕',
'🤢',
'🤮',
'🤧',
'🥵',
'🥶',
'🥴',
'😵',
'😵‍💫',
'🤯',
'🤠',
'🥳',
'🥸',
'😎',
'🤓',
'🧐',
'😕',
'🫤',
'😟',
'🙁',
'😮',
'😯',
'😲',
'😳',
'🥺',
'🥹',
'😦',
'😧',
'😨',
'😰',
'😥',
'😢',
'😭',
'😱',
'😖',
'😣',
'😞',
'😓',
'😩',
'😫',
'🥱',
'😤',
'😡',
'😠',
'🤬',
'😈',
'👿',
'💀',
'☠',
'💩',
'🤡',
'👹',
'👺',
'👻',
'👽',
'👾',
'🤖',
'😺',
'😸',
'😹',
'😻',
'😼',
'😽',
'🙀',
'😿',
'😾',
'🙈',
'🙉',
'🙊',
'👋',
'🤚',
'🖐',
'✋',
'🖖',
'🫱',
'🫲',
'🫳',
'🫴',
'👌',
'🤏',
'✌',
'🤞',
'🫰',
'🤟',
'🤘',
'🤙',
'👈',
'👉',
'👆',
'🖕',
'👇',
'☝',
'🫵',
'👍',
'👎',
'✊',
'👊',
'🤛',
'🤜',
'👏',
'🙌',
'🫶',
'👐',
'🤲',
'🤝',
'🙏',
'✍',
'💅'
]
// https://night-tailwindcss.vercel.app/docs/breakpoints
export const BREAKPOINTS = {
sm: 640,

View file

@ -9,7 +9,10 @@ export interface RootOptions {
element?: Element
}
const createShadowRoot = (name: string, options: RootOptions): Root => {
const createShadowRoot = (
name: string,
options: RootOptions
): Root & { shadowHost: Element; shadowRoot: ShadowRoot; appRoot: Element } => {
const { mode = 'open', style = '', script = '', element = '' } = options ?? {}
const shadowHost = createElement(`<${name}></${name}>`)
const shadowRoot = shadowHost.attachShadow({ mode })
@ -21,10 +24,16 @@ const createShadowRoot = (name: string, options: RootOptions): Root => {
shadowRoot.append(appStyle, appRoot, appScript, element)
return {
...reactRoot,
shadowHost,
shadowRoot,
appRoot,
render: (children: ReactNode) => {
document.body.appendChild(shadowHost)
reactRoot.render(children)
return reactRoot.render(children)
},
unmount: () => {
reactRoot.unmount()
shadowHost.remove()
}
}
}

View file

@ -7,29 +7,28 @@ import createShadowRoot from './createShadowRoot'
import StorageImpl from './impl/Storage'
import style from './index.css?inline'
void (async () => {
const store = Remesh.store({
const store = Remesh.store({
externs: [StorageImpl],
inspectors: [RemeshLogger()]
})
})
createShadowRoot(__NAME__, {
const root = createShadowRoot(__NAME__, {
style: __DEV__ ? '' : style,
mode: __DEV__ ? 'open' : 'closed'
}).render(
})
root.render(
<React.StrictMode>
<RemeshRoot store={store}>
<App />
</RemeshRoot>
</React.StrictMode>
)
)
// HMR Hack
// https://github.com/crxjs/chrome-extension-tools/issues/600
if (__DEV__) {
// HMR Hack
// https://github.com/crxjs/chrome-extension-tools/issues/600
if (__DEV__) {
await import('./index.css')
const styleElement = document.querySelector('[data-vite-dev-id]')!
const shadowRoot = document.querySelector(__NAME__)!.shadowRoot!
shadowRoot.insertBefore(styleElement, shadowRoot.firstChild)
}
})()
root.shadowRoot.insertBefore(styleElement, root.shadowRoot.firstChild)
}

View file

@ -8,3 +8,6 @@ export const cn = (...inputs: ClassValue[]) => {
export const createElement = <T extends Element>(template: string) => {
return new Range().createContextualFragment(template).firstElementChild as unknown as T
}
export const chunk = <T = any>(array: T[], size: number) =>
Array.from({ length: Math.ceil(array.length / size) }, (_v, i) => array.slice(i * size, i * size + size))

View file

@ -1,11 +1,12 @@
import { type FC } from 'react'
import { SmileIcon, CornerDownLeftIcon, ImageIcon } from 'lucide-react'
import { CornerDownLeftIcon, ImageIcon } from 'lucide-react'
import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react'
import { Button } from '@/components/ui/Button'
import MessageInput from '@/components/MessageInput'
import MessageInputDomain from '@/domain/MessageInput'
import MessageListDomain from '@/domain/MessageList'
import { MESSAGE_MAX_LENGTH } from '@/constants'
import EmojiButton from '@/components/EmojiButton'
const Footer: FC = () => {
const send = useRemeshSend()
@ -33,6 +34,10 @@ const Footer: FC = () => {
send(messageInputDomain.command.ClearCommand())
}
const handleEmojiSelect = (value: string) => {
send(messageInputDomain.command.InputCommand(messageText + value))
}
return (
<div className="grid gap-y-2 px-4 pb-4">
<MessageInput
@ -42,9 +47,7 @@ const Footer: FC = () => {
maxLength={MESSAGE_MAX_LENGTH}
></MessageInput>
<div className="grid grid-cols-[auto_auto_1fr] items-center justify-items-end">
<Button variant="ghost" size="icon">
<SmileIcon size={20} />
</Button>
<EmojiButton onSelect={handleEmojiSelect}></EmojiButton>
<Button variant="ghost" size="icon">
<ImageIcon size={20} />
</Button>

View file

@ -1,5 +1,6 @@
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import animate from 'tailwindcss-animate'
export default {
darkMode: ['class'],
@ -69,5 +70,5 @@ export default {
}
}
},
plugins: [typography()]
plugins: [animate, typography()]
} satisfies Config