From db464ea67c47b268d506a4e2264cc25019e09235 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Thu, 12 Oct 2023 13:54:21 +0200 Subject: [PATCH 1/4] Iteration on Assistant Builder (#2082) --- .../assistant_builder/AssistantBuilder.tsx | 477 ++++++++---------- .../DustAppSelectionSection.tsx | 3 + .../[wId]/builder/assistants/[aId]/index.tsx | 13 +- 3 files changed, 229 insertions(+), 264 deletions(-) diff --git a/front/components/assistant_builder/AssistantBuilder.tsx b/front/components/assistant_builder/AssistantBuilder.tsx index 9b8adba50b6a..a921ce0934a5 100644 --- a/front/components/assistant_builder/AssistantBuilder.tsx +++ b/front/components/assistant_builder/AssistantBuilder.tsx @@ -67,15 +67,18 @@ const usedModelConfigs = [ CLAUDE_INSTANT_DEFAULT_MODEL_CONFIG, ]; -// Retrieval Action - -const DATA_SOURCE_MODES = ["GENERIC", "SELECTED"] as const; -type DataSourceMode = (typeof DATA_SOURCE_MODES)[number]; -const DATA_SOURCE_MODE_TO_LABEL: Record = { - GENERIC: "None (Generic model)", - SELECTED: "Selected Data Sources", +// Actions + +const ACTION_MODES = ["GENERIC", "RETRIEVAL", "DUST_APP_RUN"] as const; +type ActionMode = (typeof ACTION_MODES)[number]; +const ACTION_MODE_TO_LABEL: Record = { + GENERIC: "No action (Generic model)", + RETRIEVAL: "Search Data Sources", + DUST_APP_RUN: "Execute Dust App", }; +// Retrieval Action + export const CONNECTOR_PROVIDER_TO_RESOURCE_NAME: Record< ConnectorProvider, { @@ -97,13 +100,6 @@ export type AssistantBuilderDataSourceConfiguration = { // DustAppRun Action -const DUST_APP_MODES = ["GENERIC", "SELECTED"] as const; -type DustAppMode = (typeof DUST_APP_MODES)[number]; -const DUST_APP_MODE_TO_LABEL: Record = { - GENERIC: "None", - SELECTED: "Selected Dust App", -}; - export type AssistantBuilderDustAppConfiguration = { app: AppType; }; @@ -111,7 +107,7 @@ export type AssistantBuilderDustAppConfiguration = { // Builder State type AssistantBuilderState = { - dataSourceMode: DataSourceMode; + actionMode: ActionMode; dataSourceConfigurations: Record< string, AssistantBuilderDataSourceConfiguration @@ -121,7 +117,6 @@ type AssistantBuilderState = { value: number; unit: TimeframeUnit; }; - dustAppMode: DustAppMode; dustAppConfiguration: AssistantBuilderDustAppConfiguration | null; handle: string | null; description: string | null; @@ -138,13 +133,12 @@ type AssistantBuilderState = { // - allows null timeFrame // - allows null dataSourceConfigurations export type AssistantBuilderInitialState = { - dataSourceMode: AssistantBuilderState["dataSourceMode"]; + actionMode: AssistantBuilderState["actionMode"]; dataSourceConfigurations: | AssistantBuilderState["dataSourceConfigurations"] | null; filteringMode: FilteringMode | null; timeFrame: AssistantBuilderState["timeFrame"] | null; - dustAppMode: AssistantBuilderState["dustAppMode"]; dustAppConfiguration: AssistantBuilderState["dustAppConfiguration"]; handle: string; description: string; @@ -167,14 +161,13 @@ type AssistantBuilderProps = { }; const DEFAULT_ASSISTANT_STATE: AssistantBuilderState = { - dataSourceMode: "GENERIC", + actionMode: "GENERIC", dataSourceConfigurations: {}, filteringMode: "SEARCH", timeFrame: { value: 1, unit: "month", }, - dustAppMode: "GENERIC", dustAppConfiguration: null, handle: null, description: null, @@ -283,7 +276,7 @@ export default function AssistantBuilder({ useEffect(() => { if (initialBuilderState) { setBuilderState({ - dataSourceMode: initialBuilderState.dataSourceMode, + actionMode: initialBuilderState.actionMode, dataSourceConfigurations: initialBuilderState.dataSourceConfigurations ?? { ...DEFAULT_ASSISTANT_STATE.dataSourceConfigurations, @@ -294,7 +287,6 @@ export default function AssistantBuilder({ timeFrame: initialBuilderState.timeFrame ?? { ...DEFAULT_ASSISTANT_STATE.timeFrame, }, - dustAppMode: initialBuilderState.dustAppMode, dustAppConfiguration: initialBuilderState.dustAppConfiguration, handle: initialBuilderState.handle, description: initialBuilderState.description, @@ -390,13 +382,13 @@ export default function AssistantBuilder({ valid = false; } - if (builderState.dataSourceMode === "SELECTED") { + if (builderState.actionMode === "RETRIEVAL") { if (!configuredDataSourceCount) { valid = false; } } - if (builderState.dustAppMode === "SELECTED") { + if (builderState.actionMode === "DUST_APP_RUN") { if (!builderState.dustAppConfiguration) { valid = false; } @@ -413,14 +405,13 @@ export default function AssistantBuilder({ setSubmitEnabled(valid); }, [ + builderState.actionMode, builderState.handle, builderState.description, builderState.instructions, - builderState.dataSourceMode, configuredDataSourceCount, builderState.filteringMode, builderState.timeFrame.value, - builderState.dustAppMode, builderState.dustAppConfiguration, assistantHandleIsAvailable, assistantHandleIsValid, @@ -468,10 +459,10 @@ export default function AssistantBuilder({ let actionParam: BodyType["assistant"]["action"] | null = null; - switch (builderState.dataSourceMode) { + switch (builderState.actionMode) { case "GENERIC": break; - case "SELECTED": + case "RETRIEVAL": const tfParam = (() => { switch (builderState.filteringMode) { case "SEARCH": @@ -513,29 +504,20 @@ export default function AssistantBuilder({ }; break; - default: - ((x: never) => { - throw new Error(`Unknown data source mode ${x}`); - })(builderState.dataSourceMode); - } - - if (builderState.dustAppConfiguration) { - switch (builderState.dustAppMode) { - case "GENERIC": - break; - case "SELECTED": + case "DUST_APP_RUN": + if (builderState.dustAppConfiguration) { actionParam = { type: "dust_app_run_configuration", appWorkspaceId: owner.sId, appId: builderState.dustAppConfiguration.app.sId, }; - break; + } + break; - default: - ((x: never) => { - throw new Error(`Unknown dust app mode ${x}`); - })(builderState.dustAppMode); - } + default: + ((x: never) => { + throw new Error(`Unknown data source mode ${x}`); + })(builderState.actionMode); } const body: t.TypeOf< @@ -744,90 +726,99 @@ export default function AssistantBuilder({ } >
-
-
-
Name
-
- Choose a name reflecting the expertise, knowledge access or - function of your assistant. Mentioning the assistant - in a conversation, like "@help"{" "} - will prompt a response from them. +
+
+ Identity card +
+
+
+
Name
+
+ Choose a name reflecting the expertise, knowledge access or + function of your assistant. Mentioning the assistant + in a conversation, like{" "} + "@help" will prompt + a response from them. +
+
+ { + setEdited(true); + setBuilderState((state) => ({ + ...state, + handle: value.trim(), + })); + }} + error={assistantHandleError} + name="assistantName" + showErrorLabel + className="text-sm" + /> +
+
+ Description +
+
+ Add a short description that will help Dust and other + workspace members understand + the assistant’s purpose. +
+
+ { + setEdited(true); + setBuilderState((state) => ({ + ...state, + description: value, + })); + }} + error={null} // TODO ? + name="assistantDescription" + className="text-sm" + /> +
-
- { - setEdited(true); - setBuilderState((state) => ({ - ...state, - handle: value.trim(), - })); - }} - error={assistantHandleError} - name="assistantName" - showErrorLabel - className="text-sm" +
+ } /> -
-
- Description -
-
- Add a short description that will help Dust and other workspace - members understand the assistant’s purpose. -
-
- { - setEdited(true); - setBuilderState((state) => ({ - ...state, - description: value, - })); +
-
- } - /> -
+
-
+
Instructions
-
- This is your assistant’s heart and soul. -
- Describe, as is you were addressing them, their purpose. Be - specific on the role ( - I want you to act as …), - their expected output, and any formatting requirements you - have ( - - ”Present your answer as a table” - - ). +
+

