From 5dd7164ea797fe99e8a41e2a2a49fb1532a23282 Mon Sep 17 00:00:00 2001 From: Reguel Wermelinger Date: Tue, 30 Jun 2026 12:37:52 +0200 Subject: [PATCH 1/3] XIVY-19081 adapt Multi-Select from Program protocol --- .../inscription-protocol/src/data/inscription.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/inscription-protocol/src/data/inscription.ts b/packages/inscription-protocol/src/data/inscription.ts index 55ed53afb..3813cbe1c 100644 --- a/packages/inscription-protocol/src/data/inscription.ts +++ b/packages/inscription-protocol/src/data/inscription.ts @@ -9,8 +9,8 @@ export type PID = string export type ContentObjectType = "STRING" | "FILE" | "FOLDER"; export type WfFieldType = "STRING" | "TEXT" | "NUMBER" | "TIMESTAMP"; -export type Widget = Script | Label | Text; -export type WidgetType = "TEXT" | "LABEL" | "SCRIPT"; +export type Widget = Script | Label | Text | MultiSelect; +export type WidgetType = "TEXT" | "LABEL" | "SCRIPT" | "MULTI_SELECT"; /** * Supports macros for dynamic content. * Macros are IvyScript expressions enclosed in '<%=' and '%>', enabling the embedding of dynamic values such as data class attributes. @@ -319,6 +319,17 @@ export interface Text { multiline: boolean; widgetType: WidgetType; } +export interface MultiSelect { + configKey: string; + items: SelectableItem[]; + multiline: boolean; + widgetType: WidgetType; +} +export interface SelectableItem { + description: string; + icon: string; + name: string; +} export interface InscriptionRequest { context: InscriptionElementContext; data: Data; From b78b0bc9573f9c32e4a7e62e8314251e0ed2bfb7 Mon Sep 17 00:00:00 2001 From: Reguel Wermelinger Date: Tue, 30 Jun 2026 12:53:29 +0200 Subject: [PATCH 2/3] XIVY-19081 multi-select as seen in RoleEditor --- integration/inscription/src/mock/meta-mock.ts | 32 +++++ packages/inscription-view/package.json | 1 + .../configuration/ConfigurationPart.tsx | 20 ++- .../multi-select/MultiSelectWidget.css | 122 ++++++++++++++++ .../multi-select/MultiSelectWidget.tsx | 130 ++++++++++++++++++ pnpm-lock.yaml | 90 ++++++++++++ 6 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.css create mode 100644 packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx diff --git a/integration/inscription/src/mock/meta-mock.ts b/integration/inscription/src/mock/meta-mock.ts index 6f442cfa8..cef43b01a 100644 --- a/integration/inscription/src/mock/meta-mock.ts +++ b/integration/inscription/src/mock/meta-mock.ts @@ -393,6 +393,38 @@ export namespace MetaMock { multiline: false } ] + }, + { + name: 'Model', + widgets: [ + { + items: [ + { + name: 'whoami', + description: 'identifies the current user', + icon: '🪪️' + }, + { + name: 'add', + description: 'calculation universe', + icon: 'https://imgcdn.stablediffusionweb.com/2024/12/15/3388743d-2e7a-4cd2-a17c-cba86a6573bd.jpg' + }, + { + name: 'weather', + description: 'provides weather information', + icon: '⛅️' + }, + { + name: 'websearch', + description: 'latest web search results', + icon: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTg0IDE0OCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNNjkuOTQyIDEyOS4yNkg2Ni40NmwtNi4zMzggNi41MTd2LTEzLjAzM2gtMi42Nzh2MjIuMjI4aDIuNjc4di03LjMybDcuMjMgNy4yMy4wOS4wOWgzLjIxNHYtLjI2OGwtOC4xMjQtOC4wMzR6bS0xNy4xNCAxMS44NzNjLS45ODIuOTgyLTIuNDEgMS41MTgtNC4wMTcgMS41MTgtMi43NjcgMC01LjUzNC0xLjY5Ni01LjUzNC01LjYyNCAwLTMuMzAzIDIuMzItNS42MjQgNS41MzQtNS42MjQgMS40MjggMCAyLjc2Ny41MzYgMy44MzkgMS41MThsLjA4OS4wODkgMS42OTYtMS43ODYtLjA5LS4wODljLTEuNjA2LTEuNTE3LTMuNDgtMi4yMzItNS42MjMtMi4yMzItNC45MSAwLTguMjEzIDMuMzAzLTguMjEzIDguMTI0IDAgNS42MjQgNC4xMDYgOC4xMjMgOC4yMTMgOC4xMjNoLjA5YzIuMTQxIDAgNC4xOTUtLjgwMyA1LjYyMy0yLjMyMWwuMDktLjA4OS0xLjc4Ni0xLjc4NXptLTE4LjEyMi0zLjQ4MWMwIDIuODU2LTEuOTYzIDQuOTk5LTQuNzMgNS4wODgtMi45NDYgMC00LjczMi0xLjc4NS00LjczMi00Ljgydi04LjY2SDIyLjU0djguNjZjMCA0LjQ2MyAyLjY3OCA3LjMyIDYuODc0IDcuMzJoLjA5YzIuMDUyIDAgMy45MjctLjk4MiA1LjE3Ny0yLjVsLjA4OS0uMTc5LjA5IDIuNDExaDIuNDk5VjEyOS4yNkgzNC42OHpNOC4zNDcgMTIyLjY1NUguMjIzdjIyLjMxN2g4LjEyNGM3Ljc2NiAwIDExLjE1OC01LjcxMyAxMS4xNTgtMTEuMzM3IDAtNS4yNjctMy40ODEtMTAuOTgtMTEuMTU4LTEwLjk4em04LjM5IDEwLjg5YzAgNC4yODUtMi41ODggOC41Ny04LjMwMSA4LjU3SDMuMDh2LTE2LjY5M2g1LjI2N2M1LjcxMyAwIDguMzkgNC4xOTYgOC4zOSA4LjEyM3ptMTM4LjI3NyAxLjY5N2g2Ljg3NHY1LjE3N2MtMS42OTYgMS40MjgtMy43NDkgMi4yMzItNS45ODEgMi4yMzItNi4wNyAwLTguODM4LTQuMzc0LTguODM4LTguNjU5IDAtNC4zNzQgMi43NjgtOS4xMDYgOC43NDktOS4xMDYgMi40MSAwIDQuNjQyLjg5MyA2LjQyNyAyLjVsLjA4OS4wODkgMS42MDctMS44NzQtLjA4OS0uMDljLTIuMTQzLTIuMTQyLTQuOTk5LTMuMjEzLTguMTI0LTMuMjEzLTMuMzkyIDAtNi4yNDggMS4wNzEtOC4zMDIgMy4yMTMtMi4xNDIgMi4xNDMtMy4zMDIgNS4xNzgtMy4yMTMgOC41NyAwIDUuMjY3IDMuMDM1IDExLjMzNyAxMS42MDUgMTEuMzM3aC4xNzhjMy4yMTQgMCA2LjI0OS0xLjMzOSA4LjM5MS0zLjc0OXYtOC44MzhoLTkuNDYydjIuNDExem0tNzMuNzM1LTEyLjU4N2gtOC4xMjR2MjIuMzE3aDguMTI0YzcuNzY2IDAgMTEuMTU4LTUuNzEzIDExLjE1OC0xMS4zMzcgMC01LjI2Ny0zLjQ4MS0xMC45OC0xMS4xNTgtMTAuOTh6bTguMzkgMTAuODljMCA0LjI4NS0yLjU4OCA4LjU3LTguMzAxIDguNTdoLTUuMjY3di0xNi42OTNoNS4yNjdjNS42MjQgMCA4LjMwMiA0LjE5NiA4LjMwMiA4LjEyM3ptODUuNjA5LTQuNjQyYy00LjczMSAwLTguMTIzIDMuNDgyLTguMTIzIDguMjEzczMuMzkyIDguMTI0IDguMTIzIDguMTI0IDguMjEzLTMuMzkzIDguMjEzLTguMTI0YzAtNC44Mi0zLjM5Mi04LjIxMy04LjIxMy04LjIxM3ptNS41MzUgOC4yMTNjMCAzLjMwMy0yLjMyMSA1LjYyNC01LjUzNSA1LjYyNC0zLjEyNCAwLTUuNDQ1LTIuMzIxLTUuNDQ1LTUuNjI0IDAtMy4zOTIgMi4yMzItNS44MDIgNS41MzQtNS44MDIgMy4xMjUuMDg5IDUuNDQ2IDIuNDk5IDUuNDQ2IDUuODAyem0tNzMuMi41MzZjMCAyLjg1Ni0xLjk2NCA0Ljk5OS00LjczMSA1LjA4OC0yLjk0NiAwLTQuNzMxLTEuNzg1LTQuNzMxLTQuODJ2LTguNjZoLTIuNjc5djguNjZjMCA0LjQ2MyAyLjY3OSA3LjMyIDYuNzg1IDcuMzJoLjA4OWMyLjA1MyAwIDMuOTI4LS45ODIgNS4xNzgtMi41bC4wODktLjE3OS4wODkgMi40MTFoMi41VjEyOS4yNmgtMi42Nzh2OC4zOTJ6bTE4LjEyMSAzLjQ4MWMtLjk4Mi45ODItMi40MSAxLjUxOC00LjAxNyAxLjUxOC0yLjc2NyAwLTUuNTM0LTEuNjk2LTUuNTM0LTUuNjI0IDAtMy4zMDMgMi4zMjEtNS42MjQgNS41MzQtNS42MjQgMS40MjkgMCAyLjc2OC41MzYgMy44MzkgMS41MThsLjA4OS4wODkgMS42OTYtMS43ODYtLjA4OS0uMDg5Yy0xLjYwNy0xLjUxNy0zLjQ4Mi0yLjIzMi01LjYyNC0yLjIzMi00LjkxIDAtOC4yMTMgMy4zMDMtOC4yMTMgOC4xMjQgMCA1LjYyNCA0LjEwNyA4LjEyMyA4LjIxMyA4LjEyM2guMDg5YzIuMTQzIDAgNC4xOTYtLjgwMyA1LjYyNC0yLjMyMWwuMDg5LS4wODktMS43ODUtMS43ODV6bTE3LjE0LTExLjg3M2gtMy40ODJsLTYuMzM4IDYuNTE3di0xMy4wMzNoLTIuNjc4djIyLjIyOGgyLjY3OHYtNy4zMmw3LjIzMSA3LjIzLjA4OS4wOWgzLjIxNHYtLjI2OGwtOC4xMjMtOC4wMzR6Ii8+CiAgPHBhdGggZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTEuOTQ2IDEwNy4xMjJjMjkuNTgxIDAgNTMuNTYxLTIzLjk4IDUzLjU2MS01My41NjFTMTIxLjUyNyAwIDkxLjk0NyAwQzYyLjM2NCAwIDM4LjM4NCAyMy45OCAzOC4zODQgNTMuNTYxczIzLjk4IDUzLjU2MSA1My41NjEgNTMuNTYxeiIgY2xpcC1ydWxlPSJldmVub2RkIi8+CiAgPHBhdGggZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTkuNDc4IDkzLjUyMmMwLS40MTguMTAzLS41MTMtMS4yMjctMy4xNjUtMy41MzUtNy4wNzktNy4wODgtMTcuMDU5LTUuNDcyLTIzLjQ5NS4yOTQtMS4xNy0zLjMzLTQzLjMwNC01Ljg5Mi00NC42NjEtMi44NDgtMS41MTgtNi4zNTEtMy45MjUtOS41NTYtNC40Ni0xLjYyNi0uMjYtMy43NTgtLjEzOC01LjQyNS4wODctLjI5Ni4wNC0uMzA4LjU3Mi0uMDI1LjY2OCAxLjA5NC4zNyAyLjQyMyAxLjAxNCAzLjIwNiAxLjk4OC4xNDguMTg0LS4wNTEuNDc0LS4yODcuNDgyLS43MzguMDI4LTIuMDc3LjMzNy0zLjg0NCAxLjgzOC0uMjA0LjE3My0uMDM1LjQ5NS4yMjguNDQzIDMuNzk3LS43NSA3LjY3NC0uMzggOS45NiAxLjY5Ni4xNDguMTM1LjA3LjM3Ny0uMTIzLjQzQzYxLjE5IDMwLjc2IDY1LjExNyA0OC4wMSA3MC4zOTYgNjkuMTc3YzQuNjU0IDE4LjY2NiA2LjQzNSAyNC44MSA3LjAxMiAyNi43MjdhLjcyLjcyIDAgMCAwIC40MjQuNDY5YzYuODUgMi42OTcgMjEuNjQ2IDIuODA1IDIxLjY0Ni0xLjgwNXoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMDIuMTk4IDk3LjQ5OGMtMi40MDYuOTQxLTcuMTE0IDEuMzYtOS44MzMgMS4zNi0zLjk4OCAwLTkuNzMtLjYyOC0xMS44MjEtMS41Ny0xLjI5My0zLjk3Ni01LjE2LTE2LjMwMy04Ljk3OC0zMS45NTZsLS4zNzQtMS41My0uMDAzLS4wMTJjLTQuNTQtMTguNTQyLTguMjQ4LTMzLjY4NyAxMi4wNzQtMzguNDQ1LjE4NS0uMDQzLjI3Ni0uMjY1LjE1My0uNDExLTIuMzMxLTIuNzY2LTYuNy0zLjY3My0xMi4yMjItMS43NjgtLjIyNy4wNzktLjQyNC0uMTUtLjI4My0uMzQ0IDEuMDgzLTEuNDkzIDMuMi0yLjY0IDQuMjQ0LTMuMTQ0LjIxNi0uMTA0LjIwMy0uNDItLjAyNS0uNDkxYTIzLjI5OSAyMy4yOTkgMCAwIDAtMy4xNTYtLjc1M2MtLjMxLS4wNS0uMzM4LS41OC0uMDI3LS42MjMgNy44My0xLjA1MyAxNi4wMDYgMS4yOTggMjAuMTA5IDYuNDY2YS4yNzIuMjcyIDAgMCAwIC4xNTUuMDk2YzE1LjAyNCAzLjIyNiAxNi4xIDI2Ljk3NyAxNC4zNjkgMjguMDU5LS4zNDEuMjEzLTEuNDM0LjA5LTIuODc3LS4wNy01Ljg0Ni0uNjU1LTE3LjQyMi0xLjk1LTcuODY4IDE1Ljg1Ni4wOTQuMTc2LS4wMy40MDktLjIyOC40NC01LjM4OC44MzcgMS41MTcgMTcuNzE5IDYuNTkxIDI4Ljg0eiIvPgogIDxwYXRoIGZpbGw9IiMzY2E4MmIiIGQ9Ik0xMDguOTE5IDc1LjkwNGMtMS4xNDQtLjUzLTUuNTQyIDIuNjI0LTguNDYxIDUuMDQ1LS42MS0uODYzLTEuNzYtMS40OS00LjM1NS0xLjA0LTIuMjcuMzk2LTMuNTI0Ljk0My00LjA4NCAxLjg4Ny0zLjU4NC0xLjM1OS05LjYxNC0zLjQ1Ni0xMS4wNy0xLjQzLTEuNTkzIDIuMjE0LjM5NyAxMi42ODcgMi41MTIgMTQuMDQ3IDEuMTA0LjcxIDYuMzg2LTIuNjg1IDkuMTQ0LTUuMDI2LjQ0NS42MjcgMS4xNjIuOTg2IDIuNjM0Ljk1MiAyLjIyOC0uMDUyIDUuODQxLS41NyA2LjQwMS0xLjYwOC4wMzQtLjA2My4wNjQtLjEzNy4wODktLjIyMiAyLjgzNSAxLjA2IDcuODI0IDIuMTggOC45MzkgMi4wMTQgMi45MDUtLjQzNy0uNDA1LTEzLjk5Ni0xLjc0OS0xNC42MTl6Ii8+CiAgPHBhdGggZmlsbD0iIzRjYmEzYyIgZD0iTTEwMC43MjUgODEuMjZjLjEyMS4yMTQuMjE3LjQ0LjMuNjcuNDA0IDEuMTMgMS4wNjIgNC43MjYuNTY0IDUuNjE0LS40OTguODg4LTMuNzMyIDEuMzE3LTUuNzI3IDEuMzUxcy0yLjQ0NC0uNjk1LTIuODQ4LTEuODI1Yy0uMzI0LS45MDQtLjQ4My0zLjAzLS40NzktNC4yNDctLjA4Mi0xLjgwNi41NzgtMi40NCAzLjYyNy0yLjkzNCAyLjI1Ny0uMzY1IDMuNDUuMDYgNC4xMzguNzg2IDMuMjA0LTIuMzkgOC41NDktNS43NjUgOS4wNy01LjE0OCAyLjYgMy4wNzUgMi45MjggMTAuMzk0IDIuMzY1IDEzLjM0LS4xODQuOTYyLTguNzkxLS45NTUtOC43OTEtMS45OTMgMC00LjMxMS0xLjExOS01LjQ5NC0yLjIxOS01LjYxNXpNODEuODcgNzkuOTEzYy43MDYtMS4xMTYgNi40MjIuMjcyIDkuNTYgMS42NjggMCAwLS42NDQgMi45MjIuMzgyIDYuMzY0LjMgMS4wMDctNy4yMiA1LjQ4OC04LjIwMSA0LjcxNy0xLjEzNC0uODkxLTMuMjIyLTEwLjQwNC0xLjc0LTEyLjc1eiIvPgogIDxwYXRoIGZpbGw9IiNmYzMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTg0LjY0IDU3LjI0OWMuNDYyLTIuMDEyIDIuNjE3LTUuODAyIDEwLjMxLTUuNzEgMy44OS0uMDE2IDguNzIyLS4wMDEgMTEuOTI2LS4zNjVhNDIuODg1IDQyLjg4NSAwIDAgMCAxMC42NS0yLjU5YzMuMzMxLTEuMjcgNC41MTMtLjk4OCA0LjkyNy0uMjI3LjQ1Ni44MzUtLjA4MSAyLjI3OC0xLjI0NSAzLjYwNi0yLjIyMyAyLjUzNi02LjIxOSA0LjUwMi0xMy4yNzcgNS4wODUtNy4wNTguNTgzLTExLjczNC0xLjMxLTEzLjc0NiAxLjc3Mi0uODY4IDEuMzMtLjE5NyA0LjQ2MyA2LjYyOCA1LjQ1IDkuMjIzIDEuMzMgMTYuNzk4LTEuNjA0IDE3LjczNC4xNjhzLTQuNDU1IDUuMzgtMTMuNjkzIDUuNDU1Yy05LjIzOS4wNzYtMTUuMDA5LTMuMjM0LTE3LjA1NS00Ljg4LTIuNTk3LTIuMDg3LTMuNzU4LTUuMTMyLTMuMTYtNy43NjR6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICA8ZyBmaWxsPSIjMTQzMDdlIiBvcGFjaXR5PSIuOCI+CiAgICA8cGF0aCBkPSJNOTYuNDA0IDM1LjI1NmMuNTE2LS44NDQgMS42NTgtMS40OTUgMy41MjgtMS40OTVzMi43NS43NDQgMy4zNTkgMS41NzNjLjEyNC4xNy0uMDY0LjM2OC0uMjU2LjI4NWwtLjE0LS4wNjFjLS42ODQtLjI5OS0xLjUyNC0uNjY2LTIuOTYzLS42ODYtMS41MzgtLjAyMi0yLjUwOC4zNjMtMy4xMi42OTUtLjIwNS4xMTItLjUzLS4xMTEtLjQwOC0uMzExem0tMjEuMDUzIDEuMDc5YzEuODE2LS43NTkgMy4yNDMtLjY2IDQuMjUyLS40MjIuMjEzLjA1LjM2LS4xNzguMTktLjMxNS0uNzgzLS42MzItMi41MzUtMS40MTYtNC44MjEtLjU2NC0yLjAzOS43Ni0zIDIuMzQtMy4wMDYgMy4zNzktLjAwMS4yNDUuNTAzLjI2NS42MzMuMDU4LjM1Mi0uNTYuOTM3LTEuMzc3IDIuNzUyLTIuMTM2eiIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTAxLjUyMSA0Ni43OTljLTEuNjA2IDAtMi45MDgtMS4yOTktMi45MDgtMi44OTdzMS4zMDItMi44OTcgMi45MDgtMi44OTdjMS42MDUgMCAyLjkwNyAxLjI5OSAyLjkwNyAyLjg5N3MtMS4zMDIgMi44OTctMi45MDcgMi44OTd6bTIuMDQ3LTMuODU3YS43NTIuNzUyIDAgMCAwLTEuNTA1IDAgLjc1Mi43NTIgMCAwIDAgMS41MDUgMHptLTIxLjMxIDIuOTY1YTMuMzgzIDMuMzgzIDAgMCAxLTMuMzg5IDMuMzc4Yy0xLjg3IDAtMy4zODktMS41MTMtMy4zODktMy4zNzhzMS41Mi0zLjM3OSAzLjM4OS0zLjM3OWEzLjM4NSAzLjM4NSAwIDAgMSAzLjM4OSAzLjM3OXptLS45OTktMS4xMmEuODc4Ljg3OCAwIDAgMC0xLjc1NSAwIC44NzYuODc2IDAgMCAwIDEuNzU1IDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICA8L2c+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTEuOTQ2IDk4LjU0NGMyNC44NDMgMCA0NC45ODMtMjAuMTQgNDQuOTgzLTQ0Ljk4MyAwLTI0Ljg0My0yMC4xNC00NC45ODMtNDQuOTgzLTQ0Ljk4My0yNC44NDMgMC00NC45ODMgMjAuMTQtNDQuOTgzIDQ0Ljk4MyAwIDI0Ljg0MyAyMC4xNCA0NC45ODMgNDQuOTgzIDQ0Ljk4M3ptMCA0LjE4NGMyNy4xNTQgMCA0OS4xNjctMjIuMDEzIDQ5LjE2Ny00OS4xNjdTMTE5LjEgNC4zOTQgOTEuOTQ2IDQuMzk0IDQyLjc4IDI2LjQwNyA0Mi43OCA1My41NnMyMi4wMTMgNDkuMTY3IDQ5LjE2NyA0OS4xNjd6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg==' + } + ], + widgetType: 'MULTI_SELECT', + configKey: 'tools', + multiline: false + } + ] } ]; diff --git a/packages/inscription-view/package.json b/packages/inscription-view/package.json index 2e682a5ab..e3981e643 100644 --- a/packages/inscription-view/package.json +++ b/packages/inscription-view/package.json @@ -26,6 +26,7 @@ "@axonivy/jsonrpc": "~14.0.0-next", "@axonivy/ui-components": "~14.0.0-next", "@axonivy/ui-icons": "~14.0.0-next", + "@base-ui/react": "^1.0", "@radix-ui/react-dialog": "^1.1", "@radix-ui/react-tabs": "^1.1", "@tanstack/react-query": "^5.64", diff --git a/packages/inscription-view/src/components/parts/program/configuration/ConfigurationPart.tsx b/packages/inscription-view/src/components/parts/program/configuration/ConfigurationPart.tsx index 1e9949678..1cfe5657d 100644 --- a/packages/inscription-view/src/components/parts/program/configuration/ConfigurationPart.tsx +++ b/packages/inscription-view/src/components/parts/program/configuration/ConfigurationPart.tsx @@ -1,4 +1,4 @@ -import type { ConfigurationData, Label, Script, Text, Widget } from '@axonivy/process-editor-inscription-protocol'; +import type { ConfigurationData, Label, MultiSelect, Script, Text, Widget } from '@axonivy/process-editor-inscription-protocol'; import { IVY_SCRIPT_TYPES } from '@axonivy/process-editor-inscription-protocol'; import { Flex, Message } from '@axonivy/ui-components'; import { IvyIcons } from '@axonivy/ui-icons'; @@ -10,6 +10,7 @@ import { usePartState, type PartProps } from '../../../editors/part/usePart'; import { MacroArea } from '../../../widgets/code-editor/MacroArea'; import { MacroInput } from '../../../widgets/code-editor/MacroInput'; import { ScriptInput } from '../../../widgets/code-editor/ScriptInput'; +import { MultiSelectWidget } from '../../../widgets/multi-select/MultiSelectWidget'; import { PathCollapsible } from '../../common/path/PathCollapsible'; import './Configuration.css'; import { useConfigurationData } from './useConfigurationData'; @@ -47,6 +48,10 @@ const ConfigurationPart = () => { return object.widgetType == 'TEXT'; } + function isMultiSelect(object: Widget): object is MultiSelect { + return object.widgetType === 'MULTI_SELECT'; + } + const renderWidgetComponent = (widget: Widget) => { if (isLabel(widget)) { const message = widget.text; @@ -98,6 +103,19 @@ const ConfigurationPart = () => { /> ); } + if (isMultiSelect(widget)) { + const multiSelectWidget = widget as MultiSelect; + const selectedValue = config.userConfig[multiSelectWidget.configKey] ?? ''; + const value = typeof selectedValue === 'string' && selectedValue.length > 0 ? selectedValue.split(',').map(v => v.trim()) : []; + return ( + updateUserConfig(multiSelectWidget.configKey, change.join(', '))} + items={multiSelectWidget.items} + configKey={multiSelectWidget.configKey} + /> + ); + } return null; }; diff --git a/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.css b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.css new file mode 100644 index 000000000..830a2a0bd --- /dev/null +++ b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.css @@ -0,0 +1,122 @@ +.ui-combobox-root { + min-height: 2.25rem; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--size-1); + border-radius: var(--border-r1); + border: 1px solid var(--border-input-color); + background-color: var(--N25); + padding: var(--size-0-75); + transition: border-color 200ms ease; +} + +.ui-combobox-root:focus-within { + border-color: var(--border-active); + outline: none; +} + +.ui-combobox-root-chip { + display: flex; + height: 1.25rem; + align-items: center; + gap: var(--size-1); + overflow: hidden; + border-radius: var(--border-r1); + background-color: var(--N100); + padding: var(--size-0-5) var(--size-0-5) var(--size-0-5) var(--size-1); + font-size: var(--font-body); + outline: none; +} + +.ui-combobox-root-chip:focus-within { + background-color: var(--P75); +} + +.ui-combobox-root-chip:hover { + background-color: var(--P75); +} + +.ui-combobox-root-input { + margin: 0; + width: 100%; + min-width: 0; + flex: 1 1 auto; + border: none; + background: transparent; + font-size: var(--font-body); + outline: none; +} + +.ui-combobox-popup { + max-height: min(var(--available-height), 23rem); + overflow: auto; + border-radius: var(--border-r1); + border: 1px solid var(--N100); + background-color: var(--background); + padding: var(--size-1); + font-size: var(--font-body); + box-shadow: var(--shadow-lg); + animation: combobox-popup-enter 0.2s ease-out; +} + +.ui-combobox-popup[data-starting-style] { + animation: combobox-popup-exit 0.2s ease-in; +} + +@keyframes combobox-popup-enter { + from { + transform: scale(0.95); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes combobox-popup-exit { + to { + transform: scale(0.95); + opacity: 0; + } +} + +.ui-combobox-item { + position: relative; + display: flex; + height: 1.9375rem; + align-items: center; + padding: var(--size-2) var(--size-2) var(--size-2) 2rem; + outline: none; + user-select: none; + cursor: pointer; + border-radius: var(--border-r1); + transition: background-color 100ms ease; +} + +.ui-combobox-item[data-highlighted]:not([data-selected]) { + background-color: var(--P50); +} + +.ui-combobox-item[data-selected] { + background-color: var(--P300); + color: var(--background); + z-index: 0; +} + +.ui-combobox-item-indicator { + position: absolute; + left: var(--size-2); + display: flex; + align-items: center; + justify-content: center; + width: 0.875rem; + height: 0.875rem; +} + +.ui-combobox-empty { + padding: var(--size-2); + line-height: 1; + color: var(--N700); +} diff --git a/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx new file mode 100644 index 000000000..d2536b57e --- /dev/null +++ b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx @@ -0,0 +1,130 @@ +import { Button, Flex, IvyIcon, useField, useReadonly } from '@axonivy/ui-components'; +import { IvyIcons } from '@axonivy/ui-icons'; +import { Combobox } from '@base-ui/react/combobox'; +import React, { useMemo, useRef } from 'react'; +import './MultiSelectWidget.css'; + +type SelectableItem = { + name: string; + description: string; + icon: string; +}; + +type MultiSelectWidgetProps = { + value: string[]; + onChange: (value: string[]) => void; + items: SelectableItem[]; + configKey: string; +}; + +function isImageUrl(url: string): boolean { + if (!url) return false; + return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/') || url.startsWith('data:'); +} + +function renderIcon(icon: string): React.ReactNode { + if (!icon || icon === '') return null; + return isImageUrl(icon) + ? + : ; +} + +export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSelectWidgetProps) { + const containerRef = useRef(null); + const { inputProps } = useField(); + const readonly = useReadonly(); + + const itemIds = useMemo(() => items.map(item => item.name), [items]); + + const mergedItems = useMemo(() => { + const merged = [...items]; + const mergedIds = merged.map(item => item.name); + value.filter(v => !mergedIds.includes(v)).forEach(v => merged.push({ name: v, description: '', icon: '' })); + return merged; + }, [items, value]); + + return ( + + + + {(selectedItems: string[]) => ( + <> + {selectedItems.map(itemName => { + const item = mergedItems.find(i => i.name === itemName); + return ( + + + {renderIcon(item?.icon || '')} + {itemName} + + } /> + + ); + })} + + + } /> + + + )} + + + + + + + + + {(itemName: string) => ( + + + + +
{itemLabel(itemName, mergedItems)}
+
+ )} +
+
+
+
+
+ ); +} + +function itemLabel(itemName: string, items: SelectableItem[]): React.ReactNode { + const item = items.find(i => i.name === itemName); + if (!item) { + return itemName; + } + + if (item.icon && item.icon !== '') { + return ( + + {renderIcon(item.icon)} + {item.name} + {item.description && {` - ${item.description}`}} + + ); + } + + if (item.description) { + return ( + + {item.name} + {item.description && {` - ${item.description}`}} + + ); + } + + return item.name; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6745cd94f..2ba2bbaf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,6 +385,9 @@ importers: '@axonivy/process-editor-inscription-protocol': specifier: workspace:~ version: link:../inscription-protocol + '@base-ui/react': + specifier: ^1.0 + version: 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-dialog': specifier: ^1.1 version: 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -555,6 +558,33 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@base-ui/react@1.6.0': + resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@date-fns/tz': ^1.2.0 + '@types/react': ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@date-fns/tz': + optional: true + '@types/react': + optional: true + date-fns: + optional: true + + '@base-ui/utils@0.3.1': + resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true @@ -757,18 +787,33 @@ packages: '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + '@floating-ui/dom@1.7.4': resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@floating-ui/react-dom@2.1.6': resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@gar/promise-retry@1.0.3': resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -3807,6 +3852,9 @@ packages: resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==} engines: {node: '>=0.10.5'} + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -4527,6 +4575,29 @@ snapshots: '@babel/runtime@7.29.7': {} + '@base-ui/react@1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@babel/runtime': 7.29.7 + '@base-ui/utils': 0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@floating-ui/utils': 0.2.11 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + + '@base-ui/utils@0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@babel/runtime': 7.29.7 + '@floating-ui/utils': 0.2.11 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + reselect: 5.2.0 + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 @@ -4758,19 +4829,36 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.10 + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + '@floating-ui/dom@1.7.4': dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + '@floating-ui/react-dom@2.1.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@floating-ui/dom': 1.7.4 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.11': {} + '@gar/promise-retry@1.0.3': {} '@hediet/json-rpc-browser@0.5.1': @@ -7914,6 +8002,8 @@ snapshots: requireindex@1.1.0: {} + reselect@5.2.0: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 From df9ea9263671b1a2a372d49a843f67e4c7792289 Mon Sep 17 00:00:00 2001 From: Reguel Wermelinger Date: Fri, 3 Jul 2026 14:16:45 +0200 Subject: [PATCH 3/3] adapt protocol --- integration/inscription/src/mock/meta-mock.ts | 12 +++-- .../src/data/inscription.ts | 5 +- .../multi-select/MultiSelectWidget.tsx | 49 ++++++++++--------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/integration/inscription/src/mock/meta-mock.ts b/integration/inscription/src/mock/meta-mock.ts index cef43b01a..69fa18a42 100644 --- a/integration/inscription/src/mock/meta-mock.ts +++ b/integration/inscription/src/mock/meta-mock.ts @@ -400,22 +400,26 @@ export namespace MetaMock { { items: [ { - name: 'whoami', + id: 'whoami', + label: 'WhoAmI', description: 'identifies the current user', icon: '🪪️' }, { - name: 'add', + id: 'add', + label: 'Add', description: 'calculation universe', icon: 'https://imgcdn.stablediffusionweb.com/2024/12/15/3388743d-2e7a-4cd2-a17c-cba86a6573bd.jpg' }, { - name: 'weather', + id: 'weather', + label: 'Weather', description: 'provides weather information', icon: '⛅️' }, { - name: 'websearch', + id: 'websearch', + label: 'Web Search', description: 'latest web search results', icon: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTg0IDE0OCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNNjkuOTQyIDEyOS4yNkg2Ni40NmwtNi4zMzggNi41MTd2LTEzLjAzM2gtMi42Nzh2MjIuMjI4aDIuNjc4di03LjMybDcuMjMgNy4yMy4wOS4wOWgzLjIxNHYtLjI2OGwtOC4xMjQtOC4wMzR6bS0xNy4xNCAxMS44NzNjLS45ODIuOTgyLTIuNDEgMS41MTgtNC4wMTcgMS41MTgtMi43NjcgMC01LjUzNC0xLjY5Ni01LjUzNC01LjYyNCAwLTMuMzAzIDIuMzItNS42MjQgNS41MzQtNS42MjQgMS40MjggMCAyLjc2Ny41MzYgMy44MzkgMS41MThsLjA4OS4wODkgMS42OTYtMS43ODYtLjA5LS4wODljLTEuNjA2LTEuNTE3LTMuNDgtMi4yMzItNS42MjMtMi4yMzItNC45MSAwLTguMjEzIDMuMzAzLTguMjEzIDguMTI0IDAgNS42MjQgNC4xMDYgOC4xMjMgOC4yMTMgOC4xMjNoLjA5YzIuMTQxIDAgNC4xOTUtLjgwMyA1LjYyMy0yLjMyMWwuMDktLjA4OS0xLjc4Ni0xLjc4NXptLTE4LjEyMi0zLjQ4MWMwIDIuODU2LTEuOTYzIDQuOTk5LTQuNzMgNS4wODgtMi45NDYgMC00LjczMi0xLjc4NS00LjczMi00Ljgydi04LjY2SDIyLjU0djguNjZjMCA0LjQ2MyAyLjY3OCA3LjMyIDYuODc0IDcuMzJoLjA5YzIuMDUyIDAgMy45MjctLjk4MiA1LjE3Ny0yLjVsLjA4OS0uMTc5LjA5IDIuNDExaDIuNDk5VjEyOS4yNkgzNC42OHpNOC4zNDcgMTIyLjY1NUguMjIzdjIyLjMxN2g4LjEyNGM3Ljc2NiAwIDExLjE1OC01LjcxMyAxMS4xNTgtMTEuMzM3IDAtNS4yNjctMy40ODEtMTAuOTgtMTEuMTU4LTEwLjk4em04LjM5IDEwLjg5YzAgNC4yODUtMi41ODggOC41Ny04LjMwMSA4LjU3SDMuMDh2LTE2LjY5M2g1LjI2N2M1LjcxMyAwIDguMzkgNC4xOTYgOC4zOSA4LjEyM3ptMTM4LjI3NyAxLjY5N2g2Ljg3NHY1LjE3N2MtMS42OTYgMS40MjgtMy43NDkgMi4yMzItNS45ODEgMi4yMzItNi4wNyAwLTguODM4LTQuMzc0LTguODM4LTguNjU5IDAtNC4zNzQgMi43NjgtOS4xMDYgOC43NDktOS4xMDYgMi40MSAwIDQuNjQyLjg5MyA2LjQyNyAyLjVsLjA4OS4wODkgMS42MDctMS44NzQtLjA4OS0uMDljLTIuMTQzLTIuMTQyLTQuOTk5LTMuMjEzLTguMTI0LTMuMjEzLTMuMzkyIDAtNi4yNDggMS4wNzEtOC4zMDIgMy4yMTMtMi4xNDIgMi4xNDMtMy4zMDIgNS4xNzgtMy4yMTMgOC41NyAwIDUuMjY3IDMuMDM1IDExLjMzNyAxMS42MDUgMTEuMzM3aC4xNzhjMy4yMTQgMCA2LjI0OS0xLjMzOSA4LjM5MS0zLjc0OXYtOC44MzhoLTkuNDYydjIuNDExem0tNzMuNzM1LTEyLjU4N2gtOC4xMjR2MjIuMzE3aDguMTI0YzcuNzY2IDAgMTEuMTU4LTUuNzEzIDExLjE1OC0xMS4zMzcgMC01LjI2Ny0zLjQ4MS0xMC45OC0xMS4xNTgtMTAuOTh6bTguMzkgMTAuODljMCA0LjI4NS0yLjU4OCA4LjU3LTguMzAxIDguNTdoLTUuMjY3di0xNi42OTNoNS4yNjdjNS42MjQgMCA4LjMwMiA0LjE5NiA4LjMwMiA4LjEyM3ptODUuNjA5LTQuNjQyYy00LjczMSAwLTguMTIzIDMuNDgyLTguMTIzIDguMjEzczMuMzkyIDguMTI0IDguMTIzIDguMTI0IDguMjEzLTMuMzkzIDguMjEzLTguMTI0YzAtNC44Mi0zLjM5Mi04LjIxMy04LjIxMy04LjIxM3ptNS41MzUgOC4yMTNjMCAzLjMwMy0yLjMyMSA1LjYyNC01LjUzNSA1LjYyNC0zLjEyNCAwLTUuNDQ1LTIuMzIxLTUuNDQ1LTUuNjI0IDAtMy4zOTIgMi4yMzItNS44MDIgNS41MzQtNS44MDIgMy4xMjUuMDg5IDUuNDQ2IDIuNDk5IDUuNDQ2IDUuODAyem0tNzMuMi41MzZjMCAyLjg1Ni0xLjk2NCA0Ljk5OS00LjczMSA1LjA4OC0yLjk0NiAwLTQuNzMxLTEuNzg1LTQuNzMxLTQuODJ2LTguNjZoLTIuNjc5djguNjZjMCA0LjQ2MyAyLjY3OSA3LjMyIDYuNzg1IDcuMzJoLjA4OWMyLjA1MyAwIDMuOTI4LS45ODIgNS4xNzgtMi41bC4wODktLjE3OS4wODkgMi40MTFoMi41VjEyOS4yNmgtMi42Nzh2OC4zOTJ6bTE4LjEyMSAzLjQ4MWMtLjk4Mi45ODItMi40MSAxLjUxOC00LjAxNyAxLjUxOC0yLjc2NyAwLTUuNTM0LTEuNjk2LTUuNTM0LTUuNjI0IDAtMy4zMDMgMi4zMjEtNS42MjQgNS41MzQtNS42MjQgMS40MjkgMCAyLjc2OC41MzYgMy44MzkgMS41MThsLjA4OS4wODkgMS42OTYtMS43ODYtLjA4OS0uMDg5Yy0xLjYwNy0xLjUxNy0zLjQ4Mi0yLjIzMi01LjYyNC0yLjIzMi00LjkxIDAtOC4yMTMgMy4zMDMtOC4yMTMgOC4xMjQgMCA1LjYyNCA0LjEwNyA4LjEyMyA4LjIxMyA4LjEyM2guMDg5YzIuMTQzIDAgNC4xOTYtLjgwMyA1LjYyNC0yLjMyMWwuMDg5LS4wODktMS43ODUtMS43ODV6bTE3LjE0LTExLjg3M2gtMy40ODJsLTYuMzM4IDYuNTE3di0xMy4wMzNoLTIuNjc4djIyLjIyOGgyLjY3OHYtNy4zMmw3LjIzMSA3LjIzLjA4OS4wOWgzLjIxNHYtLjI2OGwtOC4xMjMtOC4wMzR6Ii8+CiAgPHBhdGggZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTEuOTQ2IDEwNy4xMjJjMjkuNTgxIDAgNTMuNTYxLTIzLjk4IDUzLjU2MS01My41NjFTMTIxLjUyNyAwIDkxLjk0NyAwQzYyLjM2NCAwIDM4LjM4NCAyMy45OCAzOC4zODQgNTMuNTYxczIzLjk4IDUzLjU2MSA1My41NjEgNTMuNTYxeiIgY2xpcC1ydWxlPSJldmVub2RkIi8+CiAgPHBhdGggZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTkuNDc4IDkzLjUyMmMwLS40MTguMTAzLS41MTMtMS4yMjctMy4xNjUtMy41MzUtNy4wNzktNy4wODgtMTcuMDU5LTUuNDcyLTIzLjQ5NS4yOTQtMS4xNy0zLjMzLTQzLjMwNC01Ljg5Mi00NC42NjEtMi44NDgtMS41MTgtNi4zNTEtMy45MjUtOS41NTYtNC40Ni0xLjYyNi0uMjYtMy43NTgtLjEzOC01LjQyNS4wODctLjI5Ni4wNC0uMzA4LjU3Mi0uMDI1LjY2OCAxLjA5NC4zNyAyLjQyMyAxLjAxNCAzLjIwNiAxLjk4OC4xNDguMTg0LS4wNTEuNDc0LS4yODcuNDgyLS43MzguMDI4LTIuMDc3LjMzNy0zLjg0NCAxLjgzOC0uMjA0LjE3My0uMDM1LjQ5NS4yMjguNDQzIDMuNzk3LS43NSA3LjY3NC0uMzggOS45NiAxLjY5Ni4xNDguMTM1LjA3LjM3Ny0uMTIzLjQzQzYxLjE5IDMwLjc2IDY1LjExNyA0OC4wMSA3MC4zOTYgNjkuMTc3YzQuNjU0IDE4LjY2NiA2LjQzNSAyNC44MSA3LjAxMiAyNi43MjdhLjcyLjcyIDAgMCAwIC40MjQuNDY5YzYuODUgMi42OTcgMjEuNjQ2IDIuODA1IDIxLjY0Ni0xLjgwNXoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMDIuMTk4IDk3LjQ5OGMtMi40MDYuOTQxLTcuMTE0IDEuMzYtOS44MzMgMS4zNi0zLjk4OCAwLTkuNzMtLjYyOC0xMS44MjEtMS41Ny0xLjI5My0zLjk3Ni01LjE2LTE2LjMwMy04Ljk3OC0zMS45NTZsLS4zNzQtMS41My0uMDAzLS4wMTJjLTQuNTQtMTguNTQyLTguMjQ4LTMzLjY4NyAxMi4wNzQtMzguNDQ1LjE4NS0uMDQzLjI3Ni0uMjY1LjE1My0uNDExLTIuMzMxLTIuNzY2LTYuNy0zLjY3My0xMi4yMjItMS43NjgtLjIyNy4wNzktLjQyNC0uMTUtLjI4My0uMzQ0IDEuMDgzLTEuNDkzIDMuMi0yLjY0IDQuMjQ0LTMuMTQ0LjIxNi0uMTA0LjIwMy0uNDItLjAyNS0uNDkxYTIzLjI5OSAyMy4yOTkgMCAwIDAtMy4xNTYtLjc1M2MtLjMxLS4wNS0uMzM4LS41OC0uMDI3LS42MjMgNy44My0xLjA1MyAxNi4wMDYgMS4yOTggMjAuMTA5IDYuNDY2YS4yNzIuMjcyIDAgMCAwIC4xNTUuMDk2YzE1LjAyNCAzLjIyNiAxNi4xIDI2Ljk3NyAxNC4zNjkgMjguMDU5LS4zNDEuMjEzLTEuNDM0LjA5LTIuODc3LS4wNy01Ljg0Ni0uNjU1LTE3LjQyMi0xLjk1LTcuODY4IDE1Ljg1Ni4wOTQuMTc2LS4wMy40MDktLjIyOC40NC01LjM4OC44MzcgMS41MTcgMTcuNzE5IDYuNTkxIDI4Ljg0eiIvPgogIDxwYXRoIGZpbGw9IiMzY2E4MmIiIGQ9Ik0xMDguOTE5IDc1LjkwNGMtMS4xNDQtLjUzLTUuNTQyIDIuNjI0LTguNDYxIDUuMDQ1LS42MS0uODYzLTEuNzYtMS40OS00LjM1NS0xLjA0LTIuMjcuMzk2LTMuNTI0Ljk0My00LjA4NCAxLjg4Ny0zLjU4NC0xLjM1OS05LjYxNC0zLjQ1Ni0xMS4wNy0xLjQzLTEuNTkzIDIuMjE0LjM5NyAxMi42ODcgMi41MTIgMTQuMDQ3IDEuMTA0LjcxIDYuMzg2LTIuNjg1IDkuMTQ0LTUuMDI2LjQ0NS42MjcgMS4xNjIuOTg2IDIuNjM0Ljk1MiAyLjIyOC0uMDUyIDUuODQxLS41NyA2LjQwMS0xLjYwOC4wMzQtLjA2My4wNjQtLjEzNy4wODktLjIyMiAyLjgzNSAxLjA2IDcuODI0IDIuMTggOC45MzkgMi4wMTQgMi45MDUtLjQzNy0uNDA1LTEzLjk5Ni0xLjc0OS0xNC42MTl6Ii8+CiAgPHBhdGggZmlsbD0iIzRjYmEzYyIgZD0iTTEwMC43MjUgODEuMjZjLjEyMS4yMTQuMjE3LjQ0LjMuNjcuNDA0IDEuMTMgMS4wNjIgNC43MjYuNTY0IDUuNjE0LS40OTguODg4LTMuNzMyIDEuMzE3LTUuNzI3IDEuMzUxcy0yLjQ0NC0uNjk1LTIuODQ4LTEuODI1Yy0uMzI0LS45MDQtLjQ4My0zLjAzLS40NzktNC4yNDctLjA4Mi0xLjgwNi41NzgtMi40NCAzLjYyNy0yLjkzNCAyLjI1Ny0uMzY1IDMuNDUuMDYgNC4xMzguNzg2IDMuMjA0LTIuMzkgOC41NDktNS43NjUgOS4wNy01LjE0OCAyLjYgMy4wNzUgMi45MjggMTAuMzk0IDIuMzY1IDEzLjM0LS4xODQuOTYyLTguNzkxLS45NTUtOC43OTEtMS45OTMgMC00LjMxMS0xLjExOS01LjQ5NC0yLjIxOS01LjYxNXpNODEuODcgNzkuOTEzYy43MDYtMS4xMTYgNi40MjIuMjcyIDkuNTYgMS42NjggMCAwLS42NDQgMi45MjIuMzgyIDYuMzY0LjMgMS4wMDctNy4yMiA1LjQ4OC04LjIwMSA0LjcxNy0xLjEzNC0uODkxLTMuMjIyLTEwLjQwNC0xLjc0LTEyLjc1eiIvPgogIDxwYXRoIGZpbGw9IiNmYzMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTg0LjY0IDU3LjI0OWMuNDYyLTIuMDEyIDIuNjE3LTUuODAyIDEwLjMxLTUuNzEgMy44OS0uMDE2IDguNzIyLS4wMDEgMTEuOTI2LS4zNjVhNDIuODg1IDQyLjg4NSAwIDAgMCAxMC42NS0yLjU5YzMuMzMxLTEuMjcgNC41MTMtLjk4OCA0LjkyNy0uMjI3LjQ1Ni44MzUtLjA4MSAyLjI3OC0xLjI0NSAzLjYwNi0yLjIyMyAyLjUzNi02LjIxOSA0LjUwMi0xMy4yNzcgNS4wODUtNy4wNTguNTgzLTExLjczNC0xLjMxLTEzLjc0NiAxLjc3Mi0uODY4IDEuMzMtLjE5NyA0LjQ2MyA2LjYyOCA1LjQ1IDkuMjIzIDEuMzMgMTYuNzk4LTEuNjA0IDE3LjczNC4xNjhzLTQuNDU1IDUuMzgtMTMuNjkzIDUuNDU1Yy05LjIzOS4wNzYtMTUuMDA5LTMuMjM0LTE3LjA1NS00Ljg4LTIuNTk3LTIuMDg3LTMuNzU4LTUuMTMyLTMuMTYtNy43NjR6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICA8ZyBmaWxsPSIjMTQzMDdlIiBvcGFjaXR5PSIuOCI+CiAgICA8cGF0aCBkPSJNOTYuNDA0IDM1LjI1NmMuNTE2LS44NDQgMS42NTgtMS40OTUgMy41MjgtMS40OTVzMi43NS43NDQgMy4zNTkgMS41NzNjLjEyNC4xNy0uMDY0LjM2OC0uMjU2LjI4NWwtLjE0LS4wNjFjLS42ODQtLjI5OS0xLjUyNC0uNjY2LTIuOTYzLS42ODYtMS41MzgtLjAyMi0yLjUwOC4zNjMtMy4xMi42OTUtLjIwNS4xMTItLjUzLS4xMTEtLjQwOC0uMzExem0tMjEuMDUzIDEuMDc5YzEuODE2LS43NTkgMy4yNDMtLjY2IDQuMjUyLS40MjIuMjEzLjA1LjM2LS4xNzguMTktLjMxNS0uNzgzLS42MzItMi41MzUtMS40MTYtNC44MjEtLjU2NC0yLjAzOS43Ni0zIDIuMzQtMy4wMDYgMy4zNzktLjAwMS4yNDUuNTAzLjI2NS42MzMuMDU4LjM1Mi0uNTYuOTM3LTEuMzc3IDIuNzUyLTIuMTM2eiIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTAxLjUyMSA0Ni43OTljLTEuNjA2IDAtMi45MDgtMS4yOTktMi45MDgtMi44OTdzMS4zMDItMi44OTcgMi45MDgtMi44OTdjMS42MDUgMCAyLjkwNyAxLjI5OSAyLjkwNyAyLjg5N3MtMS4zMDIgMi44OTctMi45MDcgMi44OTd6bTIuMDQ3LTMuODU3YS43NTIuNzUyIDAgMCAwLTEuNTA1IDAgLjc1Mi43NTIgMCAwIDAgMS41MDUgMHptLTIxLjMxIDIuOTY1YTMuMzgzIDMuMzgzIDAgMCAxLTMuMzg5IDMuMzc4Yy0xLjg3IDAtMy4zODktMS41MTMtMy4zODktMy4zNzhzMS41Mi0zLjM3OSAzLjM4OS0zLjM3OWEzLjM4NSAzLjM4NSAwIDAgMSAzLjM4OSAzLjM3OXptLS45OTktMS4xMmEuODc4Ljg3OCAwIDAgMC0xLjc1NSAwIC44NzYuODc2IDAgMCAwIDEuNzU1IDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICA8L2c+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOTEuOTQ2IDk4LjU0NGMyNC44NDMgMCA0NC45ODMtMjAuMTQgNDQuOTgzLTQ0Ljk4MyAwLTI0Ljg0My0yMC4xNC00NC45ODMtNDQuOTgzLTQ0Ljk4My0yNC44NDMgMC00NC45ODMgMjAuMTQtNDQuOTgzIDQ0Ljk4MyAwIDI0Ljg0MyAyMC4xNCA0NC45ODMgNDQuOTgzIDQ0Ljk4M3ptMCA0LjE4NGMyNy4xNTQgMCA0OS4xNjctMjIuMDEzIDQ5LjE2Ny00OS4xNjdTMTE5LjEgNC4zOTQgOTEuOTQ2IDQuMzk0IDQyLjc4IDI2LjQwNyA0Mi43OCA1My41NnMyMi4wMTMgNDkuMTY3IDQ5LjE2NyA0OS4xNjd6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg==' } diff --git a/packages/inscription-protocol/src/data/inscription.ts b/packages/inscription-protocol/src/data/inscription.ts index 3813cbe1c..bb602cd8c 100644 --- a/packages/inscription-protocol/src/data/inscription.ts +++ b/packages/inscription-protocol/src/data/inscription.ts @@ -85,7 +85,7 @@ export interface Inscription { schemaKey: SchemaKey; scriptingDataArgs: ScriptingDataArgs; signalCodeRequest: SignalCodeRequest; - string: string; + string: string[]; typeSearchRequest: TypeSearchRequest; validationResult: ValidationResult[]; variableInfo: VariableInfo; @@ -328,7 +328,8 @@ export interface MultiSelect { export interface SelectableItem { description: string; icon: string; - name: string; + id: string; + label: string; } export interface InscriptionRequest { context: InscriptionElementContext; diff --git a/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx index d2536b57e..d765bb7e8 100644 --- a/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx +++ b/packages/inscription-view/src/components/widgets/multi-select/MultiSelectWidget.tsx @@ -5,7 +5,8 @@ import React, { useMemo, useRef } from 'react'; import './MultiSelectWidget.css'; type SelectableItem = { - name: string; + id: string; + label: string; description: string; icon: string; }; @@ -24,9 +25,13 @@ function isImageUrl(url: string): boolean { function renderIcon(icon: string): React.ReactNode { if (!icon || icon === '') return null; - return isImageUrl(icon) - ? - : ; + return isImageUrl(icon) ? ( + + ) : ( + + ); } export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSelectWidgetProps) { @@ -34,12 +39,12 @@ export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSe const { inputProps } = useField(); const readonly = useReadonly(); - const itemIds = useMemo(() => items.map(item => item.name), [items]); + const itemIds = useMemo(() => items.map(item => item.id), [items]); const mergedItems = useMemo(() => { const merged = [...items]; - const mergedIds = merged.map(item => item.name); - value.filter(v => !mergedIds.includes(v)).forEach(v => merged.push({ name: v, description: '', icon: '' })); + const mergedIds = merged.map(item => item.id); + value.filter(v => !mergedIds.includes(v)).forEach(v => merged.push({ id: v, label: v, description: '', icon: '' })); return merged; }, [items, value]); @@ -49,18 +54,18 @@ export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSe {(selectedItems: string[]) => ( <> - {selectedItems.map(itemName => { - const item = mergedItems.find(i => i.name === itemName); + {selectedItems.map(itemId => { + const item = mergedItems.find(i => i.id === itemId); return ( {renderIcon(item?.icon || '')} - {itemName} + {item?.label || itemId} } /> @@ -85,12 +90,12 @@ export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSe - {(itemName: string) => ( - + {(itemId: string) => ( + -
{itemLabel(itemName, mergedItems)}
+
{itemLabel(itemId, mergedItems)}
)}
@@ -101,17 +106,17 @@ export function MultiSelectWidget({ value, onChange, items, configKey }: MultiSe ); } -function itemLabel(itemName: string, items: SelectableItem[]): React.ReactNode { - const item = items.find(i => i.name === itemName); +function itemLabel(itemId: string, items: SelectableItem[]): React.ReactNode { + const item = items.find(i => i.id === itemId); if (!item) { - return itemName; + return itemId; } if (item.icon && item.icon !== '') { return ( {renderIcon(item.icon)} - {item.name} + {item.label} {item.description && {` - ${item.description}`}} ); @@ -120,11 +125,11 @@ function itemLabel(itemName: string, items: SelectableItem[]): React.ReactNode { if (item.description) { return ( - {item.name} + {item.label} {item.description && {` - ${item.description}`}} ); } - return item.name; + return item.label; }