diff --git a/.storybook/main.ts b/.storybook/main.ts index af637becd..5034f1af2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -9,7 +9,6 @@ const config: StorybookConfig = { '@storybook/addon-controls', '@storybook/addon-viewport', '@storybook/addon-toolbars', - '@storybook/addon-a11y', ], framework: '@storybook/react-vite', diff --git a/amity-uikit.config.json b/amity-uikit.config.json new file mode 100644 index 000000000..50b6395c0 --- /dev/null +++ b/amity-uikit.config.json @@ -0,0 +1,162 @@ +{ + "global_theme": { + "light_theme": { + "primary_color": "#1D1234", + "secondary_color": "#AB1234" + } + }, + "excludes": [], + "customizations": { + "page-id/*/*": { + "background_color": "#1234DB" + }, + "page-id/component-id/*": { + "background_color": "#123456" + }, + "page-id/component-id/element-id": { + "icon": "icon1", + "background_color": "#654321" + }, + "page-id/*/element-id": { + "icon": "icon2", + "background_color": "#aabbcc" + }, + "*/component-id/*": { + "background_color": "#456456" + }, + "*/*/element-id": { + "icon": "icon3", + "background_color": "#AB1234" + }, + "*/component-id/element-id": { + "icon": "icon4", + "background_color": "#ffddaa" + }, + "select_target_page": { + "page_theme": { + "light_theme": { + "primary_color": "#1D1234", + "secondary_color": "#AB1234" + } + }, + "title": "Share to" + }, + "select_target_page/*/back_button": { + "back_icon": "back" + }, + "camera_page": { + "resolution": "720p" + }, + "camera_page/*/close_button": { + "close_icon": "close" + }, + "create_story_page": {}, + "create_story_page/*/back_button": { + "back_icon": "back", + "background_color": "#1234DB" + }, + "create_story_page/*/aspect_ratio_button": { + "aspect_ratio_icon": "aspect_ratio", + "background_color": "#1234DB" + }, + "create_story_page/*/story_hyperlink_button": { + "hyperlink_button_icon": "hyperlink_button", + "background_color": "#1234DB" + }, + "create_story_page/*/hyper_link": { + "hyper_link_icon": "hyper_link", + "background_color": "#1234DB" + }, + "create_story_page/*/share_story_button": { + "share_icon": "share_story_button", + "background_color": "#1234DB", + "hide_avatar": false + }, + "story_page": {}, + "story_page/*/progress_bar": { + "progress_color": "#FFFFFF", + "background_color": "#50FFFFFF" + }, + "story_page/*/overflow_menu": { + "overflow_menu_icon": "threeDot" + }, + "story_page/*/close_button": { + "close_icon": "close" + }, + "story_page/*/story_impression_button": { + "impression_icon": "impression" + }, + "story_page/*/story_comment_button": { + "comment_icon": "comment", + "background_color": "#1234DD" + }, + "story_page/*/story_reaction_button": { + "reaction_icon": "like", + "background_color": "#1243EE" + }, + "story_page/*/create_new_story_button": { + "create_new_story_icon": "plus", + "background_color": "#1243EE" + }, + "story_page/*/speaker_button": { + "mute_icon": "mute", + "unmute_icon": "unmute", + "background_color": "#1243EE" + }, + "*/edit_comment_component/*": { + "component_theme": { + "light_theme": { + "primary_color": "#1D1234", + "secondary_color": "#AB1234" + } + } + }, + "*/edit_comment_component/cancel_button": { + "cancel_icon": "", + "cancel_button_text": "cancel", + "background_color": "#1243EE" + }, + "*/edit_comment_component/save_button": { + "save_icon": "", + "save_button_text": "Save", + "background_color": "#1243EE" + }, + "*/hyper_link_config_component/*": { + "component_theme": { + "light_theme": { + "primary_color": "#1D1234", + "secondary_color": "#AB1234" + } + } + }, + "*/hyper_link_config_component/done_button": { + "done_icon": "", + "done_button_text": "Done", + "background_color": "#1243EE" + }, + "*/hyper_link_config_component/cancel_button": { + "cancel_icon": "", + "cancel_button_text": "Cancel" + }, + "*/comment_tray_component/*": { + "component_theme": { + "light_theme": { + "primary_color": "#1D1234", + "secondary_color": "#AB1234" + } + } + }, + "*/story_tab_component/*": {}, + "*/story_tab_component/story_ring": { + "progress_color": ["#339AF9", "#78FA58"], + "background_color": ["#EBECEF"] + }, + "*/story_tab_component/create_new_story_button": { + "create_new_story_icon": "plus", + "background_color": "#1243EE" + }, + "*/*/close_button": { + "close_icon": "close" + } + } +} diff --git a/package.json b/package.json index 55588d8d6..a266f6468 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@types/lodash": "^4.14.202", "@types/prop-types": "^15.7.11", "@types/react": "^17.0.74", + "@types/react-dom": "^18.2.18", "@types/react-helmet": "^6.1.11", "@types/react-infinite-scroller": "^1.2.5", "@types/react-mentions": "^4.1.13", @@ -100,6 +101,7 @@ "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.3.4", "clsx": "^2.1.0", + "extract-colors": "^4.0.2", "filesize": "^9.0.11", "hls.js": "^1.4.14", "linkify-react": "^4.1.3", @@ -109,6 +111,8 @@ "polished": "^4.2.2", "react-hook-form": "^7.49.2", "react-infinite-scroll-component": "^6.1.0", + "react-insta-stories": "^2.6.2", + "react-modal-sheet": "^2.2.0", "react-intl": "^6.5.5", "react-loading-skeleton": "^3.3.1", "react-mentions": "^4.4.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4841e5e51..e92d27780 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: clsx: specifier: ^2.1.0 version: 2.1.0 + extract-colors: + specifier: ^4.0.2 + version: 4.0.2 filesize: specifier: ^9.0.11 version: 9.0.11 @@ -50,6 +53,9 @@ dependencies: react-infinite-scroll-component: specifier: ^6.1.0 version: 6.1.0(react@18.2.0) + react-insta-stories: + specifier: ^2.6.2 + version: 2.6.2(react@18.2.0) react-intl: specifier: ^6.5.5 version: 6.5.5(react@18.2.0)(typescript@4.9.5) @@ -59,6 +65,9 @@ dependencies: react-mentions: specifier: ^4.4.10 version: 4.4.10(react-dom@18.2.0)(react@18.2.0) + react-modal-sheet: + specifier: ^2.2.0 + version: 2.2.0(framer-motion@10.18.0)(react@18.2.0) react-sizeme: specifier: ^3.0.2 version: 3.0.2 @@ -111,10 +120,10 @@ devDependencies: version: 7.6.7 '@storybook/addon-controls': specifier: ^7.6.7 - version: 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + version: 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-essentials': specifier: ^7.6.7 - version: 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + version: 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-toolbars': specifier: ^7.6.7 version: 7.6.7 @@ -148,6 +157,9 @@ devDependencies: '@types/react': specifier: ^17.0.74 version: 17.0.74 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 '@types/react-helmet': specifier: ^6.1.11 version: 6.1.11 @@ -1758,12 +1770,26 @@ packages: engines: {node: '>=10.0.0'} dev: true + /@emotion/is-prop-valid@0.8.8: + resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} + requiresBuild: true + dependencies: + '@emotion/memoize': 0.7.4 + dev: false + optional: true + /@emotion/is-prop-valid@1.2.1: resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} dependencies: '@emotion/memoize': 0.8.1 dev: true + /@emotion/memoize@0.7.4: + resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + requiresBuild: true + dev: false + optional: true + /@emotion/memoize@0.8.1: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: true @@ -2813,7 +2839,7 @@ packages: '@babel/runtime': 7.23.7 dev: true - /@radix-ui/react-arrow@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} peerDependencies: '@types/react': '*' @@ -2827,13 +2853,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-collection@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: '@types/react': '*' @@ -2849,9 +2876,10 @@ packages: '@babel/runtime': 7.23.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -2898,7 +2926,7 @@ packages: react: 18.2.0 dev: true - /@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} peerDependencies: '@types/react': '*' @@ -2914,10 +2942,11 @@ packages: '@babel/runtime': 7.23.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -2936,7 +2965,7 @@ packages: react: 18.2.0 dev: true - /@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-focus-scope@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==} peerDependencies: '@types/react': '*' @@ -2951,9 +2980,10 @@ packages: dependencies: '@babel/runtime': 7.23.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -2973,7 +3003,7 @@ packages: react: 18.2.0 dev: true - /@radix-ui/react-popper@1.1.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} peerDependencies: '@types/react': '*' @@ -2988,21 +3018,22 @@ packages: dependencies: '@babel/runtime': 7.23.7 '@floating-ui/react-dom': 2.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-size': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/rect': 1.0.1 '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-portal@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: '@types/react': '*' @@ -3016,13 +3047,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-primitive@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: '@types/react': '*' @@ -3038,11 +3070,12 @@ packages: '@babel/runtime': 7.23.7 '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: '@types/react': '*' @@ -3057,20 +3090,21 @@ packages: dependencies: '@babel/runtime': 7.23.7 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-select@1.2.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-select@1.2.2(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} peerDependencies: '@types/react': '*' @@ -3086,31 +3120,32 @@ packages: '@babel/runtime': 7.23.7 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-popper': 1.1.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 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@17.0.74)(react@18.2.0) dev: true - /@radix-ui/react-separator@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} peerDependencies: '@types/react': '*' @@ -3124,8 +3159,9 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -3145,7 +3181,7 @@ packages: react: 18.2.0 dev: true - /@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} peerDependencies: '@types/react': '*' @@ -3162,16 +3198,17 @@ packages: '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-toggle@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-toggle@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==} peerDependencies: '@types/react': '*' @@ -3186,14 +3223,15 @@ packages: dependencies: '@babel/runtime': 7.23.7 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@radix-ui/react-toolbar@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-toolbar@1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==} peerDependencies: '@types/react': '*' @@ -3210,11 +3248,12 @@ packages: '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-separator': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-separator': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toggle-group': 1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -3321,7 +3360,7 @@ packages: react: 18.2.0 dev: true - /@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} peerDependencies: '@types/react': '*' @@ -3335,8 +3374,9 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@types/react': 17.0.74 + '@types/react-dom': 18.2.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -3347,6 +3387,29 @@ packages: '@babel/runtime': 7.23.7 dev: true + /@react-aria/ssr@3.9.1(react@18.2.0): + resolution: {integrity: sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + dependencies: + '@swc/helpers': 0.5.3 + react: 18.2.0 + dev: false + + /@react-aria/utils@3.17.0(react@18.2.0): + resolution: {integrity: sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + dependencies: + '@react-aria/ssr': 3.9.1(react@18.2.0) + '@react-stately/utils': 3.9.0(react@18.2.0) + '@react-types/shared': 3.22.0(react@18.2.0) + '@swc/helpers': 0.4.36 + clsx: 1.2.1 + react: 18.2.0 + dev: false + /@react-native-async-storage/async-storage@1.21.0(react-native@0.73.2): resolution: {integrity: sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==} peerDependencies: @@ -3699,6 +3762,23 @@ packages: react-native: 0.73.2(@babel/core@7.23.7)(@babel/preset-env@7.23.7)(react@18.2.0) dev: true + /@react-stately/utils@3.9.0(react@18.2.0): + resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + dependencies: + '@swc/helpers': 0.5.3 + react: 18.2.0 + dev: false + + /@react-types/shared@3.22.0(react@18.2.0): + resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /@remix-run/router@1.14.1: resolution: {integrity: sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==} engines: {node: '>=14.0.0'} @@ -3878,10 +3958,10 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/addon-controls@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@storybook/addon-controls@7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-DJ3gfvcdCgqi7AQxu83vx0AEUKiuJrNcSATfWV3Jqi8dH6fYO2yqpemHEeWOEy+DAHxIOaqLKwb1QjIBj+vSRQ==} dependencies: - '@storybook/blocks': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/blocks': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) lodash: 4.17.21 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -3893,7 +3973,7 @@ packages: - supports-color dev: true - /@storybook/addon-docs@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@storybook/addon-docs@7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2dfajNhweofJ3LxjGO83UE5sBMvKtJB0Agj7q8mMtK/9PUCUcbvsFSyZnO/s6X1zAjSn5ZrirbSoTXU4IqxwSA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3901,9 +3981,9 @@ packages: dependencies: '@jest/transform': 29.7.0 '@mdx-js/react': 2.3.0(react@18.2.0) - '@storybook/blocks': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/blocks': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/client-logger': 7.6.7 - '@storybook/components': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/csf-plugin': 7.6.7 '@storybook/csf-tools': 7.6.7 '@storybook/global': 5.0.0 @@ -3927,7 +4007,7 @@ packages: - supports-color dev: true - /@storybook/addon-essentials@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@storybook/addon-essentials@7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-nNLMrpIvc04z4XCA+kval/44eKAFJlUJeeL2pxwP7F/PSzjWe5BXv1bQHOiw8inRO5II0PzqwWnVCI9jsj7K5A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3935,8 +4015,8 @@ packages: dependencies: '@storybook/addon-actions': 7.6.7 '@storybook/addon-backgrounds': 7.6.7 - '@storybook/addon-controls': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-docs': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-controls': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-docs': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-highlight': 7.6.7 '@storybook/addon-measure': 7.6.7 '@storybook/addon-outline': 7.6.7 @@ -3986,7 +4066,7 @@ packages: memoizerific: 1.11.3 dev: true - /@storybook/blocks@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@storybook/blocks@7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-+QEvGQ0he/YvFS3lsZORJWxhQIyqcCDWsxbJxJiByePd+Z4my3q8xwtPhHW0TKRL0xUgNE/GnTfMMqJfevTuSw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3994,7 +4074,7 @@ packages: dependencies: '@storybook/channels': 7.6.7 '@storybook/client-logger': 7.6.7 - '@storybook/components': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 7.6.7 '@storybook/csf': 0.1.2 '@storybook/docs-tools': 7.6.7 @@ -4183,14 +4263,14 @@ packages: - supports-color dev: true - /@storybook/components@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@storybook/components@7.6.7(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1HN4p+MCI4Tx9VGZayZyqbW7SB7mXQLnS5fUbTE1gXaMYHpzFvcrRNROeV1LZPClJX6qx1jgE5ngZojhxGuxMA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@radix-ui/react-select': 1.2.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toolbar': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-select': 1.2.2(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-toolbar': 1.0.4(@types/react-dom@18.2.18)(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) '@storybook/client-logger': 7.6.7 '@storybook/csf': 0.1.2 '@storybook/global': 5.0.0 @@ -4540,6 +4620,25 @@ packages: file-system-cache: 2.3.0 dev: true + /@swc/helpers@0.4.14: + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + dependencies: + tslib: 2.6.2 + dev: false + + /@swc/helpers@0.4.36: + resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==} + dependencies: + legacy-swc-helpers: /@swc/helpers@0.4.14 + tslib: 2.6.2 + dev: false + + /@swc/helpers@0.5.3: + resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==} + dependencies: + tslib: 2.6.2 + dev: false + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -4780,6 +4879,12 @@ packages: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} dev: true + /@types/react-dom@18.2.18: + resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==} + dependencies: + '@types/react': 17.0.74 + dev: true + /@types/react-helmet@6.1.11: resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} dependencies: @@ -6178,6 +6283,11 @@ packages: engines: {node: '>=0.8'} dev: true + /clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + /clsx@2.1.0: resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} engines: {node: '>=6'} @@ -7594,6 +7704,10 @@ packages: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: true + /extract-colors@4.0.2: + resolution: {integrity: sha512-G7v2C3LJqW38U+yRUPD6nJCjBRfdLD7y8efEHn+1qONt1mhj+OZBpzFmiaS+dZiHU/k0dvgK2kgIkbAllHVbRw==} + dev: false + /extract-zip@1.7.0: resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} hasBin: true @@ -7868,6 +7982,24 @@ packages: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true + /framer-motion@10.18.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.2 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + dev: false + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -9963,7 +10095,7 @@ packages: metro-symbolicate: 0.80.4 nullthrows: 1.1.1 ob1: 0.80.4 - source-map: 0.5.6 + source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color @@ -9977,7 +10109,7 @@ packages: invariant: 2.2.4 metro-source-map: 0.80.4 nullthrows: 1.1.1 - source-map: 0.5.6 + source-map: 0.5.7 through2: 2.0.5 vlq: 1.0.1 transitivePeerDependencies: @@ -10063,7 +10195,7 @@ packages: nullthrows: 1.1.1 rimraf: 3.0.2 serialize-error: 2.1.0 - source-map: 0.5.6 + source-map: 0.5.7 strip-ansi: 6.0.1 throat: 5.0.0 ws: 7.5.9 @@ -11152,6 +11284,14 @@ packages: throttle-debounce: 2.3.0 dev: false + /react-insta-stories@2.6.2(react@18.2.0): + resolution: {integrity: sha512-eM1YHr92bV7WK5h9sECjyYnqZtPxnzJrZFr9IaoDcaZaAEOHVRav+pST513DIG8Hk8QjSTHtdvHHZ0Ka5HwH8w==} + peerDependencies: + react: '>=16.8.2' + dependencies: + react: 18.2.0 + dev: false + /react-intl@6.5.5(react@18.2.0)(typescript@4.9.5): resolution: {integrity: sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==} peerDependencies: @@ -11212,6 +11352,18 @@ packages: substyle: 9.4.1(react@18.2.0) dev: false + /react-modal-sheet@2.2.0(framer-motion@10.18.0)(react@18.2.0): + resolution: {integrity: sha512-OAIWuVWxMx3zQqrMLbYWnczadplg0WLd+AaBWmN5+ysNF5/pneqjkOV3AWaIZOCIF4TcrejiCsTduutbzCRP2Q==} + engines: {node: '>=16'} + peerDependencies: + framer-motion: '>=6' + react: '>=16' + dependencies: + '@react-aria/utils': 3.17.0(react@18.2.0) + framer-motion: 10.18.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + dev: false + /react-native-uuid@2.0.1: resolution: {integrity: sha512-cptnoIbL53GTCrWlb/+jrDC6tvb7ypIyzbXNJcpR3Vab0mkeaaVd5qnB3f0whXYzS+SMoSQLcUUB0gEWqkPC0g==} engines: {node: '>=10.0.0', npm: '>=6.0.0'} @@ -12045,6 +12197,12 @@ packages: /source-map@0.5.6: resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} engines: {node: '>=0.10.0'} + dev: false + + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: true /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..1e4e059bc --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +module.exports = { + plugins: [require('autoprefixer')()], +}; diff --git a/src/V4/icons/Add.tsx b/src/V4/icons/Add.tsx new file mode 100644 index 000000000..d57eca5e2 --- /dev/null +++ b/src/V4/icons/Add.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/AlertCircle.tsx b/src/V4/icons/AlertCircle.tsx new file mode 100644 index 000000000..1ef342a17 --- /dev/null +++ b/src/V4/icons/AlertCircle.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/ArrowLeft.tsx b/src/V4/icons/ArrowLeft.tsx new file mode 100644 index 000000000..b26ef3b48 --- /dev/null +++ b/src/V4/icons/ArrowLeft.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +import { FontAwesomeIcon as FaIcon } from '@fortawesome/react-fontawesome'; +import { faArrowLeft } from '@fortawesome/pro-regular-svg-icons'; +import { ReactNode } from 'react'; + +const ArrowLeft = styled(FaIcon).attrs<{ icon?: ReactNode }>({ icon: faArrowLeft })` + color: ${({ theme }) => theme.palette.base.shade3}; + font-size: ${({ height = 'inherit' }) => height}; +`; + +export default ArrowLeft; diff --git a/src/V4/icons/ArrowLeftCircle.tsx b/src/V4/icons/ArrowLeftCircle.tsx new file mode 100644 index 000000000..107cff7fd --- /dev/null +++ b/src/V4/icons/ArrowLeftCircle.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/ArrowRight.tsx b/src/V4/icons/ArrowRight.tsx new file mode 100644 index 000000000..91efa5ec2 --- /dev/null +++ b/src/V4/icons/ArrowRight.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/ArrowRightCircle.tsx b/src/V4/icons/ArrowRightCircle.tsx new file mode 100644 index 000000000..82408ea6a --- /dev/null +++ b/src/V4/icons/ArrowRightCircle.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Bars.tsx b/src/V4/icons/Bars.tsx new file mode 100644 index 000000000..90b4fcc9d --- /dev/null +++ b/src/V4/icons/Bars.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Close.tsx b/src/V4/icons/Close.tsx new file mode 100644 index 000000000..fd2973720 --- /dev/null +++ b/src/V4/icons/Close.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Comment.tsx b/src/V4/icons/Comment.tsx new file mode 100644 index 000000000..1a8746821 --- /dev/null +++ b/src/V4/icons/Comment.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/EllipsisH.tsx b/src/V4/icons/EllipsisH.tsx new file mode 100644 index 000000000..3206324ca --- /dev/null +++ b/src/V4/icons/EllipsisH.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Error.tsx b/src/V4/icons/Error.tsx new file mode 100644 index 000000000..2f6ff4672 --- /dev/null +++ b/src/V4/icons/Error.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Expand.tsx b/src/V4/icons/Expand.tsx new file mode 100644 index 000000000..e616bee97 --- /dev/null +++ b/src/V4/icons/Expand.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +import { FontAwesomeIcon as FaIcon } from '@fortawesome/react-fontawesome'; +import { faExpand } from '@fortawesome/pro-regular-svg-icons'; +import { ReactNode } from 'react'; + +const Aspect = styled(FaIcon).attrs<{ icon?: ReactNode }>({ icon: faExpand })` + color: ${({ theme }) => theme.palette.base.shade3}; + font-size: ${({ height = 'inherit' }) => height}; +`; + +export default Aspect; diff --git a/src/V4/icons/Eye.tsx b/src/V4/icons/Eye.tsx new file mode 100644 index 000000000..f318cc579 --- /dev/null +++ b/src/V4/icons/Eye.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Link.tsx b/src/V4/icons/Link.tsx new file mode 100644 index 000000000..066889289 --- /dev/null +++ b/src/V4/icons/Link.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +import { FontAwesomeIcon as FaIcon } from '@fortawesome/react-fontawesome'; +import { faLink } from '@fortawesome/pro-regular-svg-icons'; +import { ReactNode } from 'react'; + +const Link = styled(FaIcon).attrs<{ icon?: ReactNode }>({ icon: faLink })` + color: ${({ theme }) => theme.palette.base.shade3}; + font-size: ${({ height = 'inherit' }) => height}; +`; + +export default Link; diff --git a/src/V4/icons/Pause.tsx b/src/V4/icons/Pause.tsx new file mode 100644 index 000000000..f0d32c77c --- /dev/null +++ b/src/V4/icons/Pause.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Play.tsx b/src/V4/icons/Play.tsx new file mode 100644 index 000000000..316c11b60 --- /dev/null +++ b/src/V4/icons/Play.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/ThumbsUp.tsx b/src/V4/icons/ThumbsUp.tsx new file mode 100644 index 000000000..e49b18ecc --- /dev/null +++ b/src/V4/icons/ThumbsUp.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/Trash.tsx b/src/V4/icons/Trash.tsx new file mode 100644 index 000000000..ab1e11641 --- /dev/null +++ b/src/V4/icons/Trash.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +import { FontAwesomeIcon as FaIcon } from '@fortawesome/react-fontawesome'; +import { faTrash } from '@fortawesome/pro-regular-svg-icons'; +import { ReactNode } from 'react'; + +const Trash = styled(FaIcon).attrs<{ icon?: ReactNode }>({ icon: faTrash })` + color: ${({ theme }) => theme.palette.base.shade3}; + font-size: ${({ height = 'inherit' }) => height}; +`; + +export default Trash; diff --git a/src/V4/icons/Verified.tsx b/src/V4/icons/Verified.tsx new file mode 100644 index 000000000..6dcd489dd --- /dev/null +++ b/src/V4/icons/Verified.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/V4/icons/View.tsx b/src/V4/icons/View.tsx new file mode 100644 index 000000000..e337f55be --- /dev/null +++ b/src/V4/icons/View.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +import { FontAwesomeIcon as FaIcon } from '@fortawesome/react-fontawesome'; +import { faEye } from '@fortawesome/pro-regular-svg-icons'; +import { ReactNode } from 'react'; + +const View = styled(FaIcon).attrs<{ icon?: ReactNode }>({ icon: faEye })` + color: ${({ theme }) => theme.palette.base.shade3}; + font-size: ${({ height = 'inherit' }) => height}; +`; + +export default View; diff --git a/src/V4/icons/index.ts b/src/V4/icons/index.ts new file mode 100644 index 000000000..86835361a --- /dev/null +++ b/src/V4/icons/index.ts @@ -0,0 +1,19 @@ +export { default as ViewIcon } from './View'; +export { default as CommentIcon } from './Comment'; +export { default as DotsIcon } from './EllipsisH'; +export { default as LikeIcon } from './ThumbsUp'; +export { default as TrashIcon } from './Trash'; +export { default as ArrowLeftIcon } from './ArrowLeft'; +export { default as ArrowRightIcon } from './ArrowRight'; +export { default as ExpandIcon } from './Expand'; +export { default as LinkIcon } from './Link'; +export { default as CloseIcon } from './Close'; +export { default as VerifiedIcon } from './Verified'; +export { default as AddIcon } from './Add'; +export { default as BarsIcon } from './Bars'; +export { default as PauseIcon } from './Pause'; +export { default as PlayIcon } from './Play'; +export { default as ErrorIcon } from './Error'; +export { default as ArrowLeftCircleIcon } from './ArrowLeftCircle'; +export { default as ArrowRightCircleIcon } from './ArrowRightCircle'; +export { default as AlertCircleIcon } from './AlertCircle'; diff --git a/src/V4/social/components/StoryDraft/StoryDraft.tsx b/src/V4/social/components/StoryDraft/StoryDraft.tsx new file mode 100644 index 000000000..146c5243e --- /dev/null +++ b/src/V4/social/components/StoryDraft/StoryDraft.tsx @@ -0,0 +1,129 @@ +import React, { useEffect } from 'react'; + +import { + ActionsContainer, + IconButton, + ShareStoryButton, + ShareText, + BackIcon, + ExpandStoryIcon, + StoryLinkIcon, + ShareStoryIcon, + StoryDraftContainer, + StoryDraftHeader, + StoryDraftFooter, + DraftImage, + DraftImageContainer, +} from './styles'; +import { useIntl } from 'react-intl'; +import { extractColors } from 'extract-colors'; +import { confirm } from '~/core/components/Confirm'; +import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; +import Avatar from '~/core/components/Avatar'; +import { VideoPreview } from '~/core/components/Uploaders/Video/styles'; +import { readFileAsync } from '~/helpers'; + +type DraftStoryProps = { + file: File; + creatorAvatar: string; + onCreateStory: ( + file: File, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata | undefined, + items?: Amity.StoryItem[] | undefined, + ) => void; + onDiscardStory: () => void; +}; + +const StoryDraft = ({ file, onDiscardStory, onCreateStory }: DraftStoryProps) => { + const { formatMessage } = useIntl(); + + const [imageMode, setImageMode] = React.useState<'fit' | 'fill'>('fit'); + const [colors, setColors] = React.useState>>([]); + + const onClickImageMode = () => { + setImageMode(imageMode === 'fit' ? 'fill' : 'fit'); + if (imageMode === 'fill') { + setColors([]); + } + }; + + useEffect(() => { + const extractColorsFromImage = async (fileTarget: File) => { + const img = await readFileAsync(fileTarget); + + if (fileTarget?.type.includes('image')) { + const image = new Image(); + image.src = img as string; + + const colorsFromImage = await extractColors(image, { + crossOrigin: 'anonymous', + }); + + setColors(colorsFromImage); + } + }; + + if (file?.type.includes('image')) { + extractColorsFromImage(file); + } + }, [file, imageMode]); + + const discardCreateStory = () => { + confirm({ + title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), + content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), + cancelText: formatMessage({ id: 'general.action.cancel' }), + okText: formatMessage({ id: 'delete' }), + onOk: onDiscardStory, + }); + }; + + return ( +
+ + + + + + + {file?.type.includes('image') && ( + + + + )} + + + + + + + {file?.type.includes('image') ? ( + + + + ) : ( + + )} + + + onCreateStory(file, imageMode, {}, [])}> + + {formatMessage({ id: 'storyDraft.button.shareStory' })} + + + + +
+ ); +}; +export default StoryDraft; diff --git a/src/V4/social/components/StoryDraft/index.tsx b/src/V4/social/components/StoryDraft/index.tsx new file mode 100644 index 000000000..a3563f685 --- /dev/null +++ b/src/V4/social/components/StoryDraft/index.tsx @@ -0,0 +1 @@ +export { default as StoryDraft } from './StoryDraft'; diff --git a/src/V4/social/components/StoryDraft/styles.tsx b/src/V4/social/components/StoryDraft/styles.tsx new file mode 100644 index 000000000..426d20811 --- /dev/null +++ b/src/V4/social/components/StoryDraft/styles.tsx @@ -0,0 +1,114 @@ +import styled from 'styled-components'; +import { ArrowLeftIcon, ArrowRightIcon, LinkIcon, ExpandIcon } from '~/V4/icons'; + +export const BackIcon = styled(ArrowLeftIcon)` + color: #ffffff; +`; + +export const ExpandStoryIcon = styled(ExpandIcon)` + color: #ffffff; +`; + +export const StoryLinkIcon = styled(LinkIcon)` + color: #ffffff; +`; + +export const ShareStoryIcon = styled(ArrowRightIcon)` + color: #292b32; +`; + +export const StoryDraftContainer = styled.div` + position: relative; + display: flex; + width: 23.438rem; + height: 40.875rem; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +export const StoryDraftHeader = styled.div` + position: absolute; + top: 0; + padding: 1rem; + display: flex; + justify-content: space-between; + width: 100%; + z-index: 1; +`; + +export const IconButton = styled.button` + width: 2rem; + height: 2rem; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.5); + border: none; + cursor: pointer; +`; + +export const ActionsContainer = styled.div` + display: flex; + gap: 0.75rem; +`; + +export const DraftImageContainer = styled.div<{ colors: { hex: string }[] }>` + width: 100%; + height: 100%; + position: relative; + border-radius: 0.75rem; + overflow: hidden; + background: linear-gradient( + 180deg, + ${(props) => props.colors?.[0]?.hex || '#000'} 0%, + ${(props) => props.colors?.[props?.colors?.length - 1]?.hex || '#000'} 100% + ); +`; + +export const DraftImage = styled.img<{ imageMode: 'fit' | 'fill'; colors: { hex: string }[] }>` + width: 100%; + height: 100%; + object-fit: ${(props) => (props?.imageMode === 'fit' ? 'contain' : 'cover')}; +`; + +export const StoryDraftFooter = styled.div` + width: 100%; + position: absolute; + bottom: -50px; + background-color: #000; + display: flex; + justify-content: flex-end; + padding: 0.75rem; + overflow: hidden; +`; + +export const ShareStoryButton = styled.button` + display: inline-flex; + height: 2.5rem; + padding: 0.375rem 0.5rem 0.25rem 0.25rem; + align-items: center; + justify-content: center; + flex-shrink: 0; + border-radius: 1.5rem; + background-color: #fff; + border: none; + color: #292b32; + font-size: 0.938rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; + letter-spacing: -0.24px; + cursor: pointer; + gap: 0.5rem; +`; + +export const ShareIcon = styled.img` + width: 2rem; + height: 2rem; + border-radius: 50%; +`; + +export const ShareText = styled.span``; + +export const HiddenInput = styled.input` + display: none; +`; diff --git a/src/V4/social/components/StoryDraft/ui.stories.tsx b/src/V4/social/components/StoryDraft/ui.stories.tsx new file mode 100644 index 000000000..b8765f4ac --- /dev/null +++ b/src/V4/social/components/StoryDraft/ui.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import styled from 'styled-components'; +import { StoryDraft } from '.'; + +export default { + title: 'Ui Only/Social/Story', +}; + +const MobileContainer = styled.div` + width: 360px; + height: 640px; +`; + +export const UiStoryDraft = { + name: 'Draft Story', + render: () => { + { + return ( + + + + ); + } + }, +}; diff --git a/src/V4/social/components/StoryTab/StoryRing.tsx b/src/V4/social/components/StoryTab/StoryRing.tsx new file mode 100644 index 000000000..62ad9509a --- /dev/null +++ b/src/V4/social/components/StoryTab/StoryRing.tsx @@ -0,0 +1,113 @@ +import React, { useState, useEffect } from 'react'; +import styled, { keyframes } from 'styled-components'; + +const animateRing = keyframes` + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +`; + +const RingWrapper = styled.div` + position: relative; + display: inline-block; +`; + +const Ring = styled.svg<{ uploading?: boolean }>` + animation: ${(props) => (props.uploading ? animateRing : 'none')} 2s linear; +`; + +interface StoryRingProps extends React.SVGProps { + uploading?: boolean; + isErrored?: boolean; +} + +const StoryRing = ({ uploading = false, isErrored = false, ...props }: StoryRingProps) => { + const [rotation, setRotation] = useState(0); + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (uploading) { + interval = setInterval(() => { + setRotation((prevRotation) => (prevRotation + 1) % 360); + }, 10); + } else { + setRotation(0); + } + + return () => { + clearInterval(interval); + }; + }, [uploading]); + + if (isErrored) { + return ( + + + + + + + + + + + + ); + } + + return ( + + + + + + + + + + + + ); +}; + +export default StoryRing; diff --git a/src/V4/social/components/StoryTab/index.tsx b/src/V4/social/components/StoryTab/index.tsx new file mode 100644 index 000000000..b8f524e44 --- /dev/null +++ b/src/V4/social/components/StoryTab/index.tsx @@ -0,0 +1,86 @@ +import React, { useEffect } from 'react'; +import Truncate from 'react-truncate-markup'; +import { backgroundImage as CommunityImage } from '~/icons/Community'; +import { + AddStoryButton, + ErrorButton, + StoryAvatar, + StoryTabContainer, + StoryTitle, + StoryWrapper, +} from './styles'; + +import StoryRing from './StoryRing'; +import { notification } from '~/core/components/Notification'; + +interface StoryTabProps { + avatar: string | null; + icon?: React.ReactNode; + storyRing?: boolean; + uploadingStory?: boolean; + isErrored?: boolean; + title?: string; + onAddStory?: () => void; + onClick?: () => void; + onChange?: (file: File | null) => void; +} + +const StoryTab = ({ + title = 'Story', + storyRing = false, + uploadingStory = false, + isErrored = false, + avatar, + onClick, + onChange, +}: StoryTabProps) => { + const handleAddIconClick = (e: React.MouseEvent) => { + e.stopPropagation(); + + if (onChange) { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*,video/*'; + + input.addEventListener('change', (event) => { + const selectedFile = (event.target as HTMLInputElement).files?.[0]; + onChange(selectedFile as File); + }); + + input.click(); + } + }; + + const handleOnClick = () => { + if (!storyRing || !onClick) return; + onClick(); + }; + + useEffect(() => { + if (!isErrored) return; + notification.info({ + content: 'Failed to share story', + }); + }, [isErrored]); + + return ( + + + {storyRing && ( + + )} + + {!isErrored ? ( + + ) : ( + + )} + + + {title} + + + ); +}; + +export default StoryTab; diff --git a/src/V4/social/components/StoryTab/styles.tsx b/src/V4/social/components/StoryTab/styles.tsx new file mode 100644 index 000000000..c4862567a --- /dev/null +++ b/src/V4/social/components/StoryTab/styles.tsx @@ -0,0 +1,104 @@ +import styled from 'styled-components'; +import React from 'react'; +import Avatar from '~/core/components/Avatar'; +import { AddIcon, ErrorIcon } from '~/V4/icons'; + +export const ErrorButton = styled(ErrorIcon)` + position: absolute; + bottom: 0; + right: 0; + cursor: pointer; + z-index: 2; +`; + +export const AddStoryButton = styled(AddIcon)` + position: absolute; + bottom: 0; + right: 0; + cursor: pointer; + z-index: 2; +`; + +export const StoryWrapper = styled.div` + width: 3rem; + height: 3rem; + position: relative; + cursor: pointer; +`; + +export const StoryTabContainer = styled.div` + position: relative; + width: 3rem; + display: flex; + gap: 0.13rem; + flex-direction: column; + text-align: center; + padding: 1rem 0.75rem; + align-items: center; +`; + +export const StoryAvatar = styled(Avatar)` + width: 2.5rem; + height: 2.5rem; + position: absolute; + top: 0.25rem; + left: 0.25rem; + z-index: 1; + cursor: pointer; +`; + +export const StoryTitle = styled.div` + ${({ theme }) => theme.typography.caption}; + color: ${({ theme }) => theme.palette.base.main}; + cursor: pointer; +`; + +export const AddButton = styled(AddIcon)` + position: absolute; + bottom: 0; + right: 0; + cursor: pointer; + z-index: 2; +`; + +export const StoryRing = (props: React.SVGProps) => { + return ( + + + + + + + + + + ); +}; + +export const StoryInputLabel = styled.label.attrs({ htmlFor: 'story-input' })` + position: absolute; + bottom: -5px; + right: 0; + z-index: 9999; + cursor: pointer; +`; diff --git a/src/V4/social/components/StoryTab/ui.stories.tsx b/src/V4/social/components/StoryTab/ui.stories.tsx new file mode 100644 index 000000000..b95432470 --- /dev/null +++ b/src/V4/social/components/StoryTab/ui.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import StoryTab from '.'; +import StoryRing from './StoryRing'; +import { useArgs } from '@storybook/client-api'; + +export default { + title: 'Ui Only', +}; + +export const UiStoryTab = { + render: () => { + return ; + }, + name: 'Story Tab', +}; + +export const UiStoryRing = { + render: () => { + const [{ uploading }] = useArgs(); + return ; + }, + name: 'Story Ring', + args: { + uploading: false, + }, + argTypes: { + uploading: { control: 'boolean' }, + }, +}; diff --git a/src/V4/social/components/StoryViewer/Footer/index.tsx b/src/V4/social/components/StoryViewer/Footer/index.tsx new file mode 100644 index 000000000..eaa3c4a58 --- /dev/null +++ b/src/V4/social/components/StoryViewer/Footer/index.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + ViewStoryCompostBarContainer, + ViewStoryCompostBarViewIconContainer, + ViewStoryCompostBarEngagementContainer, + ViewStoryCompostBarEngagementIconContainer, + ViewCountIcon, + ViewStoryFailedCompostBarContainer, + ViewStoryFailedCompostBarWrapper, +} from './styles'; +import { AlertCircleIcon, CommentIcon, DotsIcon, LikeIcon } from '~/V4/icons'; +import { useIntl } from 'react-intl'; + +type StoryViewerFooterProps = { + viewCount: string; + commentCount: string; + likeCount: string; + viewIcon?: React.ReactNode; + commentIcon?: React.ReactNode; + likeIcon?: React.ReactNode; + isUploading?: boolean; + isErrored?: boolean; +}; + +const StoryViewerFooter = ({ + viewCount, + commentCount, + likeCount, + viewIcon = , + commentIcon = , + likeIcon = , + isUploading = false, + isErrored = false, +}: StoryViewerFooterProps) => { + const { formatMessage } = useIntl(); + if (isErrored) { + return ( + + + + {formatMessage({ id: 'storyViewer.footer.failed' })} + + + + ); + } + + if (isUploading) { + return ( + + {formatMessage({ id: 'storyViewer.footer.uploading' })} + + ); + } + + return ( + + + {viewIcon} + {viewCount} + + + + {commentIcon} {commentCount} + + + {likeIcon} {likeCount} + + + + ); +}; + +export default StoryViewerFooter; diff --git a/src/V4/social/components/StoryViewer/Footer/styles.tsx b/src/V4/social/components/StoryViewer/Footer/styles.tsx new file mode 100644 index 000000000..549896136 --- /dev/null +++ b/src/V4/social/components/StoryViewer/Footer/styles.tsx @@ -0,0 +1,126 @@ +import styled from 'styled-components'; +import { CommentIcon, LikeIcon, ViewIcon } from '~/V4/icons'; + +export const ViewCountIcon = styled(ViewIcon)` + color: #a5a9b5; +`; + +export const LikeButton = styled(LikeIcon)``; + +export const CommentButton = styled(CommentIcon)``; + +export const StoryContainer = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; +`; + +export const ViewStoryInfoContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + width: 100%; +`; + +export const StoryAvatar = styled.img` + width: 4rem; + height: 4rem; + object-fit: cover; + border-radius: 50%; + margin-bottom: 0.5rem; +`; + +export const StoryHeading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StorySubheading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StoryImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; + +export const StoryTabBarContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + height: 5rem; +`; + +export const ViewStoryCompostBarContainer = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: 0.75rem; + background-color: #000; + bottom: 0; + color: #ffffff; +`; + +export const ViewStoryFailedCompostBarContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 3.5rem; + padding: 0.75rem; + background-color: ${({ theme }) => theme.palette.alert.main}; + color: #ffffff; + z-index: 0; +`; + +export const ViewStoryFailedCompostBarWrapper = styled.div` + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.5rem; + width: 100%; +`; + +export const ViewStoryCompostBarViewIconContainer = styled.div` + ${({ theme }) => theme.typography.bodyBold}; + color: #fff; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.25rem; +`; + +export const ViewStoryCompostBarEngagementContainer = styled.div` + ${({ theme }) => theme.typography.bodyBold}; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +`; + +export const ViewStoryCompostBarEngagementIconContainer = styled.div` + ${({ theme }) => theme.typography.bodyBold}; + color: #fff; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.25rem; + border-radius: 1.5rem; + padding: 0.5rem 0.625rem; + background-color: #292b32; +`; + +export const ViewStoryContainer = styled.div` + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: black; +`; diff --git a/src/V4/social/components/StoryViewer/Header/index.tsx b/src/V4/social/components/StoryViewer/Header/index.tsx new file mode 100644 index 000000000..c84b92c84 --- /dev/null +++ b/src/V4/social/components/StoryViewer/Header/index.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { + ViewStoryHeaderContainer, + ViewStoryHeadingInfoContainer, + AvatarContainer, + ViewStoryHeaderListActionsContainer, + ViewStoryInfoContainer, + ViewStoryHeading, + ViewStorySubheading, + CloseButton, + VerifiedBadge, + DotsButton, + AddStoryButton, + PauseStoryButton, + PlayStoryButton, +} from './styles'; +import Avatar from '~/core/components/Avatar'; +import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; + +interface StoryViewerHeaderProps { + heading: string | undefined; + subheading: string | undefined; + avatarUrl: string | undefined; + isOfficial: boolean; + isPaused: boolean; + isCreator: boolean; + isMobile?: boolean; + onPause: () => void; + onPlay: () => void; + onClose: () => void; + onAction: () => void; + onClickCommunity: () => void; + onAddStory?: () => void; +} + +const StoryViewerHeader = ({ + heading, + subheading, + avatarUrl, + isOfficial, + isPaused, + isMobile, + isCreator, + onPause, + onPlay, + onClose, + onAction, + onAddStory, + onClickCommunity, +}: StoryViewerHeaderProps) => { + return ( + + + + + {onAddStory && ( + { + e.stopPropagation(); + onAddStory(); + }} + /> + )} + + + + {heading} + {isOfficial && } + + {subheading} + + + {!isMobile && isPaused && } + {!isMobile && !isPaused && } + {isCreator && } + + + + + ); +}; + +export default StoryViewerHeader; diff --git a/src/V4/social/components/StoryViewer/Header/styles.tsx b/src/V4/social/components/StoryViewer/Header/styles.tsx new file mode 100644 index 000000000..00764d450 --- /dev/null +++ b/src/V4/social/components/StoryViewer/Header/styles.tsx @@ -0,0 +1,217 @@ +import styled from 'styled-components'; +import { CloseIcon, DotsIcon, VerifiedIcon, AddIcon, PauseIcon, PlayIcon } from '~/V4/icons'; + +export const CloseButton = styled(CloseIcon)` + width: 1.5rem; + height: 1.5rem; + fill: #ffffff; + cursor: pointer; +`; + +export const VerifiedBadge = styled(VerifiedIcon)` + color: #ffffff; +`; + +export const DotsButton = styled(DotsIcon)` + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + color: #ffffff; +`; + +export const ViewStoryInfoContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + width: 100%; +`; + +export const StoryAvatar = styled.img` + width: 4rem; + height: 4rem; + object-fit: cover; + border-radius: 50%; + margin-bottom: 0.5rem; +`; + +export const StoryHeading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StorySubheading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StoryImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; + +export const StoryTabBarContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + height: 5rem; +`; + +export const ViewStoryCompostBarContainer = styled.div` + width: 100%; + display: flex; + position: absolute; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: 0.75rem; + background-color: #000; + bottom: 0; +`; + +export const ViewStoryCompostBarViewIconContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; +`; + +export const ViewStoryCompostBarEngagementContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.75rem; +`; + +export const ViewStoryCompostBarEngagementIconContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; + border-radius: 50%; + padding: 0.5rem 0.625rem; + background-color: #292b32; +`; + +export const StoryContentContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + height: 5rem; +`; + +export const StoryContent = styled.div` + flex: 1; +`; + +export const Header = styled.div` + height: 5rem; + padding: 0.75rem 1rem 0.625rem 1rem; +`; + +export const ViewStoryContainer = styled.div` + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: black; +`; + +export const ViewStoryHeaderContainer = styled.div` + z-index: 9999; + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + padding: 1.5rem 1rem 0.625rem 1rem; + gap: 0.5rem; +`; + +export const AvatarContainer = styled.div` + position: relative; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + flex-shrink: 0; +`; + +export const Avatar = styled.img` + width: 100%; + height: 100%; + border-radius: 50%; +`; + +export const PlayStoryButton = styled(PlayIcon)` + color: #ffffff; + + &:hover { + cursor: pointer; + } +`; + +export const PauseStoryButton = styled(PauseIcon)` + color: #ffffff; + + &:hover { + cursor: pointer; + } +`; + +export const AddStoryButton = styled(AddIcon)` + position: absolute; + bottom: 0; + right: 0; + + &:hover { + cursor: pointer; + } +`; + +export const ViewStoryHeaderListActionsContainer = styled.div` + display: flex; + gap: 1.25rem; + justify-content: flex-end; + align-items: center; +`; + +export const ViewStoryHeadingInfoContainer = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + gap: 0.75rem; + align-items: center; +`; + +export const ViewStoryHeading = styled.div` + cursor: pointer; + display: flex; + gap: 0.25rem; + color: #fff; + font-size: 0.938rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; + letter-spacing: -0.24px; + margin-right: 0.25rem; + align-items: center; +`; + +export const ViewStorySubheading = styled.span` + display: inline-flex; + gap: 0.25rem; + margin-bottom: 0.25rem; + color: #fff; + font-size: 0.813rem; + font-style: normal; + font-weight: 400; + line-height: 1.25rem; + letter-spacing: -0.1px; +`; diff --git a/src/V4/social/components/StoryViewer/StoryViewer.tsx b/src/V4/social/components/StoryViewer/StoryViewer.tsx new file mode 100644 index 000000000..7c31fdf42 --- /dev/null +++ b/src/V4/social/components/StoryViewer/StoryViewer.tsx @@ -0,0 +1,294 @@ +import React, { useEffect, useState } from 'react'; + +import StoryViewerHeader from './Header'; +import StoryViewerFooter from './Footer'; +import { + StoryActionItem, + StoryActionItemText, + StoryActionSheet, + StoryActionSheetContent, + StoryArrowLeftButton, + StoryArrowRightButton, + StoryWrapper, + ViewStoryContainer, + ViewStoryContent, + ViewStoryOverlay, +} from './styles'; + +import Stories from 'react-insta-stories'; +import useStories from '~/V4/social/hooks/useStories'; +import millify from 'millify'; + +import { formatTimeAgo } from '~/utils'; +import { StoryViewerProps } from './types'; +import { StoryDraft } from '../StoryDraft'; +import { StoryRepository } from '@amityco/ts-sdk'; +import { extractColors } from 'extract-colors'; +import { FinalColor } from 'extract-colors/lib/types/Color'; +import useImage from '~/core/hooks/useImage'; +import { useNavigation } from '~/social/providers/NavigationProvider'; +import { confirm } from '~/core/components/Confirm'; +import { useIntl } from 'react-intl'; +import { notification } from '~/core/components/Notification'; +import { useMediaQuery } from '../../hooks/useMediaQuery'; +import Trash from '~/V4/icons/Trash'; +import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; +import useSDK from '~/core/hooks/useSDK'; +import useUser from '~/core/hooks/useUser'; + +const StoryViewer = ({ targetId, duration, onClose }: StoryViewerProps) => { + const { stories, isLoading } = useStories({ + targetId, + targetType: 'community', + options: { + orderBy: 'asc', + sortBy: 'createdAt', + }, + }); + const { currentUserId } = useSDK(); + const user = useUser(currentUserId); + + const { onClickCommunity } = useNavigation(); + const { formatMessage } = useIntl(); + const isMobile = useMediaQuery('(max-width: 768px)'); + + const [paused, setPaused] = useState(false); + const [isOpen, setOpen] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + const [currentStory, setCurrentStory] = useState(); + const [file, setFile] = useState(null); + const [colors, setColors] = useState([]); + const [isUploading, setUploading] = useState(false); + + const openActionSheet = () => { + setOpen(true); + }; + + const closeActionSheet = () => { + setOpen(false); + }; + + const confirmDeleteStory = (storyId: string) => { + confirm({ + title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), + content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), + okText: formatMessage({ id: 'delete' }), + onOk: async () => { + closeActionSheet(); + previousStory(); + await StoryRepository.softDeleteStory(storyId); + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.deleted' }), + }); + if (stories?.length === 0) { + onClose(); + } + }, + }); + }; + + const deleteStory = async (storyId: string) => { + confirmDeleteStory(storyId); + }; + + const nextStory = () => { + if (currentIndex === formattedStories.length - 1) { + onClose(); + return; + } + setCurrentIndex(currentIndex + 1); + }; + + const previousStory = () => { + if (currentIndex === 0) return; + setCurrentIndex(currentIndex - 1); + }; + + useEffect(() => { + if (currentIndex >= 0 && currentIndex < formattedStories.length) { + const updatedCurrentStory = stories[currentIndex] as Amity.Story; + setCurrentStory({ ...updatedCurrentStory }); + } + }, [currentIndex, stories]); + + const formattedStories = stories?.map((story) => + story?.dataType === 'video' + ? { + id: story?.storyId, + url: story?.videoData?.fileUrl, + type: 'video', + } + : { + id: story?.storyId, + url: story?.imageData?.fileUrl, + type: 'image', + }, + ); + + const isCurrentStorySyncing = currentStory?.syncState === 'syncing'; + const isCurrentStoryErrored = currentStory?.syncState === 'error'; + const viewCount = millify(currentStory?.reach || 0); + const commentCount = millify(currentStory?.commentsCount || 0); + const likeCount = millify(currentStory?.reactions?.like || 0); + + const heading = currentStory?.community?.displayName; + const subheading = `${formatTimeAgo(currentStory?.createdAt as string)} • By ${ + currentStory?.creator?.displayName + }`; + const avatarUrl = useImage({ + fileId: currentStory?.community?.avatarFileId, + imageSize: 'small', + }); + + const isOfficial = currentStory?.community?.isOfficial || false; + + const storyPaused = isOpen || paused; + + const targetRootId = 'stories-viewer'; + + const pauseStory = () => { + setPaused(true); + }; + + const playStory = () => { + setPaused(false); + }; + + const isCreator = currentStory?.creatorId === user?._id; + + const onCreateStory = async ( + file: File, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata, + items?: Amity.StoryItem[], + ) => { + setUploading(true); + const formData = new FormData(); + formData.append('files', file); + if (file?.type.includes('image')) { + await StoryRepository.createImageStory( + 'community', + targetId, + formData, + metadata, + imageMode, + items, + ); + setUploading(false); + } else { + await StoryRepository.createVideoStory('community', targetId, formData, metadata, items); + setUploading(false); + } + }; + + useEffect(() => { + const extractColorsFromImage = async (url: string) => { + const colorsFromImage = await extractColors(url, { + crossOrigin: 'anonymous', + }); + + setColors(colorsFromImage); + }; + + if (currentStory?.dataType === 'image') { + extractColorsFromImage(currentStory?.imageData?.fileUrl as string); + } else { + setColors([]); + } + }, [currentStory?.imageData?.fileUrl]); + + if (isLoading || stories?.length === 0) { + return null; + } + + if (file) { + return ( + setFile(null)} + onCreateStory={onCreateStory} + /> + ); + } + + return ( + + {!isMobile && } + + + + onClickCommunity(targetId)} + /> + 0 ? colors[0].hex : '#000'} 0%, + ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% + )`, + }} + preventDefault={!isMobile} + isPaused={storyPaused} + currentIndex={currentIndex} + stories={formattedStories} + defaultInterval={duration || 5000} + onStoryStart={() => currentStory?.analytics.markAsSeen()} + onNext={nextStory} + onPrevious={previousStory} + onStoryEnd={nextStory} + /> + + + + + {!isCreator && ( + deleteStory(currentStory?.storyId as string)}> + + {formatMessage({ id: 'delete' })} + + )} + + + + + + + + {!isMobile && } + + ); +}; + +export default StoryViewer; diff --git a/src/V4/social/components/StoryViewer/index.tsx b/src/V4/social/components/StoryViewer/index.tsx new file mode 100644 index 000000000..804a36cae --- /dev/null +++ b/src/V4/social/components/StoryViewer/index.tsx @@ -0,0 +1 @@ +export { default as StoryViewer } from './StoryViewer'; diff --git a/src/V4/social/components/StoryViewer/sdk.stories.tsx b/src/V4/social/components/StoryViewer/sdk.stories.tsx new file mode 100644 index 000000000..381591fd3 --- /dev/null +++ b/src/V4/social/components/StoryViewer/sdk.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { StoryViewer } from '.'; +import useCommunitiesList from '~/social/hooks/useCommunitiesList'; + +export default { + title: 'SDK Connected/Social/Story', +}; + +export const SDKStory = { + render: () => { + const communityId = ''; + return ; + }, + name: 'Story Viewer', +}; diff --git a/src/V4/social/components/StoryViewer/styles.tsx b/src/V4/social/components/StoryViewer/styles.tsx new file mode 100644 index 000000000..dfb24a3c9 --- /dev/null +++ b/src/V4/social/components/StoryViewer/styles.tsx @@ -0,0 +1,339 @@ +import styled from 'styled-components'; +import { + CloseIcon, + CommentIcon, + DotsIcon, + LikeIcon, + TrashIcon, + ViewIcon, + VerifiedIcon, + ArrowRightCircleIcon, + ArrowLeftCircleIcon, +} from '~/V4/icons'; +import Sheet from 'react-modal-sheet'; + +export const StoryWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + margin: 2rem; + gap: 1rem; + background-color: black; +`; + +export const StoryActionSheet = styled(Sheet)` + margin: 0 auto; + max-width: 23.438rem; +`; + +export const CloseButton = styled(CloseIcon)` + color: #ffffff; + cursor: pointer; +`; + +export const VerifiedBadge = styled(VerifiedIcon)` + color: #ffffff; +`; + +export const DotsButton = styled(DotsIcon)` + cursor: pointer; + color: #ffffff; +`; + +export const ViewCountIcon = styled(ViewIcon)` + color: #a5a9b5; +`; + +export const LikeButton = styled(LikeIcon)``; + +export const CommentButton = styled(CommentIcon)``; + +export const StoryContainer = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; +`; + +export const ViewStoryInfoContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + width: 100%; +`; + +export const StoryAvatar = styled.img` + width: 4rem; + height: 4rem; + object-fit: cover; + border-radius: 50%; + margin-bottom: 0.5rem; +`; + +export const StoryHeading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StorySubheading = styled.span` + font-weight: bold; + margin-bottom: 0.25rem; +`; + +export const StoryImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; + +export const StoryTabBarContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + height: 5rem; +`; + +export const ViewStoryCompostBarContainer = styled.div` + width: 100%; + display: flex; + position: absolute; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: 0.75rem; + background-color: #000; + bottom: 0; +`; + +export const ViewStoryCompostBarViewIconContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; +`; + +export const ViewStoryCompostBarEngagementContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.75rem; +`; + +export const ViewStoryCompostBarEngagementIconContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; + border-radius: 50%; + padding: 0.5rem 0.625rem; + background-color: #292b32; +`; + +export const StoryContentContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + height: 5rem; +`; + +export const ViewStoryContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +export const ViewStoryContent = styled.div` + position: relative; + width: 23.438rem; + height: 40.875rem; + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 2; +`; + +export const ViewStoryOverlay = styled.div` + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.16) 55.05%, rgba(255, 255, 255, 0) 96.52%); + z-index: 3; +`; + +export const ViewStoryHeaderContainer = styled.div` + z-index: 2; + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + padding: 1.5rem 1rem 0.625rem 1rem; + gap: 0.5rem; +`; + +export const AvatarContainer = styled.div` + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 2.5rem; + height: 2.5rem; + overflow: hidden; +`; + +export const Avatar = styled.img` + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; +`; + +export const ViewStoryHeaderListActionsContainer = styled.div` + display: flex; + gap: 1.25rem; + justify-content: flex-end; + align-items: center; +`; + +export const ViewStoryHeadingInfoContainer = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + gap: 0.75rem; + align-items: center; +`; + +export const ViewStoryHeading = styled.div` + display: flex; + gap: 0.25rem; + color: #fff; + font-size: 0.938rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; + letter-spacing: -0.24px; + margin-right: 0.25rem; + align-items: center; +`; + +export const ViewStorySubheading = styled.span` + display: inline-flex; + gap: 0.25rem; + margin-bottom: 0.25rem; + color: #fff; + font-size: 0.813rem; + font-style: normal; + font-weight: 400; + line-height: 1.25rem; + letter-spacing: -0.1px; +`; + +export const ViewStoryImageContainer = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +`; + +export const ViewStoryImageContent = styled.img` + flex: 1; + width: 100%; + height: 100%; + object-fit: cover; +`; + +export const ViewStoryVideoContent = styled.video` + flex: 1; + width: 100%; + height: 100%; +`; + +export const ViewStoryContentContainer = styled.div` + flex: 1; + width: 100%; + height: 100%; +`; + +export const TabBarContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + height: 5rem; +`; + +export const FlexContainer = styled.div` + display: flex; + flex-direction: column; + min-height: 100%; +`; + +export const ActionList = styled.ul` + list-style: none; +`; + +export const ActionItem = styled.li` + margin-bottom: 0.75rem; +`; + +export const ActionButton = styled.button` + border: none; + width: 100%; + display: flex; + align-items: center; + gap: 0.5rem; + background-color: transparent; + cursor: pointer; + justify-content: flex-start; +`; + +export const DeleteIcon = styled(TrashIcon)` + width: 1.5rem; + height: 1.5rem; + color: #292b32; +`; + +export const StoryActionSheetContent = styled(Sheet.Content)` + padding: 1rem 1.3rem; +`; + +export const StoryActionItem = styled.button` + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + width: 100%; + background-color: #fff; + cursor: pointer; + color: #292b32; + padding: 0.5rem; + border-color: transparent; + border-radius: 0.25rem; + + &:hover { + background-color: ${({ theme }) => theme.palette.base.shade4}; + cursor: pointer; + } +`; + +export const StoryActionItemText = styled.div` + ${({ theme }) => theme.typography.bodyBold}; + color: ${({ theme }) => theme.palette.base.main}; +`; + +export const StoryArrowLeftButton = styled(ArrowLeftCircleIcon)` + cursor: pointer; +`; + +export const StoryArrowRightButton = styled(ArrowRightCircleIcon)` + cursor: pointer; +`; diff --git a/src/V4/social/components/StoryViewer/types.ts b/src/V4/social/components/StoryViewer/types.ts new file mode 100644 index 000000000..d727554ab --- /dev/null +++ b/src/V4/social/components/StoryViewer/types.ts @@ -0,0 +1,17 @@ +const FIVE_SECONDS = 5000; +const SEVEN_SECONDS = 7000; +const TEN_SECONDS = 10000; + +export const DURATION = { + FIVE_SECONDS, + SEVEN_SECONDS, + TEN_SECONDS, +} as const; + +export interface StoryViewerProps { + targetId: string; + onClose: () => void; + duration?: typeof DURATION[keyof typeof DURATION]; + commentIcon?: React.ReactNode; + likeIcon?: React.ReactNode; +} diff --git a/src/V4/social/hooks/index.ts b/src/V4/social/hooks/index.ts new file mode 100644 index 000000000..c3ff0466f --- /dev/null +++ b/src/V4/social/hooks/index.ts @@ -0,0 +1 @@ +export { default as useStories } from './useStories'; diff --git a/src/V4/social/hooks/useMediaQuery.tsx b/src/V4/social/hooks/useMediaQuery.tsx new file mode 100644 index 000000000..3581d4aef --- /dev/null +++ b/src/V4/social/hooks/useMediaQuery.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react'; + +const getMatches = (query: string): boolean => { + // Prevents SSR issues + if (typeof window !== 'undefined') { + return window.matchMedia(query).matches; + } + return false; +}; + +export function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(getMatches(query)); + + useEffect(() => { + function handleChange() { + setMatches(getMatches(query)); + } + + const matchMedia = window.matchMedia(query); + + // Triggered at the first client-side load and if query changes + handleChange(); + + // Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135) + if (matchMedia.addListener) { + matchMedia.addListener(handleChange); + } else { + matchMedia.addEventListener('change', handleChange); + } + + return () => { + if (matchMedia.removeListener) { + matchMedia.removeListener(handleChange); + } else { + matchMedia.removeEventListener('change', handleChange); + } + }; + }, [query]); + + return matches; +} diff --git a/src/V4/social/hooks/useNavigation.tsx b/src/V4/social/hooks/useNavigation.tsx new file mode 100644 index 000000000..0401548ef --- /dev/null +++ b/src/V4/social/hooks/useNavigation.tsx @@ -0,0 +1,333 @@ +import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { confirm } from '~/core/components/Confirm'; +import { PageTypes } from '~/social/constants'; + +type Page = + | { + type: PageTypes.Explore | PageTypes.NewsFeed; + communityId?: string; + } + | { + type: PageTypes.CommunityFeed; + communityId: string; + isNewCommunity: boolean; + } + | { + type: PageTypes.CommunityEdit; + communityId: string; + tab: string; + } + | { + type: PageTypes.Category; + categoryId: string; + communityId?: string; + } + | { + type: PageTypes.UserFeed | PageTypes.UserEdit; + userId: string; + communityId?: string; + }; + +type ContextValue = { + page: Page; + onChangePage: (type: string) => void; + onClickCategory: (categoryId: string) => void; + onClickCommunity: (communityId: string) => void; + onClickUser: (userId: string, pageType?: string) => void; + onCommunityCreated: (communityId: string) => void; + onEditCommunity: (communityId: string, tab?: string) => void; + onEditUser: (userId: string) => void; + onMessageUser: (userId: string) => void; + onBack: () => void; + setNavigationBlocker?: ( + params: + | { + title: ReactNode; + content: ReactNode; + okText: ReactNode; + } + | null + | undefined, + ) => void; +}; + +let defaultValue: ContextValue = { + page: { type: PageTypes.NewsFeed, communityId: undefined }, + onChangePage: (type: string) => {}, + onClickCategory: (categoryId: string) => {}, + onClickCommunity: (communityId: string) => {}, + onClickUser: (userId: string) => {}, + onCommunityCreated: (communityId: string) => {}, + onEditCommunity: (communityId: string) => {}, + onEditUser: (userId: string) => {}, + onMessageUser: (userId: string) => {}, + setNavigationBlocker: () => {}, + onBack: () => {}, +}; + +const defaultAskForConfirmation = ({ onSuccess: onOk, ...params }: { onSuccess: () => void }) => + confirm({ + ...params, + onOk, + }); + +export const defaultNavigationBlocker = { + title: , + content: , + okText: , +}; + +if (process.env.NODE_ENV !== 'production') { + defaultValue = { + page: { type: PageTypes.NewsFeed, communityId: undefined }, + onChangePage: (type) => console.log(`NavigationContext onChangePage(${type})`), + onClickCategory: (categoryId) => + console.log(`NavigationContext onClickCategory(${categoryId})`), + onClickCommunity: (communityId) => + console.log(`NavigationContext onClickCommunity(${communityId})`), + onClickUser: (userId) => console.log(`NavigationContext onClickUser(${userId})`), + onCommunityCreated: (communityId) => + console.log(`NavigationContext onCommunityCreated(${communityId})`), + onEditCommunity: (communityId) => + console.log(`NavigationContext onEditCommunity({${communityId})`), + onEditUser: (userId) => console.log(`NavigationContext onEditUser(${userId})`), + onMessageUser: (userId) => console.log(`NavigationContext onMessageUser(${userId})`), + onBack: () => console.log('NavigationContext onBack()'), + }; +} + +export const NavigationContext = createContext(defaultValue); + +export const useNavigation = () => useContext(NavigationContext); + +interface NavigationProviderProps { + askForConfirmation?: (params: { + title: React.ReactNode; + content: React.ReactNode; + okText: React.ReactNode; + onSuccess: () => void; + onCancel: () => void; + }) => void; + children: React.ReactNode; + onChangePage?: (data: { type: string; [x: string]: string | boolean }) => void; + onClickCategory?: (categoryId: string) => void; + onClickCommunity?: (communityId: string) => void; + onClickUser?: (userId: string) => void; + onCommunityCreated?: (communityId: string) => void; + onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; + onEditUser?: (userId: string) => void; + onMessageUser?: (userId: string) => void; +} + +export default function NavigationProvider({ + askForConfirmation = defaultAskForConfirmation, + children, + onChangePage: onChangePageProp, + onClickCategory, + onClickCommunity, + onClickUser, + onCommunityCreated, + onEditCommunity, + onEditUser, + onMessageUser, +}: NavigationProviderProps) { + const [pages, setPages] = useState([ + { type: PageTypes.NewsFeed, communityId: undefined }, + ]); + const currentPage = useMemo(() => pages[pages.length - 1], [pages]); + const [navigationBlocker, setNavigationBlocker] = useState< + | { + title: ReactNode; + content: ReactNode; + okText: ReactNode; + } + | null + | undefined + >(); + + const confirmPageChange = useCallback(async () => { + if (navigationBlocker) { + // for more info about this, see https://ekoapp.atlassian.net/browse/UP-3462?focusedCommentId=77155 + return new Promise((resolve) => { + askForConfirmation({ + ...navigationBlocker, + onSuccess: () => { + setNavigationBlocker?.(undefined); + resolve(true); + }, + onCancel: () => resolve(false), + }); + }); + } + + return true; + }, [askForConfirmation, navigationBlocker]); + + const pushPage = useCallback( + async (newPage) => { + if (!(await confirmPageChange())) return; + + setPages((prevState) => [...prevState, newPage]); + }, + [confirmPageChange], + ); + + const popPage = () => { + setPages((prevState) => (prevState.length > 1 ? prevState.slice(0, -1) : prevState)); + }; + + const onChangePage = onChangePageProp + ? async (data: { type: string; [x: string]: string | boolean }) => { + if (!(await confirmPageChange())) return; + + onChangePageProp(data); + } + : null; + + const handleChangePage = useCallback( + (type) => { + // if (onChangePageProp) return onChangePage(type); + console.log('handleChangePage', type); + pushPage({ type }); + }, + [ + // onChangePageProp, + pushPage, + ], + ); + + const handleClickCommunity = useCallback( + (communityId) => { + const next = { + type: PageTypes.CommunityFeed, + communityId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickCommunity) return onClickCommunity(communityId); + + console.log('handleClickCommunity', { communityId }); + pushPage(next); + }, + [onChangePage, onClickCommunity, pushPage], + ); + + const handleCommunityCreated = useCallback( + (communityId) => { + const next = { + type: PageTypes.CommunityFeed, + communityId, + isNewCommunity: true, + }; + + if (onChangePage) return onChangePage(next); + if (onCommunityCreated) return onCommunityCreated(communityId); + + console.log('handleCommunityCreated', { communityId }); + pushPage(next); + }, + [onChangePage, onCommunityCreated, pushPage], + ); + + const handleClickCategory = useCallback( + (categoryId) => { + const next = { + type: PageTypes.Category, + categoryId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickCategory) return onClickCategory(categoryId); + + console.log('handleClickCategory', { categoryId }); + pushPage(next); + }, + [onChangePage, onClickCategory, pushPage], + ); + + const handleClickUser = useCallback( + (userId, pageType) => { + const next = { + type: pageType ?? PageTypes.UserFeed, + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickUser) return onClickUser(userId); + + console.log('handleClickUser', { userId }); + pushPage(next); + }, + [onChangePage, onClickUser, pushPage], + ); + + const handleEditUser = useCallback( + (userId) => { + const next = { + type: PageTypes.UserEdit, + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onEditUser) return onEditUser(userId); + + console.log('handleEditUser', { userId }); + pushPage(next); + }, + [onChangePage, onEditUser, pushPage], + ); + + const handleEditCommunity = useCallback( + (communityId, tab) => { + const next = { + type: PageTypes.CommunityEdit, + communityId, + tab, + }; + + if (onChangePage) return onChangePage(next); + if (onEditCommunity) return onEditCommunity(communityId, { tab }); + + console.log('handleEditCommunity', { communityId, tab }); + pushPage(next); + }, + [onChangePage, onEditCommunity, pushPage], + ); + + const handleMessageUser = useCallback( + (userId) => { + const next = { + type: 'conversation', + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onMessageUser) return onMessageUser(userId); + + console.log('handleMessageUser', { userId }); + // pushPage(next); + }, + [onChangePage, onMessageUser], + ); + + return ( + + {children} + + ); +} diff --git a/src/V4/social/hooks/useStories.tsx b/src/V4/social/hooks/useStories.tsx new file mode 100644 index 000000000..b1b0d1dda --- /dev/null +++ b/src/V4/social/hooks/useStories.tsx @@ -0,0 +1,62 @@ +import { StoryRepository } from '@amityco/ts-sdk'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { isNonNullable } from '~/helpers/utils'; + +type UseStories = { + stories: (Amity.Story | undefined)[]; + isLoading: boolean; + error: Error | null; + hasMore: boolean; + loadMore: () => void; +}; + +const useStories = (params: Amity.GetStoriesByTargetParam): UseStories => { + const [stories, setStories] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [hasMore, setHasMore] = useState(false); + const loadMoreFnRef = useRef<(() => void) | undefined | null>(null); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + + const loadMore = useCallback(() => { + if (loadMoreFnRef.current) { + setLoadMoreHasBeenCalled(true); + loadMoreFnRef.current?.(); + } + }, [loadMoreFnRef, loadMoreHasBeenCalled, isLoading, setIsLoading]); + + useEffect(() => { + function run() { + StoryRepository.getActiveStoriesByTarget( + { + targetId: params.targetId, + targetType: params.targetType, + options: params.options, + }, + ({ data, loading, error, hasNextPage, onNextPage }) => { + if (error) { + setError(error); + return; + } + if (data) { + setStories(data.filter(isNonNullable)); + } + setIsLoading(loading); + hasNextPage && setHasMore(hasNextPage); + loadMoreFnRef.current = onNextPage; + }, + ); + } + run(); + }, [params.targetId, params.targetType]); + + return { + stories, + isLoading, + error, + hasMore, + loadMore, + }; +}; + +export default useStories; diff --git a/src/V4/social/providers/index.ts b/src/V4/social/providers/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/components/Avatar/styles.tsx b/src/core/components/Avatar/styles.tsx index 294c80458..faeca0231 100644 --- a/src/core/components/Avatar/styles.tsx +++ b/src/core/components/Avatar/styles.tsx @@ -49,7 +49,7 @@ export const AvatarContainer = styled(AvatarContainerComponent)<{ className?: st ${({ size, backgroundImage, theme }) => ` height: ${SIZES[size]}px; width: ${SIZES[size]}px; - background: ${backgroundImage || theme.palette.base.shade3}}; + background: ${backgroundImage || theme.palette.primary.shade3}}; `}; `; diff --git a/src/core/components/Notification/styles.tsx b/src/core/components/Notification/styles.tsx index 85e8e1992..38337a12c 100644 --- a/src/core/components/Notification/styles.tsx +++ b/src/core/components/Notification/styles.tsx @@ -20,7 +20,8 @@ export const ErrorIcon = styled(Remove)<{ icon?: ReactNode }>` export const Notifications = styled.div` position: fixed; padding-top: 50px; - top: 0; + top: unset; /* Remove top position for mobile */ + bottom: 0; left: 0; right: 0; display: flex; @@ -28,6 +29,19 @@ export const Notifications = styled.div` align-items: center; z-index: 9999; pointer-events: none; + + @media (min-width: 768px) { + position: fixed; + padding-top: 50px; + top: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + align-items: center; + z-index: 9999; + pointer-events: none; + } `; export const NotificationContainer = styled.div` diff --git a/src/core/components/SideMenu/index.tsx b/src/core/components/SideMenu/index.tsx index 2911dc7fd..d409547e3 100644 --- a/src/core/components/SideMenu/index.tsx +++ b/src/core/components/SideMenu/index.tsx @@ -1,4 +1,34 @@ import styled from 'styled-components'; +import { CloseIcon } from '~/V4/icons'; + +export const SideMenuHeader = styled.div` + @media (min-width: 768px) { + display: none; + } + + display: flex; + gap: 1rem; + justify-content: flex-start; + align-items: center; + padding: 1rem; + border-bottom: 1px solid #e6e6e6; +`; + +export const SideMenuTitle = styled.div` + ${({ theme }) => theme.typography.title} +`; + +export const SideMenuCloseButton = styled.div` + width: 1.5rem; + height: 1.5rem; + cursor: pointer; +`; + +export const SideMenuCloseIcon = styled(CloseIcon)` + width: 1.5rem; + height: 1.5rem; + color: ${({ theme }) => theme.palette.base.main}; +`; export default styled.div` background-color: white; diff --git a/src/core/components/SideMenuSection/index.tsx b/src/core/components/SideMenuSection/index.tsx index 5c390a349..f67e1a262 100644 --- a/src/core/components/SideMenuSection/index.tsx +++ b/src/core/components/SideMenuSection/index.tsx @@ -4,13 +4,17 @@ import styled from 'styled-components'; // TODO - confirm colour with design const SectionContainer = styled.div` border-top: 1px solid #f7f7f8; - padding: 0 8px; + padding: 0.5rem; `; const ListHeading = styled.h4` - ${({ theme }) => theme.typography.title}; - padding: 0 8px; - margin: 1em 0; + display: none; + + @media (min-width: 768px) { + ${({ theme }) => theme.typography.title}; + padding: 0 8px; + margin: 1em 0; + } `; interface SideMenuSectionProps { diff --git a/src/i18n/en.json b/src/i18n/en.json index a21160961..f3b4bcd9e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -350,6 +350,18 @@ "chat_modal.title": "Create chat", "chat_composer.placeholder.displayName": "Enter display name here", "chat_composer.placeholder.channelId": "Enter channel ID here", + + "storyViewer.actions.deleteStory": "Delete story", + "storyViewer.action.confirmModal.title": "Delete this story?", + "storyViewer.action.confirmModal.content": "This story will be permanently deleted. You’ll no longer to see and find this story.", + "storyViewer.notification.deleted": "Story deleted", + "storyViewer.notification.success": "Successfully shared story", + "storyViewer.notification.error": "Failed to share story", + "storyViewer.footer.failed": "Failed to upload", + "storyViewer.footer.uploading": "Uploading...", + + "storyDraft.button.shareStory": "Share story", + "editChatMembersModal.title": "Add/Edit chat members", "editChatMembersModal.confirm.title": "Leave without finishing?", "editChatMembersModal.confirm.content": "Your progress won’t be saved. Are you sure to leave this page now?", diff --git a/src/social/components/CommunityForm/AvatarUploader.tsx b/src/social/components/CommunityForm/AvatarUploader.tsx index 5844da4aa..696bc5541 100644 --- a/src/social/components/CommunityForm/AvatarUploader.tsx +++ b/src/social/components/CommunityForm/AvatarUploader.tsx @@ -3,9 +3,7 @@ import styled from 'styled-components'; import { FileRepository } from '@amityco/ts-sdk'; import Loader from '~/core/components/Uploaders/Loader'; -import Uploader from '~/core/components/Uploaders/Uploader'; import UploaderImage from '~/core/components/Uploaders/Image'; -import { backgroundImage as communityCoverPlaceholder } from '~/icons/CommunityCoverPicture'; import CameraIcon from '~/icons/Camera'; import useFile from '~/core/hooks/useFile'; import useFileUpload, { getUpdatedTime, isAmityFile } from '~/core/hooks/useFileUpload'; @@ -62,6 +60,7 @@ const BgImage = styled.div<{ src: string }>` background-size: cover; background-position: center center; background-repeat: no-repeat; + background-color: ${({ theme }) => theme.palette.base.shade3}; `; interface ImageRendererProps { @@ -131,7 +130,7 @@ const AvatarUploader = ({ return ( - + void; joinCommunity: (communityId: string) => void; onClickLeaveCommunity: (communityId: string) => void; + onClickStory: (communityId: string) => void; + onClickCreateStory: (communityId: string) => void; canLeaveCommunity: boolean; canReviewPosts: boolean; + isStorySyncing: boolean; + haveStories: boolean; + isStoryErrored: boolean; name: string; postSetting: ValueOf; + setStoryFile: React.Dispatch>; + uploadingStory: boolean; } const UICommunityInfo = ({ @@ -63,16 +70,22 @@ const UICommunityInfo = ({ onEditCommunity, joinCommunity, onClickLeaveCommunity, + onClickStory, canLeaveCommunity, canReviewPosts, name, postSetting, + setStoryFile, + isStorySyncing, + haveStories, + isStoryErrored, + uploadingStory, }: UICommunityInfoProps) => { const { formatMessage } = useIntl(); return ( - + )} + onClickStory(communityId)} + onChange={setStoryFile} + /> + {isJoined && canEditCommunity && (