From fa2a1df6f411994c50343c10340ace6baec19a7d Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Sat, 25 Nov 2023 13:40:36 +0100 Subject: [PATCH 1/6] feat: support context menu creation --- package.json | 1 + pnpm-lock.yaml | 224 +++++++++++++++--- .../Components/MessageContextMenu/index.tsx | 69 ++++++ .../Components/MessageContextMenu/style.ts | 65 +++++ src/Message/index.tsx | 15 +- src/Message/style/message.ts | 1 + src/Stitches/stitches.config.tsx | 4 + .../storybookOnlyAssets/custom-delete.svg | 4 + src/assets/storybookOnlyAssets/icon-id.svg | 2 +- .../storybookOnlyAssets/icon-pencil.svg | 2 +- src/assets/storybookOnlyAssets/icon-pin.svg | 2 +- src/core/ConfigContext.ts | 43 +++- src/stories/Wrapper.tsx | 52 +++- 13 files changed, 443 insertions(+), 41 deletions(-) create mode 100644 src/Message/Components/MessageContextMenu/index.tsx create mode 100644 src/Message/Components/MessageContextMenu/style.ts create mode 100644 src/assets/storybookOnlyAssets/custom-delete.svg diff --git a/package.json b/package.json index 96a0368..c193369 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "homepage": "https://widgetbot-io.github.io/message-renderer", "dependencies": { + "@radix-ui/react-context-menu": "^2.1.5", "@stitches/react": "^1.3.1-1", "autobind-decorator": "^2.4.0", "color": "^4.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73c1141..0e8755e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@radix-ui/react-context-menu': + specifier: ^2.1.5 + version: 2.1.5(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) '@stitches/react': specifier: ^1.3.1-1 version: 1.3.1-1(react@18.2.0) @@ -2819,14 +2822,12 @@ packages: resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} dependencies: '@floating-ui/utils': 0.1.1 - dev: true /@floating-ui/dom@1.5.1: resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==} dependencies: '@floating-ui/core': 1.4.1 '@floating-ui/utils': 0.1.1 - dev: true /@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==} @@ -2837,11 +2838,9 @@ packages: '@floating-ui/dom': 1.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@floating-ui/utils@0.1.1: resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} - dev: true /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} @@ -3110,7 +3109,6 @@ packages: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: '@babel/runtime': 7.22.10 - dev: true /@radix-ui/react-arrow@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} @@ -3130,7 +3128,6 @@ packages: '@types/react': 18.0.26 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/react-collection@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} @@ -3153,7 +3150,6 @@ packages: '@types/react': 18.0.26 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/react-compose-refs@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} @@ -3167,7 +3163,31 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true + + /@radix-ui/react-context-menu@2.1.5(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-menu': 2.0.6(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false /@radix-ui/react-context@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} @@ -3181,7 +3201,6 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-direction@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} @@ -3195,7 +3214,6 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-dismissable-layer@1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} @@ -3221,6 +3239,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-dismissable-layer@1.0.5(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.0.26)(react@18.2.0) + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -3233,7 +3275,6 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-focus-scope@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==} @@ -3257,6 +3298,28 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-focus-scope@1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -3270,7 +3333,43 @@ packages: '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.26)(react@18.2.0) '@types/react': 18.0.26 react: 18.2.0 - dev: true + + /@radix-ui/react-menu@2.0.6(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@types/react': 18.0.26 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.0.26)(react@18.2.0) + dev: false /@radix-ui/react-popper@1.1.2(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} @@ -3301,6 +3400,35 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-popper@1.1.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-portal@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: @@ -3321,6 +3449,47 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-portal@1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/react-primitive': 1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.1(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.26)(react@18.2.0) + '@types/react': 18.0.26 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-primitive@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -3339,7 +3508,6 @@ packages: '@types/react': 18.0.26 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@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} @@ -3367,7 +3535,6 @@ packages: '@types/react': 18.0.26 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/react-select@1.2.2(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} @@ -3442,7 +3609,6 @@ packages: '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.26)(react@18.2.0) '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-toggle-group@1.0.4(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} @@ -3530,7 +3696,6 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} @@ -3545,7 +3710,6 @@ packages: '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} @@ -3560,7 +3724,6 @@ packages: '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.26)(react@18.2.0) '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} @@ -3574,7 +3737,6 @@ packages: '@babel/runtime': 7.22.10 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-use-previous@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} @@ -3603,7 +3765,6 @@ packages: '@radix-ui/rect': 1.0.1 '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-use-size@1.0.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} @@ -3618,7 +3779,6 @@ packages: '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.26)(react@18.2.0) '@types/react': 18.0.26 react: 18.2.0 - dev: true /@radix-ui/react-visually-hidden@1.0.3(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} @@ -3644,7 +3804,6 @@ packages: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: '@babel/runtime': 7.22.10 - dev: true /@reach/router@1.3.4(react-dom@18.2.0)(react@16.14.0): resolution: {integrity: sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==} @@ -5717,6 +5876,7 @@ packages: /anymatch@2.0.0: resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} + requiresBuild: true dependencies: micromatch: 3.1.10 normalize-path: 2.1.1 @@ -5763,7 +5923,6 @@ packages: engines: {node: '>=10'} dependencies: tslib: 2.4.1 - dev: true /aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} @@ -5907,6 +6066,7 @@ packages: /async-each@1.0.6: resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==} + requiresBuild: true dev: true optional: true @@ -6102,6 +6262,7 @@ packages: /binary-extensions@1.13.1: resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} engines: {node: '>=0.10.0'} + requiresBuild: true dev: true optional: true @@ -6524,6 +6685,7 @@ packages: /chokidar@2.1.8: resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies + requiresBuild: true dependencies: anymatch: 2.0.0 async-each: 1.0.6 @@ -7180,6 +7342,7 @@ packages: /delegate@3.2.0: resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==} + requiresBuild: true dev: true optional: true @@ -7212,7 +7375,6 @@ packages: /detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dev: true /detect-package-manager@2.0.1: resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==} @@ -8475,7 +8637,6 @@ packages: /get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - dev: true /get-npm-tarball-url@2.0.3: resolution: {integrity: sha512-R/PW6RqyaBQNWYaSyfrh54/qtcnOp22FHCCiRhSSZj0FP3KQWCsxxt0DzIdVTbwTqe9CtQfvl/FPD4UIPt4pqw==} @@ -8672,6 +8833,7 @@ packages: /good-listener@1.2.2: resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} + requiresBuild: true dependencies: delegate: 3.2.0 dev: true @@ -9076,7 +9238,6 @@ packages: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: loose-envify: 1.4.0 - dev: true /ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} @@ -9158,6 +9319,7 @@ packages: /is-binary-path@1.0.1: resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} engines: {node: '>=0.10.0'} + requiresBuild: true dependencies: binary-extensions: 1.13.1 dev: true @@ -11569,7 +11731,6 @@ packages: react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.0.26)(react@18.2.0) tslib: 2.4.1 - dev: true /react-remove-scroll@2.5.5(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} @@ -11588,7 +11749,6 @@ packages: tslib: 2.4.1 use-callback-ref: 1.3.0(@types/react@18.0.26)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.0.26)(react@18.2.0) - dev: true /react-style-singleton@2.2.1(@types/react@18.0.26)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} @@ -11605,7 +11765,6 @@ packages: invariant: 2.2.4 react: 18.2.0 tslib: 2.4.1 - dev: true /react-syntax-highlighter@11.0.3(react@18.2.0): resolution: {integrity: sha512-0v0ET2qn9oAam4K/Te9Q/2jtS4R2d6wUFqgk5VcxrCBm+4MB5BE+oQf2CA0RanUHbYaYFuagt/AugICU87ufxQ==} @@ -11697,6 +11856,7 @@ packages: /readdirp@2.2.1: resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} engines: {node: '>=0.10'} + requiresBuild: true dependencies: graceful-fs: 4.2.11 micromatch: 3.1.10 @@ -12056,6 +12216,7 @@ packages: /select@1.1.2: resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==} + requiresBuild: true dev: true optional: true @@ -12784,6 +12945,7 @@ packages: /tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + requiresBuild: true dev: true optional: true @@ -12900,7 +13062,6 @@ packages: /tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - dev: true /tsutils@3.21.0(typescript@4.9.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -13145,6 +13306,7 @@ packages: /upath@1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} + requiresBuild: true dev: true optional: true @@ -13201,7 +13363,6 @@ packages: '@types/react': 18.0.26 react: 18.2.0 tslib: 2.4.1 - dev: true /use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} @@ -13228,7 +13389,6 @@ packages: detect-node-es: 1.1.0 react: 18.2.0 tslib: 2.4.1 - dev: true /use@3.1.1: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} diff --git a/src/Message/Components/MessageContextMenu/index.tsx b/src/Message/Components/MessageContextMenu/index.tsx new file mode 100644 index 0000000..c338871 --- /dev/null +++ b/src/Message/Components/MessageContextMenu/index.tsx @@ -0,0 +1,69 @@ +import * as ContextMenu from "@radix-ui/react-context-menu"; +import type { ReactElement } from "react"; +import React, { useMemo } from "react"; +import { + ContextMenuItemType, + IconType, + useConfig, +} from "../../../core/ConfigContext"; +import * as Styles from "./style"; +import type { ChatMessage } from "../../../types"; +import { error } from "../../../utils/error"; +import SvgFromUrl from "../../../SvgFromUrl"; + +interface Props { + children: ReactElement; + message: ChatMessage; +} + +export function MessageContextMenu({ children, message }: Props) { + const { messageContextMenuItems } = useConfig(); + + if (!messageContextMenuItems) return children; + + const menuItems = useMemo( + () => messageContextMenuItems(message), + [message, messageContextMenuItems] + ); + + return ( + + {children} + + + {menuItems.map((value, index) => { + switch (value.type) { + case ContextMenuItemType.Separator: + return ; + case ContextMenuItemType.Item: + return ( + + {value.content}{" "} + + {value.icon.type === IconType.Svg ? ( + + ) : ( + + )} + + + ); + default: + error("Unknown menu item type", value.type); + return <>unknown menu item type; + } + })} + + + + ); +} diff --git a/src/Message/Components/MessageContextMenu/style.ts b/src/Message/Components/MessageContextMenu/style.ts new file mode 100644 index 0000000..195e0e5 --- /dev/null +++ b/src/Message/Components/MessageContextMenu/style.ts @@ -0,0 +1,65 @@ +import * as ContextMenu from "@radix-ui/react-context-menu"; +import { + commonComponentId, + styled, + theme, +} from "../../../Stitches/stitches.config"; + +export const Content = styled.withConfig({ + displayName: "context-menu-content", + componentId: commonComponentId, +})(ContextMenu.Content, { + backgroundColor: theme.colors.backgroundFloating, + fontFamily: theme.fonts.main, + fontSize: theme.fontSizes.m, + padding: theme.space.large, + borderRadius: 4, + minWidth: 188, + maxWidth: 320, +}); + +export const Item = styled(ContextMenu.Item, { + color: theme.colors.interactiveNormal, + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingTop: theme.space.medium, + paddingBottom: theme.space.medium, + paddingLeft: theme.space.large, + paddingRight: theme.space.large, + outline: "none", + borderRadius: 2, + marginTop: theme.space.xs, + marginBottom: theme.space.xs, + + "&:hover": { + backgroundColor: theme.colors.contextMenuItemHoverPrimary, + color: theme.colors.primaryFull, + cursor: "pointer", + }, + + variants: { + isDanger: { + true: { + color: theme.colors.danger, + + "&:hover": { + backgroundColor: theme.colors.danger, + }, + }, + }, + }, +}); + +export const ItemIcon = styled("div", { + display: "flex", + alignItems: "center", +}); + +export const Separator = styled(ContextMenu.Separator, { + margin: theme.space.small, + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: theme.colors.backgroundModifierAccent, +}); diff --git a/src/Message/index.tsx b/src/Message/index.tsx index bbe6ddd..616331f 100644 --- a/src/Message/index.tsx +++ b/src/Message/index.tsx @@ -20,6 +20,7 @@ import { MessageTypeResponse, useConfig } from "../core/ConfigContext"; import ThreadStarterMessage from "./variants/ThreadStarterMessage"; import AutomodAction from "./variants/AutomodAction"; import type { ChatMessage } from "../types"; +import { MessageContextMenu } from "./Components/MessageContextMenu"; export interface MessageProps { isFirstMessage?: boolean; @@ -200,12 +201,18 @@ function Message(props: MessageProps) { if (props.showButtons) return ( - - - + + + + + ); - return ; + return ( + + + + ); } export default memo(Message); diff --git a/src/Message/style/message.ts b/src/Message/style/message.ts index 53ca466..9c90b58 100644 --- a/src/Message/style/message.ts +++ b/src/Message/style/message.ts @@ -195,6 +195,7 @@ export namespace MessageContainerStyle { padding: theme.space.medium, opacity: 0.7, backgroundColor: "transparent", + color: theme.colors.primaryOpacity100, "&:hover": { backgroundColor: theme.colors.primaryOpacity10, diff --git a/src/Stitches/stitches.config.tsx b/src/Stitches/stitches.config.tsx index 259bbe3..431cc71 100644 --- a/src/Stitches/stitches.config.tsx +++ b/src/Stitches/stitches.config.tsx @@ -14,13 +14,17 @@ const stitches = createStitches({ primaryOpacity80: "rgba(255, 255, 255, 0.8)", primaryOpacity100: "rgba(255, 255, 255, 1.0)", primaryDark: "#72767d", + primaryFull: "#fff", systemMessageDark: "#999999", textMuted: "rgb(163, 166, 170)", interactiveNormal: "#dcddde", accent: "#5865f2", + contextMenuItemHoverPrimary: "#4752C4", background: "#36393f", backgroundSecondary: "#2b2d31", backgroundTertiary: "#1e1f22", + backgroundFloating: "#111214", + backgroundModifierAccent: "rgba(78, 80, 88, 0.48)", lazyImageBackground: "#2c2e32", messageHover: "rgba(0, 0, 0, .05)", link: "#00b0f4", diff --git a/src/assets/storybookOnlyAssets/custom-delete.svg b/src/assets/storybookOnlyAssets/custom-delete.svg new file mode 100644 index 0000000..9ec13ed --- /dev/null +++ b/src/assets/storybookOnlyAssets/custom-delete.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/storybookOnlyAssets/icon-id.svg b/src/assets/storybookOnlyAssets/icon-id.svg index a81f5e5..50bd3d6 100644 --- a/src/assets/storybookOnlyAssets/icon-id.svg +++ b/src/assets/storybookOnlyAssets/icon-id.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/storybookOnlyAssets/icon-pencil.svg b/src/assets/storybookOnlyAssets/icon-pencil.svg index 9cb5fbf..cadf1d9 100644 --- a/src/assets/storybookOnlyAssets/icon-pencil.svg +++ b/src/assets/storybookOnlyAssets/icon-pencil.svg @@ -1,7 +1,7 @@ + fill="currentColor"/> \ No newline at end of file diff --git a/src/assets/storybookOnlyAssets/icon-pin.svg b/src/assets/storybookOnlyAssets/icon-pin.svg index 3228741..dda6d80 100644 --- a/src/assets/storybookOnlyAssets/icon-pin.svg +++ b/src/assets/storybookOnlyAssets/icon-pin.svg @@ -1,3 +1,3 @@ - + diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index c0f0eb8..b4d5591 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -1,4 +1,4 @@ -import type { ReactElement } from "react"; +import type { ReactElement, ReactNode } from "react"; import { createContext, useContext } from "react"; import type { APIChannel, @@ -17,6 +17,46 @@ import type { ChatMessage } from "../types"; export type PartialSvgConfig = Partial; +export enum ContextMenuItemType { + Separator, + SubMenu, + Item, +} + +export interface Separator { + type: ContextMenuItemType.Separator; +} + +export interface SubMenu { + type: ContextMenuItemType.SubMenu; + items: ContextMenuItem[]; +} + +export enum IconType { + Svg, + Url, +} + +export interface MenuItem { + type: ContextMenuItemType.Item; + icon: + | { + type: IconType.Svg; + svg: keyof SC; + } + | { + type: IconType.Url; + url: string; + }; + content: ReactNode; + isDanger?: boolean; +} + +export type ContextMenuItem = + | Separator + | SubMenu + | MenuItem; + export interface MessageButtonListOption { onClick: () => void; icon: keyof SC; @@ -41,6 +81,7 @@ export type Config = { animated: string; }; messageButtons(message: ChatMessage): MessageButtonListOption[]; + messageContextMenuItems?(message: ChatMessage): ContextMenuItem[]; resolveRole(id: Snowflake): APIRole | null; resolveChannel(id: Snowflake): APIChannel | null; resolveMember(user: APIUser, guildId: Snowflake): APIGuildMember | null; diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index d788a5f..32cd822 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -37,6 +37,7 @@ import SvgIconVoiceChannel from "../assets/storybookOnlyAssets/icon-voice-channe import SvgIconStageChannel from "../assets/storybookOnlyAssets/icon-stage-channel.svg"; import SvgIconLinkExternal from "../assets/storybookOnlyAssets/icon-link-external.svg"; import SvgIconUnknownReply from "../assets/storybookOnlyAssets/icon-unknown-reply.svg"; +import SvgCustomDelete from "../assets/storybookOnlyAssets/custom-delete.svg"; import ggSansNormal400 from "../assets/storybookOnlyAssets/gg-sans-normal-400.woff2"; import ggSansNormal500 from "../assets/storybookOnlyAssets/gg-sans-normal-500.woff2"; @@ -67,9 +68,14 @@ import { globalCss, prefix, styled, theme } from "../Stitches/stitches.config"; import getDisplayName from "../utils/getDisplayName"; import type { ChatBadgeProps, + ContextMenuItem, MessageButtonListOption, } from "../core/ConfigContext"; -import { MessageTypeResponse } from "../core/ConfigContext"; +import { + ContextMenuItemType, + IconType, + MessageTypeResponse, +} from "../core/ConfigContext"; import { testTextChannel, testVoiceChannel } from "./commonTestData"; import type { Decorator } from "@storybook/react"; import type { ChatMessage } from "../types"; @@ -111,6 +117,8 @@ const svgUrls = { IconStageChannel: SvgIconStageChannel, IconLinkExternal: SvgIconLinkExternal, MiscDiscordImageFailure: SvgMiscDiscordImageFailure, + + CustomDelete: SvgCustomDelete, }; function getButtons( @@ -125,6 +133,47 @@ function getButtons( ]; } +function getContextMenuItems(): ContextMenuItem[] { + return [ + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconPencil", + }, + content: "Edit Message", + }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconPin", + }, + content: "Pin Message", + }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "CustomDelete", + }, + isDanger: true, + content: "Delete Message", + }, + { + type: ContextMenuItemType.Separator, + }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconId", + }, + content: "Copy Message ID", + }, + ]; +} + function resolveRole(id: Snowflake): APIRole | null { console.log("id", id); @@ -383,6 +432,7 @@ const Wrapper: Decorator = (Story) => { animated: automodAvatarAnimated, }} messageButtons={getButtons} + messageContextMenuItems={getContextMenuItems} resolveRole={resolveRole} resolveChannel={resolveChannel} resolveMember={resolveMember} From d0a580e1732888f80fc460cbf96f5235b02e1c72 Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Sat, 25 Nov 2023 13:51:17 +0100 Subject: [PATCH 2/6] add onSelect to items --- .../Components/MessageContextMenu/index.tsx | 6 +++++- src/core/ConfigContext.ts | 1 + src/stories/Wrapper.tsx | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Message/Components/MessageContextMenu/index.tsx b/src/Message/Components/MessageContextMenu/index.tsx index c338871..64d9254 100644 --- a/src/Message/Components/MessageContextMenu/index.tsx +++ b/src/Message/Components/MessageContextMenu/index.tsx @@ -37,7 +37,11 @@ export function MessageContextMenu({ children, message }: Props) { return ; case ContextMenuItemType.Item: return ( - + {value.content}{" "} {value.icon.type === IconType.Svg ? ( diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index b4d5591..719f381 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -50,6 +50,7 @@ export interface MenuItem { }; content: ReactNode; isDanger?: boolean; + onSelect?(): void; } export type ContextMenuItem = diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index 32cd822..df6f852 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -133,7 +133,9 @@ function getButtons( ]; } -function getContextMenuItems(): ContextMenuItem[] { +function getContextMenuItems( + message: ChatMessage +): ContextMenuItem[] { return [ { type: ContextMenuItemType.Item, @@ -142,6 +144,9 @@ function getContextMenuItems(): ContextMenuItem[] { svg: "IconPencil", }, content: "Edit Message", + onSelect() { + alert(`Edit Message selected on ${message.id}`); + }, }, { type: ContextMenuItemType.Item, @@ -150,6 +155,9 @@ function getContextMenuItems(): ContextMenuItem[] { svg: "IconPin", }, content: "Pin Message", + onSelect() { + alert(`Pin Message selected on ${message.id}`); + }, }, { type: ContextMenuItemType.Item, @@ -159,6 +167,9 @@ function getContextMenuItems(): ContextMenuItem[] { }, isDanger: true, content: "Delete Message", + onSelect() { + alert(`Delete Message selected on ${message.id}`); + }, }, { type: ContextMenuItemType.Separator, @@ -170,6 +181,9 @@ function getContextMenuItems(): ContextMenuItem[] { svg: "IconId", }, content: "Copy Message ID", + onSelect() { + alert(`Copy Message ID selected on ${message.id}`); + }, }, ]; } From cb757c8dce75f25ae1c0ee57302537b83f001180 Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Sat, 25 Nov 2023 14:45:24 +0100 Subject: [PATCH 3/6] add submenus --- src/Content/style.ts | 1 + .../Components/MessageContextMenu/index.tsx | 90 +++++++++++-------- .../Components/MessageContextMenu/style.ts | 22 +++-- .../storybookOnlyAssets/icon-attachment.svg | 4 +- .../storybookOnlyAssets/icon-command.svg | 3 +- src/assets/storybookOnlyAssets/misc-caret.svg | 7 ++ src/core/ConfigContext.ts | 1 + src/core/svgs.ts | 2 + src/stories/Wrapper.tsx | 44 +++++++++ 9 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 src/assets/storybookOnlyAssets/misc-caret.svg diff --git a/src/Content/style.ts b/src/Content/style.ts index 9bd7163..dbc0d1a 100644 --- a/src/Content/style.ts +++ b/src/Content/style.ts @@ -180,6 +180,7 @@ export const ReplyIcon = styled.withConfig({ marginLeft: theme.space.small, width: 20, height: 20, + color: theme.colors.primaryOpacity100, }); export const StickerTooltipIcon = styled.withConfig({ diff --git a/src/Message/Components/MessageContextMenu/index.tsx b/src/Message/Components/MessageContextMenu/index.tsx index 64d9254..b35752f 100644 --- a/src/Message/Components/MessageContextMenu/index.tsx +++ b/src/Message/Components/MessageContextMenu/index.tsx @@ -1,6 +1,10 @@ import * as ContextMenu from "@radix-ui/react-context-menu"; import type { ReactElement } from "react"; import React, { useMemo } from "react"; +import type { + ContextMenuItem, + PartialSvgConfig, +} from "../../../core/ConfigContext"; import { ContextMenuItemType, IconType, @@ -8,8 +12,54 @@ import { } from "../../../core/ConfigContext"; import * as Styles from "./style"; import type { ChatMessage } from "../../../types"; -import { error } from "../../../utils/error"; import SvgFromUrl from "../../../SvgFromUrl"; +import type { SvgConfig } from "../../../core/svgs"; + +function MenuItem({ + menuItem, +}: { + menuItem: ContextMenuItem; +}) { + switch (menuItem.type) { + case ContextMenuItemType.Separator: + return ; + case ContextMenuItemType.SubMenu: + return ( + + + {menuItem.content} + + + + + + + {menuItem.items.map((subMenuItem, index) => ( + + ))} + + + + ); + case ContextMenuItemType.Item: + return ( + + {menuItem.content} + + {menuItem.icon.type === IconType.Svg ? ( + + ) : ( + + )} + + + ); + } +} interface Props { children: ReactElement; @@ -31,41 +81,9 @@ export function MessageContextMenu({ children, message }: Props) { {children} - {menuItems.map((value, index) => { - switch (value.type) { - case ContextMenuItemType.Separator: - return ; - case ContextMenuItemType.Item: - return ( - - {value.content}{" "} - - {value.icon.type === IconType.Svg ? ( - - ) : ( - - )} - - - ); - default: - error("Unknown menu item type", value.type); - return <>unknown menu item type; - } - })} + {menuItems.map((value, index) => ( + + ))} diff --git a/src/Message/Components/MessageContextMenu/style.ts b/src/Message/Components/MessageContextMenu/style.ts index 195e0e5..a8a56f4 100644 --- a/src/Message/Components/MessageContextMenu/style.ts +++ b/src/Message/Components/MessageContextMenu/style.ts @@ -18,7 +18,10 @@ export const Content = styled.withConfig({ maxWidth: 320, }); -export const Item = styled(ContextMenu.Item, { +export const Item = styled.withConfig({ + displayName: "context-menu-item", + componentId: commonComponentId, +})(ContextMenu.Item, { color: theme.colors.interactiveNormal, display: "flex", flexDirection: "row", @@ -33,7 +36,7 @@ export const Item = styled(ContextMenu.Item, { marginTop: theme.space.xs, marginBottom: theme.space.xs, - "&:hover": { + '&[data-state="open"], &[data-highlighted]': { backgroundColor: theme.colors.contextMenuItemHoverPrimary, color: theme.colors.primaryFull, cursor: "pointer", @@ -44,7 +47,7 @@ export const Item = styled(ContextMenu.Item, { true: { color: theme.colors.danger, - "&:hover": { + '&[data-state="open"], &[data-highlighted]': { backgroundColor: theme.colors.danger, }, }, @@ -52,12 +55,21 @@ export const Item = styled(ContextMenu.Item, { }, }); -export const ItemIcon = styled("div", { +export const ItemIcon = styled.withConfig({ + displayName: "context-menu-item-icon", + componentId: commonComponentId, +})("div", { display: "flex", alignItems: "center", + justifyContent: "center", + width: 18, + height: 18, }); -export const Separator = styled(ContextMenu.Separator, { +export const Separator = styled.withConfig({ + displayName: "context-menu-separator", + componentId: commonComponentId, +})(ContextMenu.Separator, { margin: theme.space.small, borderBottomWidth: 1, borderBottomStyle: "solid", diff --git a/src/assets/storybookOnlyAssets/icon-attachment.svg b/src/assets/storybookOnlyAssets/icon-attachment.svg index e5a58e4..b2ed835 100644 --- a/src/assets/storybookOnlyAssets/icon-attachment.svg +++ b/src/assets/storybookOnlyAssets/icon-attachment.svg @@ -1,3 +1,5 @@ - + diff --git a/src/assets/storybookOnlyAssets/icon-command.svg b/src/assets/storybookOnlyAssets/icon-command.svg index bea8e4b..2c5698c 100644 --- a/src/assets/storybookOnlyAssets/icon-command.svg +++ b/src/assets/storybookOnlyAssets/icon-command.svg @@ -1,3 +1,4 @@ - + diff --git a/src/assets/storybookOnlyAssets/misc-caret.svg b/src/assets/storybookOnlyAssets/misc-caret.svg new file mode 100644 index 0000000..55c8954 --- /dev/null +++ b/src/assets/storybookOnlyAssets/misc-caret.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index 719f381..9f4b101 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -29,6 +29,7 @@ export interface Separator { export interface SubMenu { type: ContextMenuItemType.SubMenu; + content: ReactNode; items: ContextMenuItem[]; } diff --git a/src/core/svgs.ts b/src/core/svgs.ts index d41e892..65506c9 100644 --- a/src/core/svgs.ts +++ b/src/core/svgs.ts @@ -43,6 +43,7 @@ export type Svg = | "IconForumChannel" | "IconLinkExternal" | "MiscDiscordImageFailure" + | "MiscCaret" | `Custom${string}`; const defaultSvgUrls: Record = { @@ -82,6 +83,7 @@ const defaultSvgUrls: Record = { IconStageChannel: SvgMissingAsset, IconForumChannel: SvgMissingAsset, MiscDiscordImageFailure: SvgMissingAsset, + MiscCaret: SvgMissingAsset, IconLinkExternal: SvgMissingAsset, }; diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index df6f852..64f5fea 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -55,6 +55,7 @@ import automodAvatarStill from "../assets/storybookOnlyAssets/automod-avatar.png import automodAvatarAnimated from "../assets/storybookOnlyAssets/automod-avatar.gif"; import SvgMiscDiscordImageFailure from "../assets/storybookOnlyAssets/misc-discord-image-failure.svg"; +import SvgMiscCaret from "../assets/storybookOnlyAssets/misc-caret.svg"; import type { APIChannel, APIGuild, @@ -117,6 +118,7 @@ const svgUrls = { IconStageChannel: SvgIconStageChannel, IconLinkExternal: SvgIconLinkExternal, MiscDiscordImageFailure: SvgMiscDiscordImageFailure, + MiscCaret: SvgMiscCaret, CustomDelete: SvgCustomDelete, }; @@ -148,6 +150,48 @@ function getContextMenuItems( alert(`Edit Message selected on ${message.id}`); }, }, + { + type: ContextMenuItemType.SubMenu, + content: "Submenu", + items: [ + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconAttachment", + }, + content: "Submenu Item 1", + onSelect() { + alert("Submenu Item 1 selected"); + }, + }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconCommand", + }, + content: "Submenu Item 2", + onSelect() { + alert("Submenu Item 2 selected"); + }, + }, + { + type: ContextMenuItemType.Separator, + }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconDownload", + }, + content: "Submenu Item 3", + onSelect() { + alert("Submenu Item 3 selected"); + }, + }, + ], + }, { type: ContextMenuItemType.Item, icon: { From 7b8b31ffa540848f6d11777529b0911aa56f9ea9 Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Sat, 25 Nov 2023 14:56:13 +0100 Subject: [PATCH 4/6] disabled items --- src/Message/Components/MessageContextMenu/index.tsx | 13 +++++++++++-- src/Message/Components/MessageContextMenu/style.ts | 7 +++++++ src/core/ConfigContext.ts | 2 ++ src/stories/Wrapper.tsx | 12 ++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Message/Components/MessageContextMenu/index.tsx b/src/Message/Components/MessageContextMenu/index.tsx index b35752f..67a4a78 100644 --- a/src/Message/Components/MessageContextMenu/index.tsx +++ b/src/Message/Components/MessageContextMenu/index.tsx @@ -26,7 +26,11 @@ function MenuItem({ case ContextMenuItemType.SubMenu: return ( - + {menuItem.content} @@ -43,7 +47,12 @@ function MenuItem({ ); case ContextMenuItemType.Item: return ( - + {menuItem.content} {menuItem.icon.type === IconType.Svg ? ( diff --git a/src/Message/Components/MessageContextMenu/style.ts b/src/Message/Components/MessageContextMenu/style.ts index a8a56f4..f8ab3bd 100644 --- a/src/Message/Components/MessageContextMenu/style.ts +++ b/src/Message/Components/MessageContextMenu/style.ts @@ -43,6 +43,13 @@ export const Item = styled.withConfig({ }, variants: { + isDisabled: { + true: { + opacity: 0.7, + cursor: "not-allowed", + }, + }, + isDanger: { true: { color: theme.colors.danger, diff --git a/src/core/ConfigContext.ts b/src/core/ConfigContext.ts index 9f4b101..339f6c6 100644 --- a/src/core/ConfigContext.ts +++ b/src/core/ConfigContext.ts @@ -30,6 +30,7 @@ export interface Separator { export interface SubMenu { type: ContextMenuItemType.SubMenu; content: ReactNode; + isDisabled?: boolean; items: ContextMenuItem[]; } @@ -51,6 +52,7 @@ export interface MenuItem { }; content: ReactNode; isDanger?: boolean; + isDisabled?: boolean; onSelect?(): void; } diff --git a/src/stories/Wrapper.tsx b/src/stories/Wrapper.tsx index 64f5fea..ef033d2 100644 --- a/src/stories/Wrapper.tsx +++ b/src/stories/Wrapper.tsx @@ -203,6 +203,18 @@ function getContextMenuItems( alert(`Pin Message selected on ${message.id}`); }, }, + { + type: ContextMenuItemType.Item, + icon: { + type: IconType.Svg, + svg: "IconFullscreen", + }, + content: "Pop Out", + isDisabled: true, + onSelect() { + alert(`Pop Out selected on ${message.id}`); + }, + }, { type: ContextMenuItemType.Item, icon: { From d3e28865db82c4ca4ecd94a09a1357c868ad8c33 Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Sun, 14 Jan 2024 14:37:47 +0100 Subject: [PATCH 5/6] persist hover css when menu is open --- src/Message/Components/MessageContextMenu/index.tsx | 2 +- src/Message/Components/MessageContextMenu/style.ts | 6 ++++++ src/Message/style/message.ts | 8 +++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Message/Components/MessageContextMenu/index.tsx b/src/Message/Components/MessageContextMenu/index.tsx index 67a4a78..1e0988c 100644 --- a/src/Message/Components/MessageContextMenu/index.tsx +++ b/src/Message/Components/MessageContextMenu/index.tsx @@ -87,7 +87,7 @@ export function MessageContextMenu({ children, message }: Props) { return ( - {children} + {children} {menuItems.map((value, index) => ( diff --git a/src/Message/Components/MessageContextMenu/style.ts b/src/Message/Components/MessageContextMenu/style.ts index f8ab3bd..597c3cd 100644 --- a/src/Message/Components/MessageContextMenu/style.ts +++ b/src/Message/Components/MessageContextMenu/style.ts @@ -4,6 +4,12 @@ import { styled, theme, } from "../../../Stitches/stitches.config"; +import * as MessageStyles from "../../style/message"; + +export const Trigger = styled(ContextMenu.Trigger, { + [`&[data-state="open"] ${MessageStyles.Message}`]: + MessageStyles.messageHoverStyles, +}); export const Content = styled.withConfig({ displayName: "context-menu-content", diff --git a/src/Message/style/message.ts b/src/Message/style/message.ts index 9c90b58..1e595cb 100644 --- a/src/Message/style/message.ts +++ b/src/Message/style/message.ts @@ -33,6 +33,10 @@ export const LargeTimestamp = styled.withConfig({ lineHeight: `1.375rem`, }); +export const messageHoverStyles = { + backgroundColor: theme.colors.messageHover, +}; + export const Message = styled.withConfig({ displayName: "message", componentId: commonComponentId, @@ -58,9 +62,7 @@ export const Message = styled.withConfig({ // IF THERE IS A BUG WITH THE HOVER COLOR IT'S BECAUSE WE MOVED THIS // FROM THE CONTAINER TO HERE - "&:hover": { - backgroundColor: theme.colors.messageHover, - }, + "&:hover": messageHoverStyles, variants: { isMentioned: { From c90d20885c3ba759a15f226e71ff8f970d7a365e Mon Sep 17 00:00:00 2001 From: Tuur Martens Date: Mon, 15 Jan 2024 15:41:23 +0100 Subject: [PATCH 6/6] fix runtime warnings --- src/Message/MessageAuthor.tsx | 1 - src/Message/variants/AutomodAction.tsx | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Message/MessageAuthor.tsx b/src/Message/MessageAuthor.tsx index 53a8969..589674b 100644 --- a/src/Message/MessageAuthor.tsx +++ b/src/Message/MessageAuthor.tsx @@ -34,7 +34,6 @@ export function AutomodAuthor({ isAvatarAnimated }: AutomodAuthorProps) { interface MessageAuthorProps extends ComponentProps { author: APIUser; - isAvatarAnimated?: boolean; onlyShowUsername?: boolean; crossPost?: boolean; referenceGuild?: Snowflake; diff --git a/src/Message/variants/AutomodAction.tsx b/src/Message/variants/AutomodAction.tsx index f3d3c1b..7bdc5ce 100644 --- a/src/Message/variants/AutomodAction.tsx +++ b/src/Message/variants/AutomodAction.tsx @@ -218,21 +218,20 @@ function AutomodAction({ message, isHovered }: AutomodActionProps) { {messageContent.map((content, index) => ( - <> - {content} + + {content} {index < messageContent.length - 1 && ( - + {matches?.[index]} )} - + ))}