From b3fb52c7ea9007c4db4e9214702cb34a91e263d5 Mon Sep 17 00:00:00 2001 From: sacrodge Date: Fri, 3 Oct 2025 19:26:31 -0700 Subject: [PATCH 1/2] msg: Working; Complete new UI --- example/src/commands.ts | 3 +- example/src/config.ts | 5 + example/src/main.ts | 274 ++++++++++----- example/src/samples/sample-data.ts | 174 +++++++++- src/components/chat-item/chat-wrapper.ts | 47 +++ src/components/modified-files-tracker.ts | 318 ++++++++++++++++++ src/helper/store.ts | 3 +- src/helper/test-ids.ts | 9 + src/main.ts | 5 + src/static.ts | 10 +- .../components/_modified-files-container.scss | 7 + .../components/_modified-files-tracker.scss | 134 ++++++++ src/styles/styles.scss | 2 + 13 files changed, 902 insertions(+), 89 deletions(-) create mode 100644 src/components/modified-files-tracker.ts create mode 100644 src/styles/components/_modified-files-container.scss create mode 100644 src/styles/components/_modified-files-tracker.scss diff --git a/example/src/commands.ts b/example/src/commands.ts index b71dfbfb..5f68c0b9 100644 --- a/example/src/commands.ts +++ b/example/src/commands.ts @@ -28,5 +28,6 @@ export enum Commands { CLEAR_CONTEXT_ITEMS = '/clear-context-items', CLEAR_LOGS = '/clear-logs', SHOW_CUSTOM_FORM = '/show-custom-form', - VOTE = '/vote' + VOTE = '/vote', + MODIFIED_FILES_TRACKER = '/modified-files-tracker' } \ No newline at end of file diff --git a/example/src/config.ts b/example/src/config.ts index 4f5c8e02..82ecc970 100644 --- a/example/src/config.ts +++ b/example/src/config.ts @@ -72,6 +72,11 @@ export const tabbarButtons: TabBarMainAction[] = [ text: 'Show code diff!', icon: MynahIcons.CODE_BLOCK, }, + { + id: 'show-modified-files', + text: 'Show modified files tracker demo', + icon: MynahIcons.FILE, + }, { id: 'insert-code', icon: MynahIcons.CURSOR_INSERT, diff --git a/example/src/main.ts b/example/src/main.ts index 61096c7a..9a47b46f 100644 --- a/example/src/main.ts +++ b/example/src/main.ts @@ -17,7 +17,8 @@ import { QuickActionCommand, ChatItemButton, CustomQuickActionCommand, - DropdownListOption + DropdownListOption, + ModifiedFilesTracker } from '@aws/mynah-ui'; import { mcpButton, mynahUIDefaults, promptTopBarTitle, rulesButton, tabbarButtons } from './config'; import { Log, LogClear } from './logger'; @@ -55,6 +56,9 @@ import { mcpToolRunSampleCardInit, sampleRulesList, accountDetailsTabData, + sampleModifiedFiles, + sampleModifiedFilesEmpty, + sampleModifiedFilesLarge, } from './samples/sample-data'; import escapeHTML from 'escape-html'; import './styles/styles.scss'; @@ -139,15 +143,14 @@ Model - ${optionsValues['model-select'] !== '' ? optionsValues['model-select'] : }); } Log(`Prompt options change for tab ${tabId}:
- ${ - optionsValues - ? `
Options:
${Object.keys(optionsValues) - .map((optionId) => { - return `${optionId}: ${(optionsValues as Record)[optionId] ?? ''}`; - }) - .join('
')}` - : '' - } + ${optionsValues + ? `
Options:
${Object.keys(optionsValues) + .map((optionId) => { + return `${optionId}: ${(optionsValues as Record)[optionId] ?? ''}`; + }) + .join('
')}` + : '' + } `); }, onSplashLoaderActionClick: (action) => { @@ -169,7 +172,7 @@ Model - ${optionsValues['model-select'] !== '' ? optionsValues['model-select'] : }); } }, - onDropDownOptionChange: (tabId: string, messageId: string, value: DropdownListOption []) => { + onDropDownOptionChange: (tabId: string, messageId: string, value: DropdownListOption[]) => { Log(`Dropdown Option changed in message ${messageId} on tab ${tabId}`) }, onDropDownLinkClick: (tabId, actionId, destination) => { @@ -182,35 +185,39 @@ Model - ${optionsValues['model-select'] !== '' ? optionsValues['model-select'] : Log(`MynahUI focus state changed: ${focusState.toString()}`); }, onPromptTopBarItemAdded: (tabId: string, item: QuickActionCommand) => { - Log(`Prompt top bar item ${item.command} added on tab ${tabId}`); + Log(`Prompt top bar item ${item.command} added on tab ${tabId}`); mynahUI.updateStore(tabId, { - promptTopBarContextItems: [ - ...((mynahUI.getTabData(tabId).getValue('promptTopBarContextItems') as QuickActionCommand[]).filter((existingItem) => existingItem.command !== item.command) ?? []), - item, - ], - }); + promptTopBarContextItems: [ + ...((mynahUI.getTabData(tabId).getValue('promptTopBarContextItems') as QuickActionCommand[]).filter((existingItem) => existingItem.command !== item.command) ?? []), + item, + ], + }); }, onPromptTopBarItemRemoved: (tabId: string, item: QuickActionCommand) => { - Log(`Prompt top bar item ${item.command} removed on tab ${tabId}`); + Log(`Prompt top bar item ${item.command} removed on tab ${tabId}`); mynahUI.updateStore(tabId, { - promptTopBarContextItems:(mynahUI.getTabData(tabId).getValue('promptTopBarContextItems') as QuickActionCommand[]).filter((existingItem) => existingItem.command !== item.command) - }); + promptTopBarContextItems: (mynahUI.getTabData(tabId).getValue('promptTopBarContextItems') as QuickActionCommand[]).filter((existingItem) => existingItem.command !== item.command) + }); }, onPromptTopBarButtonClick: (tabId: string, button: ChatItemButton) => { - Log(`Top bar button ${button.id} clicked on tab ${tabId}`); + Log(`Top bar button ${button.id} clicked on tab ${tabId}`); - const topBarOverlay = mynahUI.openTopBarButtonOverlay({tabId, topBarButtonOverlay: sampleRulesList, - events: { - onClose: () => {Log(`Top bar overlay closed on tab ${tabId}`)}, - onGroupClick: (group) => {Log(`Top bar overlay group clicked ${group} on tab ${tabId}`)}, - onItemClick: (item) => { Log(`Top bar overlay item clicked ${item.id} on tab ${tabId}`); topBarOverlay.update(sampleRulesList)}, - onKeyPress: (e) => {Log(`Key pressed on top bar overlay`); if (e.key === KeyMap.ESCAPE) { - topBarOverlay.close(); - }} + const topBarOverlay = mynahUI.openTopBarButtonOverlay({ + tabId, topBarButtonOverlay: sampleRulesList, + events: { + onClose: () => { Log(`Top bar overlay closed on tab ${tabId}`) }, + onGroupClick: (group) => { Log(`Top bar overlay group clicked ${group} on tab ${tabId}`) }, + onItemClick: (item) => { Log(`Top bar overlay item clicked ${item.id} on tab ${tabId}`); topBarOverlay.update(sampleRulesList) }, + onKeyPress: (e) => { + Log(`Key pressed on top bar overlay`); if (e.key === KeyMap.ESCAPE) { + topBarOverlay.close(); + } + } - }}) + } + }) }, @@ -332,6 +339,45 @@ Model - ${optionsValues['model-select'] !== '' ? optionsValues['model-select'] : }, }); mynahUI.addChatItem(tabId, defaultFollowUps); + } else if (buttonId === 'show-modified-files') { + // Demo the ModifiedFilesTracker as a separate component above the chat input + mynahUI.updateStore(tabId, { + modifiedFilesData: sampleModifiedFiles + }); + + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + body: `## Modified Files Tracker Demo + +The ModifiedFilesTracker is now displayed as a separate component above the chat input! + +Features: +- Integrated into the main UI structure +- Uses existing fileList data structure from ChatItem +- Individual file undo buttons +- Undo all functionality +- Automatic visibility management +- Collapsible content + +Try the buttons below to interact with it:`, + buttons: [ + { + id: 'add-more-files', + text: 'Add More Files', + status: 'primary' + }, + { + id: 'clear-files', + text: 'Clear All Files', + status: 'clear' + }, + { + id: 'show-large-set', + text: 'Show Large Set', + status: 'clear' + } + ] + }); } else if (buttonId === 'insert-code') { mynahUI.addToUserPrompt(tabId, exampleCodeBlockToInsert, 'code'); } else if (buttonId === 'show-avatars') { @@ -343,18 +389,19 @@ Model - ${optionsValues['model-select'] !== '' ? optionsValues['model-select'] : ); } else if (buttonId === 'show-pinned-context') { showPinnedContext = !showPinnedContext; - if (showPinnedContext){ - Object.keys(mynahUI.getAllTabs()).forEach((tabIdFromStore) => - mynahUI.updateStore(tabIdFromStore, { - promptTopBarTitle: promptTopBarTitle, - promptTopBarButton: rulesButton, - }), - ); } else { - Object.keys(mynahUI.getAllTabs()).forEach((tabIdFromStore) => - mynahUI.updateStore(tabIdFromStore, { - promptTopBarTitle: ``, - }), - ) + if (showPinnedContext) { + Object.keys(mynahUI.getAllTabs()).forEach((tabIdFromStore) => + mynahUI.updateStore(tabIdFromStore, { + promptTopBarTitle: promptTopBarTitle, + promptTopBarButton: rulesButton, + }), + ); + } else { + Object.keys(mynahUI.getAllTabs()).forEach((tabIdFromStore) => + mynahUI.updateStore(tabIdFromStore, { + promptTopBarTitle: ``, + }), + ) } } else if (buttonId === 'splash-loader') { mynahUI.toggleSplashLoader(true, 'Showing splash loader...'); @@ -1260,16 +1307,15 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs }, onChatPrompt: (tabId: string, prompt: ChatPrompt) => { - + Log(`New prompt on tab: ${tabId}
prompt: ${prompt.prompt !== undefined && prompt.prompt !== '' ? prompt.prompt : '{command only}'}
command: ${prompt.command ?? '{none}'}
- options: {${ - Object.keys(prompt.options ?? {}) - .map((op) => `'${op}': '${prompt.options?.[op] as string}'`) - .join(',') ?? '' - }}
+ options: {${Object.keys(prompt.options ?? {}) + .map((op) => `'${op}': '${prompt.options?.[op] as string}'`) + .join(',') ?? '' + }}
context: [${(prompt.context ?? []).map((ctx) => `${JSON.stringify(ctx)}`).join('], [')}]`); if (tabId === 'tab-1') { mynahUI.updateStore(tabId, { @@ -1378,7 +1424,27 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs Log(`Images dropped: ${commands.map(cmd => `
- ${cmd.command}`).join('')}`); }, onInBodyButtonClicked: (tabId: string, messageId: string, action) => { - if (action.id === 'allow-readonly-tools') { + if (action.id === 'add-more-files') { + const currentData = mynahUI.getTabData(tabId).getValue('modifiedFilesData'); + const newFiles = ['src/new-component.tsx', 'tests/new-component.test.ts', 'docs/README.md']; + mynahUI.updateStore(tabId, { + modifiedFilesData: { + ...currentData, + fileList: { + ...currentData?.fileList, + filePaths: [...(currentData?.fileList?.filePaths || []), ...newFiles] + } + } + }); + } else if (action.id === 'clear-files') { + mynahUI.updateStore(tabId, { + modifiedFilesData: null + }); + } else if (action.id === 'show-large-set') { + mynahUI.updateStore(tabId, { + modifiedFilesData: sampleModifiedFilesLarge + }); + } else if (action.id === 'allow-readonly-tools') { mynahUI.updateChatAnswerWithMessageId(tabId, messageId, { muted: true, header: { @@ -1454,15 +1520,14 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs Log(`Body action clicked in message ${messageId}:
Action Id: ${action.id}
Action Text: ${action.text}
- ${ - action.formItemValues - ? `
Options:
${Object.keys(action.formItemValues) - .map((optionId) => { - return `${optionId}: ${(action.formItemValues as Record)[optionId] ?? ''}`; - }) - .join('
')}` - : '' - } + ${action.formItemValues + ? `
Options:
${Object.keys(action.formItemValues) + .map((optionId) => { + return `${optionId}: ${(action.formItemValues as Record)[optionId] ?? ''}`; + }) + .join('
')}` + : '' + } `); }, onQuickCommandGroupActionClick: (tabId: string, action) => { @@ -1522,15 +1587,14 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs event.preventDefault(); event.stopImmediatePropagation(); Log(`Form keypress Enter submit on tab ${tabId}:
- ${ - formData - ? `
Options:
${Object.keys(formData) - .map((optionId) => { - return `${optionId}: ${(formData as Record)[optionId] ?? ''}`; - }) - .join('
')}` - : '' - } + ${formData + ? `
Options:
${Object.keys(formData) + .map((optionId) => { + return `${optionId}: ${(formData as Record)[optionId] ?? ''}`; + }) + .join('
')}` + : '' + } `); return true; } @@ -1540,15 +1604,14 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs Log(`Custom form action clicked for tab ${tabId}:
Action Id: ${action.id}
Action Text: ${action.text}
- ${ - action.formItemValues - ? `
Options:
${Object.keys(action.formItemValues) - .map((optionId) => { - return `${optionId}: ${(action.formItemValues as Record)[optionId] ?? ''}`; - }) - .join('
')}` - : '' - } + ${action.formItemValues + ? `
Options:
${Object.keys(action.formItemValues) + .map((optionId) => { + return `${optionId}: ${(action.formItemValues as Record)[optionId] ?? ''}`; + }) + .join('
')}` + : '' + } `); }, onChatItemEngagement: (tabId, messageId, engagement) => { @@ -1625,6 +1688,47 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs mynahUI.addChatItem(tabId, exampleVoteChatItem); mynahUI.addChatItem(tabId, defaultFollowUps); break; + case Commands.MODIFIED_FILES_TRACKER: + // Demo the ModifiedFilesTracker as a separate component above the chat input + mynahUI.updateStore(tabId, { + modifiedFilesData: sampleModifiedFiles + }); + + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + body: `## Modified Files Tracker Demo + +The ModifiedFilesTracker is now displayed as a separate component above the chat input! + +Features: +- Integrated into the main UI structure +- Uses existing fileList data structure from ChatItem +- Individual file undo buttons +- Undo all functionality +- Automatic visibility management +- Collapsible content + +Try the buttons below to interact with it:`, + buttons: [ + { + id: 'add-more-files', + text: 'Add More Files', + status: 'primary' + }, + { + id: 'clear-files', + text: 'Clear All Files', + status: 'clear' + }, + { + id: 'show-large-set', + text: 'Show Large Set', + status: 'clear' + } + ] + }); + mynahUI.addChatItem(tabId, defaultFollowUps); + break; case Commands.CARD_WITH_MARKDOWN_LIST: getGenerativeAIAnswer(tabId, sampleMarkdownList); break; @@ -1951,13 +2055,13 @@ used as a context to generate this message.`, mynahUI.updateStore(tabId, { ...(optionalParts != null ? { - promptInputProgress: { - status: 'info', - ...(percentage > 50 ? { text: 'Almost done...' } : {}), - valueText: `${parseInt(percentage.toString())}%`, - value: percentage, - }, - } + promptInputProgress: { + status: 'info', + ...(percentage > 50 ? { text: 'Almost done...' } : {}), + valueText: `${parseInt(percentage.toString())}%`, + value: percentage, + }, + } : {}), }); return false; @@ -1990,8 +2094,8 @@ used as a context to generate this message.`, } Log(`Stream ended with details:
${Object.keys(cardDetails) - .map((key) => `${key}: ${cardDetails[key].toString()}`) - .join('
')} + .map((key) => `${key}: ${cardDetails[key].toString()}`) + .join('
')} `); mynahUI.addChatItem(tabId, { ...defaultFollowUps, messageId: generateUID() }); streamingMessageId = null; diff --git a/example/src/samples/sample-data.ts b/example/src/samples/sample-data.ts index a5b05be2..464b02bb 100644 --- a/example/src/samples/sample-data.ts +++ b/example/src/samples/sample-data.ts @@ -677,6 +677,10 @@ export const defaultFollowUps: ChatItem = { pillText: 'Sticky card', command: Commands.SHOW_STICKY_CARD, }, + { + pillText: 'Modified Files Tracker', + command: Commands.MODIFIED_FILES_TRACKER, + }, { pillText: 'Some auto reply', prompt: 'Some random auto reply here.', @@ -2660,4 +2664,172 @@ export const sampleMCPDetails = (title: string): DetailedList => { export const sampleRulesList: DetailedList = {selectable: 'clickable', list: [{children: [{id: 'README', icon: MynahIcons.CHECK_LIST, description: 'README',actions: [{ id: 'README.md', icon: MynahIcons.OK, status: 'clear' }]}]}, {groupName: '.amazonq/rules', childrenIndented: true, icon: MynahIcons.FOLDER , actions: [{ id: 'java-expert.md', icon: MynahIcons.OK, status: 'clear' }], children: [{id: 'java-expert.md', icon: MynahIcons.CHECK_LIST, - description: 'java-expert',actions: [{ id: 'java-expert.md', icon: MynahIcons.OK, status: 'clear' }]}]}]} \ No newline at end of file + description: 'java-expert',actions: [{ id: 'java-expert.md', icon: MynahIcons.OK, status: 'clear' }]}]}]} + +// Sample modified files data using the new ModifiedFilesTrackerData structure +export const sampleModifiedFiles = { + title: 'Modified Files', + visible: true, + showUndoAll: true, + showFileCount: true, + initialCollapsed: false, + fileList: { + filePaths: [ + 'src/components/Button.tsx', + 'src/utils/helpers.ts', + 'README.md', + 'package.json', + 'tests/Button.test.tsx' + ], + deletedFiles: [ + 'src/styles/main.scss' + ], + details: { + 'src/components/Button.tsx': { + visibleName: 'Button.tsx', + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, // modified + description: 'Modified component file' + }, + 'src/utils/helpers.ts': { + visibleName: 'helpers.ts', + icon: MynahIcons.CODE_BLOCK, + status: 'success' as const, // added + description: 'New utility functions' + }, + 'README.md': { + icon: MynahIcons.DOC, + status: 'warning' as const, // modified + description: 'Updated documentation' + }, + 'package.json': { + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, // modified + description: 'Updated dependencies' + }, + 'src/styles/main.scss': { + visibleName: 'main.scss', + icon: MynahIcons.CODE_BLOCK, + status: 'error' as const, // deleted + description: 'Removed stylesheet' + }, + 'tests/Button.test.tsx': { + visibleName: 'Button.test.tsx', + icon: MynahIcons.CHECK_LIST, + status: 'success' as const, // added + description: 'New test file' + } + }, + collapsed: false, + hideFileCount: false + } +}; + +export const sampleModifiedFilesEmpty = { + title: 'Empty State', + visible: true, + showUndoAll: false, + showFileCount: true, + initialCollapsed: false, + fileList: { + filePaths: [], + deletedFiles: [], + details: {}, + collapsed: false, + hideFileCount: false + } +}; + +export const sampleModifiedFilesLarge = { + title: 'Large File Set', + visible: true, + showUndoAll: true, + showFileCount: true, + initialCollapsed: true, + fileList: { + filePaths: [ + 'src/components/Button.tsx', + 'src/utils/helpers.ts', + 'README.md', + 'package.json', + 'tests/Button.test.tsx', + 'src/components/Modal.tsx', + 'src/components/Input.tsx', + 'src/hooks/useLocalStorage.ts', + 'src/types/index.ts', + 'docs/api.md', + 'config/webpack.config.js' + ], + deletedFiles: [ + 'src/styles/main.scss' + ], + details: { + 'src/components/Button.tsx': { + visibleName: 'Button.tsx', + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, + description: 'Modified component file' + }, + 'src/utils/helpers.ts': { + visibleName: 'helpers.ts', + icon: MynahIcons.CODE_BLOCK, + status: 'success' as const, + description: 'New utility functions' + }, + 'README.md': { + icon: MynahIcons.DOC, + status: 'warning' as const, + description: 'Updated documentation' + }, + 'package.json': { + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, + description: 'Updated dependencies' + }, + 'src/styles/main.scss': { + visibleName: 'main.scss', + icon: MynahIcons.CODE_BLOCK, + status: 'error' as const, + description: 'Removed stylesheet' + }, + 'tests/Button.test.tsx': { + visibleName: 'Button.test.tsx', + icon: MynahIcons.CHECK_LIST, + status: 'success' as const, + description: 'New test file' + }, + 'src/components/Modal.tsx': { + icon: MynahIcons.CODE_BLOCK, + status: 'success' as const, + description: 'New modal component' + }, + 'src/components/Input.tsx': { + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, + description: 'Modified input component' + }, + 'src/hooks/useLocalStorage.ts': { + icon: MynahIcons.CODE_BLOCK, + status: 'success' as const, + description: 'New custom hook' + }, + 'src/types/index.ts': { + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, + description: 'Updated type definitions' + }, + 'docs/api.md': { + icon: MynahIcons.DOC, + status: 'success' as const, + description: 'New API documentation' + }, + 'config/webpack.config.js': { + icon: MynahIcons.CODE_BLOCK, + status: 'warning' as const, + description: 'Updated webpack configuration' + } + }, + collapsed: true, + hideFileCount: false + } +}; \ No newline at end of file diff --git a/src/components/chat-item/chat-wrapper.ts b/src/components/chat-item/chat-wrapper.ts index ebcc1d4e..e9b17427 100644 --- a/src/components/chat-item/chat-wrapper.ts +++ b/src/components/chat-item/chat-wrapper.ts @@ -30,6 +30,7 @@ import { StyleLoader } from '../../helper/style-loader'; import { Icon } from '../icon'; import { cancelEvent, MynahUIGlobalEvents } from '../../helper/events'; import { TopBarButtonOverlayProps } from './prompt-input/prompt-top-bar/top-bar-button'; +import { ModifiedFilesTracker, ModifiedFilesTrackerData } from '../modified-files-tracker'; export const CONTAINER_GAP = 12; export interface ChatWrapperProps { @@ -58,6 +59,8 @@ export class ChatWrapper { private readonly dragBlurOverlay: HTMLElement; private dragOverlayVisibility: boolean = true; private imageContextFeatureEnabled: boolean = false; + private modifiedFilesTracker: ModifiedFilesTracker | null = null; + private readonly modifiedFilesContainer: ExtendedHTMLElement; constructor (props: ChatWrapperProps) { StyleLoader.getInstance().load('components/chat/_chat-wrapper.scss'); @@ -207,6 +210,17 @@ export class ChatWrapper { this.promptInfo = new ChatPromptInputInfo({ tabId: this.props.tabId }).render; this.promptStickyCard = new ChatPromptInputStickyCard({ tabId: this.props.tabId }).render; + + // Initialize ModifiedFilesTracker container + this.modifiedFilesContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-modified-files-container' ] + }); + + // Listen for modifiedFilesData changes + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'modifiedFilesData', (modifiedFilesData: ModifiedFilesTrackerData) => { + this.updateModifiedFilesTracker(modifiedFilesData); + }); if (Config.getInstance().config.showPromptField) { this.promptInput = new ChatPromptInput({ tabId: this.props.tabId, onStopChatResponse: this.props?.onStopChatResponse }); this.promptInputElement = this.promptInput.render; @@ -310,6 +324,7 @@ export class ChatWrapper { } }).render, this.promptStickyCard, + this.modifiedFilesContainer, this.promptInputElement, this.footerSpacer, this.promptInfo, @@ -537,4 +552,36 @@ export class ChatWrapper { this.dragOverlayContent.style.display = visible ? 'flex' : 'none'; this.dragBlurOverlay.style.display = visible ? 'block' : 'none'; } + + private updateModifiedFilesTracker (modifiedFilesData: ModifiedFilesTrackerData | null): void { + if (modifiedFilesData != null) { + if (this.modifiedFilesTracker == null) { + // Create new ModifiedFilesTracker + this.modifiedFilesTracker = new ModifiedFilesTracker({ + tabId: this.props.tabId, + modifiedFilesData, + onFileUndo: (filePath: string) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.MODIFIED_FILES_FILE_UNDO, { + tabId: this.props.tabId, + filePath + }); + }, + onUndoAll: () => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.MODIFIED_FILES_UNDO_ALL, { + tabId: this.props.tabId + }); + } + }); + this.modifiedFilesContainer.clear(); + this.modifiedFilesContainer.insertChild('beforeend', this.modifiedFilesTracker.render); + } else { + // Update existing ModifiedFilesTracker + this.modifiedFilesTracker.updateModifiedFilesData(modifiedFilesData); + } + } else { + // Clear ModifiedFilesTracker + this.modifiedFilesContainer.clear(); + this.modifiedFilesTracker = null; + } + } } diff --git a/src/components/modified-files-tracker.ts b/src/components/modified-files-tracker.ts new file mode 100644 index 00000000..11cd60e0 --- /dev/null +++ b/src/components/modified-files-tracker.ts @@ -0,0 +1,318 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { MynahUIGlobalEvents } from '../helper/events'; +import { MynahEventNames, TreeNodeDetails, ChatItemContent } from '../static'; +import { CollapsibleContent } from './collapsible-content'; +import { Icon, MynahIcons } from './icon'; +import { Button } from './button'; + +export interface ModifiedFilesTrackerData extends Pick { + title?: string | ExtendedHTMLElement; + visible?: boolean; + showUndoAll?: boolean; + showFileCount?: boolean; + initialCollapsed?: boolean; +} + +export interface ModifiedFilesTrackerProps { + tabId: string; + modifiedFilesData: ModifiedFilesTrackerData; + onFileUndo?: (filePath: string) => void; + onUndoAll?: () => void; + testId?: string; + classNames?: string[]; + // Add these to match chat-item-card button handling + onButtonClick?: (buttonId: string, messageId: string) => void; +} + +export class ModifiedFilesTracker { + render: ExtendedHTMLElement; + private readonly props: ModifiedFilesTrackerProps; + private collapsibleContent: CollapsibleContent | null = null; + private fileItemsContainer: ExtendedHTMLElement | null = null; + + constructor (props: ModifiedFilesTrackerProps) { + this.props = { + testId: 'mynah-modified-files-tracker', + classNames: [], + ...props, + modifiedFilesData: { + title: 'Modified Files', + visible: true, + showUndoAll: true, + showFileCount: true, + initialCollapsed: false, + ...props.modifiedFilesData, + fileList: { + collapsed: false, + hideFileCount: false, + ...props.modifiedFilesData.fileList + } + } + }; + + this.render = this.buildCard(); + this.updateVisibility(); + } + + private buildCard (): ExtendedHTMLElement { + const cardWrapper = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ 'mynah-modified-files-tracker', ...(this.props.classNames ?? []) ], + children: [] + }); + + if (this.getFileCount() > 0) { + this.buildCollapsibleContent(cardWrapper); + } + + return cardWrapper; + } + + private buildCollapsibleContent (parent: ExtendedHTMLElement): void { + this.fileItemsContainer = this.buildFileItems(); + + const titleWithCount = this.buildTitleWithCount(); + + this.collapsibleContent = new CollapsibleContent({ + title: titleWithCount, + initialCollapsedState: this.props.modifiedFilesData.initialCollapsed ?? this.props.modifiedFilesData.fileList?.collapsed ?? false, + testId: `${this.props.testId ?? 'mynah-modified-files-tracker'}-collapsible`, + children: [ this.fileItemsContainer ], + onCollapseStateChange: (collapsed) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.MODIFIED_FILES_COLLAPSE_STATE_CHANGE, { + tabId: this.props.tabId, + collapsed + }); + } + }); + + parent.insertChild('beforeend', this.collapsibleContent.render); + } + + private buildTitleWithCount (): ExtendedHTMLElement { + const fileCount = this.getFileCount(); + const title = this.props.modifiedFilesData.title ?? 'Modified Files'; + const showFileCount = this.props.modifiedFilesData.showFileCount !== false && !(this.props.modifiedFilesData.fileList?.hideFileCount ?? false); + + const titleContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-modified-files-tracker-title-container' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-modified-files-tracker-title' ], + children: typeof title === 'string' ? [ title ] : [ title ] + }, + ...(showFileCount + ? [ { + type: 'span', + classNames: [ 'mynah-modified-files-tracker-count' ], + children: [ `(${fileCount.toString()})` ] + } ] + : []) + ] + }); + + // Add undo all button if there are files, showUndoAll is enabled, and onUndoAll callback is provided + if (fileCount > 0 && this.props.modifiedFilesData.showUndoAll === true && this.props.onUndoAll != null) { + const undoAllButton = new Button({ + icon: new Icon({ icon: MynahIcons.UNDO }).render, + label: 'Undo All', + status: 'clear', + onClick: () => { + // Use the same button click pattern as ChatItemCard + if (this.props.onButtonClick != null) { + // Create a messageId that matches the pattern used for undo all + const messageId = 'undo-all-changes'; + this.props.onButtonClick('undo-all-changes', messageId); + } + + // Keep the original callback for backward compatibility + this.props.onUndoAll?.(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.MODIFIED_FILES_UNDO_ALL, { + tabId: this.props.tabId + }); + }, + testId: `${this.props.testId ?? 'mynah-modified-files-tracker'}-undo-all` + }); + + titleContainer.insertChild('beforeend', undoAllButton.render); + } + + return titleContainer; + } + + private buildFileItems (): ExtendedHTMLElement { + const container = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-modified-files-tracker-items' ], + children: [] + }); + + const fileList = this.props.modifiedFilesData.fileList; + if (fileList == null) { + return container; + } + + const allFiles = [ + ...(fileList.filePaths ?? []), + ...(fileList.deletedFiles ?? []) + ]; + + allFiles.forEach((filePath) => { + const fileItem = this.buildFileItem(filePath); + container.insertChild('beforeend', fileItem); + }); + + return container; + } + + private buildFileItem (filePath: string): ExtendedHTMLElement { + const fileList = this.props.modifiedFilesData.fileList; + const isDeleted = fileList?.deletedFiles?.includes(filePath) ?? false; + const details = fileList?.details?.[filePath]; + const displayName = details?.visibleName ?? filePath.split('/').pop() ?? filePath; + const fileIcon = details?.icon ?? this.getDefaultIconForFile(filePath); + const status = this.getFileStatus(filePath, isDeleted, details); + + const fileItem = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ + 'mynah-modified-files-tracker-item', + ...(status != null ? [ `mynah-modified-files-tracker-item-${status}` ] : []) + ], + children: [ + { + type: 'div', + classNames: [ 'mynah-modified-files-tracker-item-content' ], + children: [ + new Icon({ + icon: fileIcon, + classNames: [ 'mynah-modified-files-tracker-item-icon' ], + status: details?.iconForegroundStatus + }).render, + { + type: 'span', + classNames: [ 'mynah-modified-files-tracker-item-name' ], + children: [ displayName ], + attributes: { + title: filePath + } + } + ] + } + ] + }); + + // Add undo button if callback is provided + if (this.props.onFileUndo != null) { + const undoButton = new Button({ + icon: new Icon({ icon: MynahIcons.UNDO }).render, + status: 'clear', + onClick: () => { + // Use the same button click pattern as ChatItemCard + if (this.props.onButtonClick != null) { + // Create a messageId that matches the pattern used in chat-item-card + // This should match the toolUseId from the language server + const messageId = `file-undo-${filePath}`; + this.props.onButtonClick('undo-changes', messageId); + } + + // Keep the original callback for backward compatibility + this.props.onFileUndo?.(filePath); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.MODIFIED_FILES_FILE_UNDO, { + tabId: this.props.tabId, + filePath + }); + }, + testId: `${this.props.testId ?? 'mynah-modified-files-tracker'}-file-undo-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}` + }); + + fileItem.insertChild('beforeend', undoButton.render); + } + + return fileItem; + } + + private getFileStatus (_filePath: string, isDeleted: boolean, details?: TreeNodeDetails): string | null { + if (isDeleted) { + return 'deleted'; + } + + // Use the status from details if available + if (details?.status != null) { + switch (details.status) { + case 'success': + return 'added'; + case 'warning': + return 'modified'; + case 'error': + return 'deleted'; + default: + return 'modified'; + } + } + + // Default to modified for non-deleted files + return 'modified'; + } + + private getDefaultIconForFile (filePath: string): MynahIcons { + const extension = filePath.split('.').pop()?.toLowerCase(); + switch (extension) { + case 'html': + return MynahIcons.CODE_BLOCK; + default: + return MynahIcons.FILE; + } + } + + private getFileCount (): number { + const fileList = this.props.modifiedFilesData.fileList; + if (fileList == null) { + return 0; + } + + const filePathsCount = fileList.filePaths?.length ?? 0; + const deletedFilesCount = fileList.deletedFiles?.length ?? 0; + return filePathsCount + deletedFilesCount; + } + + private updateVisibility (): void { + const shouldBeVisible = this.props.modifiedFilesData.visible !== false && this.getFileCount() > 0; + if (shouldBeVisible) { + this.render.removeClass('mynah-hidden'); + } else { + this.render.addClass('mynah-hidden'); + } + } + + // Public methods for updating the component + public updateModifiedFilesData (newData: Partial): void { + this.props.modifiedFilesData = { + ...this.props.modifiedFilesData, + ...newData, + fileList: { + ...this.props.modifiedFilesData.fileList, + ...newData.fileList + } + }; + + // Rebuild the component + this.render.innerHTML = ''; + if (this.getFileCount() > 0) { + this.buildCollapsibleContent(this.render); + } + this.updateVisibility(); + } + + public getModifiedFilesData (): ModifiedFilesTrackerData { + return { ...this.props.modifiedFilesData }; + } +} diff --git a/src/helper/store.ts b/src/helper/store.ts index e6edae95..547f2e70 100644 --- a/src/helper/store.ts +++ b/src/helper/store.ts @@ -43,7 +43,8 @@ const emptyDataModelObject: Required = { compactMode: false, tabHeaderDetails: null, tabMetadata: {}, - customContextCommand: [] + customContextCommand: [], + modifiedFilesData: null }; const dataModelKeys = Object.keys(emptyDataModelObject); export class EmptyMynahUIDataModel { diff --git a/src/helper/test-ids.ts b/src/helper/test-ids.ts index f399b8ef..77b85a96 100644 --- a/src/helper/test-ids.ts +++ b/src/helper/test-ids.ts @@ -175,5 +175,14 @@ export default { option: 'dropdown-list-option', optionLabel: 'dropdown-list-option-label', checkIcon: 'dropdown-list-check-icon' + }, + modifiedFilesTracker: { + wrapper: 'modified-files-tracker', + collapsible: 'modified-files-tracker-collapsible', + titleContainer: 'modified-files-tracker-title-container', + undoAll: 'modified-files-tracker-undo-all', + items: 'modified-files-tracker-items', + item: 'modified-files-tracker-item', + fileUndo: 'modified-files-tracker-file-undo' } }; diff --git a/src/main.ts b/src/main.ts index 64e56233..2f265d7f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -96,6 +96,11 @@ export { ChatItemCardContent, ChatItemCardContentProps } from './components/chat-item/chat-item-card-content'; +export { + ModifiedFilesTracker, + ModifiedFilesTrackerProps, + ModifiedFilesTrackerData +} from './components/modified-files-tracker'; export { default as MynahUITestIds } from './helper/test-ids'; export interface MynahUIProps { diff --git a/src/static.ts b/src/static.ts index beaa8604..0324f67e 100644 --- a/src/static.ts +++ b/src/static.ts @@ -9,6 +9,7 @@ import { FormItemPillBoxAbstract, FormItemPillBoxProps } from './components/form import { SwitchAbstract, SwitchProps } from './components/form-items/switch'; import { CustomIcon, MynahIcons, MynahIconsType } from './components/icon'; import { ChatItemBodyRenderer } from './helper/dom'; +import { ModifiedFilesTrackerData } from './components/modified-files-tracker'; import { SelectAbstract, SelectProps, @@ -192,6 +193,10 @@ export interface MynahUIDataModel { * Custom context commands to be inserted into the prompt input. */ customContextCommand?: QuickActionCommand[]; + /** + * Modified files tracker data to show above the prompt input. + */ + modifiedFilesData?: ModifiedFilesTrackerData | null; } export interface MynahUITabStoreTab { @@ -259,7 +264,10 @@ export enum MynahEventNames { TOP_BAR_BUTTON_CLICK = 'promptInputTopBarButtonClick', CONTEXT_PINNED = 'contextPinned', FILES_DROPPED = 'filesDropped', - RESET_TOP_BAR_CLICKED = 'resetTopBarClicked' + RESET_TOP_BAR_CLICKED = 'resetTopBarClicked', + MODIFIED_FILES_COLLAPSE_STATE_CHANGE = 'modifiedFilesCollapseStateChange', + MODIFIED_FILES_FILE_UNDO = 'modifiedFilesFileUndo', + MODIFIED_FILES_UNDO_ALL = 'modifiedFilesUndoAll' } export enum MynahPortalNames { diff --git a/src/styles/components/_modified-files-container.scss b/src/styles/components/_modified-files-container.scss new file mode 100644 index 00000000..e60f118f --- /dev/null +++ b/src/styles/components/_modified-files-container.scss @@ -0,0 +1,7 @@ +.mynah-modified-files-container { + margin-bottom: var(--mynah-sizing-2); + + .mynah-modified-files-tracker { + margin: 0; + } +} \ No newline at end of file diff --git a/src/styles/components/_modified-files-tracker.scss b/src/styles/components/_modified-files-tracker.scss new file mode 100644 index 00000000..a666bf82 --- /dev/null +++ b/src/styles/components/_modified-files-tracker.scss @@ -0,0 +1,134 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +.mynah-modified-files-tracker { + width: 100%; + margin-bottom: var(--mynah-sizing-2); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-card-radius); + background-color: var(--mynah-color-bg); + transition: all var(--mynah-short-transition-rev); + + &.mynah-modified-files-tracker-hidden { + display: none; + } + + .mynah-modified-files-tracker-title-container { + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + width: 100%; + + .mynah-modified-files-tracker-title { + flex: 1; + font-weight: var(--mynah-font-weight-medium); + color: var(--mynah-color-text-default); + } + + .mynah-modified-files-tracker-count { + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-small); + font-weight: var(--mynah-font-weight-normal); + } + + .mynah-button { + margin-left: auto; + } + } + + .mynah-modified-files-tracker-items { + display: flex; + flex-direction: column; + gap: var(--mynah-sizing-1); + padding: var(--mynah-sizing-2) 0; + } + + .mynah-modified-files-tracker-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + border-radius: var(--mynah-input-radius); + transition: background-color var(--mynah-short-transition); + + &:hover { + background-color: var(--mynah-color-bg-alt); + } + + .mynah-modified-files-tracker-item-content { + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + flex: 1; + min-width: 0; // Allow text truncation + } + + .mynah-modified-files-tracker-item-icon { + flex-shrink: 0; + color: var(--mynah-color-text-weak); + } + + .mynah-modified-files-tracker-item-name { + flex: 1; + color: var(--mynah-color-text-default); + font-size: var(--mynah-font-size-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .mynah-button { + flex-shrink: 0; + opacity: 0; + transition: opacity var(--mynah-short-transition); + } + + &:hover .mynah-button { + opacity: 1; + } + + // Status-specific styles + &.mynah-modified-files-tracker-item-added { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-success); + } + } + + &.mynah-modified-files-tracker-item-modified { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-warning); + } + } + + &.mynah-modified-files-tracker-item-deleted { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-error); + } + + .mynah-modified-files-tracker-item-name { + text-decoration: line-through; + color: var(--mynah-color-text-weak); + } + } + } + + // Collapsible content overrides + .mynah-collapsible-content-wrapper { + border: none; + background: transparent; + + .mynah-collapsible-content-label { + padding: var(--mynah-sizing-3); + + .mynah-collapsible-content-label-title-wrapper { + width: 100%; + } + + .mynah-collapsible-content-label-content-wrapper { + padding: 0 var(--mynah-sizing-3) var(--mynah-sizing-2); + } + } + } +} \ No newline at end of file diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 6938bfc2..644f90e4 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -8,6 +8,8 @@ @import './components/main-container'; @import './components/detailed-list'; @import './components/dropdown-list'; +@import './components/modified-files-tracker'; +@import './components/modified-files-container'; :root { --mynah-sizing-half: calc(var(--mynah-sizing-base) / 2); --mynah-sizing-1: var(--mynah-sizing-base); From 369dec0edebe16d1d18ed91b4c88b584d29f0d17 Mon Sep 17 00:00:00 2001 From: sacrodge Date: Fri, 3 Oct 2025 19:45:22 -0700 Subject: [PATCH 2/2] msg: Working; Complete new UI --- .../components/_modified-files-container.scss | 12 +- .../components/_modified-files-tracker.scss | 228 +++++++++--------- 2 files changed, 120 insertions(+), 120 deletions(-) diff --git a/src/styles/components/_modified-files-container.scss b/src/styles/components/_modified-files-container.scss index e60f118f..bd9eb799 100644 --- a/src/styles/components/_modified-files-container.scss +++ b/src/styles/components/_modified-files-container.scss @@ -1,7 +1,7 @@ .mynah-modified-files-container { - margin-bottom: var(--mynah-sizing-2); - - .mynah-modified-files-tracker { - margin: 0; - } -} \ No newline at end of file + margin-bottom: var(--mynah-sizing-2); + + .mynah-modified-files-tracker { + margin: 0; + } +} diff --git a/src/styles/components/_modified-files-tracker.scss b/src/styles/components/_modified-files-tracker.scss index a666bf82..731d95aa 100644 --- a/src/styles/components/_modified-files-tracker.scss +++ b/src/styles/components/_modified-files-tracker.scss @@ -4,131 +4,131 @@ */ .mynah-modified-files-tracker { - width: 100%; - margin-bottom: var(--mynah-sizing-2); - border: var(--mynah-border-width) solid var(--mynah-color-border-default); - border-radius: var(--mynah-card-radius); - background-color: var(--mynah-color-bg); - transition: all var(--mynah-short-transition-rev); - - &.mynah-modified-files-tracker-hidden { - display: none; - } - - .mynah-modified-files-tracker-title-container { - display: flex; - align-items: center; - gap: var(--mynah-sizing-2); width: 100%; - - .mynah-modified-files-tracker-title { - flex: 1; - font-weight: var(--mynah-font-weight-medium); - color: var(--mynah-color-text-default); - } - - .mynah-modified-files-tracker-count { - color: var(--mynah-color-text-weak); - font-size: var(--mynah-font-size-small); - font-weight: var(--mynah-font-weight-normal); + margin-bottom: var(--mynah-sizing-2); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-card-radius); + background-color: var(--mynah-color-bg); + transition: all var(--mynah-short-transition-rev); + + &.mynah-modified-files-tracker-hidden { + display: none; } - .mynah-button { - margin-left: auto; - } - } - - .mynah-modified-files-tracker-items { - display: flex; - flex-direction: column; - gap: var(--mynah-sizing-1); - padding: var(--mynah-sizing-2) 0; - } - - .mynah-modified-files-tracker-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--mynah-sizing-1) var(--mynah-sizing-2); - border-radius: var(--mynah-input-radius); - transition: background-color var(--mynah-short-transition); - - &:hover { - background-color: var(--mynah-color-bg-alt); - } - - .mynah-modified-files-tracker-item-content { - display: flex; - align-items: center; - gap: var(--mynah-sizing-2); - flex: 1; - min-width: 0; // Allow text truncation - } + .mynah-modified-files-tracker-title-container { + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + width: 100%; - .mynah-modified-files-tracker-item-icon { - flex-shrink: 0; - color: var(--mynah-color-text-weak); + .mynah-modified-files-tracker-title { + flex: 1; + font-weight: var(--mynah-font-weight-medium); + color: var(--mynah-color-text-default); + } + + .mynah-modified-files-tracker-count { + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-small); + font-weight: var(--mynah-font-weight-normal); + } + + .mynah-button { + margin-left: auto; + } } - .mynah-modified-files-tracker-item-name { - flex: 1; - color: var(--mynah-color-text-default); - font-size: var(--mynah-font-size-medium); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .mynah-modified-files-tracker-items { + display: flex; + flex-direction: column; + gap: var(--mynah-sizing-1); + padding: var(--mynah-sizing-2) 0; } - .mynah-button { - flex-shrink: 0; - opacity: 0; - transition: opacity var(--mynah-short-transition); + .mynah-modified-files-tracker-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + border-radius: var(--mynah-input-radius); + transition: background-color var(--mynah-short-transition); + + &:hover { + background-color: var(--mynah-color-bg-alt); + } + + .mynah-modified-files-tracker-item-content { + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + flex: 1; + min-width: 0; // Allow text truncation + } + + .mynah-modified-files-tracker-item-icon { + flex-shrink: 0; + color: var(--mynah-color-text-weak); + } + + .mynah-modified-files-tracker-item-name { + flex: 1; + color: var(--mynah-color-text-default); + font-size: var(--mynah-font-size-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .mynah-button { + flex-shrink: 0; + opacity: 0; + transition: opacity var(--mynah-short-transition); + } + + &:hover .mynah-button { + opacity: 1; + } + + // Status-specific styles + &.mynah-modified-files-tracker-item-added { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-success); + } + } + + &.mynah-modified-files-tracker-item-modified { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-warning); + } + } + + &.mynah-modified-files-tracker-item-deleted { + .mynah-modified-files-tracker-item-icon { + color: var(--mynah-color-status-error); + } + + .mynah-modified-files-tracker-item-name { + text-decoration: line-through; + color: var(--mynah-color-text-weak); + } + } } - &:hover .mynah-button { - opacity: 1; - } + // Collapsible content overrides + .mynah-collapsible-content-wrapper { + border: none; + background: transparent; - // Status-specific styles - &.mynah-modified-files-tracker-item-added { - .mynah-modified-files-tracker-item-icon { - color: var(--mynah-color-status-success); - } - } + .mynah-collapsible-content-label { + padding: var(--mynah-sizing-3); - &.mynah-modified-files-tracker-item-modified { - .mynah-modified-files-tracker-item-icon { - color: var(--mynah-color-status-warning); - } - } + .mynah-collapsible-content-label-title-wrapper { + width: 100%; + } - &.mynah-modified-files-tracker-item-deleted { - .mynah-modified-files-tracker-item-icon { - color: var(--mynah-color-status-error); - } - - .mynah-modified-files-tracker-item-name { - text-decoration: line-through; - color: var(--mynah-color-text-weak); - } - } - } - - // Collapsible content overrides - .mynah-collapsible-content-wrapper { - border: none; - background: transparent; - - .mynah-collapsible-content-label { - padding: var(--mynah-sizing-3); - - .mynah-collapsible-content-label-title-wrapper { - width: 100%; - } - - .mynah-collapsible-content-label-content-wrapper { - padding: 0 var(--mynah-sizing-3) var(--mynah-sizing-2); - } + .mynah-collapsible-content-label-content-wrapper { + padding: 0 var(--mynah-sizing-3) var(--mynah-sizing-2); + } + } } - } -} \ No newline at end of file +}