This is your assistant’s heart and soul.

+

+ Describe, as is you were addressing them, their purpose. Be + specific on the role ( + I want you to act as …), + their expected output, and any formatting requirements + you have ( + + ”Present your answer as a table” + + ). +

-
- Data Sources -
+
Actions
- Aside from common knowledge, your assistant can retrieve - knowledge from selected sources - to answer questions. The Data Sources to pick - from are managed by administrators. + You can ask the assistant to perform actions before answering, + like{" "} + + searching in your Data Sources + + , or use a Dust Application you have built for your specific + needs.
-
    -
  • -
    -
    - Only set data sources if they are necessary. -
    -
    - By default, the assistant will follow its instructions - with common knowledge. It will answer faster when not - using Data Sources. -
    -
    -
  • -
  • -
    -
    - Select your Data Sources carefully. -
    -
    - More is not necessarily better. The quality of your - assistant’s answers to specific questions will depend on - the quality of the underlying data. -
    -
    -
  • -
-
+
- Data Sources: + Action:
-
- { - setShowDataSourcesModal(true); - }} - canAddDataSource={configurableDataSources.length > 0} - onManageDataSource={(name) => { - setDataSourceToManage( - builderState.dataSourceConfigurations[name] - ); - setShowDataSourcesModal(true); - }} - onDelete={deleteDataSource} - filteringMode={builderState.filteringMode} - setFilteringMode={(filteringMode: FilteringMode) => { - setEdited(true); - setBuilderState((state) => ({ - ...state, - filteringMode, - })); - }} - timeFrame={builderState.timeFrame} - setTimeFrame={(timeFrame) => { - setEdited(true); - setBuilderState((state) => ({ - ...state, - timeFrame, - })); - }} - timeFrameError={timeFrameError} - /> -
-
-
- {dustApps.length > 0 && ( -
-
- Dust App Execution -
-
- Your assistant can execute a Dust App of your design before - answering. The output of the app (last block) is injeced in - context for the model to generate an answer. The inputs of the - app will be automatically infered from the context of the - conversation based on the descriptions you provided in the app - input block dataset. -
-
-
- Dust App: -
- - -
+ + )} + + {builderState.actionMode === "DUST_APP_RUN" && ( + <> +
+ Your assistant can execute a Dust Application of your + design before answering. The output of the app (last + block) is injeced in context for the model to generate an + answer. The inputs of the app will be automatically + generated from the context of the conversation based on + the descriptions you provided in the application's input + block dataset schema. +
+
+ { + setShowDustAppsModal(true); + }} + onDelete={deleteDustApp} + canSelectDustApp={dustApps.length !== 0} + /> +
+ + )}
- )} +
@@ -1200,7 +1165,7 @@ function SlackIntegration({
-
+
Slack Integration
diff --git a/front/components/assistant_builder/DustAppSelectionSection.tsx b/front/components/assistant_builder/DustAppSelectionSection.tsx index dc8a7b0c90fa..b2315c20408c 100644 --- a/front/components/assistant_builder/DustAppSelectionSection.tsx +++ b/front/components/assistant_builder/DustAppSelectionSection.tsx @@ -15,11 +15,13 @@ export default function DustAppSelectionSection({ dustAppConfiguration, openDustAppModal, onDelete, + canSelectDustApp, }: { show: boolean; dustAppConfiguration: AssistantBuilderDustAppConfiguration | null; openDustAppModal: () => void; onDelete?: (sId: string) => void; + canSelectDustApp: boolean; }) { return (
); case "content_fragment": - return null; + return ( +
+
+ +
+
+ ); default: ((message: never) => { console.error("Unknown message type", message); diff --git a/front/components/assistant/conversation/ConversationMessage.tsx b/front/components/assistant/conversation/ConversationMessage.tsx index bd902caad857..ccf144f9f9c8 100644 --- a/front/components/assistant/conversation/ConversationMessage.tsx +++ b/front/components/assistant/conversation/ConversationMessage.tsx @@ -27,6 +27,8 @@ export function ConversationMessage({ buttons, reactions, avatarBusy = false, + // avatarBackgroundColor, + enableEmojis = true, }: { owner: WorkspaceType; user: UserType; @@ -34,7 +36,7 @@ export function ConversationMessage({ messageId: string; children?: React.ReactNode; name: string | null; - pictureUrl?: string | null; + pictureUrl?: string | React.ReactNode | null; buttons?: { label: string; icon: ComponentType; @@ -42,6 +44,8 @@ export function ConversationMessage({ }[]; reactions: MessageReactionType[]; avatarBusy?: boolean; + avatarBackgroundColor?: string; + enableEmojis: boolean; }) { const [emojiData, setEmojiData] = useState(null); @@ -123,6 +127,7 @@ export function ConversationMessage({ name={name || undefined} size="xs" busy={avatarBusy} + // backgroundColor={avatarBackgroundColor} />
{name}
@@ -179,49 +184,51 @@ export function ConversationMessage({
{/* EMOJIS */} -
- await handleEmojiClick("+1")} - /> - await handleEmojiClick("-1")} - /> - {otherReactions.map((reaction) => { - const hasReacted = reaction.users.some( - (u) => u.userId === user.id - ); - const emoji = emojiData?.emojis[reaction.emoji]; - const nativeEmoji = emoji?.skins[0].native; - if (!nativeEmoji) { - return null; - } - return ( - - await handleEmoji({ - emoji: reaction.emoji, - isToRemove: hasReacted, - }) - } - /> - ); - })} - {hasMoreReactions && ( - +{hasMoreReactions} - )} -
+ {enableEmojis && ( +
+ await handleEmojiClick("+1")} + /> + await handleEmojiClick("-1")} + /> + {otherReactions.map((reaction) => { + const hasReacted = reaction.users.some( + (u) => u.userId === user.id + ); + const emoji = emojiData?.emojis[reaction.emoji]; + const nativeEmoji = emoji?.skins[0].native; + if (!nativeEmoji) { + return null; + } + return ( + + await handleEmoji({ + emoji: reaction.emoji, + isToRemove: hasReacted, + }) + } + /> + ); + })} + {hasMoreReactions && ( + +{hasMoreReactions} + )} +
+ )}
@@ -271,82 +278,89 @@ export function ConversationMessage({ )} {/* EMOJIS */} -
- await handleEmojiClick("+1")} - /> - await handleEmojiClick("-1")} - /> - {otherReactions.map((reaction) => { - const hasReacted = reaction.users.some( - (u) => u.userId === user.id - ); - const emoji = emojiData?.emojis[reaction.emoji]; - const nativeEmoji = emoji?.skins[0].native; - if (!nativeEmoji) { - return null; - } - return ( - - await handleEmoji({ - emoji: reaction.emoji, - isToRemove: hasReacted, - }) - } - /> - ); - })} - {hasMoreReactions && ( - +{hasMoreReactions} - )} -
-
- - -
+ + {enableEmojis && ( +
+ await handleEmojiClick("+1")} + /> + await handleEmojiClick("-1")} + /> + {otherReactions.map((reaction) => { + const hasReacted = reaction.users.some( + (u) => u.userId === user.id + ); + const emoji = emojiData?.emojis[reaction.emoji]; + const nativeEmoji = emoji?.skins[0].native; + if (!nativeEmoji) { + return null; + } + return ( + + await handleEmoji({ + emoji: reaction.emoji, + isToRemove: hasReacted, + }) + } + /> + ); + })} + {hasMoreReactions && ( + +{hasMoreReactions} + )} +
+ )} + {enableEmojis && ( +
+ + +
+ )}
diff --git a/front/components/assistant/conversation/UserMessage.tsx b/front/components/assistant/conversation/UserMessage.tsx index 4bccd3ce2202..a8b1ed6a7030 100644 --- a/front/components/assistant/conversation/UserMessage.tsx +++ b/front/components/assistant/conversation/UserMessage.tsx @@ -36,6 +36,7 @@ export function UserMessage({ pictureUrl={message.context.profilePictureUrl} name={message.context.fullName} reactions={reactions} + enableEmojis={true} >
diff --git a/front/lib/api/assistant/conversation.ts b/front/lib/api/assistant/conversation.ts index 2eff8a82fc6a..a60913a50d8a 100644 --- a/front/lib/api/assistant/conversation.ts +++ b/front/lib/api/assistant/conversation.ts @@ -36,6 +36,7 @@ import { generateModelSId } from "@app/lib/utils"; import logger from "@app/logger/logger"; import { AgentMessageType, + ContentFragmentContentType, ContentFragmentType, ConversationType, ConversationVisibility, @@ -330,6 +331,8 @@ function renderContentFragment({ version: message.version, title: contentFragment.title, content: contentFragment.content, + url: contentFragment.url, + contentType: contentFragment.contentType, }; } @@ -1486,7 +1489,15 @@ export async function postNewContentFragment( conversation, title, content, - }: { conversation: ConversationType; title: string; content: string } + url, + contentType, + }: { + conversation: ConversationType; + title: string; + content: string; + url: string | null; + contentType: ContentFragmentContentType; + } ): Promise { const owner = auth.workspace(); @@ -1497,7 +1508,7 @@ export async function postNewContentFragment( const { contentFragmentRow, messageRow } = await front_sequelize.transaction( async (t) => { const contentFragmentRow = await ContentFragment.create( - { content, title }, + { content, title, url, contentType }, { transaction: t } ); const nextMessageRank = diff --git a/front/lib/models/assistant/conversation.ts b/front/lib/models/assistant/conversation.ts index 18c39d01dc50..620439bb37b4 100644 --- a/front/lib/models/assistant/conversation.ts +++ b/front/lib/models/assistant/conversation.ts @@ -14,6 +14,7 @@ import { User } from "@app/lib/models/user"; import { Workspace } from "@app/lib/models/workspace"; import { AgentMessageStatus, + ContentFragmentContentType, ConversationVisibility, MessageVisibility, ParticipantActionType, @@ -349,6 +350,8 @@ export class ContentFragment extends Model< declare title: string; declare content: string; + declare url: string | null; + declare contentType: ContentFragmentContentType; } ContentFragment.init( @@ -376,6 +379,14 @@ ContentFragment.init( type: DataTypes.TEXT, allowNull: false, }, + url: { + type: DataTypes.TEXT, + allowNull: true, + }, + contentType: { + type: DataTypes.STRING, + allowNull: false, + }, }, { modelName: "content_fragment", diff --git a/front/lib/utils.ts b/front/lib/utils.ts index e2ac782d9bf7..e65d987816e1 100644 --- a/front/lib/utils.ts +++ b/front/lib/utils.ts @@ -130,3 +130,7 @@ export function ioTsEnum( t.identity ); } + +export function assertNever(x: never): never { + throw new Error(`${x} is not of type never. This should never happen.`); +} diff --git a/front/package-lock.json b/front/package-lock.json index ab9437cfa3c1..08b1d1a8d633 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@dust-tt/sparkle": "0.2.5", + "@dust-tt/sparkle": "^0.2.7", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "@headlessui/react": "^1.7.7", @@ -727,9 +727,9 @@ "license": "Apache-2.0" }, "node_modules/@dust-tt/sparkle": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@dust-tt/sparkle/-/sparkle-0.2.5.tgz", - "integrity": "sha512-LHVWETWiHznHkKlXQuDMnjU4g00ss04/WL5Af8cWBpe7S+ehSG/9zi4mdBeStkBqpE7+rYXSeAsE1bP1+VxdTg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@dust-tt/sparkle/-/sparkle-0.2.7.tgz", + "integrity": "sha512-+FRv5PSgzSMjbzwQEcHH11T3XeZ7pbDfDWdXaiGy5Sncd41zBMg9MGL2qmptefqiN+T29CkO+jeSwHIi+TEHPw==", "dependencies": { "@headlessui/react": "^1.7.17" }, diff --git a/front/package.json b/front/package.json index c1f3f88d1041..7b24edf1145a 100644 --- a/front/package.json +++ b/front/package.json @@ -13,7 +13,7 @@ "initdb": "env $(cat .env.local) npx tsx admin/db.ts" }, "dependencies": { - "@dust-tt/sparkle": "0.2.5", + "@dust-tt/sparkle": "^0.2.7", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "@headlessui/react": "^1.7.7", diff --git a/front/pages/api/v1/w/[wId]/assistant/conversations/[cId]/content_fragments.ts b/front/pages/api/v1/w/[wId]/assistant/conversations/[cId]/content_fragments.ts index 9be986d310aa..d3b2e08ddf72 100644 --- a/front/pages/api/v1/w/[wId]/assistant/conversations/[cId]/content_fragments.ts +++ b/front/pages/api/v1/w/[wId]/assistant/conversations/[cId]/content_fragments.ts @@ -19,6 +19,8 @@ export type PostContentFragmentsResponseBody = { export const PostContentFragmentRequestBodySchema = t.type({ title: t.string, content: t.string, + url: t.union([t.string, t.null]), + contentType: t.literal("slack_thread_content"), }); async function handler( @@ -74,7 +76,7 @@ async function handler( }); } - const { content, title } = bodyValidation.right; + const { content, title, url, contentType } = bodyValidation.right; if (content.length === 0 || content.length > 64 * 1024) { return apiError(req, res, { @@ -91,6 +93,8 @@ async function handler( conversation, title, content, + url, + contentType, }); res.status(200).json({ contentFragment }); diff --git a/front/pages/api/v1/w/[wId]/assistant/conversations/index.ts b/front/pages/api/v1/w/[wId]/assistant/conversations/index.ts index 5991ead43588..3408e5ec16f9 100644 --- a/front/pages/api/v1/w/[wId]/assistant/conversations/index.ts +++ b/front/pages/api/v1/w/[wId]/assistant/conversations/index.ts @@ -123,9 +123,18 @@ async function handler( conversation, title: contentFragment.title, content: contentFragment.content, + url: contentFragment.url, + contentType: contentFragment.contentType, }); newContentFragment = cf; + const updatedConversation = await getConversation( + auth, + conversation.sId + ); + if (updatedConversation) { + conversation = updatedConversation; + } } if (message) { diff --git a/front/types/assistant/conversation.ts b/front/types/assistant/conversation.ts index 2fc4590ff5ad..b30b3ddca668 100644 --- a/front/types/assistant/conversation.ts +++ b/front/types/assistant/conversation.ts @@ -124,6 +124,7 @@ export function isAgentMessageType( /** * Content Fragments */ +export type ContentFragmentContentType = "slack_thread_content"; export type ContentFragmentType = { id: ModelId; @@ -135,6 +136,8 @@ export type ContentFragmentType = { title: string; content: string; + url: string | null; + contentType: ContentFragmentContentType; }; export function isContentFragmentType( From c41ba3db8edeefa8b5519412ea3d95dfc6c5f582 Mon Sep 17 00:00:00 2001 From: Philippe Rolet Date: Thu, 12 Oct 2023 16:21:01 +0200 Subject: [PATCH 3/4] Right-side modal type (#2089) * Right-side modal type * bump sparkle --- sparkle/package.json | 2 +- sparkle/src/components/Modal.tsx | 42 ++++++++++++++++++++------- sparkle/src/stories/Modal.stories.tsx | 35 +++++++++++++++++++--- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/sparkle/package.json b/sparkle/package.json index fe285362fe0f..5067b49af028 100644 --- a/sparkle/package.json +++ b/sparkle/package.json @@ -1,6 +1,6 @@ { "name": "@dust-tt/sparkle", - "version": "0.2.7", + "version": "0.2.8", "scripts": { "build": "rm -rf dist && rollup -c", "build:with-tw-base": "rollup -c --environment INCLUDE_TW_BASE:true", diff --git a/sparkle/src/components/Modal.tsx b/sparkle/src/components/Modal.tsx index 56c62e54e2d0..60fe6d44bb36 100644 --- a/sparkle/src/components/Modal.tsx +++ b/sparkle/src/components/Modal.tsx @@ -14,7 +14,7 @@ interface ModalProps { hasChanged: boolean; onSave?: () => void; title?: string; - isFullScreen?: boolean; + type?: "full-screen" | "right-side" | "default"; } export function Modal({ @@ -25,7 +25,7 @@ export function Modal({ hasChanged, onSave, title, - isFullScreen = false, + type = "default", }: ModalProps) { const buttonBarProps: BarHeaderButtonBarProps = hasChanged ? { @@ -56,25 +56,43 @@ export function Modal({
{children} diff --git a/sparkle/src/stories/Modal.stories.tsx b/sparkle/src/stories/Modal.stories.tsx index 90a0dec4662b..e38b54080bb5 100644 --- a/sparkle/src/stories/Modal.stories.tsx +++ b/sparkle/src/stories/Modal.stories.tsx @@ -1,7 +1,7 @@ import type { Meta } from "@storybook/react"; import React, { useState } from "react"; -import { Button, Modal } from "../index_with_tw_base"; +import { Button, Input, Modal } from "../index_with_tw_base"; const meta = { title: "Molecule/Modal", @@ -17,7 +17,8 @@ export const ModalExample = () => { const [isFullScreenModalOpen, setIsFullScreenModalOpen] = useState(false); const [isFullScreenModalOverflowOpen, setIsFullScreenModalOverflowOpen] = useState(false); - + const [isRightSideModalOpen, setIsRightSideModalOpen] = useState(false); + const [inputValue, setInputValue] = useState("initial value"); return ( <> { >
I'm the modal content
+ setIsRightSideModalOpen(false)} + type="right-side" + title="Modal title" + hasChanged={inputValue !== "initial value"} + > +
+
+ I'm the modal content +
+
+ +
+
+
setIsOpenWithActionAndChange(false)} @@ -45,7 +68,7 @@ export const ModalExample = () => { isOpen={isFullScreenModalOpen} onClose={() => setIsFullScreenModalOpen(false)} hasChanged={true} - isFullScreen={true} + type="full-screen" title="Modal title" >
I'm the modal content
@@ -54,7 +77,7 @@ export const ModalExample = () => { isOpen={isFullScreenModalOverflowOpen} onClose={() => setIsFullScreenModalOverflowOpen(false)} hasChanged={true} - isFullScreen={true} + type="full-screen" title="Modal title" >
@@ -86,6 +109,10 @@ export const ModalExample = () => { label="Modal full screen with overflowing content" onClick={() => setIsFullScreenModalOverflowOpen(true)} /> +