From 280f5960c731d675bc065254603cdd95a666874b Mon Sep 17 00:00:00 2001 From: Jacob Bassett Date: Wed, 6 Aug 2025 00:55:30 +0000 Subject: [PATCH 001/180] Add warning of no newline in diff to comment box suggestion block (#10127) * 361f35 fix lint * 435900 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into add-newline-suggestion * c8407c remove end of file no newline warning * 7796c5 remove commented code * 11c646 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into add-newline-suggestion * 6cff80 add warning on no newline in diff to comment box suggestion * d4de0f clean up pr comment editor buttons code * 38093f Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 15873a Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 39c940 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b0da05 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 75c316 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5 --- .../conversation/pull-request-comment-box.tsx | 180 +++++++++++------- 1 file changed, 106 insertions(+), 74 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index da8cb9dbfe..2dd43a4cb3 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -43,20 +43,25 @@ interface TextSelection { end: number } -enum TextSelectionBehavior { - Capture = 'capture', - Split = 'split', - Parse = 'parse' +interface ParsedSelection extends TextSelection { + text: string + textLines: string[] } -interface StringSelection { +interface ParsedComment { + originalComment: string + injectedPreString: string + injectedPostString: string beforeSelection: string - selection: string - selectionLines: string[] - selectionStart: number - selectionEnd: number + beforeSelectionLine: string + selection: ParsedSelection afterSelection: string - previousLine: string +} + +enum BuildBehavior { + Capture = 'capture', + Split = 'split', + Parse = 'parse' } interface ToolbarItem { @@ -209,24 +214,6 @@ export const PullRequestCommentBox = ({ fileInputRef.current?.click() } - const handleAiSummary = (currentTextSelection: TextSelection) => { - if (handleAiPullRequestSummary) { - setShowAiLoader(true) - - const originalComment = comment - - parseAndSetComment(comment, currentTextSelection, TextSelectionBehavior.Parse, 'Generating AI Summary...') - - handleAiPullRequestSummary() - .then(response => { - parseAndSetComment(originalComment, currentTextSelection, TextSelectionBehavior.Parse, response.summary) - }) - .finally(() => { - setShowAiLoader(false) - }) - } - } - const handleFileChange = (event: ChangeEvent) => { const file = event.target.files?.[0] if (file) { @@ -268,6 +255,28 @@ export const PullRequestCommentBox = ({ handlePaste(event, handleUploadCallback) } + const handleAiSummary = (currentTextSelection: TextSelection) => { + if (handleAiPullRequestSummary) { + setShowAiLoader(true) + + const originalComment = comment + + const parsedPlaceholder = parseComment(comment, currentTextSelection, 'Generating AI Summary...') + + setCommentAndTextSelection(buildComment(parsedPlaceholder, BuildBehavior.Parse), parsedPlaceholder.selection) + + handleAiPullRequestSummary() + .then(response => { + const parsedResponse = parseComment(originalComment, currentTextSelection, response.summary) + + setCommentAndTextSelection(buildComment(parsedResponse, BuildBehavior.Parse), parsedResponse.selection) + }) + .finally(() => { + setShowAiLoader(false) + }) + } + } + useEffect(() => { if (textAreaRef.current) { textAreaRef.current.addEventListener('select', onCommentSelect) @@ -287,7 +296,12 @@ export const PullRequestCommentBox = ({ } }, [comment, textSelection]) - const parseComment = (comment: string, textSelection: TextSelection, injectedPreString: string): StringSelection => { + const parseComment = ( + comment: string, + textSelection: TextSelection, + injectedPreString: string = '', + injectedPostString: string = '' + ): ParsedComment => { const isStartOfLineSelected = textSelection.start === 0 || comment.substring(0, textSelection.start).endsWith('\n') const isEndOfLineSelected = textSelection.end === comment.length || comment.substring(textSelection.end).startsWith('\n') @@ -309,47 +323,41 @@ export const PullRequestCommentBox = ({ newTextSelectionStart + (textSelection.end - textSelection.start) + injectedNewline.length return { + originalComment: comment, + injectedPreString: injectedPreString, + injectedPostString: injectedPostString, beforeSelection: beforeSelection, - selection: selection, - selectionLines: selectionLines, - afterSelection: afterSelection, - previousLine: previousLine, - selectionStart: newTextSelectionStart, - selectionEnd: newTextSelectionEnd + beforeSelectionLine: previousLine, + selection: { + text: selection, + textLines: selectionLines, + start: newTextSelectionStart, + end: newTextSelectionEnd + }, + afterSelection: afterSelection } } - const parseAndSetComment = ( - comment: string, - textSelection: TextSelection, - textSelectionBehavrior: TextSelectionBehavior, - injectedPreString: string, - injectedPostString: string = '' - ) => { - const parsedComment = parseComment(comment, textSelection, injectedPreString) - + const buildComment = (parsedComment: ParsedComment, buildBehavior: BuildBehavior): string => { let injectedString = '' - switch (textSelectionBehavrior) { - case TextSelectionBehavior.Capture: - injectedString = `${injectedPreString}${parsedComment.selection}${injectedPostString}` + switch (buildBehavior) { + case BuildBehavior.Capture: + injectedString = `${parsedComment.injectedPreString}${parsedComment.selection.text}${parsedComment.injectedPostString}` break - case TextSelectionBehavior.Split: - injectedString = parsedComment.selectionLines.reduce((prev, selectionLine, currentIndex, array) => { - return `${prev}${injectedPreString}${selectionLine}${injectedPostString}${currentIndex < array.length - 1 ? '\n' : ''}` + case BuildBehavior.Split: + injectedString = parsedComment.selection.textLines.reduce((prev, selectionLine, currentIndex, array) => { + return `${prev}${parsedComment.injectedPreString}${selectionLine}${parsedComment.injectedPostString}${currentIndex < array.length - 1 ? '\n' : ''}` }, '') break - case TextSelectionBehavior.Parse: - injectedString = isEmpty(parsedComment.selection) - ? `${injectedPreString}${parseDiff(diff, sideKey, lineNumber, lineFromNumber)}${injectedPostString}` - : `${injectedPreString}${parsedComment.selection}${injectedPostString}` + case BuildBehavior.Parse: + injectedString = isEmpty(parsedComment.selection.text) + ? `${parsedComment.injectedPreString}${parseDiff(diff, sideKey, lineNumber, lineFromNumber)}${parsedComment.injectedPostString}` + : `${parsedComment.injectedPreString}${parsedComment.selection.text}${parsedComment.injectedPostString}` break } - setCommentAndTextSelection(`${parsedComment.beforeSelection}${injectedString}${parsedComment.afterSelection}`, { - start: parsedComment.selectionStart, - end: parsedComment.selectionEnd - }) + return `${parsedComment.beforeSelection}${injectedString}${parsedComment.afterSelection}` } const parseDiff = ( @@ -419,30 +427,54 @@ export const PullRequestCommentBox = ({ case ToolbarAction.AI_SUMMARY: handleAiSummary(textSelection) break - case ToolbarAction.SUGGESTION: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Parse, '```suggestion\n', '\n```') + case ToolbarAction.SUGGESTION: { + const parsedSuggestion = parseComment(comment, textSelection, '```suggestion\n', '\n```') + + setCommentAndTextSelection(buildComment(parsedSuggestion, BuildBehavior.Parse), parsedSuggestion.selection) break - case ToolbarAction.HEADER: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Capture, '# ') + } + case ToolbarAction.HEADER: { + const parsedHeader = parseComment(comment, textSelection, '# ') + + setCommentAndTextSelection(buildComment(parsedHeader, BuildBehavior.Capture), parsedHeader.selection) break - case ToolbarAction.BOLD: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Capture, '**', '**') + } + case ToolbarAction.BOLD: { + const parsedBold = parseComment(comment, textSelection, '**', '**') + + setCommentAndTextSelection(buildComment(parsedBold, BuildBehavior.Capture), parsedBold.selection) break - case ToolbarAction.ITALIC: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Capture, '*', '*') + } + case ToolbarAction.ITALIC: { + const parsedItalic = parseComment(comment, textSelection, '*', '*') + + setCommentAndTextSelection(buildComment(parsedItalic, BuildBehavior.Capture), parsedItalic.selection) break + } case ToolbarAction.UPLOAD: handleFileSelect() break - case ToolbarAction.UNORDERED_LIST: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Split, '- ') + case ToolbarAction.UNORDERED_LIST: { + const parsedUnorderedList = parseComment(comment, textSelection, '- ') + + setCommentAndTextSelection( + buildComment(parsedUnorderedList, BuildBehavior.Split), + parsedUnorderedList.selection + ) break - case ToolbarAction.CHECK_LIST: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Split, '- [ ] ') + } + case ToolbarAction.CHECK_LIST: { + const parsedCheckList = parseComment(comment, textSelection, '- [ ] ') + + setCommentAndTextSelection(buildComment(parsedCheckList, BuildBehavior.Split), parsedCheckList.selection) break - case ToolbarAction.CODE_BLOCK: - parseAndSetComment(comment, textSelection, TextSelectionBehavior.Capture, '```' + lang + '\n', '\n```') + } + case ToolbarAction.CODE_BLOCK: { + const parsedCodeBlock = parseComment(comment, textSelection, '```' + lang + '\n', '\n```') + + setCommentAndTextSelection(buildComment(parsedCodeBlock, BuildBehavior.Capture), parsedCodeBlock.selection) break + } } // Return cursor to proper place to continue typing based on where action above set selection index @@ -490,10 +522,10 @@ export const PullRequestCommentBox = ({ case 'Enter': { const parsedComment = parseComment(comment, textSelection, '') - if (isListString(parsedComment.previousLine)) { + if (isListString(parsedComment.beforeSelectionLine)) { handleActionClick(ToolbarAction.UNORDERED_LIST, comment, textSelection) } - if (isListSelectString(parsedComment.previousLine)) { + if (isListSelectString(parsedComment.beforeSelectionLine)) { handleActionClick(ToolbarAction.CHECK_LIST, comment, textSelection) } break From 284393ed45ceb6c30d7dc8cd17330a07e6427ed3 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 6 Aug 2025 17:14:26 +0300 Subject: [PATCH 002/180] Fix PR title layout at list and in PR page (#2051) --- .../ui/src/components/scope/scope-tag.tsx | 7 ++-- packages/ui/src/components/stacked-list.tsx | 16 +++++++-- .../components/pull-request-header.tsx | 12 +++---- .../pull-request-item-description.tsx | 9 ++--- .../components/pull-request-item-title.tsx | 36 ++++++++++--------- .../components/pull-request-list.tsx | 3 +- 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/ui/src/components/scope/scope-tag.tsx b/packages/ui/src/components/scope/scope-tag.tsx index 5a9e67d031..d02818e582 100644 --- a/packages/ui/src/components/scope/scope-tag.tsx +++ b/packages/ui/src/components/scope/scope-tag.tsx @@ -2,18 +2,19 @@ import { ScopeType } from '@views/common' import { Tag, TagProps } from '../tag' -interface ScopeTagProps extends Pick { +interface ScopeTagProps extends Pick { scopeType: ScopeType scopedPath?: string } -const ScopeTag: React.FC = ({ scopeType, scopedPath, size }) => { +const ScopeTag: React.FC = ({ scopeType, scopedPath, size, className }) => { const tagProps: TagProps = { variant: 'secondary', theme: 'gray', size, value: scopedPath || '', - showIcon: true + showIcon: true, + className } switch (scopeType) { diff --git a/packages/ui/src/components/stacked-list.tsx b/packages/ui/src/components/stacked-list.tsx index a7202aa171..0dc1b3219b 100644 --- a/packages/ui/src/components/stacked-list.tsx +++ b/packages/ui/src/components/stacked-list.tsx @@ -43,6 +43,7 @@ interface ListFieldProps extends Omit, 'title'>, Var description?: React.ReactNode label?: boolean primary?: boolean + disableTruncate?: boolean } interface ListProps extends React.ComponentProps<'div'> { @@ -117,20 +118,29 @@ const ListItem = ({ ListItem.displayName = 'StackedListItem' -const ListField = ({ className, title, description, label, primary, right, ...props }: ListFieldProps) => ( +const ListField = ({ + className, + title, + description, + label, + primary, + right, + disableTruncate = false, + ...props +}: ListFieldProps) => (
{title && ( {title} )} {description && ( - + {description} )} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index 43c9962b03..31679e598b 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -65,15 +65,13 @@ export const PullRequestHeader: React.FC = ({ return ( <> - - - - {title} - - + + + {title} + #{number} - +
- {/* Sticky right border */} -
) } From 90325d0bfdd2732ec415e2e541a7722f88690b2f Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal Date: Wed, 6 Aug 2025 21:03:19 +0000 Subject: [PATCH 008/180] fix: design on commit details, make route backward compatible (#10137) * 882f48 feat: add loading state to pr-details * 2603ca fix: design on commit details, route backkward compatible --- .../src/pages-v2/repo/repo-commit-details.tsx | 3 +- apps/gitness/src/routes.tsx | 42 ++++----- .../repo/components/CommitTitleWithPRLink.tsx | 3 +- .../components/commit-diff.tsx | 4 +- .../repo-commit-details-view.tsx | 92 +++++++++++-------- 5 files changed, 79 insertions(+), 65 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx b/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx index 0faecfd603..66b03621be 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx @@ -15,7 +15,7 @@ export default function RepoCommitDetailsPage({ showSidebar = true }: { showSide const { setCommitData } = useCommitDetailsStore() const routes = useRoutes() const { repoId, spaceId } = useParams() - const { data: { body: commitData } = {} } = useGetCommitQuery({ + const { data: { body: commitData } = {}, isLoading: loadingCommitDetails } = useGetCommitQuery({ repo_ref: repoRef, commit_sha: commitSHA || '' }) @@ -35,6 +35,7 @@ export default function RepoCommitDetailsPage({ showSidebar = true }: { showSide } useCommitDetailsStore={useCommitDetailsStore} showSidebar={showSidebar} + loadingCommitDetails={loadingCommitDetails} /> ) } diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 1ffd139525..683f9a777a 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -257,28 +257,28 @@ export const repoRoutes: CustomRouteObject[] = [ pageTitle: Page.Commits, routeName: RouteConstants.toRepoTagCommits } - }, + } + ] + }, + { + path: 'commit/:commitSHA', + element: , + handle: { + breadcrumb: ({ commitSHA }: { commitSHA: string }) => ( + <> + {commitSHA.substring(0, 7)} + + ), + routeName: RouteConstants.toRepoCommitDetails + }, + children: [ { - path: 'commit/:commitSHA', - element: , - handle: { - breadcrumb: ({ commitSHA }: { commitSHA: string }) => ( - <> - {commitSHA.substring(0, 7)} - - ), - routeName: RouteConstants.toRepoCommitDetails - }, - children: [ - { - index: true, - element: ( - - - - ) - } - ] + index: true, + element: ( + + + + ) } ] }, diff --git a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx index f2c8e53cf1..bd326b45d8 100644 --- a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx +++ b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx @@ -36,9 +36,10 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => {  ( #{pullRequestId} diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx index 29d35af6cd..a74933b4b5 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx @@ -13,8 +13,8 @@ export const CommitDiff: React.FC = ({ useCommitDetailsSto const { diffs, diffStats } = useCommitDetailsStore() return ( -
-

+

+

{t('views:commits.commitDetailsDiffShowing', 'Showing')}{' '} {formatNumber(diffStats?.files_changed ?? 0)}{' '} diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index b08deba5a1..9b11ebe4df 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Avatar, Button, CommitCopyActions, Text, TimeAgoCard } from '@/components' +import { Avatar, Button, CommitCopyActions, Layout, SkeletonList, Text, TimeAgoCard } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { ICommitDetailsStore, SandboxLayout } from '@/views' @@ -14,6 +14,7 @@ interface RoutingProps { export interface RepoCommitDetailsViewProps extends RoutingProps { useCommitDetailsStore: () => ICommitDetailsStore showSidebar?: boolean + loadingCommitDetails?: boolean } export const RepoCommitDetailsView: FC = ({ @@ -21,7 +22,8 @@ export const RepoCommitDetailsView: FC = ({ showSidebar = true, toCommitDetails, toPullRequest, - toCode + toCode, + loadingCommitDetails = false }) => { const { Outlet, Link } = useRouterContext() const { t } = useTranslation() @@ -29,46 +31,56 @@ export const RepoCommitDetailsView: FC = ({ return ( - - - {t('views:commits.commitDetailsTitle', 'Commit')} - -

-
- - -
-
-
- {commitData?.author?.identity?.name && ( - - )} - {commitData?.author?.identity?.name || ''} - - committed on{' '} - - + {loadingCommitDetails ? ( + + + + ) : ( + + + {t('views:commits.commitDetailsTitle', 'Commit')} + + {commitData?.sha?.slice(0, 7)} + + + +
+
+ + +
+
+
+ {commitData?.author?.identity?.name && ( + + )} + {commitData?.author?.identity?.name || ''} + + committed on{' '} + + +
+
-
-
- {!showSidebar && } - + {!showSidebar && } + + )} {showSidebar && ( From 272898e98562162edc3c161de05513f9e95df66a Mon Sep 17 00:00:00 2001 From: Harish Viswanathan Date: Thu, 7 Aug 2025 04:37:56 +0000 Subject: [PATCH 009/180] feat: split closed/merged PR states and updated file search window (#10129) * 7ecf88 feat: split closed/merged PR states and add dedicated empty states for each --- .../pull-request-list-store.ts | 1 + .../pull-request/pull-request-list.tsx | 96 +++++--- .../stores/pull-request-list-store.tsx | 11 +- apps/gitness/src/pages-v2/repo/repo-list.tsx | 14 +- packages/filters/src/Filter.tsx | 1 + packages/ui/src/components/filters/types.ts | 3 + packages/ui/src/components/search-files.tsx | 66 +++--- packages/ui/src/styles/styles.css | 4 +- .../ui/src/views/components/FilterGroup.tsx | 2 + packages/ui/src/views/repo/common/util.ts | 28 ++- .../views/repo/constants/filter-options.ts | 8 +- .../pull-request-compare-diff-list.tsx | 6 +- .../components/pull-request-diff-viewer.tsx | 2 +- .../components/pull-request-list-header.tsx | 29 ++- .../components/pull-request-list.tsx | 213 ++++++++++++------ .../changes/pull-request-changes-filter.tsx | 8 +- .../pull-request/pull-request-list-page.tsx | 30 ++- .../repo/pull-request/pull-request.types.ts | 2 + .../components/commit-sidebar.tsx | 2 +- .../views/repo/repo-list/repo-list-page.tsx | 6 +- .../ui/src/views/repo/repo-sidebar/index.tsx | 2 +- .../views/repo/repo-summary/repo-summary.tsx | 2 +- 22 files changed, 336 insertions(+), 200 deletions(-) diff --git a/apps/design-system/src/subjects/views/pull-request-list/pull-request-list-store.ts b/apps/design-system/src/subjects/views/pull-request-list/pull-request-list-store.ts index f947f852f2..bbb4f398e0 100644 --- a/apps/design-system/src/subjects/views/pull-request-list/pull-request-list-store.ts +++ b/apps/design-system/src/subjects/views/pull-request-list/pull-request-list-store.ts @@ -36,6 +36,7 @@ export const pullRequestListStore: PullRequestListStore = { page: 1, openPullReqs: 1, closedPullReqs: 0, + mergedPullReqs: 0, setPage: noop, setLabelsQuery: noop, prState: ['open'], diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx index 9e3f382571..2e71c29b20 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { useParams, useSearchParams } from 'react-router-dom' import { @@ -9,13 +9,18 @@ import { useListPullReqQuery, usePrCandidatesQuery } from '@harnessio/code-service-client' -import { PullRequestListPage as SandboxPullRequestListPage, type PRListFilters } from '@harnessio/ui/views' +import { + EnumPullReqState, + PullRequestListPage as SandboxPullRequestListPage, + type PRListFilters +} from '@harnessio/ui/views' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useMFEContext } from '../../framework/hooks/useMFEContext' import { parseAsInteger, useQueryState } from '../../framework/hooks/useQueryState' import { useGitRef } from '../../hooks/useGitRef' import { PathParams } from '../../RouteDefinitions' +import { PageResponseHeader } from '../../types' import { useLabelsStore } from '../project/stores/labels-store' import { usePopulateLabelStore } from '../repo/labels/hooks/use-populate-label-store' import { buildPRFilters } from './pull-request-utils' @@ -41,15 +46,6 @@ export default function PullRequestListPage() { const { accountId = '', orgIdentifier, projectIdentifier } = scope || {} usePopulateLabelStore({ queryPage, query: labelsQuery, enabled: populateLabelStore, inherited: true }) - const [shouldSwitchToClosed, setShouldSwitchToClosed] = useState(false) - - const shouldSwitchToClosedTab = useCallback( - (hasAuthorFilter: boolean, openCount: number, closedCount: number): boolean => { - return hasAuthorFilter && openCount === 0 && closedCount > 0 - }, - [] - ) - const { data: { body: pullRequestData, headers } = {}, isFetching: fetchingPullReqData } = useListPullReqQuery( { queryParams: { @@ -70,15 +66,14 @@ export default function PullRequestListPage() { ) // Make separate API calls to get open and closed PR counts for the filtered author - const { data: { body: openPRData } = {} } = useListPullReqQuery( + const { data: { headers: openHeaders } = {}, isLoading: isLoadingOpen } = useListPullReqQuery( { queryParams: { page: 1, state: ['open'], query: query ?? '', + limit: 0, exclude_description: true, - sort: 'updated', - order: 'desc', ...filterValues }, repo_ref: repoRef, @@ -87,20 +82,18 @@ export default function PullRequestListPage() { } }, { - retry: false, - enabled: !!defaultAuthorId // Only make this call when author filter is applied + retry: false } ) - const { data: { body: closedPRData } = {} } = useListPullReqQuery( + const { data: { headers: closedHeaders } = {}, isLoading: isLoadingClosed } = useListPullReqQuery( { queryParams: { page: 1, - state: ['closed', 'merged'], + state: ['closed'], query: query ?? '', + limit: 0, exclude_description: true, - sort: 'updated', - order: 'desc', ...filterValues }, repo_ref: repoRef, @@ -109,8 +102,27 @@ export default function PullRequestListPage() { } }, { - retry: false, - enabled: !!defaultAuthorId // Only make this call when author filter is applied + retry: false + } + ) + + const { data: { headers: mergedHeaders } = {}, isLoading: isLoadingMerged } = useListPullReqQuery( + { + queryParams: { + page: 1, + state: ['merged'], + query: query ?? '', + limit: 0, + exclude_description: true, + ...filterValues + }, + repo_ref: repoRef, + stringifyQueryParamsOptions: { + arrayFormat: 'repeat' + } + }, + { + retry: false } ) @@ -188,25 +200,35 @@ export default function PullRequestListPage() { }, [pullRequestData, headers, setPullRequests]) useEffect(() => { - if (defaultAuthorId && openPRData && closedPRData) { - const openCount = Array.isArray(openPRData) ? openPRData.length : 0 - const closedCount = Array.isArray(closedPRData) ? closedPRData.length : 0 + if (openHeaders && closedHeaders && mergedHeaders) { + const openCount = parseInt(openHeaders?.get(PageResponseHeader.xTotal) || '0') + const closedCount = parseInt(closedHeaders?.get(PageResponseHeader.xTotal) || '0') + const mergedCount = parseInt(mergedHeaders?.get(PageResponseHeader.xTotal) || '0') + + const [currState] = prState - const shouldSwitch = shouldSwitchToClosedTab(!!defaultAuthorId, openCount, closedCount) + const determineState = (): EnumPullReqState[] => { + // If current state has PRs, maintain it + if (currState === 'open' && openCount > 0) return ['open'] + if (currState === 'merged' && mergedCount > 0) return ['merged'] + if (currState === 'closed' && closedCount > 0) return ['closed'] - if (shouldSwitch && !shouldSwitchToClosed) { - setShouldSwitchToClosed(true) - setPrState(['closed', 'merged']) + // Otherwise follow priority: open > merged > closed + if (openCount > 0) return ['open'] + if (mergedCount > 0) return ['merged'] + if (closedCount > 0) return ['closed'] + + return ['open'] } - setOpenClosePullRequests(openCount, closedCount) - } - }, [defaultAuthorId, openPRData, closedPRData, shouldSwitchToClosed, shouldSwitchToClosedTab]) + const [newState] = determineState() + if (newState !== currState) { + setPrState([newState]) + } - useEffect(() => { - const { num_open_pulls = 0, num_closed_pulls = 0, num_merged_pulls = 0 } = repoData || {} - setOpenClosePullRequests(num_open_pulls, num_closed_pulls + num_merged_pulls) - }, [repoData, setOpenClosePullRequests]) + setOpenClosePullRequests(openCount, closedCount, mergedCount) + } + }, [openHeaders, closedHeaders, mergedHeaders, setOpenClosePullRequests, setPrState]) useEffect(() => { setQueryPage(page) @@ -223,7 +245,7 @@ export default function PullRequestListPage() { @@ -22,7 +23,7 @@ interface PullRequestListStore { setPage: (page: number) => void setLabelsQuery: (query: string) => void setPullRequests: (data: PullRequestInterface[], headers?: Headers) => void - setOpenClosePullRequests: (openPullReqs: number, closedPullReqs: number) => void + setOpenClosePullRequests: (openPullReqs: number, closedPullReqs: number, mergedPullReqs: number) => void } export const usePullRequestListStore = create(set => ({ @@ -33,6 +34,7 @@ export const usePullRequestListStore = create(set => ({ prState: ['open'], openPullReqs: 0, closedPullReqs: 0, + mergedPullReqs: 0, labels: [], labelsQuery: '', setPage: page => set({ page }), @@ -68,13 +70,14 @@ export const usePullRequestListStore = create(set => ({ set({ pullRequests: transformedPullRequests, totalItems: parseInt(headers?.get(PageResponseHeader.xTotal) || '0'), - pageSize: parseInt(headers?.get(PageResponseHeader.xPerPage) || '10') + pageSize: parseInt(headers?.get(PageResponseHeader.xPerPage) || '30') }) }, - setOpenClosePullRequests: (openPullReqs, closedPullReqs) => { + setOpenClosePullRequests: (openPullReqs, closedPullReqs, mergedPullReqs) => { set({ openPullReqs, - closedPullReqs + closedPullReqs, + mergedPullReqs }) }, diff --git a/apps/gitness/src/pages-v2/repo/repo-list.tsx b/apps/gitness/src/pages-v2/repo/repo-list.tsx index 8ffd73b4f3..03203f3711 100644 --- a/apps/gitness/src/pages-v2/repo/repo-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-list.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { useParams } from 'react-router-dom' import { @@ -49,8 +49,8 @@ export default function ReposListPage() { const [favorite, setFavorite] = useQueryState('favorite') const [recursive, setRecursive] = useQueryState('recursive') const { scope } = useMFEContext() - const [sort, setSort] = useQueryState('sort') - const [order, setOrder] = useQueryState('order') + const [sort, setSort] = useState('last_git_push') + const [order, setOrder] = useState('desc') const { data: { body: repoData, headers } = {}, @@ -204,11 +204,11 @@ export default function ReposListPage() { }} onSortChange={(sortValues: string) => { const [type, direction] = sortValues?.split(',') || [] - const sortKey = type as ListReposQueryQueryParams['sort'] | undefined - const orderKey = direction as ListReposQueryQueryParams['order'] | undefined + const sortKey = type as ListReposQueryQueryParams['sort'] + const orderKey = direction as ListReposQueryQueryParams['order'] - setSort(sortKey ?? null) - setOrder(orderKey ?? null) + setSort(sortKey) + setOrder(orderKey) }} /> ) diff --git a/packages/filters/src/Filter.tsx b/packages/filters/src/Filter.tsx index fce31f3af3..7f0d0938f1 100644 --- a/packages/filters/src/Filter.tsx +++ b/packages/filters/src/Filter.tsx @@ -19,6 +19,7 @@ export interface FilterProps, K extends keyof value?: Parser extends undefined ? string : T[K] removeFilter: (filterKey?: K) => void }) => React.ReactNode + defaultValue?: T[K] parser?: SpreadParser sticky?: boolean className?: string diff --git a/packages/ui/src/components/filters/types.ts b/packages/ui/src/components/filters/types.ts index b92d8574b1..393da054a0 100644 --- a/packages/ui/src/components/filters/types.ts +++ b/packages/ui/src/components/filters/types.ts @@ -28,6 +28,8 @@ interface FilterOptionConfigBase { // filter-key with which the filter is identified value: Key parser?: Parser + defaultValue?: V + sticky?: boolean } interface ComboBoxFilterOptionConfig extends FilterOptionConfigBase { @@ -84,6 +86,7 @@ type FilterOptionConfig> | CheckboxFilterOptionConfig | MultiSelectFilterOptionConfig | CustomFilterOptionConfig + type FilterValueTypes = string | number | unknown export type { diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index 0455c02f40..6afb4baf3b 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -1,10 +1,11 @@ -import { ReactNode, useCallback, useState } from 'react' +import { ReactNode, useCallback, useEffect, useState } from 'react' import { Command, Popover, SearchInput, SearchInputProps, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@utils/cn' const markedFileClassName = 'w-full text-cn-foreground-1' +const MAX_FILES = 50 /** * Get marked file component with query @@ -26,7 +27,7 @@ const getMarkedFileElement = (file: string, query: string, matchIndex: number): const endText = file.slice(matchIndex + query.length) return ( - + {startText && {startText}} {matchedText && {matchedText}} {endText && {endText}} @@ -56,43 +57,42 @@ export const SearchFiles = ({ }: SearchFilesProps) => { const [isOpen, setIsOpen] = useState(false) const [filteredFiles, setFilteredFiles] = useState([]) + const [currentQuery, setCurrentQuery] = useState('') const { t } = useTranslation() - const filterQuery = useCallback( - (query: string) => { - if (!filesList) { - setFilteredFiles([]) - return - } + useEffect(() => { + if (!filesList || !currentQuery) { + setFilteredFiles([]) + return + } - const lowerCaseQuery = query.toLowerCase() + const lowerCaseQuery = currentQuery.toLowerCase() + const filteredFiles: FilteredFile[] = [] - const filtered = filesList.reduce((acc, file) => { - const lowerCaseFile = file.toLowerCase() - const matchIndex = lowerCaseFile.indexOf(lowerCaseQuery) + for (const file of filesList) { + const lowerCaseFile = file.toLowerCase() + const matchIndex = lowerCaseFile.indexOf(lowerCaseQuery) - if (matchIndex > -1) { - acc.push({ - file, - element: getMarkedFileElement(file, lowerCaseQuery, matchIndex) - }) - } + if (matchIndex > -1) { + filteredFiles.push({ + file, + element: getMarkedFileElement(file, lowerCaseQuery, matchIndex) + }) + } - return acc - }, []) + // Limiting the result to 50, refactor this once backend supports pagination + if (filteredFiles.length === MAX_FILES) { + break + } + } - setFilteredFiles(filtered) - }, - [filesList] - ) + setFilteredFiles(filteredFiles) + }, [filesList, currentQuery]) - const handleInputChange = useCallback( - (searchQuery: string) => { - setIsOpen(searchQuery !== '') - filterQuery(searchQuery) - }, - [filterQuery] - ) + const handleInputChange = useCallback((searchQuery: string) => { + setIsOpen(searchQuery !== '') + setCurrentQuery(searchQuery) + }, []) return ( @@ -107,11 +107,11 @@ export const SearchFiles = ({ onOpenAutoFocus={event => { event.preventDefault() }} - className={cn('!p-1', contentClassName)} + className={cn('!p-1', 'width-popover-max-width', contentClassName)} > [cmdk-group]]:!p-0' }} + scrollAreaProps={{ className: 'max-h-96', classNameContent: 'overflow-hidden [&>[cmdk-group]]:!p-0' }} > {filteredFiles.length ? ( diff --git a/packages/ui/src/styles/styles.css b/packages/ui/src/styles/styles.css index be00390afa..084356a68c 100644 --- a/packages/ui/src/styles/styles.css +++ b/packages/ui/src/styles/styles.css @@ -613,6 +613,6 @@ mark { padding-inline: var(--cn-input-md-pl) var(--cn-input-md-pr); } -.width-popover-width { - width: var(--radix-popper-anchor-width); +.width-popover-max-width { + max-width: var(--radix-popover-content-available-width); } diff --git a/packages/ui/src/views/components/FilterGroup.tsx b/packages/ui/src/views/components/FilterGroup.tsx index 424d9785d9..dda769ae81 100644 --- a/packages/ui/src/views/components/FilterGroup.tsx +++ b/packages/ui/src/views/components/FilterGroup.tsx @@ -171,6 +171,8 @@ const FilterGroupInner = < parser={filterOption.parser as any} filterKey={filterOption.value} + sticky={filterOption.sticky} + defaultValue={filterOption.defaultValue as T[keyof T]} key={filterOption.value} > {({ onChange, removeFilter, value }) => diff --git a/packages/ui/src/views/repo/common/util.ts b/packages/ui/src/views/repo/common/util.ts index 3b01d8b21d..15a62f455b 100644 --- a/packages/ui/src/views/repo/common/util.ts +++ b/packages/ui/src/views/repo/common/util.ts @@ -10,22 +10,28 @@ export const getFilterScopeOptions = ({ }: { t: TFunctionWithFallback scope: Scope -}): ComboBoxOptions[] => { - if (accountId && orgIdentifier && projectIdentifier) return [] +}): { options: ComboBoxOptions[]; defaultValue: ComboBoxOptions } => { + if (accountId && orgIdentifier && projectIdentifier) return { options: [], defaultValue: { label: '', value: '' } } if (accountId && orgIdentifier) { - return [ - { label: t('views:scope.orgAndProject', 'Organizations and projects'), value: ExtendedScope.OrgProg }, - { label: t('views:scope.orgOnly', 'Organizations only'), value: ExtendedScope.Organization } - ] + return { + options: [ + { label: t('views:scope.orgAndProject', 'Organizations and projects'), value: ExtendedScope.OrgProg }, + { label: t('views:scope.orgOnly', 'Organizations only'), value: ExtendedScope.Organization } + ], + defaultValue: { label: t('views:scope.orgOnly', 'Organizations only'), value: ExtendedScope.Organization } + } } if (accountId) { - return [ - { label: t('views:scope.all', 'Account, organizations and projects'), value: ExtendedScope.All }, - { label: t('views:scope.accountOnly', 'Account only'), value: ExtendedScope.Account } - ] + return { + options: [ + { label: t('views:scope.all', 'Account, organizations and projects'), value: ExtendedScope.All }, + { label: t('views:scope.accountOnly', 'Account only'), value: ExtendedScope.Account } + ], + defaultValue: { label: t('views:scope.accountOnly', 'Account only'), value: ExtendedScope.Account } + } } - return [] + return { options: [], defaultValue: { label: '', value: '' } } } diff --git a/packages/ui/src/views/repo/constants/filter-options.ts b/packages/ui/src/views/repo/constants/filter-options.ts index 040d68297c..55d9b8e54e 100644 --- a/packages/ui/src/views/repo/constants/filter-options.ts +++ b/packages/ui/src/views/repo/constants/filter-options.ts @@ -36,6 +36,7 @@ interface PRListFilterOptions { t: TFunctionWithFallback onAuthorSearch: (name: string) => void isPrincipalsLoading?: boolean + isProjectLevel?: boolean principalData: { label: string; value: string }[] customFilterOptions?: PRListFilterOptionConfig } @@ -45,10 +46,11 @@ export const getPRListFilterOptions = ({ onAuthorSearch, isPrincipalsLoading, principalData, + isProjectLevel, customFilterOptions = [], scope }: PRListFilterOptions & { scope: Scope }): PRListFilterOptionConfig => { - const scopeFilterOptions = getFilterScopeOptions({ t, scope }) + const { options: scopeFilterOptions, defaultValue: scopeFilterDefaultValue } = getFilterScopeOptions({ t, scope }) const { accountId, orgIdentifier, projectIdentifier } = scope return [ { @@ -84,17 +86,19 @@ export const getPRListFilterOptions = ({ /** * Scope filter is only applicable at Account and Organization scope */ - ...(!(accountId && orgIdentifier && projectIdentifier) + ...(!(accountId && orgIdentifier && projectIdentifier) && isProjectLevel ? [ { label: t('views:scope.label', 'Scope'), value: 'include_subspaces' as keyof PRListFilters, type: FilterFieldTypes.ComboBox as FilterFieldTypes.ComboBox, + defaultValue: scopeFilterDefaultValue, filterFieldConfig: { options: scopeFilterOptions, placeholder: 'Select scope', allowSearch: false }, + sticky: true, parser: { parse: (value: string): ComboBoxOptions => { let selectedValue: string diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index b616683082..63445d4b6e 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -106,7 +106,7 @@ const PullRequestCompareDiffList: FC = ({ {t('views:commits.commitDetailsDiffAdditionsAnd', 'additions and')}{' '} {formatNumber(diffStats?.deletions || 0)} {t('views:commits.commitDetailsDiffDeletions', 'deletions')} - + {diffData?.map(diff => ( = ({ - - {diff.filePath} - + {diff.filePath} {diff.addedLines != null && diff.addedLines > 0 && ( diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index 40628f7ec3..c815a0d3cf 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -685,7 +685,7 @@ const PullRequestDiffViewer = ({ {/* @ts-ignore */} ref={ref} - className="bg-tr w-full text-cn-foreground-3" + className="bg-tr text-cn-foreground-1 w-full" renderWidgetLine={renderWidgetLine} renderExtendLine={renderExtendLine} diffFile={diffFileInstance} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-list-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-list-header.tsx index f851bd162d..990b8d65ed 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-list-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-list-header.tsx @@ -1,23 +1,27 @@ import { FC } from 'react' -import { IconV2, Text } from '@/components' +import { Button, IconV2, Text } from '@/components' import { cn } from '@utils/cn' import { PRState } from '../pull-request.types' interface PullRequestListHeaderProps { onOpenClick: () => void + onMergedClick: () => void onCloseClick: () => void headerFilter: Array closedPRs?: number + mergedPRs?: number openPRs?: number } export const PullRequestListHeader: FC = ({ onCloseClick, onOpenClick, + onMergedClick, headerFilter, closedPRs, + mergedPRs, openPRs }) => { return ( @@ -27,7 +31,7 @@ export const PullRequestListHeader: FC = ({ sm ghost variant button is adding more space between buttons because of the px padding. Have to check with design team. */} - - + + +
) } diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx index c054e0c1e2..67223678a4 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx @@ -2,16 +2,124 @@ import { FC } from 'react' import { NoData, StackedList } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { PullRequestListProps } from '@/views' +import { PRState, PullRequestListProps } from '@/views' import { noop } from 'lodash-es' import { PullRequestItemDescription } from './pull-request-item-description' import { PullRequestItemTitle } from './pull-request-item-title' import { PullRequestListHeader } from './pull-request-list-header' +// Define constants for state values to avoid string literals +enum PR_STATE { + OPEN = 'open', + CLOSED = 'closed', + MERGED = 'merged' +} + +type EmptyStateProps = { + headerFilter: Array + onOpenClick: () => void + onMergedClick: () => void + onCloseClick: () => void + openPRs?: number + mergedPRs?: number + closedPRs?: number + repoId?: string + spaceId?: string + state: PR_STATE +} + +const EmptyStateView: FC = ({ + headerFilter, + onOpenClick, + onMergedClick, + onCloseClick, + openPRs, + mergedPRs, + closedPRs, + repoId, + spaceId, + state +}) => { + const { t } = useTranslation() + + const getTitleAndDescription = () => { + switch (state) { + case PR_STATE.OPEN: + return { + title: t('views:noData.title.noOpenPullRequests', 'No open pull requests yet'), + description: [ + t( + 'views:noData.noOpenPullRequests', + `There are no open pull requests in this ${repoId ? 'repo' : 'project'} yet.` + ), + t('views:noData.createNewPullRequest', 'Create a new pull request.') + ] + } + case PR_STATE.CLOSED: + return { + title: t('views:noData.title.noClosedPullRequests', 'No closed pull requests yet'), + description: [ + t('views:noData.noClosedPullRequests', 'There are no closed pull requests in this project yet.'), + t('views:noData.createNewPullRequest', 'Create a new pull request.') + ] + } + case PR_STATE.MERGED: + return { + title: t('views:noData.title.noMergedPullRequests', 'No merged pull requests yet'), + description: [ + t('views:noData.noMergedPullRequests', 'There are no merged pull requests in this project yet.'), + t('views:noData.createNewPullRequest', 'Create a new pull request.') + ] + } + default: + return { + title: '', + description: [''] + } + } + } + + const { title, description } = getTitleAndDescription() + + return ( + + + + } + /> + + + + ) +} + export const PullRequestList: FC = ({ pullRequests = [], openPRs, + mergedPRs, closedPRs, handleOpenClick, handleCloseClick, @@ -27,91 +135,44 @@ export const PullRequestList: FC = ({ }) => { const { identifier: repoId } = repo || {} const { Link } = useRouterContext() - const { t } = useTranslation() const onOpenClick = () => { setHeaderFilter(['open']) handleOpenClick?.() } const onCloseClick = () => { - setHeaderFilter(['closed', 'merged']) + setHeaderFilter(['closed']) handleCloseClick?.() } - if (!pullRequests.length && headerFilter.includes('open') && openPRs === 0 && !!closedPRs) { - return ( - - - - } - /> - - - - ) + const onMergedClick = () => { + setHeaderFilter(['merged']) + } + + let state: (typeof PR_STATE)[keyof typeof PR_STATE] | undefined + + if (headerFilter.includes('open') && !openPRs) { + state = PR_STATE.OPEN + } else if (headerFilter.includes('closed') && !closedPRs) { + state = PR_STATE.CLOSED + } else if (headerFilter.includes('merged') && !mergedPRs) { + state = PR_STATE.MERGED } - if (!pullRequests.length && headerFilter.includes('closed') && closedPRs === 0 && openPRs && openPRs > 0) { + if (!pullRequests.length && state) { return ( - - - - } - /> - - - - + ) } @@ -119,14 +180,16 @@ export const PullRequestList: FC = ({ return ( - + } diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index b431ce1291..7612d6f797 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react' -import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton } from '@/components' +import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton, Text } from '@/components' import { useTranslation } from '@/context' import { TypesUser } from '@/types' import { formatNumber } from '@/utils' @@ -250,7 +250,7 @@ export const PullRequestChangesFilter: React.FC = {formatNumber(pullReqStats?.deletions || 0)} {t('views:commits.commitDetailsDiffDeletions', 'deletions')}

- + {diffData?.map(diff => ( = - - {diff.filePath} - + {diff.filePath} } diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index ad3c209504..e6204e7159 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -73,6 +73,7 @@ const PullRequestListPage: FC = ({ page, setPage, openPullReqs, + mergedPullReqs, closedPullReqs, setLabelsQuery, setPrState, @@ -165,6 +166,7 @@ const PullRequestListPage: FC = ({ onAuthorSearch: searchText => { setPrincipalsSearchQuery?.(searchText) }, + isProjectLevel, isPrincipalsLoading, customFilterOptions, principalData: computedPrincipalData.map(userInfo => ({ @@ -193,13 +195,18 @@ const PullRequestListPage: FC = ({ const [selectedFiltersCnt, setSelectedFiltersCnt] = useState(0) - const noData = !(pullRequests && pullRequests.length > 0) && closedPullReqs === 0 && openPullReqs === 0 + const noData = + !(pullRequests && pullRequests.length > 0) && + closedPullReqs === 0 && + openPullReqs === 0 && + mergedPullReqs === 0 && + !isProjectLevel const onFilterSelectionChange = (filterValues: PRListFiltersKeys[]) => { setSelectedFiltersCnt(filterValues.length) } - const hasActiveFilters = selectedFiltersCnt > 0 || searchQuery || !!defaultSelectedAuthor + const hasActiveFilters = selectedFiltersCnt > 0 || searchQuery const showTopBar = !noData || hasActiveFilters @@ -266,6 +273,7 @@ const PullRequestListPage: FC = ({ pullRequests={pullRequests || []} // Do not show Open and close count if project level closedPRs={!isProjectLevel ? closedPullReqs : undefined} + mergedPRs={!isProjectLevel ? mergedPullReqs : undefined} openPRs={!isProjectLevel ? openPullReqs : undefined} headerFilter={prState} setHeaderFilter={setPrState} @@ -447,6 +455,8 @@ const PullRequestListPage: FC = ({ {({ onChange, removeFilter, value }) => @@ -484,13 +494,15 @@ const PullRequestListPage: FC = ({ )} {renderListContent()} {isProjectLevel ? ( - 1} - hasNext={(pullRequests?.length || 0) === pageSize} - onPrevious={() => setPage(page - 1)} - onNext={() => setPage(page + 1)} - /> + !!pullRequests?.length && ( + 1} + hasNext={(pullRequests.length || 0) === pageSize} + onPrevious={() => setPage(page - 1)} + onNext={() => setPage(page + 1)} + /> + ) ) : ( )} diff --git a/packages/ui/src/views/repo/pull-request/pull-request.types.ts b/packages/ui/src/views/repo/pull-request/pull-request.types.ts index 75449a8f91..c26ab24735 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request.types.ts +++ b/packages/ui/src/views/repo/pull-request/pull-request.types.ts @@ -57,6 +57,7 @@ export interface PullRequestListStore { setLabelsQuery: (query: string) => void openPullReqs: number closedPullReqs: number + mergedPullReqs: number } export interface RepoRepositoryOutput { @@ -267,6 +268,7 @@ export interface PullRequestListProps extends Partial { hasActiveFilters?: boolean query?: string openPRs?: number + mergedPRs?: number handleOpenClick?: () => void closedPRs?: number handleCloseClick?: () => void diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx index 323a523686..6a875735ab 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx @@ -19,7 +19,7 @@ export const CommitSidebar = ({ navigateToFile, filesList, children }: CommitsSi
diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index b3502a0df0..77fa763f4e 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -101,12 +101,13 @@ const SandboxRepoListPage: FC = ({ { label: 'Last push', value: RepoSortMethod.LastPush } ] - const scopeFilterOptions = getFilterScopeOptions({ t, scope }) + const { options: scopeFilterOptions, defaultValue: scopeFilterDefaultValue } = getFilterScopeOptions({ t, scope }) const filterOptions: FilterOptionConfig[] = [ { label: t('views:connectors.filterOptions.statusOption.favorite', 'Favorites'), value: 'favorite', type: FilterFieldTypes.Checkbox, + sticky: true, filterFieldConfig: { label: }, @@ -119,6 +120,7 @@ const SandboxRepoListPage: FC = ({ label: t('views:scope.label', 'Scope'), value: 'recursive', type: FilterFieldTypes.ComboBox, + defaultValue: scopeFilterDefaultValue, filterFieldConfig: { options: scopeFilterOptions, placeholder: 'Select scope', @@ -159,7 +161,7 @@ const SandboxRepoListPage: FC = ({ simpleSortConfig={{ sortOptions: FilterSortOptions, - defaultSort: RepoSortMethod.Identifier, + defaultSort: RepoSortMethod.LastPush, onSortChange }} onFilterValueChange={onFilterValueChange} diff --git a/packages/ui/src/views/repo/repo-sidebar/index.tsx b/packages/ui/src/views/repo/repo-sidebar/index.tsx index cacc3d29bf..9af02a0a10 100644 --- a/packages/ui/src/views/repo/repo-sidebar/index.tsx +++ b/packages/ui/src/views/repo/repo-sidebar/index.tsx @@ -32,7 +32,7 @@ export const RepoSidebar = ({ navigateToFile={navigateToFile} filesList={filesList} searchInputSize="md" - contentClassName="w-[280px]" + contentClassName="w-[800px]" /> diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 0e8544a8eb..c8f38857b1 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -192,7 +192,7 @@ export function RepoSummaryView({ filesList={filesList} searchInputSize="md" inputContainerClassName="max-w-80 min-w-40 w-full" - contentClassName="width-popover-width" + contentClassName="w-[800px]" /> From c9a9ec5c47b13cfd6b4cd68f16c9e735869cb1a8 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla Date: Thu, 7 Aug 2025 05:41:21 +0000 Subject: [PATCH 010/180] Merge page design fixes (#10138) * 23279c Reverted label * c847f5 Fixed padding issues * 3c2fac Merge page design fixed --- packages/ui/src/components/split-button.tsx | 7 +++++-- .../ui/src/views/layouts/PullRequestLayout.tsx | 8 ++------ .../conversation/pull-request-panel.tsx | 15 +++++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/split-button.tsx b/packages/ui/src/components/split-button.tsx index d74555ce51..464ec271e8 100644 --- a/packages/ui/src/components/split-button.tsx +++ b/packages/ui/src/components/split-button.tsx @@ -27,6 +27,7 @@ interface SplitButtonBaseProps { disableDropdown?: boolean children: ReactNode dropdownContentClassName?: string + size?: 'sm' | 'md' | 'xs' } // For solid variant with primary theme @@ -67,7 +68,8 @@ export const SplitButton = ({ disableDropdown = false, disableButton = false, children, - dropdownContentClassName + dropdownContentClassName, + size = 'md' }: SplitButtonProps) => { return (
@@ -75,6 +77,7 @@ export const SplitButton = ({ className={cn('rounded-r-none border-r-0', buttonClassName)} theme={theme} variant={variant} + size={size} onClick={handleButtonClick} type="button" disabled={disabled || disableButton} @@ -84,7 +87,7 @@ export const SplitButton = ({ diff --git a/packages/ui/src/views/layouts/PullRequestLayout.tsx b/packages/ui/src/views/layouts/PullRequestLayout.tsx index e17964f189..9cbc8ef754 100644 --- a/packages/ui/src/views/layouts/PullRequestLayout.tsx +++ b/packages/ui/src/views/layouts/PullRequestLayout.tsx @@ -53,7 +53,7 @@ export const PullRequestLayout: FC = ({ {t('views:pullRequests.conversation', 'Conversation')} @@ -63,11 +63,7 @@ export const PullRequestLayout: FC = ({ {t('views:pullRequests.commits', 'Commits')} - + {t('views:pullRequests.changes', 'Changes')} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index a8fba04b14..ae77085aae 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -8,7 +8,6 @@ import { Checkbox, CounterBadge, IconV2, - Input, Layout, MoreActionsTooltip, SplitButton, @@ -16,6 +15,7 @@ import { StatusBadge, Text, Textarea, + TextInput, TimeAgoCard, type ButtonThemes } from '@/components' @@ -518,6 +518,7 @@ const PullRequestPanel = ({ handleMergeTypeSelect(mergeButtonValue) } }} + size="md" > {actions[parseInt(mergeButtonValue)].title} @@ -586,14 +587,14 @@ const PullRequestPanel = ({ } /> {showMergeInputs && ( - - - + + setMergeTitle(e.target.value)} + onChange={(e: React.ChangeEvent) => setMergeTitle(e.target.value)} optional placeholder="Enter commit message (optional)" /> @@ -602,8 +603,10 @@ const PullRequestPanel = ({ label="Commit Description" className="w-full" value={mergeMessage} - onChange={e => setMergeMessage(e.target.value)} + onChange={(e: React.ChangeEvent) => setMergeMessage(e.target.value)} optional + resizable + rows={5} placeholder="Enter commit description (optional)" /> From 40556dc49a5cc433a88d370e7af42dc85c7b92da Mon Sep 17 00:00:00 2001 From: Pranesh TG Date: Thu, 7 Aug 2025 05:43:10 +0000 Subject: [PATCH 011/180] Update on click markdown image preview to open in newtab (#10141) * f5a7c4 Update on click markdown image preview to open in newtab --- .../src/components/markdown-viewer/index.tsx | 32 ++++++------------- .../pull-request-timeline-item.tsx | 1 + 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/index.tsx b/packages/ui/src/components/markdown-viewer/index.tsx index 97c3a1a8c4..dbec04a42f 100644 --- a/packages/ui/src/components/markdown-viewer/index.tsx +++ b/packages/ui/src/components/markdown-viewer/index.tsx @@ -1,6 +1,6 @@ -import { CSSProperties, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { CSSProperties, useCallback, useEffect, useMemo, useRef } from 'react' -import { CopyButton, ImageCarousel } from '@/components' +import { CopyButton } from '@/components' import MarkdownPreview from '@uiw/react-markdown-preview' import rehypeExternalLinks from 'rehype-external-links' import { getCodeString, RehypeRewriteOptions } from 'rehype-rewrite' @@ -45,11 +45,8 @@ export function MarkdownViewer({ showLineNumbers = false }: MarkdownViewerProps) { const { navigate } = useRouterContext() - const [isOpen, setIsOpen] = useState(false) - const [imgEvent, setImageEvent] = useState([]) const refRootHref = useMemo(() => document.getElementById('repository-ref-root')?.getAttribute('href'), []) const ref = useRef(null) - const [initialSlide, setInitialSlide] = useState(0) const styles: CSSProperties = maxHeight ? { maxHeight } : {} @@ -106,16 +103,14 @@ export function MarkdownViewer({ (event: MouseEvent) => { const { target } = event - const imgTags = ref.current?.querySelectorAll('.wmde-markdown img') || [] - - if (target instanceof HTMLImageElement && !!imgTags.length) { - const imgsArr: string[] = Array.from(imgTags).map(img => img.src) - const dataSrc = target.getAttribute('src') - const index = imgsArr.findIndex(val => val === dataSrc) - - setImageEvent(imgsArr) - setInitialSlide(index > -1 ? index : 0) - setIsOpen(true) + // Handle image clicks - open in new tab + if (target instanceof HTMLImageElement) { + event.preventDefault() + const imageSrc = target.getAttribute('src') + if (imageSrc) { + window.open(imageSrc, '_blank', 'noopener,noreferrer') + } + return } if (target instanceof HTMLAnchorElement) { @@ -249,13 +244,6 @@ export function MarkdownViewer({ } }} /> - -
) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx index 35c856b3be..e93ba8e2c3 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx @@ -412,6 +412,7 @@ const PullRequestTimelineItem: FC = ({ setHideReplyHere?.(true)} onClick={() => setHideReplyHere?.(true)} onChange={e => setComment(e.target.value)} /> From ee49799e2f2b88e1cf38738e5848cf76603c3e22 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor Date: Thu, 7 Aug 2025 05:50:43 +0000 Subject: [PATCH 012/180] feat: add support to bypass branch rules via branch dialog when blocked via rules (#10132) * 0783b6 test: added unit test for util * b3a88a fix: branch and tag dialog to branch selector background color * 4f1b77 fix typecheck * 0786e9 feat: add support to bypass branch rules via branch dialog when blocked via rules --- .../views/repo-tags/repo-tags-list.tsx | 3 + .../components-v2/create-branch-dialog.tsx | 36 +++++++++--- .../src/components-v2/git-commit-dialog.tsx | 4 +- .../src/pages-v2/repo/repo-branch-list.tsx | 51 +++++++++++++---- .../repo/repo-tags-list-container.tsx | 1 + .../src/utils/__tests__/git-utils.test.ts | 19 +++++++ apps/gitness/src/utils/git-utils.ts | 14 +++++ packages/ui/locales/en/component.json | 29 +++++++++- packages/ui/locales/en/views.json | 2 +- packages/ui/locales/fr/component.json | 26 ++++++++- packages/ui/locales/fr/views.json | 2 +- packages/ui/src/components/alert-dialog.tsx | 2 +- .../dialogs/delete-alert-dialog.tsx | 32 +++++++++-- .../components/create-branch-dialog.tsx | 55 +++++++++++++++++-- .../ui/src/views/repo/repo-branch/types.ts | 3 + 15 files changed, 240 insertions(+), 39 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx index a212eefa05..d117fdefcc 100644 --- a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx +++ b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx @@ -98,6 +98,9 @@ export const RepoTagsList = () => { preSelectedTab={preSelectedTab} /> )} + violation={false} + bypassable={false} + resetViolation={noop} /> { const repo_ref = useGetRepoRef() - + const [error, setError] = useState() const [selectedBranchOrTag, setSelectedBranchOrTag] = useState(null) + const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() const selectBranchOrTag = useCallback((branchTagName: BranchSelectorListItem) => { setSelectedBranchOrTag(branchTagName) @@ -40,7 +42,6 @@ export const CreateBranchDialog = ({ const { mutateAsync: createBranch, - error: createBranchError, isLoading: isCreatingBranch, reset: resetBranchMutation } = useCreateBranchMutation( @@ -55,12 +56,25 @@ export const CreateBranchDialog = ({ const handleCreateBranch = async (data: CreateBranchFormFields) => { onBranchQueryChange?.(data.name) - await createBranch({ - repo_ref, - body: { - ...data + try { + await createBranch({ + repo_ref, + body: { + ...data, + bypass_rules: bypassed + } + }) + } catch (_error: any) { + if (_error?.violations?.length > 0) { + setAllStates({ + violation: true, + bypassed: true, + bypassable: _error?.violations[0]?.bypassable + }) + } else { + setError(_error as UsererrorError) } - }) + } } useEffect(() => { @@ -82,11 +96,15 @@ export const CreateBranchDialog = ({ selectedBranchOrTag={selectedBranchOrTag} onSubmit={handleCreateBranch} isCreatingBranch={isCreatingBranch} - error={createBranchError?.message} + error={error?.message} prefilledName={prefilledName} + violation={violation} + bypassable={bypassable} + resetViolation={resetViolation} renderProp={ { @@ -101,16 +104,7 @@ export function RepoBranchesListPage() { isLoading: isDeletingBranch, error: deleteBranchError, reset: resetDeleteBranch - } = useDeleteBranchMutation( - { repo_ref: repoRef }, - { - onSuccess: () => { - setIsDeleteDialogOpen(false) - handleResetDeleteBranch() - handleInvalidateBranchList() - } - } - ) + } = useDeleteBranchMutation({ repo_ref: repoRef }) const { mutateAsync: saveBranch, isLoading: isCreatingBranch, error: createBranchError } = useCreateBranchMutation({}) @@ -121,10 +115,43 @@ export function RepoBranchesListPage() { setCreateBranchDialogOpen(false) } + const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() + const handleDeleteBranch = (branch_name: string) => { - deleteBranch({ branch_name, queryParams: {} }) + deleteBranch({ branch_name, queryParams: { dry_run_rules: false, bypass_rules: bypassed } }) + .then(() => { + setIsDeleteDialogOpen(false) + handleResetDeleteBranch() + handleInvalidateBranchList() + resetViolation() + }) + .catch(error => { + console.error(error) + }) + } + + const handleDeleteBranchWithDryRun = (branch_name: string) => { + deleteBranch({ branch_name, queryParams: { dry_run_rules: true, bypass_rules: false } }) + .then(res => { + if (!isEmpty(res?.body?.rule_violations)) { + setAllStates({ + violation: true, + bypassed: true, + bypassable: res?.body?.rule_violations?.[0]?.bypassable + }) + } else setAllStates({ bypassable: true }) + }) + .catch(error => { + console.error(error) + }) } + useEffect(() => { + if (isDeleteDialogOpen && deleteBranchName) { + handleDeleteBranchWithDryRun(deleteBranchName) + } + }, [isDeleteDialogOpen, deleteBranchName]) + useEffect(() => { setPaginationFromHeaders( parseInt(headers?.get(PageResponseHeader.xNextPage) || ''), @@ -200,6 +227,8 @@ export function RepoBranchesListPage() { type="branch" identifier={deleteBranchName ?? undefined} isLoading={isDeletingBranch} + violation={violation} + bypassable={bypassable} /> ) diff --git a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx index 8480361876..6ac09c3529 100644 --- a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx @@ -161,6 +161,7 @@ export const RepoTagsListContainer = () => { selectedBranchOrTag={selectedBranchOrTag} branchSelectorRenderer={() => ( { expect(normalizeGitRef('')).toBe('') }) }) + + describe('deNormalizeGitRef', () => { + it('should handle undefined input', () => { + expect(deNormalizeGitRef(undefined)).toBe(undefined) + }) + + it('should denormalize git references correctly', () => { + const tag = REFS_TAGS_PREFIX + 'v1.0.0' + const branch = REFS_BRANCH_PREFIX + 'main' + const commit = '1234567890abcdef1234567890abcdef12345678' + + expect(deNormalizeGitRef(tag)).toBe('v1.0.0') + expect(deNormalizeGitRef(branch)).toBe('main') + expect(deNormalizeGitRef(commit)).toBe(commit) + expect(deNormalizeGitRef('main')).toBe('main') + expect(deNormalizeGitRef('')).toBe('') + }) + }) }) describe('getTrimmedSha', () => { diff --git a/apps/gitness/src/utils/git-utils.ts b/apps/gitness/src/utils/git-utils.ts index ed8f06406e..132d322911 100644 --- a/apps/gitness/src/utils/git-utils.ts +++ b/apps/gitness/src/utils/git-utils.ts @@ -189,6 +189,20 @@ export const normalizeGitRef = (gitRef: string | undefined) => { } } +export const deNormalizeGitRef = (gitRef: string | undefined) => { + if (isRefATag(gitRef)) { + return gitRef?.replace('refs/tags/', '') + } else if (isRefABranch(gitRef)) { + return gitRef?.replace('refs/heads/', '') + } else if (gitRef === '') { + return '' + } else if (gitRef && isRefACommitSHA(gitRef)) { + return gitRef + } else { + return gitRef + } +} + const TRIMMED_SHA_LIMIT = 7 export const getTrimmedSha = (sha: string): string => { diff --git a/packages/ui/locales/en/component.json b/packages/ui/locales/en/component.json index 9635d8b7b7..b48cebd150 100644 --- a/packages/ui/locales/en/component.json +++ b/packages/ui/locales/en/component.json @@ -77,11 +77,16 @@ "account": "Account" }, "deleteDialog": { - "descriptionWithType": "This will permanently delete your {{type}} and remove all data. This action cannot be undone.", + "descriptionWithType": "This will permanently delete your {{type}} {{identifier}} and remove all data. This action cannot be undone.", "description": "This will permanently remove all data. This action cannot be undone.", "title": "Are you sure?", "inputLabel": "To confirm, type", - "cancel": "Cancel" + "violationMessages": { + "bypassed": "Some rules will be bypassed while deleting {{type}}", + "notAllow": "Some rules don't allow you to delete {{type}}" + }, + "cancel": "Cancel", + "bypassButton": "Bypass rule and confirm delete" }, "dropdownMenu": { "noOptions": "No options available" @@ -196,6 +201,26 @@ "pullRequests": "Pull Requests", "title": "Title" }, + "branchDialog": { + "violationMessages": { + "bypassed": "Some rules will be bypassed while creating branch", + "notAllow": "Some rules don't allow creating branch " + }, + "loading": "Creating branch...", + "default": "Create branch", + "bypassButton": "Bypass rules and create branch", + "title": "Create a branch", + "form": { + "name": { + "label": "Branch name", + "placeholder": "Add a branch name" + }, + "target": { + "label": "Based on", + "placeholder": "Add a branch name" + } + } + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 138c208705..fce1866907 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -151,7 +151,6 @@ } }, "createBranchTitle": "Create a branch", - "createBranchButton": "Create branch", "createNewRepo": "Create a new repository", "repoContains": "A repository contains all project files, including the revision history. Already have a project repository elsewhere?", "importRepo": "Import a repository", @@ -319,6 +318,7 @@ "creatingWebhook": "Creating webhook...", "updateWebhook": "Update webhook", "createWebhook": "Create webhook", + "createBranchButton": "Create branch", "newBranch": "New branch", "emptyRepo": "This repository is empty.", "afterComparingOpenPullRequest": "After comparing changes, you may open a pull request to contribute your changes upstream.", diff --git a/packages/ui/locales/fr/component.json b/packages/ui/locales/fr/component.json index cbe9bb3626..5784154e1b 100644 --- a/packages/ui/locales/fr/component.json +++ b/packages/ui/locales/fr/component.json @@ -77,10 +77,14 @@ "account": "Compte" }, "deleteDialog": { - "descriptionWithType": "This will permanently delete your {{type}} and remove all data. This action cannot be undone.", + "descriptionWithType": "This will permanently delete your {{type}} {{identifier}} and remove all data. This action cannot be undone.", "description": "This will permanently remove all data. This action cannot be undone.", "title": "Êtes-vous sûr ?", "inputLabel": "To confirm, type", + "violationMessages": { + "bypassed": "", + "notAllow": "" + }, "cancel": "Annuler" }, "dropdownMenu": { @@ -197,6 +201,26 @@ "pullRequests": "Demandes de fusion", "title": "Titre" }, + "branchDialog": { + "violationMessages": { + "bypassed": "Some rules will be bypassed while creating branch", + "notAllow": "Some rules don't allow creating branch " + }, + "loading": "Creating branch...", + "default": "Create branch", + "bypassButton": "Bypass rules and create branch", + "title": "Create a branch", + "form": { + "name": { + "label": "Branch name", + "placeholder": "Add a branch name" + }, + "target": { + "label": "Based on", + "placeholder": "Add a branch name" + } + } + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 7842e1b6be..63f266d3e8 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -151,7 +151,6 @@ } }, "createBranchTitle": "Créer une branche", - "createBranchButton": "Créer une branche", "createNewRepo": "Create a new repository", "repoContains": "A repository contains all project files, including the revision history. Already have a project repository elsewhere?", "importRepo": "Import a repository", @@ -313,6 +312,7 @@ "creatingWebhook": "Creating webhook...", "updateWebhook": "Update webhook", "createWebhook": "Create webhook", + "createBranchButton": "Créer une branche", "newBranch": "Nouvelle branche", "emptyRepo": "Ce référentiel est vide.", "afterComparingOpenPullRequest": "Après avoir comparé les modifications, vous pouvez ouvrir une demande d'extraction pour contribuer à vos modifications en amont.", diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx index 27016d8d51..09ed6babde 100644 --- a/packages/ui/src/components/alert-dialog.tsx +++ b/packages/ui/src/components/alert-dialog.tsx @@ -113,7 +113,7 @@ const Cancel = ({ children = 'Cancel', ...props }: { children?: ReactNode }) => } Cancel.displayName = 'AlertDialog.Cancel' -const Confirm = ({ children = 'Confirm', ...props }: { children?: ReactNode }) => { +const Confirm = ({ children = 'Confirm', ...props }: { children?: ReactNode; disabled?: boolean }) => { const context = useContext(AlertDialogContext) if (!context) throw new Error('AlertDialog.Confirm must be used within AlertDialog.Root') diff --git a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx index cf88f3ff71..c70699ea5c 100644 --- a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx +++ b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx @@ -1,6 +1,6 @@ import { ChangeEvent, FC, useMemo, useState } from 'react' -import { Alert, AlertDialog, Fieldset, Input } from '@/components' +import { Alert, AlertDialog, Fieldset, Input, Message, MessageTheme } from '@/components' import { useTranslation } from '@/context' import { getErrorMessage } from '@utils/utils' @@ -15,6 +15,8 @@ export interface DeleteAlertDialogProps { withForm?: boolean message?: string deletionKeyword?: string + violation?: boolean + bypassable?: boolean } export const DeleteAlertDialog: FC = ({ @@ -27,7 +29,9 @@ export const DeleteAlertDialog: FC = ({ error, withForm = false, message, - deletionKeyword = 'DELETE' + deletionKeyword = 'DELETE', + violation = false, + bypassable = false }) => { const { t } = useTranslation() const [verification, setVerification] = useState('') @@ -50,15 +54,15 @@ export const DeleteAlertDialog: FC = ({ if (type) { return t( 'component:deleteDialog.descriptionWithType', - `This will permanently delete your ${type} and remove all data. This action cannot be undone.`, - { type: type } + `This will permanently delete your ${type} ${identifier} and remove all data. This action cannot be undone.`, + { type: type, identifier: identifier } ) } return t( 'component:deleteDialog.description', `This will permanently remove all data. This action cannot be undone.` ) - }, [type, t, message]) + }, [type, t, message, identifier]) return ( @@ -78,6 +82,20 @@ export const DeleteAlertDialog: FC = ({ )} + {violation && ( + + {bypassable + ? t( + 'component:deleteDialog.violationMessages.bypassed', + `Some rules will be bypassed while deleting ${type}`, + { type: type } + ) + : t('component:deleteDialog.violationMessages.notAllow', `Some rules don't allow you to delete ${type}`, { + type: type + })} + + )} + {!!error && ( Failed to perform delete operation @@ -88,7 +106,9 @@ export const DeleteAlertDialog: FC = ({ )} - Yes, delete {type} + + {violation && bypassable ? `Bypass rules and delete ${type}` : `Yes, delete ${type}`} + ) diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index 50199a27c2..615b89662e 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -1,7 +1,18 @@ import { useCallback, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { Alert, Button, ButtonLayout, ControlGroup, Dialog, FormInput, FormWrapper, Label } from '@/components' +import { + Alert, + Button, + ButtonLayout, + ControlGroup, + Dialog, + FormInput, + FormWrapper, + Label, + Message, + MessageTheme +} from '@/components' import { TFunctionWithFallback, useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -32,6 +43,9 @@ export function CreateBranchDialog({ onClose, onSubmit, isCreatingBranch, + violation, + bypassable, + resetViolation, error, selectedBranchOrTag, renderProp: branchSelectorContainer, @@ -45,7 +59,7 @@ export function CreateBranchDialog({ defaultValues: INITIAL_FORM_VALUES }) - const { register, handleSubmit, setValue, reset, clearErrors } = formMethods + const { register, handleSubmit, setValue, reset, clearErrors, watch } = formMethods const resetForm = useCallback(() => { clearErrors() @@ -65,6 +79,7 @@ export function CreateBranchDialog({ name: prefilledName || '', target: selectedBranchOrTag?.name }) + resetViolation() }, [open, prefilledName, reset]) useEffect(() => { @@ -73,6 +88,12 @@ export function CreateBranchDialog({ } }, [selectedBranchOrTag, setValue]) + const branchName = watch('name') + + useEffect(() => { + resetViolation() + }, [branchName]) + const handleClose = () => { resetForm() onClose() @@ -99,6 +120,20 @@ export function CreateBranchDialog({ {branchSelectorContainer} + {violation && ( + + {bypassable + ? t( + 'component:branchDialog.violationMessages.bypassed', + 'Some rules will be bypassed while creating branch' + ) + : t( + 'component:branchDialog.violationMessages.notAllow', + "Some rules don't allow you to create branch" + )} + + )} + {error && ( @@ -113,9 +148,19 @@ export function CreateBranchDialog({ {t('views:repos.cancel', 'Cancel')} - + {!bypassable ? ( + + ) : ( + + )} diff --git a/packages/ui/src/views/repo/repo-branch/types.ts b/packages/ui/src/views/repo/repo-branch/types.ts index 4ec0e00a86..ad378d84cc 100644 --- a/packages/ui/src/views/repo/repo-branch/types.ts +++ b/packages/ui/src/views/repo/repo-branch/types.ts @@ -68,6 +68,8 @@ export interface RepoBranchListViewProps extends Partial { export interface CreateBranchDialogProps { open: boolean + violation: boolean + bypassable: boolean onClose: () => void onSubmit: (formValues: CreateBranchFormFields) => Promise error?: string @@ -75,4 +77,5 @@ export interface CreateBranchDialogProps { selectedBranchOrTag: BranchSelectorListItem | null renderProp: React.ReactNode prefilledName?: string + resetViolation: () => void } From a5f31fb54c10e262c76c559b673d937c08456c94 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor Date: Thu, 7 Aug 2025 08:12:07 +0000 Subject: [PATCH 013/180] fix: revert commits tab should be selected by default in case PR is already present" (#10143) * 4e9773 fix: default value change * 9f1278 Revert Pull Request #10135 "fix: commits tab should be selected by default in case PR is already present" --- .../compare/pull-request-compare-page.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 8c750e2d77..35db3ccc3a 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -157,6 +157,12 @@ export const PullRequestComparePage: FC = ({ const { navigate } = useRouterContext() const { t } = useTranslation() + const [activeTab, setActiveTab] = useState(prBranchCombinationExists ? 'commits' : 'overview') + + useEffect(() => { + setActiveTab(prBranchCombinationExists ? 'commits' : 'overview') + }, [prBranchCombinationExists]) + const formMethods = useForm({ resolver: zodResolver(getPullRequestFormSchema(t)), mode: 'onChange', @@ -216,13 +222,6 @@ export const PullRequestComparePage: FC = ({ // Return a default value return review_decision } - - const [activeTab, setActiveTab] = useState(prBranchCombinationExists ? 'commits' : 'overview') - - useEffect(() => { - setActiveTab(prBranchCombinationExists ? 'commits' : 'overview') - }, [prBranchCombinationExists]) - return ( @@ -355,7 +354,7 @@ export const PullRequestComparePage: FC = ({ )} {isBranchSelected ? ( - + setActiveTab(val)}> {!prBranchCombinationExists && ( From 9c25ef07bbbf87b603ada6f625e5256f757100f1 Mon Sep 17 00:00:00 2001 From: Vardan Bansal Date: Thu, 7 Aug 2025 08:12:47 +0000 Subject: [PATCH 014/180] fix: Fix few P3 bugs on Repo listing page (#10142) * fcb9ef cleanup * 7cd162 aligning label for tag, repository and pr page * ea247b add order asc sort option on repo list page --- packages/ui/locales/en/views.json | 15 ++++++++++++--- packages/ui/locales/fr/views.json | 15 ++++++++++++--- .../pull-request/components/pull-request-list.tsx | 6 +++--- .../repo/pull-request/pull-request-list-page.tsx | 6 ++++-- .../src/views/repo/repo-list/repo-list-page.tsx | 8 ++++---- .../ui/src/views/repo/repo-list/repo-list.tsx | 7 +------ packages/ui/src/views/repo/repo-list/types.ts | 3 ++- .../views/repo/repo-tags/repo-tags-list-page.tsx | 7 ++----- 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index fce1866907..c1aa9a2e72 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -70,6 +70,7 @@ "label": "Label by" } }, + "createPullReq": "Create Pull request", "BlockBranchCreation": "Block branch creation", "BlockBranchCreationDescription": "Only allow users with bypass permission to create matching branches", "BlockBranchDeletion": "Block branch deletion", @@ -180,7 +181,7 @@ } }, "repositories": "Repositories", - "new-repository": "New repository", + "createRepository": "Create repository", "import-repository": "Import repository", "import-repositories": "Import repositories", "importing": "Importing…", @@ -298,7 +299,7 @@ "commit": "Commit", "tagger": "Tagger", "creationDate": "Creation date", - "newTag": "New Tag", + "createTag": "Create Tag", "enableWebhookToggle": "Enable the webhook", "toggleDescription": "We will deliver event details when this hook is triggered", "urlPlaceholder": "https://example.com/harness", @@ -395,9 +396,17 @@ "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", "compareChanges": "Compare and review just about anything", "compareChangesDescription": "Branches and commit ranges can be reviewed within the same repository.", + "title": { + "noOpenPullRequests": "No open pull requests yet", + "noClosedPullRequests": "No closed pull requests yet", + "noMergedPullRequests": "No merged pull requests yet" + }, "noOpenPullRequests": "There are no open pull requests in this project yet.", - "createNewPullRequest": "Create a new pull request.", "noClosedPullRequests": "There are no closed pull requests in this project yet.", + "noMergedPullRequests": "There are no merged pull requests in this project yet.", + "button": { + "createPullRequest": "Create pull request" + }, "clearFilters": "Clear filters", "noPullRequestsInRepo": "Start your contribution journey by creating a new pull request draft.", "noPullRequestsInProject": "There are no pull requests in this project yet.", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 63f266d3e8..e72e5495cb 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -70,6 +70,7 @@ "label": "Label by" } }, + "createPullReq": "Créer une requête de tirage.", "BlockBranchCreation": "Bloquer la création de branches", "BlockBranchCreationDescription": "Autoriser uniquement les utilisateurs ayant des permissions spécifiques à créer ces branches", "BlockBranchDeletion": "Bloquer la suppression de branches", @@ -180,7 +181,7 @@ } }, "repositories": "Dépôts", - "new-repository": "Créer un dépôt", + "createRepository": "Créer un dépôt", "import-repository": "Importer un dépôt", "import-repositories": "Import repositories", "importing": "Importing…", @@ -292,7 +293,7 @@ "commit": "Commit", "tagger": "Tagger", "creationDate": "Creation date", - "newTag": "New Tag", + "createTag": "Créer une balise", "enableWebhookToggle": "Activer le webhook", "toggleDescription": "Nous enverrons les détails lorsque ce webhook sera déclenché", "urlPlaceholder": "https://example.com/harness", @@ -387,9 +388,17 @@ "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", "compareChanges": "Comparez et révisez pratiquement tout", "compareChangesDescription": "Branches, tags, plages de commits et périodes. Dans le même dépôt et entre les forks.", + "title": { + "noOpenPullRequests": "Il n'y a pas encore de demandes de tirage ouvertes", + "noClosedPullRequests": "Il n'y a pas encore de demandes de tirage fermées", + "noMergedPullRequests": "Il n'y a pas encore de demandes de tirage fusionnées" + }, "noOpenPullRequests": "Il n'y a pas encore de demandes de tirage ouvertes dans ce projet.", - "createNewPullRequest": "Créez une nouvelle demande de tirage.", "noClosedPullRequests": "Il n'y a pas encore de demandes de tirage fermées dans ce projet.", + "noMergedPullRequests": "Il n'y a pas encore de demandes de tirage fusionnées dans ce projet.", + "button": { + "createPullRequest": "Créer une demande de tirage" + }, "clearFilters": "Effacer les filtres", "noPullRequestsInRepo": "Commencez votre parcours de contribution en créant un brouillon de demande de tirage.", "noPullRequestsInProject": "There are no pull requests in this project yet.", diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx index 67223678a4..7defc1ffcc 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx @@ -53,7 +53,7 @@ const EmptyStateView: FC = ({ 'views:noData.noOpenPullRequests', `There are no open pull requests in this ${repoId ? 'repo' : 'project'} yet.` ), - t('views:noData.createNewPullRequest', 'Create a new pull request.') + t('views:repos.createPullReq', 'Create Pull request.') ] } case PR_STATE.CLOSED: @@ -61,7 +61,7 @@ const EmptyStateView: FC = ({ title: t('views:noData.title.noClosedPullRequests', 'No closed pull requests yet'), description: [ t('views:noData.noClosedPullRequests', 'There are no closed pull requests in this project yet.'), - t('views:noData.createNewPullRequest', 'Create a new pull request.') + t('views:repos.createPullReq', 'Create Pull request.') ] } case PR_STATE.MERGED: @@ -69,7 +69,7 @@ const EmptyStateView: FC = ({ title: t('views:noData.title.noMergedPullRequests', 'No merged pull requests yet'), description: [ t('views:noData.noMergedPullRequests', 'There are no merged pull requests in this project yet.'), - t('views:noData.createNewPullRequest', 'Create a new pull request.') + t('views:repos.createPullReq', 'Create Pull request.') ] } default: diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index e6204e7159..ecb474924b 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -250,7 +250,7 @@ const PullRequestListPage: FC = ({ 'views:noData.noPullRequestsInRepo', `Start your contribution journey by creating a new pull request draft.` ), - t('views:noData.createNewPullRequest', 'Create a new pull request.') + t('views:repos.createPullReq', 'Create Pull request.') ] : [t('views:noData.noPullRequestsInProject', `There are no pull requests in this project yet.`)] } @@ -442,7 +442,9 @@ const PullRequestListPage: FC = ({ */} {repoId ? ( ) : null} diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index 77fa763f4e..4cb57d33cd 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -95,7 +95,8 @@ const SandboxRepoListPage: FC = ({ const { projectIdentifier, orgIdentifier, accountId } = scope const FilterSortOptions = [ - { label: 'Name', value: RepoSortMethod.Identifier }, + { label: 'Name (A->Z, 0->9)', value: RepoSortMethod.Identifier_Asc }, + { label: 'Name (Z->A, 9->0)', value: RepoSortMethod.Identifier_Desc }, { label: 'Newest', value: RepoSortMethod.Newest }, { label: 'Oldest', value: RepoSortMethod.Oldest }, { label: 'Last push', value: RepoSortMethod.LastPush } @@ -186,7 +187,7 @@ const SandboxRepoListPage: FC = ({ options={[ { value: 'new', - label: t('views:repos.new-repository', 'New repository') + label: t('views:repos.createRepository', 'Create repository') }, { value: 'import', @@ -198,8 +199,7 @@ const SandboxRepoListPage: FC = ({ } ]} > - - {t('views:repos.new-repository', 'New Repository')} + {t('views:repos.createRepository', 'Create repository')} } filterOptions={filterOptions} diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index 8e6aa6a561..fbfb426e7b 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -123,12 +123,7 @@ export function RepoList({ t('views:noData.createOrImportRepos', 'Create new or import an existing repository.') ]} primaryButton={{ - label: ( - <> - - {t('views:repos.new-repository', 'New repository')} - - ), + label: <>{t('views:repos.createRepository', 'Create repository')}, to: toCreateRepo?.() }} secondaryButton={{ diff --git a/packages/ui/src/views/repo/repo-list/types.ts b/packages/ui/src/views/repo/repo-list/types.ts index 0e06375055..609099ac11 100644 --- a/packages/ui/src/views/repo/repo-list/types.ts +++ b/packages/ui/src/views/repo/repo-list/types.ts @@ -58,7 +58,8 @@ export interface RepoListPageProps extends Partial, FavoriteProps, } export enum RepoSortMethod { - Identifier = 'identifier,desc', + Identifier_Desc = 'identifier,desc', + Identifier_Asc = 'identifier,asc', Newest = 'created,desc', Oldest = 'created,asc', LastPush = 'last_git_push,desc' diff --git a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx index e5a9bcdbb2..9c516e5a49 100644 --- a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx +++ b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, IconV2, Layout, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' +import { Button, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { RepoTagsListViewProps, SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -71,10 +71,7 @@ export const RepoTagsListView: FC = ({ From c8bb2519cfedc3875640c363f378e4d51877c36e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Aug 2025 15:10:24 +0300 Subject: [PATCH 015/180] Align PR list with design (#2053) --- packages/ui/src/components/no-data.tsx | 23 ++++++++----------- packages/ui/src/components/stacked-list.tsx | 2 +- packages/ui/src/components/time-ago-card.tsx | 7 +++--- .../ui/src/views/repo/repo-list/repo-list.tsx | 6 ++++- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/components/no-data.tsx b/packages/ui/src/components/no-data.tsx index 94164f35a9..108509757e 100644 --- a/packages/ui/src/components/no-data.tsx +++ b/packages/ui/src/components/no-data.tsx @@ -48,8 +48,8 @@ export const NoData: FC = ({ align="center" justify="center" className={cn( - 'h-full w-full my-auto', - { 'h-auto grow border border-cn-borders-4 rounded-md': withBorder }, + 'h-full w-full my-auto py-cn-4xl', + { 'h-auto grow border border-cn-borders-3 rounded-md': withBorder }, className )} > @@ -57,15 +57,12 @@ export const NoData: FC = ({ {title} - {description && ( -
- {description.map((line, index) => ( - - {line} - - ))} -
- )} + {!!description && + description.map((line, index) => ( + + {line} + + ))}
{(primaryButton || secondaryButton) && ( @@ -82,7 +79,7 @@ export const NoData: FC = ({ {secondaryButton && (secondaryButton.to ? ( ) : ( - ))} diff --git a/packages/ui/src/components/stacked-list.tsx b/packages/ui/src/components/stacked-list.tsx index 0dc1b3219b..1b44c06624 100644 --- a/packages/ui/src/components/stacked-list.tsx +++ b/packages/ui/src/components/stacked-list.tsx @@ -21,7 +21,7 @@ const listItemVariants = cva( } ) -const listFieldVariants = cva('flex flex-1 flex-col items-stretch gap-[0.3125rem] justify-start', { +const listFieldVariants = cva('flex flex-1 flex-col items-stretch gap-cn-2xs justify-start', { variants: { right: { true: 'justify-end items-end' diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index 703b8ee628..6f9e06fadc 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -1,4 +1,4 @@ -import { FC, forwardRef, Fragment, memo, Ref, useState } from 'react' +import { FC, forwardRef, Fragment, memo, MouseEvent, Ref, useState } from 'react' import { Popover, StatusBadge, Text, TextProps } from '@/components' import { LOCALE } from '@utils/TimeUtils' @@ -153,12 +153,13 @@ export const TimeAgoCard = memo( ) } - const handleClick = (event: React.MouseEvent) => { + const handleClick = (event: MouseEvent) => { event.preventDefault() + event.stopPropagation() setIsOpen(prev => !prev) } - const handleClickContent = (event: React.MouseEvent) => { + const handleClickContent = (event: MouseEvent) => { event.stopPropagation() } diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index fbfb426e7b..2ac8df52d0 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -170,7 +170,11 @@ export function RepoList({ className="grid" primary description={ - repo.importing ? t('views:repos.importing', 'Importing…') : {repo.description} + repo.importing ? ( + t('views:repos.importing', 'Importing…') + ) : repo?.description ? ( + {repo.description} + ) : undefined } title={ Date: Thu, 7 Aug 2025 16:58:17 +0400 Subject: [PATCH 016/180] Ds migration skeleton (#2049) * feat: update skeleton components * feat: enhance Skeleton component documentation and examples * fix pretty * feat: update Skeleton component examples and improve layout structure * feat: enhance Skeleton component usage and improve loading state representation for files, list, summary * feat: add SkeletonFileExplorer component and integrate it into FileExplorer for improved loading state * feat: enhance loading states in Explorer and RepoSidebar components; refactor Skeleton components for improved usage * fix: replace SkeletonList with Skeleton component in RepoCommitDetailsView for consistency * fix: update Skeleton.List in RepoList to include actions and increase line count --- .../src/subjects/views/repo-create-rule.tsx | 4 +- .../src/components-v2/FileExplorer.tsx | 11 +- .../src/components-v2/file-content-viewer.tsx | 4 +- .../project/rules/project-rules-container.tsx | 4 +- .../rules/project-tag-rules-container.tsx | 4 +- .../pull-request-conversation.tsx | 4 +- .../src/pages-v2/repo/repo-sidebar.tsx | 7 +- .../rules/repo-branch-rules-container.tsx | 4 +- .../repo/rules/repo-rules-container.tsx | 4 +- .../repo/rules/repo-tag-rules-container.tsx | 4 +- .../webhooks/create-webhook-container.tsx | 4 +- .../src/content/docs/components/skeleton.mdx | 420 ++++++++++++++++++ packages/ui/src/components/avatar.tsx | 2 +- .../ui/src/components/icon-v2/icon-v2.tsx | 2 +- .../ui/src/components/inputs/base-input.tsx | 2 +- .../ui/src/components/logo-v2/logo-v2.tsx | 2 +- packages/ui/src/components/multi-select.tsx | 4 +- .../skeletons/components/skeleton.tsx | 12 +- packages/ui/src/components/skeletons/index.ts | 34 +- .../components/skeletons/skeleton-avatar.tsx | 39 ++ .../components/skeletons/skeleton-form.tsx | 92 +++- .../components/skeletons/skeleton-icon.tsx | 34 ++ .../components/skeletons/skeleton-list.tsx | 92 ++-- .../components/skeletons/skeleton-logo.tsx | 31 ++ .../components/skeletons/skeleton-table.tsx | 54 +-- .../skeletons/skeleton-typography.tsx | 34 ++ .../components/skeletons/skeleton-utils.ts | 7 + .../skeletons/skeloton-file-explorer.tsx | 25 ++ packages/ui/src/components/text.tsx | 38 +- .../connector-details-activities-list.tsx | 6 +- .../connector-details-references-list.tsx | 6 +- .../connectors-list/connectors-list.tsx | 7 +- .../components/delegate-connectivity-list.tsx | 6 +- .../ui/src/views/labels/label-form-page.tsx | 4 +- .../ui/src/views/labels/labels-list-page.tsx | 4 +- .../execution-list/execution-list.tsx | 4 +- .../pipelines/pipeline-list/pipeline-list.tsx | 4 +- .../platform/entity-reference-component.tsx | 4 +- .../components/profile-settings-keys-list.tsx | 4 +- .../profile-settings-tokens-list.tsx | 4 +- .../profile-settings-general-page.tsx | 4 +- .../project-general/project-general-page.tsx | 4 +- .../components/project-member-list.tsx | 4 +- .../src/views/repo/components/repo-header.tsx | 6 +- .../commits/pull-request-commits.tsx | 4 +- .../compare/pull-request-compare-page.tsx | 4 +- .../details/pull-request-changes-page.tsx | 4 +- .../pull-request/pull-request-list-page.tsx | 4 +- .../repo-branch/components/branch-list.tsx | 4 +- .../repo-commit-details-view.tsx | 4 +- .../repo/repo-commits/repo-commits-view.tsx | 4 +- .../views/repo/repo-files/repo-files-view.tsx | 4 +- .../ui/src/views/repo/repo-list/repo-list.tsx | 4 +- .../repo-settings-general-features.tsx | 14 +- .../components/repo-settings-general-form.tsx | 4 +- .../repo-settings-general-rules.tsx | 6 +- .../repo-settings-general-security.tsx | 14 +- .../views/repo/repo-summary/repo-summary.tsx | 4 +- .../repo-tags/components/repo-tags-list.tsx | 4 +- .../repo-webhook-executions-list-page.tsx | 4 +- .../webhook-list/repo-webhook-list-page.tsx | 4 +- .../run-pipeline-drawer-content.tsx | 4 +- .../search/components/search-results-list.tsx | 4 +- .../semantic-search-results-list.tsx | 4 +- .../gcp-regions-multiselect-view.tsx | 4 +- .../secrets/secrets-list/secrets-list.tsx | 6 +- .../unified-pipeline-studio-entity-form.tsx | 4 +- .../unified-pipeline-step-palette-drawer.tsx | 4 +- .../components/users-list/users-list.tsx | 4 +- .../tailwind-utils-config/components/index.ts | 2 + .../components/skeleton.ts | 125 ++++++ 71 files changed, 1038 insertions(+), 262 deletions(-) create mode 100644 apps/portal/src/content/docs/components/skeleton.mdx create mode 100644 packages/ui/src/components/skeletons/skeleton-avatar.tsx create mode 100644 packages/ui/src/components/skeletons/skeleton-icon.tsx create mode 100644 packages/ui/src/components/skeletons/skeleton-logo.tsx create mode 100644 packages/ui/src/components/skeletons/skeleton-typography.tsx create mode 100644 packages/ui/src/components/skeletons/skeleton-utils.ts create mode 100644 packages/ui/src/components/skeletons/skeloton-file-explorer.tsx create mode 100644 packages/ui/tailwind-utils-config/components/skeleton.ts diff --git a/apps/design-system/src/subjects/views/repo-create-rule.tsx b/apps/design-system/src/subjects/views/repo-create-rule.tsx index b3bfc37016..7a161f13bf 100644 --- a/apps/design-system/src/subjects/views/repo-create-rule.tsx +++ b/apps/design-system/src/subjects/views/repo-create-rule.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { useRepoRulesStore } from '@subjects/views/repo-general-settings/use-repo-rules-store' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { RepoBranchSettingsRulesPage } from '@harnessio/ui/views' const errors = { @@ -36,7 +36,7 @@ export const RepoCreateRule = () => { }, []) if (!loaded) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } return ( diff --git a/apps/gitness/src/components-v2/FileExplorer.tsx b/apps/gitness/src/components-v2/FileExplorer.tsx index 3570354bf7..7912acb814 100644 --- a/apps/gitness/src/components-v2/FileExplorer.tsx +++ b/apps/gitness/src/components-v2/FileExplorer.tsx @@ -4,7 +4,7 @@ import { useLocation, useParams } from 'react-router-dom' import { useQuery, useQueryClient } from '@tanstack/react-query' import { getContent, OpenapiContentInfo, OpenapiGetContentOutput } from '@harnessio/code-service-client' -import { Alert, FileExplorer, IconV2 } from '@harnessio/ui/components' +import { Alert, FileExplorer, SkeletonFileExplorer } from '@harnessio/ui/components' import { useOpenFolderPaths } from '../framework/context/ExplorerPathsContext' import { useRoutes } from '../framework/context/NavigationContext' @@ -16,6 +16,7 @@ import { normalizeGitRef } from '../utils/git-utils' interface ExplorerProps { selectedBranch?: string repoDetails: OpenapiGetContentOutput + isLoading?: boolean } const sortEntriesByType = (entries: OpenapiContentInfo[]): OpenapiContentInfo[] => { @@ -32,7 +33,7 @@ const sortEntriesByType = (entries: OpenapiContentInfo[]): OpenapiContentInfo[] /** * TODO: This code was migrated from V2 and needs to be refactored. */ -export default function Explorer({ selectedBranch, repoDetails }: ExplorerProps) { +export default function Explorer({ selectedBranch, repoDetails, isLoading: isLoadingProp }: ExplorerProps) { const repoRef = useGetRepoRef() const { spaceId, repoId } = useParams<PathParams>() const { fullGitRef, fullResourcePath } = useCodePathDetails() @@ -148,7 +149,7 @@ export default function Explorer({ selectedBranch, repoDetails }: ExplorerProps) } if (isLoading) { - return <IconV2 name="loader" className="animate-spin" /> + return <SkeletonFileExplorer /> } if (error) { @@ -240,8 +241,8 @@ export default function Explorer({ selectedBranch, repoDetails }: ExplorerProps) }} value={openFolderPaths} > - {isRootLoading ? ( - <IconV2 name="loader" className="animate-spin" /> + {isRootLoading || isLoadingProp ? ( + <SkeletonFileExplorer linesCount={12} /> ) : rootError ? ( <Alert.Root theme="danger"> <Alert.Title>Error loading root folder</Alert.Title> diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index d4105c0061..14f43dc1a7 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -7,7 +7,7 @@ import { getIsMarkdown, MarkdownViewer, Pagination, - SkeletonList, + Skeleton, Tabs, ViewTypeValue } from '@harnessio/ui/components' @@ -225,7 +225,7 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp <Tabs.Content value="history"> {isFetchingCommits ? ( - <SkeletonList /> + <Skeleton.List /> ) : ( <> <CommitsList diff --git a/apps/gitness/src/pages-v2/project/rules/project-rules-container.tsx b/apps/gitness/src/pages-v2/project/rules/project-rules-container.tsx index 7a9f2615cd..1d613b4c19 100644 --- a/apps/gitness/src/pages-v2/project/rules/project-rules-container.tsx +++ b/apps/gitness/src/pages-v2/project/rules/project-rules-container.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import { useParams } from 'react-router-dom' import { OpenapiRule, useSpaceRuleGetQuery } from '@harnessio/code-service-client' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { NotFoundPage } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../framework/hooks/useGetSpaceParam' @@ -43,7 +43,7 @@ export const ProjectRulesContainer = () => { // Show loading state while fetching rule data if (fetchRuleIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } // Show error page if rule not found diff --git a/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx b/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx index 0fc3453a20..2b11e560c6 100644 --- a/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx +++ b/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx @@ -10,7 +10,7 @@ import { useSpaceRuleGetQuery, useSpaceRuleUpdateMutation } from '@harnessio/code-service-client' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { useTranslation } from '@harnessio/ui/context' import { PrincipalType } from '@harnessio/ui/types' import { @@ -213,7 +213,7 @@ export const ProjectTagRulesContainer = () => { } if (!!ruleIdentifier && fetchRuleIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } if (!!ruleIdentifier && !!fetchRuleError) { diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 13734cc8cb..c47aacc8bb 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -29,7 +29,7 @@ import { useReviewerListPullReqQuery, useUpdatePullReqMutation } from '@harnessio/code-service-client' -import { SkeletonList } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { CodeOwnersData, DefaultReviewersDataProps, @@ -910,7 +910,7 @@ export default function PullRequestConversationPage() { ]) if (prPanelData?.PRStateLoading || (changesLoading && !!pullReqMetadata?.closed)) { - return <SkeletonList /> + return <Skeleton.List /> } return ( <> diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index 9e25c3c0d3..ebf76390ea 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -9,6 +9,7 @@ import { useListPathsQuery, useListTagsQuery } from '@harnessio/code-service-client' +import { SkeletonFileExplorer } from '@harnessio/ui/components' import { BranchSelectorListItem, BranchSelectorTab, RepoSidebar as RepoSidebarView } from '@harnessio/ui/views' import { BranchSelectorContainer } from '../../components-v2/branch-selector-container' @@ -45,7 +46,8 @@ export const RepoSidebar = () => { preSelectedTab, setPreSelectedTab, prefixedDefaultBranch, - fullGitRefWoDefault + fullGitRefWoDefault, + isLoading } = useGitRef() const { data: { body: selectedGitRefBranch } = {} } = useGetBranchQuery( @@ -250,8 +252,9 @@ export const RepoSidebar = () => { /> } > + {isLoading && <SkeletonFileExplorer linesCount={12} />} {!!repoDetails?.body?.content?.entries?.length && ( - <Explorer repoDetails={repoDetails?.body} selectedBranch={fullGitRef} /> + <Explorer isLoading={isLoading} repoDetails={repoDetails?.body} selectedBranch={fullGitRef} /> )} </RepoSidebarView> </div> diff --git a/apps/gitness/src/pages-v2/repo/rules/repo-branch-rules-container.tsx b/apps/gitness/src/pages-v2/repo/rules/repo-branch-rules-container.tsx index 785c569d51..396300fad9 100644 --- a/apps/gitness/src/pages-v2/repo/rules/repo-branch-rules-container.tsx +++ b/apps/gitness/src/pages-v2/repo/rules/repo-branch-rules-container.tsx @@ -10,7 +10,7 @@ import { useRepoRuleGetQuery, useRepoRuleUpdateMutation } from '@harnessio/code-service-client' -import { MessageTheme, MultiSelectOption, SkeletonForm } from '@harnessio/ui/components' +import { MessageTheme, MultiSelectOption, Skeleton } from '@harnessio/ui/components' import { useTranslation } from '@harnessio/ui/context' import { PrincipalType } from '@harnessio/ui/types' import { @@ -252,7 +252,7 @@ export const RepoBranchRulesContainer = () => { } if (!!identifier && fetchRuleIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } if (!!identifier && !!fetchRuleError) { diff --git a/apps/gitness/src/pages-v2/repo/rules/repo-rules-container.tsx b/apps/gitness/src/pages-v2/repo/rules/repo-rules-container.tsx index 281f7272f7..96ec85d402 100644 --- a/apps/gitness/src/pages-v2/repo/rules/repo-rules-container.tsx +++ b/apps/gitness/src/pages-v2/repo/rules/repo-rules-container.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import { useParams } from 'react-router-dom' import { useRepoRuleGetQuery } from '@harnessio/code-service-client' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { NotFoundPage } from '@harnessio/ui/views' import { useGetRepoRef } from '../../../framework/hooks/useGetRepoPath' @@ -40,7 +40,7 @@ export const RepoRulesContainer = () => { // Show loading state while fetching rule data if (fetchRuleIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } // Show error page if rule not found diff --git a/apps/gitness/src/pages-v2/repo/rules/repo-tag-rules-container.tsx b/apps/gitness/src/pages-v2/repo/rules/repo-tag-rules-container.tsx index bad3c0c17b..66222e18b7 100644 --- a/apps/gitness/src/pages-v2/repo/rules/repo-tag-rules-container.tsx +++ b/apps/gitness/src/pages-v2/repo/rules/repo-tag-rules-container.tsx @@ -10,7 +10,7 @@ import { useRepoRuleGetQuery, useRepoRuleUpdateMutation } from '@harnessio/code-service-client' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { useTranslation } from '@harnessio/ui/context' import { PrincipalType } from '@harnessio/ui/types' import { @@ -219,7 +219,7 @@ export const RepoTagRulesContainer = () => { } if (!!identifier && fetchRuleIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } if (!!identifier && !!fetchRuleError) { diff --git a/apps/gitness/src/pages-v2/webhooks/create-webhook-container.tsx b/apps/gitness/src/pages-v2/webhooks/create-webhook-container.tsx index 23e18d3e20..4fb4611f1c 100644 --- a/apps/gitness/src/pages-v2/webhooks/create-webhook-container.tsx +++ b/apps/gitness/src/pages-v2/webhooks/create-webhook-container.tsx @@ -6,7 +6,7 @@ import { useGetRepoWebhookQuery, useUpdateRepoWebhookMutation } from '@harnessio/code-service-client' -import { SkeletonForm } from '@harnessio/ui/components' +import { Skeleton } from '@harnessio/ui/components' import { CreateWebhookFormFields, NotFoundPage, @@ -128,7 +128,7 @@ export const CreateWebhookContainer = () => { const apiError = createWebHookError?.message || updateWebhookError?.message || null if (!!webhookId && getWebhookIsLoading) { - return <SkeletonForm className="mt-7" /> + return <Skeleton.Form className="mt-7" /> } if (!!webhookId && !webhookData) return <NotFoundPage pageTypeText="webhooks" /> diff --git a/apps/portal/src/content/docs/components/skeleton.mdx b/apps/portal/src/content/docs/components/skeleton.mdx new file mode 100644 index 0000000000..b3a4a89709 --- /dev/null +++ b/apps/portal/src/content/docs/components/skeleton.mdx @@ -0,0 +1,420 @@ +--- +title: Skeleton +description: Skeleton component for displaying loading states +--- + +import { DocsPage } from "@/components/docs-page"; + +The `Skeleton` component is a placeholder UI element that represents the loading state of a component. It can be used to indicate that content is being loaded or processed, providing a visual cue to users. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <Skeleton.Box className="w-80 h-20" /> + </div>`} +/> + +## Usage + +```typescript jsx +import { Skeleton } from "@harnessio/ui/components"; + +//... + +return ( + <Skeleton.Box className="w-[400px] h-[80px]" /> +) +``` + +## Anatomy + +### Skeleton Box + +The `Skeleton.Box` component is a simple, flexible rectangular placeholder that can be used to indicate loading states for various UI elements. +It is a free-form box — you can specify its width and height to match the shape of the content being loaded. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <Skeleton.Box className="w-80 h-20" /> + <Skeleton.Box className="w-[400px] h-[100px]" /> + </div>`} +/> + +### Skeleton Avatar + +The `Skeleton.Avatar` component is a placeholder for user profile images or icons. It can be used to indicate loading states for avatar images. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <div className="flex items-start gap-2"> + <Skeleton.Avatar size="sm" /> + <Skeleton.Avatar size="sm" rounded /> + </div> + <div className="flex items-start gap-2"> + <Skeleton.Avatar /> + <Skeleton.Avatar rounded /> + </div> + <div className="flex items-start gap-2"> + <Skeleton.Avatar size="lg" /> + <Skeleton.Avatar size="lg" rounded /> + </div> + </div>`} +/> + +### Skeleton Icon + +The `Skeleton.Icon` component is a placeholder for icons. It can be used to indicate loading states for icon-based UI elements. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <div className="flex items-start gap-2"> + <Skeleton.Icon size="2xs" /> + <Skeleton.Icon size="xs" /> + <Skeleton.Icon size="sm" /> + <Skeleton.Icon size="md" /> + <Skeleton.Icon size="lg" /> + <Skeleton.Icon size="xl" /> + </div> + </div>`} +/> + +### Skeleton Logo + +The `Skeleton.Logo` component is a placeholder for logos. It can be used to indicate loading states for logo images. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <div className="flex items-start gap-2"> + <Skeleton.Logo size="sm" /> + <Skeleton.Logo size="md" /> + <Skeleton.Logo size="lg" /> + </div> + </div>`} +/> + +### Skeleton Typography + +The `Skeleton.Typography` component is a placeholder for text elements. It can be used to indicate loading states for text content. Variants include different text styles such as headings, body text, and captions. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-1"> + Typography + <div className="flex items-end gap-4"> + Heading + <div> + <Skeleton.Typography variant="heading-hero" /> + </div> + <div> + <Skeleton.Typography variant="heading-section" /> + </div> + <div> + <Skeleton.Typography variant="heading-subsection" /> + </div> + <div> + <Skeleton.Typography variant="heading-base" /> + </div> + <div> + <Skeleton.Typography variant="heading-small" /> + </div> + </div> + + <div className="flex items-end gap-4"> + Body + <div> + <Skeleton.Typography variant="body" /> + </div> + <div> + <Skeleton.Typography variant="body-single-line" /> + </div> + <div> + <Skeleton.Typography variant="body-code" /> + </div> + <div> + <Skeleton.Typography variant="body-single-line-code" /> + </div> + </div> + + <div className="flex items-end gap-4"> + Caption + <div> + <Skeleton.Typography variant="caption" /> + </div> + <div> + <Skeleton.Typography variant="caption-single-line" /> + </div> + </div> + + </div>`} +/> + +### Skeleton Form + +The `Skeleton.Form` component is a placeholder for form elements. It can be used to indicate loading states for input fields, labels, and other form controls. Orientation variants include vertical and horizontal layouts. `linesCount` can be used to specify the number of lines in the skeleton form item. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <div className="grid grid-cols-1 gap-4"> + <div> + <Skeleton.Form /> + </div> + <div> + <Skeleton.Form itemProps={{ withLabel: false }} linesCount={2} /> + </div> + <div> + <Skeleton.Form itemProps={{ withLabel: false, size: 'sm' }} linesCount={2} /> + </div> + <div> + <Skeleton.Form itemProps={{ orientation: 'horizontal' }} linesCount={4} /> + </div> + </div> + </div>`} +/> + +### Skeleton List + +The `Skeleton.List` component is a placeholder for list items. It can be used to indicate loading states for list content. `linesCount` can be used to specify the number of lines in the skeleton list item. The `hasActions` prop can be used to indicate that the list items have action buttons. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-8"> + <div> + <Skeleton.List linesCount={2} /> + </div> + <div> + <Skeleton.List hasActions /> + </div> + </div>`} +/> + +### Skeleton Table + +The `Skeleton.Table` component is a placeholder for table elements. It can be used to indicate loading states for table content. `countRows` can be used to specify the number of rows in the skeleton table. `countColumns` can be used to specify the number of columns in the skeleton table. +`showHeader` can be used to indicate that the table has a header row. + +<DocsPage.ComponentExample + client:only + code={`<div className="w-[600px] grid gap-4"> + <div> + <Skeleton.Table countColumns={5} countRows={5} showHeader /> + </div> + </div>`} +/> + +## API Reference + +### `Skeleton.Box` + +<DocsPage.PropsTable + props={[ + { + name: "className", + description: "Custom class name", + required: false, + value: "string", + }, + ]} +/> + +### `Skeleton.Avatar` + +<DocsPage.PropsTable + props={[ + { + name: "size", + description: "Size of the avatar", + required: false, + value: "'sm' | 'md' | 'lg'", + defaultValue: "'md'", + }, + { + name: "rounded", + description: "Defines whether the avatar should be rounded", + required: false, + value: "boolean", + defaultValue: "false", + }, + { + name: "className", + description: "Custom class name", + required: false, + value: "string", + }, + ]} +/> + +### `Skeleton.Icon` + +<DocsPage.PropsTable + props={[ + { + name: "size", + description: "Size of the icon", + required: false, + value: "'2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'", + defaultValue: "'sm'", + }, + { + name: "className", + description: "Custom class name", + required: false, + value: "string", + }, + ]} +/> + +### `Skeleton.Logo` + +<DocsPage.PropsTable + props={[ + { + name: "size", + description: "Size of the logo", + required: false, + value: "'sm' | 'md' | 'lg'", + defaultValue: "'lg'", + }, + { + name: "className", + description: "Custom class name", + required: false, + value: "string", + }, + ]} +/> + +### `Skeleton.Typography` + +<DocsPage.PropsTable + props={[ + { + name: "variant", + description: "Variant of the skeleton text", + required: false, + value: + "'heading-hero' | 'heading-section' | 'heading-subsection' | 'heading-base' | 'heading-small' | 'body' | 'body-code' | 'body-single-line' | 'body-single-line-code' | 'caption' | 'caption-single-line'", + defaultValue: "'body'", + }, + { + name: "className", + description: "Custom class name", + required: false, + value: "string", + }, + ]} +/> + +### `Skeleton.Form` + +<DocsPage.PropsTable + props={[ + { + name: "linesCount", + description: "Number of lines in the skeleton form", + required: false, + value: "1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9", + defaultValue: 9, + }, + { + name: "className", + description: "Custom class name for the skeleton field", + required: false, + value: "string", + }, + { + name: "itemProps", + description: "Props for the skeleton form item", + required: false, + value: + "{ withLabel?: boolean; orientation?: 'vertical' | 'horizontal'; size?: 'sm' | 'md'; labelClassName?: string, inputClassName?: string }", + defaultValue: + "{ withLabel: true, orientation: 'vertical', size: 'md', linesCount: 3 }", + }, + ]} +/> + +### `Skeleton.List` + +<DocsPage.PropsTable + props={[ + { + name: "linesCount", + description: "Number of lines in the skeleton list", + required: false, + value: "number", + defaultValue: 8, + }, + { + name: "className", + description: "Custom class name for the skeleton list wrapper", + required: false, + value: "string", + }, + { + name: "hasActions", + description: + "Defines whether the skeleton list has right skeleton square", + required: false, + value: "boolean", + defaultValue: "false", + }, + { + name: "classNames", + description: "Defines the custom class names for the skeleton list items", + required: false, + value: + "{ item?: string; leftTitle?: string; leftDescription?: string, rightTitle?: string, rightDescription?: string, actions?: string }", + }, + ]} +/> + +### `Skeleton.Table` + +<DocsPage.PropsTable + props={[ + { + name: "countRows", + description: "Number of rows in the skeleton table", + required: false, + value: "number", + defaultValue: 12, + }, + { + name: "countColumns", + description: "Number of columns in the skeleton table", + required: false, + value: "number", + defaultValue: 5, + }, + { + name: "showHeader", + description: "Defines whether the skeleton table has a header row", + required: false, + value: "boolean", + defaultValue: "false", + }, + { + name: "className", + description: "Custom class name for the skeleton table root element", + required: false, + value: "string", + }, + { + name: "classNameHeader", + description: "Custom class name for the skeleton table header element", + required: false, + value: "string", + }, + { + name: "classNameBody", + description: "Custom class name for the skeleton table body element", + required: false, + value: "string", + }, + ]} +/> diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx index d758dedbfb..8a266699c6 100644 --- a/packages/ui/src/components/avatar.tsx +++ b/packages/ui/src/components/avatar.tsx @@ -8,7 +8,7 @@ import { cva, type VariantProps } from 'class-variance-authority' import { IconV2 } from './icon-v2' import { Tooltip, TooltipProps } from './tooltip' -const avatarVariants = cva('cn-avatar', { +export const avatarVariants = cva('cn-avatar', { variants: { size: { md: '', diff --git a/packages/ui/src/components/icon-v2/icon-v2.tsx b/packages/ui/src/components/icon-v2/icon-v2.tsx index fd87967953..b5e4ace43f 100644 --- a/packages/ui/src/components/icon-v2/icon-v2.tsx +++ b/packages/ui/src/components/icon-v2/icon-v2.tsx @@ -7,7 +7,7 @@ import { IconNameMapV2 } from './icon-name-map' export type IconV2NamesType = keyof typeof IconNameMapV2 -const iconVariants = cva('cn-icon', { +export const iconVariants = cva('cn-icon', { variants: { size: { '2xs': 'cn-icon-2xs', diff --git a/packages/ui/src/components/inputs/base-input.tsx b/packages/ui/src/components/inputs/base-input.tsx index 41a8efbdab..2ab0ecf28f 100644 --- a/packages/ui/src/components/inputs/base-input.tsx +++ b/packages/ui/src/components/inputs/base-input.tsx @@ -20,7 +20,7 @@ export interface BaseInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix' | 'suffix'>, VariantProps<typeof inputVariants> {} -const inputVariants = cva('cn-input-container', { +export const inputVariants = cva('cn-input-container', { variants: { size: { md: '', diff --git a/packages/ui/src/components/logo-v2/logo-v2.tsx b/packages/ui/src/components/logo-v2/logo-v2.tsx index a76f1b40a7..7b5088fdb2 100644 --- a/packages/ui/src/components/logo-v2/logo-v2.tsx +++ b/packages/ui/src/components/logo-v2/logo-v2.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority' import { LogoNameMapV2 } from './logo-name-map' -const logoVariants = cva('cn-logo', { +export const logoVariants = cva('cn-logo', { variants: { size: { sm: 'cn-logo-sm', diff --git a/packages/ui/src/components/multi-select.tsx b/packages/ui/src/components/multi-select.tsx index 06c89c3e1a..e7a5fb1768 100644 --- a/packages/ui/src/components/multi-select.tsx +++ b/packages/ui/src/components/multi-select.tsx @@ -20,7 +20,7 @@ import { IconV2NamesType, Label, Layout, - SkeletonList, + Skeleton, Tag } from '@/components' import { generateAlphaNumericHash } from '@/utils' @@ -387,7 +387,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>( }} > {isLoading ? ( - <SkeletonList /> + <Skeleton.List /> ) : availableOptions?.length === 0 ? ( disallowCreation ? ( <Command.Item value="-" disabled> diff --git a/packages/ui/src/components/skeletons/components/skeleton.tsx b/packages/ui/src/components/skeletons/components/skeleton.tsx index b3d85f20e5..dda0eadf31 100644 --- a/packages/ui/src/components/skeletons/components/skeleton.tsx +++ b/packages/ui/src/components/skeletons/components/skeleton.tsx @@ -1,9 +1,11 @@ -import { HTMLAttributes } from 'react' +import { forwardRef, HTMLAttributes } from 'react' import { cn } from '@utils/cn' -function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement>) { - return <div className={cn('bg-[image:var(--cn-comp-skeleton-bg)] animate-pulse rounded-3xl', className)} {...props} /> -} +const SkeletonBase = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => { + return <div className={cn('cn-skeleton-base', className)} ref={ref} {...props} /> +}) -export { Skeleton } +SkeletonBase.displayName = 'SkeletonBase' + +export { SkeletonBase } diff --git a/packages/ui/src/components/skeletons/index.ts b/packages/ui/src/components/skeletons/index.ts index ec9664236b..881ee25a16 100644 --- a/packages/ui/src/components/skeletons/index.ts +++ b/packages/ui/src/components/skeletons/index.ts @@ -1,4 +1,30 @@ -export * from './components/skeleton' -export * from './skeleton-list' -export * from './skeleton-form' -export * from './skeleton-table' +import { SkeletonBase } from './components/skeleton' +import { SkeletonAvatar } from './skeleton-avatar' +import { SkeletonForm } from './skeleton-form' +import { SkeletonIcon } from './skeleton-icon' +import { SkeletonList } from './skeleton-list' +import { SkeletonLogo } from './skeleton-logo' +import { SkeletonTable } from './skeleton-table' +import { SkeletonTypography } from './skeleton-typography' +import { SkeletonFileExplorer } from './skeloton-file-explorer' + +export type { SkeletonFormProps } from './skeleton-form' +export type { SkeletonIconProps } from './skeleton-icon' +export type { SkeletonListProps } from './skeleton-list' +export type { SkeletonLogoProps } from './skeleton-logo' +export type { SkeletonTableProps } from './skeleton-table' +export type { SkeletonTypographyProps } from './skeleton-typography' +export type { SkeletonAvatarProps } from './skeleton-avatar' + +const Skeleton = { + Box: SkeletonBase, + Avatar: SkeletonAvatar, + List: SkeletonList, + Form: SkeletonForm, + Table: SkeletonTable, + Icon: SkeletonIcon, + Logo: SkeletonLogo, + Typography: SkeletonTypography +} + +export { Skeleton, SkeletonFileExplorer } diff --git a/packages/ui/src/components/skeletons/skeleton-avatar.tsx b/packages/ui/src/components/skeletons/skeleton-avatar.tsx new file mode 100644 index 0000000000..cd6119277d --- /dev/null +++ b/packages/ui/src/components/skeletons/skeleton-avatar.tsx @@ -0,0 +1,39 @@ +import { forwardRef } from 'react' + +import { avatarVariants } from '@/components' +import { cn } from '@utils/cn' +import { cva, VariantProps } from 'class-variance-authority' + +import { SkeletonBase } from './components/skeleton' + +const skeletonAvatarVariants: typeof avatarVariants = cva('cn-skeleton-avatar', { + variants: { + size: { + sm: 'cn-skeleton-avatar-sm', + md: 'cn-skeleton-avatar-md', + lg: 'cn-skeleton-avatar-lg' + }, + rounded: { + true: 'cn-skeleton-avatar-rounded', + false: '' + } + }, + defaultVariants: { + size: 'md', + rounded: false + } +}) + +export interface SkeletonAvatarProps { + size: VariantProps<typeof skeletonAvatarVariants>['size'] + rounded?: boolean + className?: string +} + +export const SkeletonAvatar = forwardRef<HTMLDivElement, SkeletonAvatarProps>( + ({ size = 'md', rounded = false, className, ...props }, ref) => { + return <SkeletonBase className={cn(skeletonAvatarVariants({ size, rounded }), className)} ref={ref} {...props} /> + } +) + +SkeletonAvatar.displayName = 'SkeletonAvatar' diff --git a/packages/ui/src/components/skeletons/skeleton-form.tsx b/packages/ui/src/components/skeletons/skeleton-form.tsx index 9a9c0cdb8c..c721aca1f7 100644 --- a/packages/ui/src/components/skeletons/skeleton-form.tsx +++ b/packages/ui/src/components/skeletons/skeleton-form.tsx @@ -1,33 +1,89 @@ +import { FC } from 'react' + +import { ControlGroup, InputOrientationProp } from '@components/form-primitives' +import { inputVariants } from '@components/inputs/base-input' +import { Layout } from '@components/layout' import { cn } from '@utils/cn' +import { cva, VariantProps } from 'class-variance-authority' + +import { SkeletonTypography } from './skeleton-typography' -import { Skeleton } from './components/skeleton' +const skeletonFormVariants: (props?: Pick<VariantProps<typeof inputVariants>, 'size'>) => string = cva( + 'cn-skeleton-form-item', + { + variants: { + size: { + sm: 'cn-skeleton-form-item-sm', + md: 'cn-skeleton-form-item-md' + } + }, + defaultVariants: { + size: 'md' + } + } +) -const listItems = [1, 2, 3, 4, 5, 6, 7, 8, 9] +export interface SkeletonFormItemProps extends InputOrientationProp { + withLabel?: boolean + className?: string + labelClassName?: string + inputClassName?: string + size?: VariantProps<typeof skeletonFormVariants>['size'] +} -const SkeletonFormItem = () => { +export const SkeletonFormItem: FC<SkeletonFormItemProps> = ({ + orientation, + withLabel, + labelClassName, + inputClassName, + size +}) => { return ( - <div className="flex flex-col gap-y-2.5"> - <Skeleton className="h-2.5 w-[24%]" /> - <div className="border-cn-borders-4 rounded border px-3 py-3.5"> - <Skeleton className="h-2.5 w-[41%]" /> - </div> - </div> + <ControlGroup.Root orientation={orientation}> + {withLabel && ( + <ControlGroup.LabelWrapper className={labelClassName}> + <SkeletonTypography className="w-[71px]" /> + </ControlGroup.LabelWrapper> + )} + + <ControlGroup.InputWrapper className={cn(skeletonFormVariants({ size }), inputClassName)}> + <SkeletonTypography className="w-1/3" /> + </ControlGroup.InputWrapper> + </ControlGroup.Root> ) } -export const SkeletonForm = ({ - className, - linesCount = 9 -}: { +SkeletonFormItem.displayName = 'SkeletonFormItem' + +export interface SkeletonFormProps { className?: string linesCount?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 + itemProps?: SkeletonFormItemProps +} + +export const SkeletonForm: FC<SkeletonFormProps> = ({ + className, + linesCount = 2, + itemProps: { + withLabel = true, + labelClassName: itemLabelClassName, + inputClassName: itemInputClassName, + size = 'md', + orientation = 'vertical' + } = {} }) => { return ( - <div className={cn('relative flex flex-col gap-y-7', className)}> - {listItems.slice(0, linesCount).map(item => ( - <SkeletonFormItem key={item} /> + <Layout.Grid gap="xl" className={cn('cn-skeleton-form-field', className)}> + {Array.from({ length: linesCount }).map((_, index) => ( + <SkeletonFormItem + key={index} + withLabel={withLabel} + labelClassName={itemLabelClassName} + inputClassName={itemInputClassName} + size={size as VariantProps<typeof skeletonFormVariants>['size']} + orientation={orientation as InputOrientationProp['orientation']} + /> ))} - <div className="to-background absolute bottom-0 z-10 size-full bg-gradient-to-b from-transparent" /> - </div> + </Layout.Grid> ) } diff --git a/packages/ui/src/components/skeletons/skeleton-icon.tsx b/packages/ui/src/components/skeletons/skeleton-icon.tsx new file mode 100644 index 0000000000..2d976e581a --- /dev/null +++ b/packages/ui/src/components/skeletons/skeleton-icon.tsx @@ -0,0 +1,34 @@ +import { forwardRef } from 'react' + +import { iconVariants } from '@/components' +import { cn } from '@utils/cn' +import { cva, VariantProps } from 'class-variance-authority' + +import { SkeletonBase } from './components/skeleton' + +const skeletonIconVariants: typeof iconVariants = cva('cn-skeleton-icon', { + variants: { + size: { + '2xs': 'cn-skeleton-icon-2xs', + xs: 'cn-skeleton-icon-xs', + sm: 'cn-skeleton-icon-sm', + md: 'cn-skeleton-icon-md', + lg: 'cn-skeleton-icon-lg', + xl: 'cn-skeleton-icon-xl' + } + }, + defaultVariants: { + size: 'sm' + } +}) + +export interface SkeletonIconProps { + size: VariantProps<typeof skeletonIconVariants>['size'] + className?: string +} + +export const SkeletonIcon = forwardRef<HTMLDivElement, SkeletonIconProps>(({ size, className, ...props }, ref) => { + return <SkeletonBase className={cn(skeletonIconVariants({ size }), className)} ref={ref} {...props} /> +}) + +SkeletonIcon.displayName = 'SkeletonIcon' diff --git a/packages/ui/src/components/skeletons/skeleton-list.tsx b/packages/ui/src/components/skeletons/skeleton-list.tsx index 051eb6cf25..1552131192 100644 --- a/packages/ui/src/components/skeletons/skeleton-list.tsx +++ b/packages/ui/src/components/skeletons/skeleton-list.tsx @@ -1,61 +1,49 @@ -import { useMemo } from 'react' +import { FC } from 'react' -import { StackedList } from '@/components' +import { Skeleton, StackedList } from '@/components' import { cn } from '@utils/cn' -import { Skeleton } from './components/skeleton' - -// Helper function to generate random percentage width within a range -const getRandomPercentageWidth = (min: number, max: number) => `${Math.floor(Math.random() * (max - min + 1)) + min}%` - -// Helper function to generate random pixel width within a range -const getRandomPixelWidth = (min: number, max: number) => `${Math.floor(Math.random() * (max - min + 1)) + min}px` - -const listItems = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - -interface SkeletonListProps { +export interface SkeletonListProps { className?: string + linesCount?: number + hasActions?: boolean + classNames?: { + item?: string + leftTitle?: string + leftDescription?: string + rightTitle?: string + rightDescription?: string + actions?: string + } } -interface RandomWidths { - titleWidth: string - descriptionWidth: string - secondaryTitleWidth: string - secondaryDescriptionWidth: string -} - -export const SkeletonList = ({ className }: SkeletonListProps) => { - // Calculate random widths only once on mount - const randomWidths = useMemo<RandomWidths[]>(() => { - return listItems.map(() => ({ - titleWidth: getRandomPercentageWidth(20, 60), - descriptionWidth: getRandomPercentageWidth(30, 80), - secondaryTitleWidth: getRandomPixelWidth(80, 150), - secondaryDescriptionWidth: getRandomPixelWidth(150, 250) - })) - }, []) // Empty dependency array ensures this runs only once on mount - +export const SkeletonList: FC<SkeletonListProps> = ({ classNames = {}, linesCount = 8, hasActions, className }) => { + const { item, leftTitle, leftDescription, rightTitle, rightDescription, actions } = classNames return ( - <div className={cn('relative h-full w-full transition-opacity delay-500 duration-500 ease-in-out', className)}> - <StackedList.Root> - {listItems.map((itm, index) => ( - <StackedList.Item key={itm} className="py-4" isLast={listItems.length === itm}> - <StackedList.Field - // Use pre-calculated widths from the randomWidths array - title={<Skeleton className="mb-2 h-2.5" style={{ width: randomWidths[index].titleWidth }} />} - description={<Skeleton className="h-2.5" style={{ width: randomWidths[index].descriptionWidth }} />} - /> - <StackedList.Field - title={<Skeleton className="mb-2 h-2.5" style={{ width: randomWidths[index].secondaryTitleWidth }} />} - description={ - <Skeleton className="h-2.5" style={{ width: randomWidths[index].secondaryDescriptionWidth }} /> - } - right - /> - </StackedList.Item> - ))} - </StackedList.Root> - <div className="to-background absolute bottom-0 z-10 size-full bg-gradient-to-b from-transparent" /> - </div> + <StackedList.Root className={cn('cn-skeleton-list', className)}> + {Array.from({ length: linesCount }).map((_, index) => ( + <StackedList.Item + key={index} + isLast={linesCount === index + 1} + className={item} + actions={hasActions && <Skeleton.Box className={cn('cn-skeleton-list-actions', actions)} />} + disableHover + > + <StackedList.Field + title={<Skeleton.Typography variant="body-single-line-normal" className={cn('w-[129px]', leftTitle)} />} + description={ + <Skeleton.Typography variant="body-single-line-normal" className={cn('w-[240px]', leftDescription)} /> + } + /> + <StackedList.Field + title={<Skeleton.Typography variant="body-single-line-normal" className={cn('w-[147px]', rightTitle)} />} + description={ + <Skeleton.Typography variant="body-single-line-normal" className={cn('w-[68px]', rightDescription)} /> + } + right + /> + </StackedList.Item> + ))} + </StackedList.Root> ) } diff --git a/packages/ui/src/components/skeletons/skeleton-logo.tsx b/packages/ui/src/components/skeletons/skeleton-logo.tsx new file mode 100644 index 0000000000..11dabfd567 --- /dev/null +++ b/packages/ui/src/components/skeletons/skeleton-logo.tsx @@ -0,0 +1,31 @@ +import { forwardRef } from 'react' + +import { logoVariants } from '@/components' +import { cn } from '@utils/cn' +import { cva, VariantProps } from 'class-variance-authority' + +import { SkeletonBase } from './components/skeleton' + +const skeletonLogoVariants: typeof logoVariants = cva('cn-skeleton-logo', { + variants: { + size: { + sm: 'cn-skeleton-logo-sm', + md: 'cn-skeleton-logo-md', + lg: 'cn-skeleton-logo-lg' + } + }, + defaultVariants: { + size: 'lg' + } +}) + +export interface SkeletonLogoProps { + size: VariantProps<typeof skeletonLogoVariants>['size'] + className?: string +} + +export const SkeletonLogo = forwardRef<HTMLDivElement, SkeletonLogoProps>(({ size, className, ...props }, ref) => { + return <SkeletonBase className={cn(skeletonLogoVariants({ size }), className)} ref={ref} {...props} /> +}) + +SkeletonLogo.displayName = 'SkeletonLogo' diff --git a/packages/ui/src/components/skeletons/skeleton-table.tsx b/packages/ui/src/components/skeletons/skeleton-table.tsx index 8c6b80ed55..4dc1476a51 100644 --- a/packages/ui/src/components/skeletons/skeleton-table.tsx +++ b/packages/ui/src/components/skeletons/skeleton-table.tsx @@ -1,40 +1,42 @@ -import { useMemo } from 'react' - -import { Table } from '@/components' +import { Skeleton, Table } from '@/components' import { cn } from '@utils/cn' -import { Skeleton } from './components/skeleton' - -// Helper function to generate random percentage width within a range -const getRandomPercentageWidth = (min: number, max: number) => `${Math.floor(Math.random() * (max - min + 1)) + min}%` - -interface SkeletonTableProps { +export interface SkeletonTableProps { className?: string + classNameHeader?: string + classNameBody?: string countRows?: number countColumns?: number + showHeader?: boolean } -export const SkeletonTable = ({ className, countRows = 12, countColumns = 5 }: SkeletonTableProps) => { - // Calculate random widths only once on mount - const randomWidths = useMemo(() => { - const widths: string[][] = [] - for (let i = 0; i < countRows; i++) { - widths[i] = [] - for (let j = 0; j < countColumns; j++) { - widths[i][j] = getRandomPercentageWidth(30, 80) - } - } - return widths - }, [countRows, countColumns]) // Only recalculate if rows or columns count changes - +export const SkeletonTable = ({ + className, + classNameHeader, + classNameBody, + countRows = 12, + countColumns = 5, + showHeader = false +}: SkeletonTableProps) => { return ( - <Table.Root> - <Table.Body className={cn('relative h-full w-full', className)}> + <Table.Root className={cn('cn-skeleton-table', className)} disableHighlightOnHover> + {showHeader && ( + <Table.Header className={classNameHeader}> + <Table.Row> + {Array.from({ length: countColumns }).map((_, columnIndex) => ( + <Table.Head key={`header-cell-${columnIndex}`}> + <Skeleton.Typography variant="caption-normal" className="w-[65px]" /> + </Table.Head> + ))} + </Table.Row> + </Table.Header> + )} + <Table.Body className={classNameBody}> {Array.from({ length: countRows }).map((_, rowIndex) => ( <Table.Row key={`row-${rowIndex}`}> {Array.from({ length: countColumns }).map((_, columnIndex) => ( - <Table.Cell className="h-12 flex-1 content-center" key={`cell-${rowIndex}-${columnIndex}`}> - <Skeleton className="h-2.5" style={{ width: randomWidths[rowIndex][columnIndex] }} /> + <Table.Cell key={`cell-${rowIndex}-${columnIndex}`}> + <Skeleton.Typography className="w-full" /> </Table.Cell> ))} </Table.Row> diff --git a/packages/ui/src/components/skeletons/skeleton-typography.tsx b/packages/ui/src/components/skeletons/skeleton-typography.tsx new file mode 100644 index 0000000000..aec8d434ee --- /dev/null +++ b/packages/ui/src/components/skeletons/skeleton-typography.tsx @@ -0,0 +1,34 @@ +import { forwardRef, HTMLAttributes } from 'react' + +import { typographyVariantConfig } from '@components/text' +import { cn } from '@utils/cn' +import { cva, VariantProps } from 'class-variance-authority' + +import { SkeletonBase } from './components/skeleton' + +const skeletonTypographyVariants = cva('cn-skeleton-typography-wrapper', { + variants: { + variant: typographyVariantConfig + }, + defaultVariants: { + variant: 'body-normal' + } +}) + +export interface SkeletonTypographyProps { + variant?: VariantProps<typeof skeletonTypographyVariants>['variant'] + wrapperClassName?: string + className?: string +} + +export const SkeletonTypography = forwardRef<HTMLDivElement, SkeletonTypographyProps & HTMLAttributes<HTMLDivElement>>( + ({ variant, className, wrapperClassName, ...props }, ref) => { + return ( + <div className={cn(skeletonTypographyVariants({ variant }), wrapperClassName)} ref={ref} {...props}> + <SkeletonBase className={cn('cn-skeleton-typography-child', className)} /> + </div> + ) + } +) + +SkeletonTypography.displayName = 'SkeletonTypography' diff --git a/packages/ui/src/components/skeletons/skeleton-utils.ts b/packages/ui/src/components/skeletons/skeleton-utils.ts new file mode 100644 index 0000000000..4bb112e999 --- /dev/null +++ b/packages/ui/src/components/skeletons/skeleton-utils.ts @@ -0,0 +1,7 @@ +// Helper function to generate random percentage width within a range +export const getRandomPercentageWidth = (min: number, max: number) => + `${Math.floor(Math.random() * (max - min + 1)) + min}%` + +// Helper function to generate random pixel width within a range +export const getRandomPixelWidth = (min: number, max: number) => + `${Math.floor(Math.random() * (max - min + 1)) + min}px` diff --git a/packages/ui/src/components/skeletons/skeloton-file-explorer.tsx b/packages/ui/src/components/skeletons/skeloton-file-explorer.tsx new file mode 100644 index 0000000000..40151fa47d --- /dev/null +++ b/packages/ui/src/components/skeletons/skeloton-file-explorer.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react' + +import { Layout, Skeleton } from '@/components' +import { cn } from '@utils/cn' + +import { getRandomPixelWidth } from './skeleton-utils' + +export interface SkeletonFileExplorerProps { + className?: string + linesCount?: number +} + +export const SkeletonFileExplorer: FC<SkeletonFileExplorerProps> = ({ className, linesCount = 1 }) => { + return ( + <Layout.Vertical gap="4xs" className={cn({ 'cn-skeleton-file-explorer': linesCount > 1 }, className)}> + {Array.from({ length: linesCount }).map((_, index) => ( + <Layout.Horizontal key={index} gap="2xs" className="p-cn-2xs"> + <Skeleton.Icon size="md" /> + + <Skeleton.Typography style={{ width: getRandomPixelWidth(30, 120) }} className="w-full" /> + </Layout.Horizontal> + ))} + </Layout.Vertical> + ) +} diff --git a/packages/ui/src/components/text.tsx b/packages/ui/src/components/text.tsx index 9ec58b5769..93742f954e 100644 --- a/packages/ui/src/components/text.tsx +++ b/packages/ui/src/components/text.tsx @@ -35,25 +35,27 @@ type TextElement = | 'dt' | 'dd' -const textVariants = cva('', { +export const typographyVariantConfig = { + 'heading-hero': 'font-heading-hero', + 'heading-section': 'font-heading-section', + 'heading-subsection': 'font-heading-subsection', + 'heading-base': 'font-heading-base', + 'heading-small': 'font-heading-small', + 'body-normal': 'font-body-normal', + 'body-single-line-normal': 'font-body-single-line-normal', + 'body-strong': 'font-body-strong', + 'body-single-line-strong': 'font-body-single-line-strong', + 'body-code': 'font-body-code', + 'caption-normal': 'font-caption-normal', + 'caption-soft': 'font-caption-soft', + 'caption-strong': 'font-caption-strong', + 'caption-single-line-normal': 'font-caption-single-line-normal', + 'caption-single-line-soft': 'font-caption-single-line-soft' +} + +export const textVariants = cva('', { variants: { - variant: { - 'heading-hero': 'font-heading-hero', - 'heading-section': 'font-heading-section', - 'heading-subsection': 'font-heading-subsection', - 'heading-base': 'font-heading-base', - 'heading-small': 'font-heading-small', - 'body-normal': 'font-body-normal', - 'body-single-line-normal': 'font-body-single-line-normal', - 'body-strong': 'font-body-strong', - 'body-single-line-strong': 'font-body-single-line-strong', - 'body-code': 'font-body-code', - 'caption-normal': 'font-caption-normal', - 'caption-soft': 'font-caption-soft', - 'caption-strong': 'font-caption-strong', - 'caption-single-line-normal': 'font-caption-single-line-normal', - 'caption-single-line-soft': 'font-caption-single-line-soft' - }, + variant: typographyVariantConfig, align: { left: 'text-left', center: 'text-center', diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx index 5b174c01fc..4f1482df62 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx @@ -1,4 +1,4 @@ -import { NoData, SkeletonList, SkeletonTable, Table, TimeAgoCard } from '@/components' +import { NoData, Skeleton, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { cn } from '@utils/cn' import { ExecutionState } from '@views/index' @@ -34,7 +34,7 @@ const ConnectorDetailsActivitiesList = ({ isLoading, activities }: ConnectorDeta const { t } = useTranslation() const content = activities?.content if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!content.length) { @@ -60,7 +60,7 @@ const ConnectorDetailsActivitiesList = ({ isLoading, activities }: ConnectorDeta </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={12} countColumns={5} /> + <Skeleton.Table countRows={12} countColumns={5} /> ) : ( <Table.Body> {content.map(({ referredEntity, activityTime, description, activityStatus }: ConnectorActivityItem) => { diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx index 9593a53618..206e3f80b9 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx @@ -1,4 +1,4 @@ -import { NoData, SkeletonList, SkeletonTable, Table, TimeAgoCard } from '@/components' +import { NoData, Skeleton, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { ConnectorReferenceItem, ConnectorReferenceListProps } from './types' @@ -23,7 +23,7 @@ const ConnectorDetailsReferenceList = ({ const { t } = useTranslation() const content = entities?.content if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!content.length) { @@ -52,7 +52,7 @@ const ConnectorDetailsReferenceList = ({ </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={12} countColumns={5} /> + <Skeleton.Table countRows={12} countColumns={5} /> ) : ( <Table.Body> {content.map(({ referredByEntity, createdAt }: ConnectorReferenceItem) => { diff --git a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx index ec1c69f950..bbafb0013c 100644 --- a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx +++ b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx @@ -7,8 +7,7 @@ import { LogoV2, MoreActionsTooltip, NoData, - SkeletonList, - SkeletonTable, + Skeleton, Table, Text, TimeAgoCard, @@ -84,7 +83,7 @@ export function ConnectorsList({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!connectors.length) { @@ -119,7 +118,7 @@ export function ConnectorsList({ </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={12} countColumns={5} /> + <Skeleton.Table countRows={12} countColumns={5} /> ) : ( <Table.Body> {connectors.map(({ name, identifier, type, spec, status, lastModifiedAt, isFavorite }) => { diff --git a/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx b/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx index c60f85a83c..296c83e88c 100644 --- a/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx +++ b/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx @@ -1,4 +1,4 @@ -import { IconV2, NoData, SkeletonList, SkeletonTable, StatusBadge, Table, TimeAgoCard } from '@/components' +import { IconV2, NoData, Skeleton, StatusBadge, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { cn } from '@utils/cn' import { defaultTo } from 'lodash-es' @@ -18,7 +18,7 @@ export function DelegateConnectivityList({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!delegates.length) { @@ -47,7 +47,7 @@ export function DelegateConnectivityList({ </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={12} countColumns={5} /> + <Skeleton.Table countRows={12} countColumns={5} /> ) : ( <Table.Body> {delegates.map( diff --git a/packages/ui/src/views/labels/label-form-page.tsx b/packages/ui/src/views/labels/label-form-page.tsx index eaf6665d8c..b09c46c8a6 100644 --- a/packages/ui/src/views/labels/label-form-page.tsx +++ b/packages/ui/src/views/labels/label-form-page.tsx @@ -12,7 +12,7 @@ import { IconV2, Label, Layout, - SkeletonForm, + Skeleton, Tag, Text } from '@/components' @@ -149,7 +149,7 @@ export const LabelFormPage: FC<LabelFormPageProps> = ({ : t('views:labelData.form.createTitle', 'Create a label')} </Text> - {isLoading && <SkeletonForm />} + {isLoading && <Skeleton.Form />} {!isLoading && ( <FormWrapper {...formMethods} className="gap-y-10" onSubmit={handleSubmit(onSubmit)}> diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index 7d3df8b71f..4c97f0a3e9 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, Checkbox, ListActions, Pagination, SearchInput, SkeletonList, Text } from '@/components' +import { Button, Checkbox, ListActions, Pagination, SearchInput, Skeleton, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { ILabelsStore, SandboxLayout } from '@/views' @@ -89,7 +89,7 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ </ListActions.Root> )} - {isLoading && <SkeletonList className="mb-8 mt-5" />} + {isLoading && <Skeleton.List className="mb-8 mt-5" />} {!isLoading && ( <LabelsListView diff --git a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx index 5ecf082c70..cb64babc5b 100644 --- a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx +++ b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx @@ -1,4 +1,4 @@ -import { IconV2, NoData, SkeletonList, StackedList, TimeAgoCard } from '@/components' +import { IconV2, NoData, Skeleton, StackedList, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { timeDistance } from '@/utils' import { PipelineExecutionStatus } from '@/views' @@ -56,7 +56,7 @@ export const ExecutionList = ({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (noData) { diff --git a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx index e1c25d3306..94d8935cfa 100644 --- a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx +++ b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx @@ -1,6 +1,6 @@ import { useTranslation } from '@/context' -import { IconV2, NoData, SkeletonList, StackedList } from '../../../components' +import { IconV2, NoData, Skeleton, StackedList } from '../../../components' import { Meter } from '../../../components/meter' import { PipelineExecutionStatus } from '../common/execution-types' import { ExecutionStatusIcon } from '../components/execution-status-icon' @@ -49,7 +49,7 @@ export const PipelineList = ({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (noData) { diff --git a/packages/ui/src/views/platform/entity-reference-component.tsx b/packages/ui/src/views/platform/entity-reference-component.tsx index 520d94788a..41473571d7 100644 --- a/packages/ui/src/views/platform/entity-reference-component.tsx +++ b/packages/ui/src/views/platform/entity-reference-component.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import { Checkbox, IconV2, ListActions, SearchBox, SkeletonList, StackedList } from '@/components' +import { Checkbox, IconV2, ListActions, SearchBox, Skeleton, StackedList } from '@/components' import { useDebounceSearch } from '@hooks/use-debounce-search' import { cn } from '@utils/cn' @@ -192,7 +192,7 @@ export function EntityReference<T extends BaseEntityProps, S = string, F = strin </ListActions.Root> )} {isLoading ? ( - <SkeletonList /> + <Skeleton.List /> ) : ( <EntityReferenceList<T, S, F> entities={entities} diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx index 2115a90c5d..41d6abddd7 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { IconV2, MoreActionsTooltip, SkeletonTable, Table, TimeAgoCard } from '@/components' +import { IconV2, MoreActionsTooltip, Skeleton, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { KeysList } from '../types' @@ -29,7 +29,7 @@ export const ProfileKeysList: FC<ProfileKeysListProps> = ({ publicKeys, isLoadin </Table.Header> {isLoading ? ( - <SkeletonTable countRows={4} countColumns={4} /> + <Skeleton.Table countRows={4} countColumns={4} /> ) : ( <Table.Body> {publicKeys.length ? ( diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx index 41c77cb68e..ee7d220a11 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { IconV2, MoreActionsTooltip, SkeletonTable, Table, TimeAgoCard } from '@/components' +import { IconV2, MoreActionsTooltip, Skeleton, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { TokensList } from '../types' @@ -29,7 +29,7 @@ export const ProfileTokensList: FC<ProfileTokensListProps> = ({ tokens, isLoadin </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={5} /> + <Skeleton.Table countRows={5} /> ) : ( <Table.Body> {tokens.length ? ( diff --git a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx index e12197b180..3d7c0a4fe3 100644 --- a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx +++ b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx @@ -13,9 +13,9 @@ import { FormWrapper, IconV2, Legend, + Skeleton, Text } from '@/components' -import { SkeletonForm } from '@/components/skeletons' import { TFunctionWithFallback, useTranslation } from '@/context' import { IProfileSettingsStore, ProfileSettingsErrorType, SandboxLayout } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' @@ -185,7 +185,7 @@ export const SettingsAccountGeneralPage: FC<SettingsAccountGeneralPageProps> = ( {t('views:profileSettings.accountSettings', 'Account settings')} </Text> - {isLoadingUser && <SkeletonForm />} + {isLoadingUser && <Skeleton.Form />} {!isLoadingUser && ( <> diff --git a/packages/ui/src/views/project/project-general/project-general-page.tsx b/packages/ui/src/views/project/project-general/project-general-page.tsx index 626a33e2ae..b6c86aa86b 100644 --- a/packages/ui/src/views/project/project-general/project-general-page.tsx +++ b/packages/ui/src/views/project/project-general/project-general-page.tsx @@ -12,7 +12,7 @@ import { FormWrapper, IconV2, Legend, - SkeletonForm, + Skeleton, Text } from '@/components' import { useTranslation } from '@/context' @@ -106,7 +106,7 @@ export const ProjectSettingsGeneralPage = ({ {t('views:projectSettings.general.mainTitle', 'Project Settings')} </Text> - {isLoading && <SkeletonForm />} + {isLoading && <Skeleton.Form />} {!isLoading && ( <> diff --git a/packages/ui/src/views/project/project-members/components/project-member-list.tsx b/packages/ui/src/views/project/project-members/components/project-member-list.tsx index 280d7040ae..c630a673fa 100644 --- a/packages/ui/src/views/project/project-members/components/project-member-list.tsx +++ b/packages/ui/src/views/project/project-members/components/project-member-list.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { NoData, Pagination, SkeletonList } from '@/components' +import { NoData, Pagination, Skeleton } from '@/components' import { useTranslation } from '@/context' import { MembersList } from './member-list' @@ -20,7 +20,7 @@ const ProjectMembersList: FC<ProjectMembersListProps> = ({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!memberList.length) { diff --git a/packages/ui/src/views/repo/components/repo-header.tsx b/packages/ui/src/views/repo/components/repo-header.tsx index 4ee092e219..607767fef9 100644 --- a/packages/ui/src/views/repo/components/repo-header.tsx +++ b/packages/ui/src/views/repo/components/repo-header.tsx @@ -34,10 +34,10 @@ export const RepoHeader = ({ {isLoading ? ( <> <Layout.Flex gap="xs" justify="start" align="center"> - <Skeleton className="bg-cn-background-0 h-[var(--cn-line-height-7-tight)] w-28" /> - <Skeleton className="bg-cn-background-0 h-6 w-14" /> + <Skeleton.Box className="h-[var(--cn-line-height-7-tight)] w-28" /> + <Skeleton.Box className="h-6 w-14" /> </Layout.Flex> - <Skeleton className="bg-cn-background-0 h-6 w-14" /> + <Skeleton.Box className="h-6 w-14" /> </> ) : ( <> diff --git a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx index d745985838..c4064403be 100644 --- a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx +++ b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx @@ -1,6 +1,6 @@ import { FC, useCallback } from 'react' -import { NoData, Pagination, SkeletonList } from '@/components' +import { NoData, Pagination, Skeleton } from '@/components' import { useTranslation } from '@/context' import { CommitsList, IPullRequestCommitsStore, TypesCommit } from '@/views' @@ -29,7 +29,7 @@ const PullRequestCommitsView: FC<RepoPullRequestCommitsViewProps> = ({ }, [xNextPage]) if (isFetchingCommits) { - return <SkeletonList /> + return <Skeleton.List /> } return ( diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 35db3ccc3a..6c3409963f 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -1,7 +1,7 @@ import { FC, ReactElement, useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' -import { Avatar, Button, IconV2, Layout, Link, LinkProps, NoData, SkeletonList, Spacer, Tabs, Text } from '@/components' +import { Avatar, Button, IconV2, Layout, Link, LinkProps, NoData, Skeleton, Spacer, Tabs, Text } from '@/components' import { TFunctionWithFallback, useRouterContext, useTranslation } from '@/context' import { TypesDiffStats } from '@/types' import { @@ -420,7 +420,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ <Tabs.Content className="pt-7" value="commits"> {/* TODO: add pagination to this */} {isFetchingCommits ? ( - <SkeletonList /> + <Skeleton.List /> ) : (commitData ?? []).length > 0 ? ( <CommitsList toCode={toCode} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index 066f1ba9b7..a08e32c879 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -1,6 +1,6 @@ import { FC, useMemo } from 'react' -import { SkeletonList } from '@/components' +import { Skeleton } from '@/components' import { TypesUser } from '@/types' import { DiffModeEnum } from '@git-diff-view/react' import { activityToCommentItem, HandleUploadType, TypesCommit } from '@views/index' @@ -124,7 +124,7 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ const renderContent = () => { if (loadingRawDiff) { - return <SkeletonList /> + return <Skeleton.List /> } return ( <PullRequestChanges diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index ecb474924b..755177002a 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -8,7 +8,7 @@ import { NoData, Pagination, SearchInput, - SkeletonList, + Skeleton, Spacer, StackedList, Text @@ -212,7 +212,7 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ const renderListContent = () => { if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (noData) { diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index a4cfb80bab..bc9ced7d4b 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -8,7 +8,7 @@ import { MoreActionsTooltip, NoData, Separator, - SkeletonTable, + Skeleton, StatusBadge, Table, Tag, @@ -83,7 +83,7 @@ export const BranchesList: FC<BranchListPageProps> = ({ } if (isLoading) { - return <SkeletonTable countRows={12} countColumns={6} /> + return <Skeleton.Table countRows={12} countColumns={6} /> } return ( diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index 9b11ebe4df..f39eed2991 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Avatar, Button, CommitCopyActions, Layout, SkeletonList, Text, TimeAgoCard } from '@/components' +import { Avatar, Button, CommitCopyActions, Layout, Skeleton, Text, TimeAgoCard } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { ICommitDetailsStore, SandboxLayout } from '@/views' @@ -33,7 +33,7 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ <SandboxLayout.Main className="overflow-visible" fullWidth> {loadingCommitDetails ? ( <SandboxLayout.Content> - <SkeletonList /> + <Skeleton.List /> </SandboxLayout.Content> ) : ( <SandboxLayout.Content> diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index 196ae64236..bbb991045c 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -1,6 +1,6 @@ import { FC, useCallback } from 'react' -import { IconV2, Layout, NoData, Pagination, SkeletonList, Text } from '@/components' +import { IconV2, Layout, NoData, Pagination, Skeleton, Text } from '@/components' import { useTranslation } from '@/context' import { CommitsList, SandboxLayout, TypesCommit } from '@/views' @@ -58,7 +58,7 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ <BranchSelectorContainer /> </div> - {isFetchingCommits && <SkeletonList />} + {isFetchingCommits && <Skeleton.List />} {!isFetchingCommits && ( <> diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index ea83d76382..40e433c016 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode, useMemo } from 'react' -import { NoData, PathParts, SkeletonList } from '@/components' +import { NoData, PathParts, Skeleton } from '@/components' import { useTranslation } from '@/context' import { BranchInfoBar, @@ -72,7 +72,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ const isView = useMemo(() => codeMode === CodeModes.VIEW, [codeMode]) const content = useMemo(() => { - if (loading || isLoadingRepoDetails) return <SkeletonList /> + if (loading || isLoadingRepoDetails) return <Skeleton.Table countColumns={3} countRows={8} showHeader /> if (!isView) return children diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index 2ac8df52d0..e9b957bd47 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -4,7 +4,7 @@ import { Layout, NoData, ScopeTag, - SkeletonList, + Skeleton, StackedList, StatusBadge, Text, @@ -91,7 +91,7 @@ export function RepoList({ const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List linesCount={8} hasActions /> } if (!repos.length) { diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx index a98da7c0aa..9cb3e3b4b4 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx @@ -1,17 +1,7 @@ import { FC, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { - Checkbox, - ControlGroup, - Fieldset, - Layout, - Message, - MessageTheme, - SkeletonForm, - Spacer, - Text -} from '@/components' +import { Checkbox, ControlGroup, Fieldset, Layout, Message, MessageTheme, Skeleton, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -76,7 +66,7 @@ export const RepoSettingsFeaturesForm: FC<RepoSettingsFeaturesFormProps> = ({ <Layout.Vertical gap="xl"> <Text variant="heading-subsection">{t('views:repos.features', 'Features')}</Text> {isLoadingFeaturesSettings ? ( - <SkeletonForm linesCount={2} /> + <Skeleton.Form linesCount={2} /> ) : ( <ControlGroup> <Checkbox diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx index 5b2374ea7c..fbbd54463c 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx @@ -12,7 +12,7 @@ import { Label, Layout, Radio, - SkeletonForm, + Skeleton, Text } from '@/components' import { useTranslation } from '@/context' @@ -105,7 +105,7 @@ export const RepoSettingsGeneralForm: FC<{ return ( <Fieldset> {isLoadingRepoData ? ( - <SkeletonForm /> + <Skeleton.Form /> ) : ( <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> {/* NAME */} diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index a1911b17b1..1e5c679615 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -10,7 +10,7 @@ import { ScopeTag, SearchInput, Separator, - SkeletonList, + Skeleton, Spacer, SplitButton, StackedList, @@ -172,7 +172,7 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ {isLoading ? ( <> <Spacer size={7} /> - <SkeletonList /> + <Skeleton.List /> </> ) : rules?.length !== 0 ? ( <StackedList.Root> @@ -271,7 +271,7 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ ) : ( <NoData withBorder - className="min-h-0 py-cn-3xl" + className="py-cn-3xl min-h-0" textWrapperClassName="max-w-[350px]" imageName={'no-data-cog'} title={t('views:noData.noRules', 'No rules yet')} diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx index a99bec2e1e..6f06e9e2b7 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx @@ -1,17 +1,7 @@ import { FC, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { - Checkbox, - ControlGroup, - Fieldset, - Layout, - Message, - MessageTheme, - SkeletonForm, - Spacer, - Text -} from '@/components' +import { Checkbox, ControlGroup, Fieldset, Layout, Message, MessageTheme, Skeleton, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -99,7 +89,7 @@ export const RepoSettingsSecurityForm: FC<RepoSettingsSecurityFormProps> = ({ <Layout.Vertical gap="xl"> <Text variant="heading-subsection">{t('views:repos.security', 'Security')}</Text> {isLoadingSecuritySettings ? ( - <SkeletonForm linesCount={2} /> + <Skeleton.Form linesCount={2} /> ) : ( <ControlGroup> <Layout.Vertical gap="sm"> diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index c8f38857b1..cd2c5e2a42 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -7,7 +7,7 @@ import { ListActions, MarkdownViewer, SearchFiles, - SkeletonList, + Skeleton, Spacer, StackedList, Text @@ -128,7 +128,7 @@ export function RepoSummaryView({ return ( <SandboxLayout.Main fullWidth> <SandboxLayout.Content> - <SkeletonList /> + <Skeleton.Table countColumns={3} countRows={10} showHeader /> </SandboxLayout.Content> </SandboxLayout.Main> ) diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index ef47ef26eb..f03d41b942 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -6,7 +6,7 @@ import { CommitCopyActions, MoreActionsTooltip, NoData, - SkeletonTable, + Skeleton, Table, Tag, Text, @@ -94,7 +94,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ } if (isLoading) { - return <SkeletonTable countRows={12} countColumns={5} /> + return <Skeleton.Table countRows={12} countColumns={5} /> } return ( diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx index 93b27f1116..eee91edeaf 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useMemo } from 'react' -import { FormSeparator, NoData, Pagination, SkeletonList, StatusBadge, Table, Text, TimeAgoCard } from '@/components' +import { FormSeparator, NoData, Pagination, Skeleton, StatusBadge, Table, Text, TimeAgoCard } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { SandboxLayout, WebhookStore } from '@/views' @@ -47,7 +47,7 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ Executions </Text> {isLoading ? ( - <SkeletonList /> + <Skeleton.List /> ) : executions && executions.length > 0 ? ( <> <Table.Root disableHighlightOnHover> diff --git a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx index 4c6a9f9f49..4bfcca0434 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, ListActions, SearchInput, SkeletonList, Spacer, Text } from '@/components' +import { Button, ListActions, SearchInput, Skeleton, Spacer, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -75,7 +75,7 @@ const RepoWebhookListPage: FC<RepoWebhookListPageProps> = ({ )} {webhookLoading ? ( - <SkeletonList /> + <Skeleton.List /> ) : ( <RepoWebhookList error={error} diff --git a/packages/ui/src/views/run-pipeline/run-pipeline-drawer-content.tsx b/packages/ui/src/views/run-pipeline/run-pipeline-drawer-content.tsx index 48b47593a0..48db073061 100644 --- a/packages/ui/src/views/run-pipeline/run-pipeline-drawer-content.tsx +++ b/packages/ui/src/views/run-pipeline/run-pipeline-drawer-content.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from 'react' -import { Button, ButtonLayout, Drawer, SkeletonList } from '@/components' +import { Button, ButtonLayout, Drawer, Skeleton } from '@/components' import { cn } from '@/utils' import { InputFactory } from '@harnessio/forms' @@ -71,7 +71,7 @@ export function RunPipelineDrawerContent(props: RunPipelineDrawerProps) { <Drawer.Body className={cn({ 'p-0 [&>div]:h-full': view === 'yaml' })}> {loading ? ( - <SkeletonList className="p-5" /> + <Skeleton.List className="p-5" /> ) : ( <div className="flex grow flex-col"> <RunPipelineFormInputs diff --git a/packages/ui/src/views/search/components/search-results-list.tsx b/packages/ui/src/views/search/components/search-results-list.tsx index 3d803b3ea2..e993336a2a 100644 --- a/packages/ui/src/views/search/components/search-results-list.tsx +++ b/packages/ui/src/views/search/components/search-results-list.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react' -import { Alert, Card, Layout, SkeletonList, Spacer, Tag, Text } from '@/components' +import { Alert, Card, Layout, Skeleton, Spacer, Tag, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { cn } from '@utils/cn' @@ -48,7 +48,7 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ const { Link } = useRouterContext() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!results?.length) { diff --git a/packages/ui/src/views/search/components/semantic-search-results-list.tsx b/packages/ui/src/views/search/components/semantic-search-results-list.tsx index 4b725ed321..bc3079a965 100644 --- a/packages/ui/src/views/search/components/semantic-search-results-list.tsx +++ b/packages/ui/src/views/search/components/semantic-search-results-list.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react' -import { Alert, Card, Layout, Link, SkeletonList, Spacer, Text } from '@/components' +import { Alert, Card, Layout, Link, Skeleton, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@utils/cn' @@ -37,7 +37,7 @@ export const SemanticSearchResultsList: FC<SemanticSearchResultsListProps> = ({ const [expandedItems, setExpandedItems] = useState<Record<string, boolean>>({}) if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!semanticResults?.length) { diff --git a/packages/ui/src/views/secrets/components/gcp-regions-multiselect-view.tsx b/packages/ui/src/views/secrets/components/gcp-regions-multiselect-view.tsx index 25b60236bb..1d937e5fa9 100644 --- a/packages/ui/src/views/secrets/components/gcp-regions-multiselect-view.tsx +++ b/packages/ui/src/views/secrets/components/gcp-regions-multiselect-view.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' -import { Alert, MultiSelect, MultiSelectOption, SkeletonList } from '@/components' +import { Alert, MultiSelect, MultiSelectOption, Skeleton } from '@/components' export interface GcpRegionsMultiSelectProps { value?: string | string[] @@ -38,7 +38,7 @@ export function GcpRegionsMultiSelect(props: GcpRegionsMultiSelectProps): React. return ( <> {isLoading ? ( - <SkeletonList /> + <Skeleton.List /> ) : error ? ( <Alert.Root theme="danger" className="my-2"> <Alert.Description>{error?.toString() || 'Failed to fetch regions'}</Alert.Description> diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx index adcb509301..e1f67a6d51 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx @@ -1,4 +1,4 @@ -import { IconV2, MoreActionsTooltip, NoData, SkeletonList, SkeletonTable, Table, TimeAgoCard } from '@/components' +import { IconV2, MoreActionsTooltip, NoData, Skeleton, Table, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { SecretListProps } from './types' @@ -11,7 +11,7 @@ export function SecretList({ secrets, isLoading, toSecretDetails, onDeleteSecret const { t } = useTranslation() if (isLoading) { - return <SkeletonList /> + return <Skeleton.List /> } if (!secrets.length) { @@ -39,7 +39,7 @@ export function SecretList({ secrets, isLoading, toSecretDetails, onDeleteSecret </Table.Row> </Table.Header> {isLoading ? ( - <SkeletonTable countRows={12} countColumns={5} /> + <Skeleton.Table countRows={12} countColumns={5} /> ) : ( <Table.Body> {secrets.map(secret => ( diff --git a/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx b/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx index c23257d5bf..8979883a40 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx @@ -1,6 +1,6 @@ import { ElementType, useEffect, useState } from 'react' -import { Button, ButtonLayout, Drawer, EntityFormLayout, IconV2, SkeletonList, Text } from '@/components' +import { Button, ButtonLayout, Drawer, EntityFormLayout, IconV2, Skeleton, Text } from '@/components' import { useUnifiedPipelineStudioContext } from '@views/unified-pipeline-studio/context/unified-pipeline-studio-context' import { addNameInput } from '@views/unified-pipeline-studio/utils/entity-form-utils' import { get, isEmpty, isUndefined, omit, omitBy } from 'lodash-es' @@ -256,7 +256,7 @@ export const UnifiedPipelineStudioEntityForm = (props: UnifiedPipelineStudioEnti {error?.message ? ( <Text color="danger">{error.message}</Text> ) : loading ? ( - <SkeletonList /> + <Skeleton.List /> ) : ( <RenderForm className="space-y-5" factory={inputComponentFactory} inputs={formDefinition} /> )} diff --git a/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/unified-pipeline-step-palette-drawer.tsx b/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/unified-pipeline-step-palette-drawer.tsx index 2f53198f18..6ece97204d 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/unified-pipeline-step-palette-drawer.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/unified-pipeline-step-palette-drawer.tsx @@ -8,7 +8,7 @@ import { Layout, Pagination, SearchInput, - SkeletonList, + Skeleton, Spacer, Text } from '@/components' @@ -132,7 +132,7 @@ export const UnifiedPipelineStudioStepPalette = (props: PipelineStudioStepFormPr {templatesError ? ( <Text color="danger">{templatesError.message}</Text> ) : isFetchingTemplates ? ( - <SkeletonList /> + <Skeleton.List /> ) : ( <> <StepPaletteSection diff --git a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/users-list.tsx b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/users-list.tsx index b43c012961..02f02e3e53 100644 --- a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/users-list.tsx +++ b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/users-list.tsx @@ -1,4 +1,4 @@ -import { Avatar, MoreActionsTooltip, SkeletonList, StatusBadge, Table } from '@/components' +import { Avatar, MoreActionsTooltip, Skeleton, StatusBadge, Table } from '@/components' import { DialogLabels } from '@/views/user-management/components/dialogs' import { useDialogData } from '@/views/user-management/components/dialogs/hooks/use-dialog-data' import { ErrorState } from '@/views/user-management/components/page-components/content/components/users-list/components/error-state' @@ -20,7 +20,7 @@ export const UsersList = () => { const { fetchUsersError } = errorStates if (isFetchingUsers) { - return <SkeletonList /> + return <Skeleton.List /> } if (fetchUsersError) { diff --git a/packages/ui/tailwind-utils-config/components/index.ts b/packages/ui/tailwind-utils-config/components/index.ts index 40e13c0331..c7ec5cf7e4 100644 --- a/packages/ui/tailwind-utils-config/components/index.ts +++ b/packages/ui/tailwind-utils-config/components/index.ts @@ -29,6 +29,7 @@ import scrollAreaStyles from './scroll-area' import selectStyles from './select' import shortcutStyle from './shortcut' import sidebarStyles from './sidebar' +import skeletonStyles from './skeleton' import switchStyles from './switch' import tableV2Styles from './table-v2' import tabsStyles from './tabs' @@ -71,6 +72,7 @@ export const ComponentStyles = [ toggleStyles, toggleGroupStyles, buttonGroupStyle, + skeletonStyles, // Form styles selectStyles, diff --git a/packages/ui/tailwind-utils-config/components/skeleton.ts b/packages/ui/tailwind-utils-config/components/skeleton.ts new file mode 100644 index 0000000000..905f3b3a3f --- /dev/null +++ b/packages/ui/tailwind-utils-config/components/skeleton.ts @@ -0,0 +1,125 @@ +import { CSSRuleObject } from 'tailwindcss/types/config' + +const sizesAvatar = ['sm', 'md', 'lg'] as const +const iconSizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'] as const +const logoSizes = ['sm', 'md', 'lg'] as const +const inputSizes = ['sm'] as const + +function createSkeletonAvatarVariantStyles() { + const styles: CSSRuleObject = {} + + sizesAvatar.forEach(size => { + styles[`&:where(.cn-skeleton-avatar-${size})`] = { + width: `var(--cn-avatar-size-${size})`, + height: `var(--cn-avatar-size-${size})` + } + }) + + return styles +} + +function createIconandLogoSizeStyles(entity: 'icon' | 'logo') { + const sizes = entity === 'icon' ? iconSizes : logoSizes + + const styles: CSSRuleObject = {} + + sizes.forEach(size => { + const style: CSSRuleObject = {} + style[`width`] = `var(--cn-${entity}-size-${size})` + style[`min-width`] = `var(--cn-${entity}-size-${size})` + style[`height`] = `var(--cn-${entity}-size-${size})` + style[`min-height`] = `var(--cn-${entity}-size-${size})` + + styles[`&:where(.cn-skeleton-${entity}-${size})`] = style + }) + + return styles +} + +function createInputThemeStyles() { + const styles: CSSRuleObject = {} + + inputSizes.forEach(size => { + styles[`&.cn-skeleton-form-item-${size}`] = { + padding: `var(--cn-input-${size}-py) var(--cn-input-${size}-pr) var(--cn-input-${size}-py) var(--cn-input-${size}-pl)`, + height: `var(--cn-input-size-${size})` + } + }) + + return styles +} + +export default { + '.cn-skeleton': { + '&-base': { + backgroundImage: 'var(--cn-comp-skeleton-bg)', + borderRadius: `var(--cn-rounded-2)`, + '@apply animate-pulse': '' + }, + + '&-avatar': { + display: 'inline-block', + borderRadius: `var(--cn-avatar-radius-default)`, + + '&:where(.cn-skeleton-avatar-rounded)': { + borderRadius: `var(--cn-avatar-radius-rounded)` + }, + + ...createSkeletonAvatarVariantStyles() + }, + + '&-icon': { + ...createIconandLogoSizeStyles('icon') + }, + '&-logo': { + ...createIconandLogoSizeStyles('logo') + }, + + '&-typography': { + '&-wrapper': { + display: 'inline' + }, + '&-child': { + '&::before': { + content: '"\u200B"' // Zero-width space to ensure the element has a width + }, + + display: 'inline-block', + minWidth: '45px', + lineHeight: '1', + verticalAlign: 'middle' + } + }, + + '&-table': { + maskImage: 'var(--cn-comp-skeleton-mask)' + }, + + '&-form': { + '&-field': { + maskImage: 'var(--cn-comp-skeleton-mask)' + }, + '&-item': { + height: 'var(--cn-input-size-md)', + padding: 'var(--cn-input-md-py) var(--cn-input-md-pr) var(--cn-input-md-py) var(--cn-input-md-pl)', + border: 'var(--cn-input-border) solid var(--cn-border-2)', + borderRadius: 'var(--cn-input-radius)', + backgroundColor: 'var(--cn-comp-input-bg)', + + ...createInputThemeStyles() + } + }, + + '&-list': { + maskImage: 'var(--cn-comp-skeleton-mask)', + + '&-actions': { + '@apply size-9': '' + } + }, + + '&-file-explorer': { + maskImage: 'var(--cn-comp-skeleton-mask)' + } + } +} From 8e90478926cd160be014fb55e36b05a0ff853fce Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Thu, 7 Aug 2025 16:11:13 +0300 Subject: [PATCH 017/180] Change comment markdown size text (#2054) --- packages/ui/src/components/markdown-viewer/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 96c64e2d6f..1c57a683bf 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -280,7 +280,7 @@ } &.comment { - @apply !bg-transparent; + @apply font-body-normal !bg-transparent; pre code { @apply bg-transparent; From 42229efc2465608e8875f22d2d6dde659047a371 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Thu, 7 Aug 2025 13:23:43 +0000 Subject: [PATCH 018/180] fix: jump to a diff functionality for MFE + sticky filters/accordion padding + PR diff memory cleanup optimizations (#10144) * f71e78 fix: PR pages memory cleanup optimizations * 4badcf fix: jump to a diff functionality for MFE and sticky filters - accordion padding --- .../hooks/usePRCommonInteractions.ts | 27 ++++++++++++++++++- .../pull-request/pull-request-changes.tsx | 15 ++++++++++- .../pull-request-compare-diff-list.tsx | 2 +- .../components/pull-request-accordian.tsx | 2 +- .../changes/pull-request-changes-filter.tsx | 6 ++++- .../changes/pull-request-changes.tsx | 6 ++--- .../details/pull-request-utils.ts | 25 +++++++++++++---- 7 files changed, 70 insertions(+), 13 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts b/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts index b8a7215e94..f4113697bc 100644 --- a/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts +++ b/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts @@ -72,7 +72,7 @@ export function usePRCommonInteractions({ const reader = new FileReader() // Set up a function to be called when the load event is triggered - reader.onload = async function () { + const handleLoad = async function () { if (blob.type.startsWith('image/') || blob.type.startsWith('video/')) { const markdown = await uploadImage(reader.result) @@ -82,8 +82,33 @@ export function usePRCommonInteractions({ setMarkdownContent(`${currentComment} \n ${markdown}`) // Set the markdown content } } + + // Clean up event handlers after processing + reader.onload = null + reader.onerror = null + reader.onabort = null + } + + const handleError = function () { + console.error('FileReader error occurred') + // Clean up event handlers on error + reader.onload = null + reader.onerror = null + reader.onabort = null } + const handleAbort = function () { + console.log('FileReader operation was aborted') + // Clean up event handlers on abort + reader.onload = null + reader.onerror = null + reader.onabort = null + } + + reader.onload = handleLoad + reader.onerror = handleError + reader.onabort = handleAbort + reader.readAsArrayBuffer(blob) // This will trigger the onload function when the reading is complete }, [uploadImage] diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx index c43d3e87fa..058b95a9d3 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx @@ -277,6 +277,9 @@ export default function PullRequestChanges() { if (loadingRawDiff || cachedDiff.path !== path || typeof cachedDiff.raw !== 'string') { return } + + let processTimeoutId: NodeJS.Timeout | null = null + if (cachedDiff.raw) { const parsed = Diff2Html.parse(cachedDiff.raw, DIFF2HTML_CONFIG) let currentIndex = 0 @@ -313,13 +316,23 @@ export default function PullRequestChanges() { currentIndex = endIndex if (currentIndex < parsed.length) { - setTimeout(processNextChunk, 0) + processTimeoutId = setTimeout(processNextChunk, 0) + } else { + processTimeoutId = null } } processNextChunk() } else { setDiffs([]) } + + // Cleanup function to clear any pending timeouts + return () => { + if (processTimeoutId) { + clearTimeout(processTimeoutId) + processTimeoutId = null + } + } }, [loadingRawDiff, path, cachedDiff]) const { data: { body: activityData } = {} } = useListPullReqActivitiesQuery({ diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index 63445d4b6e..32e79e966f 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -68,7 +68,7 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ ) useEffect(() => { if (!jumpToDiff) return - jumpToFile(jumpToDiff, diffBlocks, setJumpToDiff) + jumpToFile(jumpToDiff, diffBlocks, setJumpToDiff, undefined, diffsContainerRef) }, [jumpToDiff, diffBlocks, setJumpToDiff]) const setCollapsed = useCallback( diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 673d7cce0a..7e48ac0e5d 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -319,7 +319,7 @@ export const PullRequestAccordion: React.FC<{ <Accordion.Item value={header?.text ?? ''} className="border-cn-borders-2 rounded-3 border"> <Accordion.Trigger className="bg-cn-background-2 rounded-tl-3 rounded-tr-3 px-4 py-2 [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" - headerClassName="sticky top-[136px] z-10 border-cn-borders-2 border-b" + headerClassName="sticky top-[138px] z-10 border-cn-borders-2 border-b" > <LineTitle header={header} diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 7612d6f797..101db18950 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -189,7 +189,11 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = // } return ( - <Layout.Horizontal align="center" justify="between" className="gap-x-5 sticky top-[100px] z-20 bg-cn-background-1"> + <Layout.Horizontal + align="center" + justify="between" + className="gap-x-5 sticky top-[100px] z-20 bg-cn-background-1 pb-2 pt-1" + > <Layout.Horizontal className="grow gap-x-5"> <DropdownMenu.Root> <DropdownMenu.Trigger className="group flex items-center gap-x-1.5"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx index 1067506a49..053edfd0a5 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx @@ -151,14 +151,14 @@ function PullRequestChangesInternal({ useEffect(() => { if (jumpToDiff) { - jumpToFile(jumpToDiff, diffBlocks, setJumpToDiff) + jumpToFile(jumpToDiff, diffBlocks, setJumpToDiff, undefined, diffsContainerRef) } if (commentId) { data.map(diffItem => { const fileComments = getFileComments(diffItem) const diffHasComment = fileComments.some(thread => thread.some(comment => String(comment.id) === commentId)) if (commentId && diffHasComment) { - jumpToFile(diffItem.text, diffBlocks, setJumpToDiff, commentId) + jumpToFile(diffItem.text, diffBlocks, setJumpToDiff, commentId, diffsContainerRef) } }) } @@ -186,7 +186,7 @@ function PullRequestChangesInternal({ ) || [] return ( - <div className="pt-4" key={item.filePath}> + <div className={`${blockIndex === 0 ? 'pt-2' : 'pt-4'}`} key={item.filePath}> <InViewDiffRenderer key={item.filePath} blockName={innerBlockName(item.filePath)} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts index a3651c9e72..b9d34061f6 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts @@ -1,3 +1,5 @@ +import { RefObject } from 'react' + import { TypesUser } from '@/types' import { dispatchCustomEvent } from '@hooks/use-event-listener' import { get, isEmpty } from 'lodash-es' @@ -278,16 +280,20 @@ export const jumpToFile = ( filePath: string, diffBlocks: DiffHeaderProps[][], setJumpToDiff: (filePath: string) => void, - commentId?: string + commentId?: string, + diffsContainerRef?: RefObject<Element> ) => { let loopCount = 0 + let timeoutId: NodeJS.Timeout | null = null const blockIndex = diffBlocks.findIndex(block => block.some(diff => diff.filePath === filePath)) - if (blockIndex < 0) return + if (blockIndex < 0) return () => {} // Return empty cleanup function function attemptScroll() { // Retrieve the top-level block + the sub-block + the final diff DOM - const outerDOM = document.querySelector(`[data-block="${outterBlockName(blockIndex)}"]`) as HTMLElement | null + const outerDOM = diffsContainerRef?.current?.querySelector( + `[data-block="${outterBlockName(blockIndex)}"]` + ) as HTMLElement | null const innerDOM = outerDOM?.querySelector(`[data-block="${innerBlockName(filePath)}"]`) as HTMLElement | null const diffDOM = innerDOM?.querySelector(`[data-diff-file-path="${filePath}"]`) as HTMLElement | null @@ -303,9 +309,9 @@ export const jumpToFile = ( }) } - // Re-check after a short delay if it’s truly in viewport + // Re-check after a short delay if it's truly in viewport // If not in viewport and loopCount < 100 => re-run - setTimeout(() => { + timeoutId = setTimeout(() => { if (loopCount++ < 100) { if ( !outerDOM || @@ -319,11 +325,20 @@ export const jumpToFile = ( } } else { setJumpToDiff('') + timeoutId = null } }, 0) } attemptScroll() + + // Return cleanup function + return () => { + if (timeoutId) { + clearTimeout(timeoutId) + timeoutId = null + } + } } export const getDefaultReviewersApprovalCount = (data: DefaultReviewersApprovalsData): string => { From 60d6ab390d671a68510c6daebc9ae959669541d9 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Thu, 7 Aug 2025 14:25:25 +0000 Subject: [PATCH 019/180] fix: diff file links in case of delete branch (#10145) * 02d5b4 fix: diff file links in case of delete branch --- .../pull-request/pull-request-changes.tsx | 27 +++++++++++ .../components/pull-request-accordian.tsx | 11 +++-- .../changes/pull-request-changes.tsx | 5 +- .../conversation/pull-request-panel.tsx | 46 +++++++++++++------ .../details/pull-request-changes-page.tsx | 5 +- 5 files changed, 74 insertions(+), 20 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx index 058b95a9d3..6be115fca2 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx @@ -16,6 +16,7 @@ import { useFileViewAddPullReqMutation, useFileViewDeletePullReqMutation, useFileViewListPullReqQuery, + useGetBranchQuery, useListPrincipalsQuery, useListPullReqActivitiesQuery, useRawDiffQuery, @@ -171,6 +172,31 @@ export default function PullRequestChanges() { { enabled: !!repoRef && !!diffApiPath } ) + const { error: sourceBranchError } = useGetBranchQuery( + { + repo_ref: repoRef, + branch_name: pullReqMetadata?.source_branch || '', + queryParams: { include_checks: true, include_rules: true } + }, + { + // Don't cache the result to ensure we always get fresh data + cacheTime: 0, + staleTime: 0 + } + ) + + const currentRefForDiff = useMemo(() => { + if (sourceBranchError) { + return pullReqMetadata?.source_sha + } + + if (!sourceBranchError && pullReqMetadata?.source_branch) { + return pullReqMetadata.source_branch + } else if (!sourceBranchError && sourceRef) { + return sourceRef + } else return commitSHA + }, [sourceBranchError, pullReqMetadata?.source_branch, pullReqMetadata?.source_sha, sourceRef, commitSHA]) + useEffect(() => { if (PRDiffStats) { setPullReqStats(PRDiffStats) @@ -525,6 +551,7 @@ export default function PullRequestChanges() { isMfe ? `/repos/${repoId}/${path}` : `/${spaceId}/repos/${repoId}/${path}` } isApproving={isApproving} + currentRefForDiff={currentRefForDiff} /> </> ) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 7e48ac0e5d..8cf02fb285 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -80,6 +80,7 @@ interface LineTitleProps { useFullDiff?: boolean toRepoFileDetails?: ({ path }: { path: string }) => string sourceBranch?: string + currentRefForDiff?: string } export const LineTitle: React.FC<LineTitleProps> = ({ @@ -93,7 +94,8 @@ export const LineTitle: React.FC<LineTitleProps> = ({ toggleFullDiff, useFullDiff, toRepoFileDetails, - sourceBranch + sourceBranch, + currentRefForDiff }) => { const { t } = useTranslation() const { text, addedLines, deletedLines, filePath, checksumAfter } = header @@ -114,7 +116,7 @@ export const LineTitle: React.FC<LineTitleProps> = ({ <IconV2 name={useFullDiff ? 'collapse-code' : 'expand-code'} /> </Button> <Link - to={toRepoFileDetails?.({ path: `files/${sourceBranch}/~/${filePath}` }) ?? ''} + to={toRepoFileDetails?.({ path: `files/${currentRefForDiff || sourceBranch}/~/${filePath}` }) ?? ''} className="font-medium leading-tight text-cn-foreground-1" > {text} @@ -185,6 +187,7 @@ export const PullRequestAccordion: React.FC<{ principalProps: PrincipalPropsType hideViewedCheckbox?: boolean addWidget?: boolean + currentRefForDiff?: string }> = ({ header, diffMode, @@ -217,7 +220,8 @@ export const PullRequestAccordion: React.FC<{ sourceBranch, principalProps, hideViewedCheckbox = false, - addWidget = true + addWidget = true, + currentRefForDiff }) => { const { t: _ts } = useTranslation() const { highlight, wrap, fontsize } = useDiffConfig() @@ -333,6 +337,7 @@ export const PullRequestAccordion: React.FC<{ useFullDiff={useFullDiff} toRepoFileDetails={toRepoFileDetails} sourceBranch={sourceBranch} + currentRefForDiff={currentRefForDiff} /> </Accordion.Trigger> <Accordion.Content className="pb-0" containerClassName="rounded-bl-3 rounded-br-3"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx index 053edfd0a5..bacb15e902 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx @@ -55,6 +55,7 @@ interface DataProps { toRepoFileDetails?: ({ path }: { path: string }) => string pullReqMetadata?: TypesPullReq principalProps: PrincipalPropsType + currentRefForDiff?: string } function PullRequestChangesInternal({ @@ -86,7 +87,8 @@ function PullRequestChangesInternal({ setJumpToDiff, toRepoFileDetails, pullReqMetadata, - principalProps + principalProps, + currentRefForDiff }: DataProps) { const [openItems, setOpenItems] = useState<string[]>([]) const diffBlocks = useMemo(() => chunk(data, PULL_REQUEST_DIFF_RENDERING_BLOCK_SIZE), [data]) @@ -227,6 +229,7 @@ function PullRequestChangesInternal({ setCollapsed={val => setCollapsed(item.text, val)} toRepoFileDetails={toRepoFileDetails} sourceBranch={pullReqMetadata?.source_branch} + currentRefForDiff={currentRefForDiff} /> </InViewDiffRenderer> </div> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index ae77085aae..55067f2856 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -79,15 +79,31 @@ interface ButtonStateProps { } const HeaderTitle = ({ ...props }: HeaderProps) => { - const { pullReqMetadata, spaceId, repoId } = props + const { + pullReqMetadata, + spaceId, + repoId, + onRevertPR, + onDeleteBranch, + onRestoreBranch, + headerMsg, + showDeleteBranchButton, + showRestoreBranchButton, + isDraft, + isClosed, + unchecked, + mergeable, + isOpen, + ruleViolation + } = props const areRulesBypassed = pullReqMetadata?.merge_violations_bypassed const mergeMethod = getMergeMethodDisplay(pullReqMetadata?.merge_method as MergeStrategy) - if (props?.pullReqMetadata?.state === PullRequestFilterOption.MERGED) { + if (pullReqMetadata?.state === PullRequestFilterOption.MERGED) { return ( <> <div className="inline-flex w-full items-center justify-between gap-2"> <Text className="flex items-center space-x-1" variant="body-single-line-strong" as="h2" color="foreground-1"> - <span>{props?.pullReqMetadata?.merger?.display_name}</span> + <span>{pullReqMetadata?.merger?.display_name}</span> <span>{areRulesBypassed ? `bypassed rules and ${mergeMethod}` : `${mergeMethod}`}</span> <span>into</span> <PullRequestBranchBadge @@ -104,23 +120,23 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { <TimeAgoCard timestamp={pullReqMetadata?.merged} /> </Text> <Layout.Horizontal> - <Button variant="secondary" onClick={props.onRevertPR}> + <Button variant="secondary" onClick={onRevertPR}> Revert </Button> - {props.showDeleteBranchButton ? ( - <Button variant="primary" theme="danger" onClick={props.onDeleteBranch}> + {showDeleteBranchButton ? ( + <Button variant="primary" theme="danger" onClick={onDeleteBranch}> Delete Branch </Button> - ) : props.showRestoreBranchButton ? ( - <Button variant="outline" onClick={props.onRestoreBranch}> + ) : showRestoreBranchButton ? ( + <Button variant="outline" onClick={onRestoreBranch}> Restore Branch </Button> ) : null} </Layout.Horizontal> </div> - {props.headerMsg && ( + {headerMsg && ( <div className="flex w-full justify-end"> - <span className="text-1 text-cn-foreground-danger">{props.headerMsg}</span> + <span className="text-1 text-cn-foreground-danger">{headerMsg}</span> </div> )} </> @@ -130,15 +146,15 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { return ( <div className="inline-flex items-center gap-2"> <Text variant="body-single-line-strong" as="h2" color="foreground-1"> - {props.isDraft + {isDraft ? 'This pull request is still a work in progress' - : props.isClosed + : isClosed ? 'This pull request is closed' - : props.unchecked + : unchecked ? 'Checking for ability to merge automatically...' - : props.mergeable === false && props.isOpen + : mergeable === false && isOpen ? 'Cannot merge pull request' - : props.ruleViolation + : ruleViolation ? 'Cannot merge pull request' : `Pull request can be merged`} </Text> diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index a08e32c879..38387afc58 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -58,6 +58,7 @@ interface RepoPullRequestChangesPageProps { jumpToDiff?: string setJumpToDiff: (fileName: string) => void toRepoFileDetails?: ({ path }: { path: string }) => string + currentRefForDiff?: string principalProps: PrincipalPropsType } const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ @@ -100,7 +101,8 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ jumpToDiff, setJumpToDiff, toRepoFileDetails, - principalProps + principalProps, + currentRefForDiff }) => { const { diffs, pullReqStats } = usePullRequestProviderStore() @@ -173,6 +175,7 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ setJumpToDiff={setJumpToDiff} toRepoFileDetails={toRepoFileDetails} pullReqMetadata={pullReqMetadata} + currentRefForDiff={currentRefForDiff} /> ) } From e75b62dd51ab1be62b89d5d9dfdd57f8d394261a Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Thu, 7 Aug 2025 14:28:09 +0000 Subject: [PATCH 020/180] feat: move current author to top (#10136) * 78417c feat: move current author to top --- .../pull-request/pull-request-list-page.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index 755177002a..029504c3ae 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -110,6 +110,25 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ return principalData || (defaultSelectedAuthor && !principalsSearchQuery ? [defaultSelectedAuthor] : []) }, [principalData, defaultSelectedAuthor, principalsSearchQuery]) + const userSelectOptions = useMemo(() => { + const otherUserOptions = computedPrincipalData + .filter(user => !currentUser?.id || String(user?.id) !== String(currentUser?.id)) + .map(user => ({ + label: user?.display_name || '', + value: String(user?.id) + })) + + if (currentUser?.id && !principalsSearchQuery) { + const currentUserOption = { + label: currentUser.display_name || '', + value: String(currentUser.id) + } + return [currentUserOption, ...otherUserOptions] + } + + return otherUserOptions + }, [computedPrincipalData, currentUser, principalsSearchQuery]) + const labelsFilterConfig: CustomFilterOptionConfig<keyof PRListFilters, LabelsValue> = { label: t('views:repos.prListFilterOptions.labels.label', 'Label'), value: 'label_by', @@ -169,10 +188,7 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ isProjectLevel, isPrincipalsLoading, customFilterOptions, - principalData: computedPrincipalData.map(userInfo => ({ - label: userInfo?.display_name || '', - value: String(userInfo?.id) - })), + principalData: userSelectOptions, scope }) From 83074a63d10469f96c0b35c6b1edb9fce6958a4a Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Thu, 7 Aug 2025 14:56:11 +0000 Subject: [PATCH 021/180] Fix fetch errors for getting inherited label values (#10139) * dc0cce fix fetch errors for inherited label values * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 50f0dc Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 38093f Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 15873a Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 39c940 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b0da05 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 75c316 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * ccdae0 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * c37051 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PRO --- ...el-store-with-project-label-values-data.ts | 41 +++++++++++-------- packages/ui/src/views/common/types.ts | 8 ++++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts b/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts index 8eb43c60c1..6b43c1f24a 100644 --- a/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts +++ b/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts @@ -1,7 +1,7 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { listSpaceLabelValues, useListSpaceLabelsQuery } from '@harnessio/code-service-client' -import { ILabelType, LabelValuesType, LabelValueType } from '@harnessio/ui/views' +import { ILabelType, LabelValuesType, LabelValueType, ScopeValue } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../../framework/hooks/useGetSpaceParam' import { PageResponseHeader } from '../../../../types' @@ -28,15 +28,8 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ const space_ref = useGetSpaceURLParam() const [isLoadingValues, setIsLoadingValues] = useState(false) - const { - labels: storeLabels, - setLabels, - setValues, - setRepoSpaceRef, - resetLabelsAndValues, - setIsLoading, - getParentScopeLabels - } = useLabelsStore() + const { setLabels, setValues, setRepoSpaceRef, resetLabelsAndValues, setIsLoading, getParentScopeLabels } = + useLabelsStore() const { data: { body: labels, headers } = {}, isLoading: isLoadingSpaceLabels } = useListSpaceLabelsQuery( { @@ -57,6 +50,8 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ } }, [resetLabelsAndValues]) + const labelsData = useMemo(() => (labels || []) as ILabelType[], [labels]) + /** * Get values for each label * @@ -67,8 +62,9 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ // I use useLabelsStore.getState().labels to retrieve data synchronously, // ensuring I get the latest state immediately without waiting for React's re-renders or state updates. // If I use storeLabels, the data in this hook will not be updated immediately after clearing the store. - const syncStoreLabelsData = useLabelsStore.getState().labels - if (!space_ref || !syncStoreLabelsData.length) return + // const syncStoreLabelsData = useLabelsStore.getState().labels + + if (!space_ref || !labelsData.length) return const controller = new AbortController() const { signal } = controller @@ -76,10 +72,23 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ const fetchAllLabelValues = async () => { setIsLoadingValues(true) - const promises = syncStoreLabelsData.reduce<Promise<LabelValuesResponseResultType>[]>((acc, item) => { + const promises = labelsData.reduce<Promise<LabelValuesResponseResultType>[]>((acc, item) => { if (item.value_count !== 0) { + let ref = space_ref + + switch (item.scope as ScopeValue) { + case ScopeValue.Account: { + ref = space_ref.split('/').at(0) ?? space_ref + break + } + case ScopeValue.Organization: { + ref = space_ref.split('/').slice(0, 2).join('/') + break + } + } + acc.push( - listSpaceLabelValues({ space_ref: `${space_ref}/+`, key: item.key, signal }).then( + listSpaceLabelValues({ space_ref: `${ref}/+`, key: item.key, signal }).then( data => ({ key: item.key, data: data.body as LabelValueType[] }), error => ({ key: item.key, error }) ) @@ -115,7 +124,7 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ return () => { controller.abort() } - }, [storeLabels, space_ref, setValues]) + }, [labelsData, space_ref]) /** * Set labels data from API to store diff --git a/packages/ui/src/views/common/types.ts b/packages/ui/src/views/common/types.ts index 103be45f74..2401d5a91d 100644 --- a/packages/ui/src/views/common/types.ts +++ b/packages/ui/src/views/common/types.ts @@ -10,3 +10,11 @@ export enum ScopeType { Project = 'Project', Repository = 'Repository' } + +export enum ScopeValue { + // Backend 'scope' Values + Repository = 0, + Account = 1, + Organization = 2, + Project = 3 +} From e5ea02850bd8ed0a84dd6d8b463c1c56a3186f24 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Thu, 7 Aug 2025 15:51:42 +0000 Subject: [PATCH 022/180] fix: update description when checkboxes are checked/unchecked (#10146) * b1782c fix: update description when checkboxes are checked/unchecked --- .../src/components/markdown-viewer/index.tsx | 43 ++++++++++++++++++- .../pull-request-description-box.tsx | 10 ++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/index.tsx b/packages/ui/src/components/markdown-viewer/index.tsx index dbec04a42f..9cc84e9b08 100644 --- a/packages/ui/src/components/markdown-viewer/index.tsx +++ b/packages/ui/src/components/markdown-viewer/index.tsx @@ -31,6 +31,7 @@ type MarkdownViewerProps = { isSuggestion?: boolean markdownClassName?: string showLineNumbers?: boolean + onCheckboxChange?: (source: string) => void } export function MarkdownViewer({ @@ -42,12 +43,19 @@ export function MarkdownViewer({ suggestionCheckSum, isSuggestion, markdownClassName, - showLineNumbers = false + showLineNumbers = false, + onCheckboxChange }: MarkdownViewerProps) { const { navigate } = useRouterContext() const refRootHref = useMemo(() => document.getElementById('repository-ref-root')?.getAttribute('href'), []) const ref = useRef<HTMLDivElement>(null) + // Track checkbox indices and set to data-checkbox-index + const checkboxCounter = useRef<number>(0) + + // Reset checkbox counter at the start of each render + checkboxCounter.current = 0 + const styles: CSSProperties = maxHeight ? { maxHeight } : {} const rewriteRelativeLinks = useCallback( @@ -130,6 +138,36 @@ export function MarkdownViewer({ [navigate] ) + // Handle checkbox state changes + const handleCheckboxChange = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + if (onCheckboxChange) { + const TODO_LIST_ITEM_CLASS = 'task-list-item' + const targetIsListItem = (event.target as HTMLElement).classList.contains(TODO_LIST_ITEM_CLASS) + const target = (event.target as HTMLElement)?.closest?.(`.${TODO_LIST_ITEM_CLASS}`) + const input = target?.firstElementChild as HTMLInputElement + const checked = targetIsListItem ? !input?.checked : input?.checked + const checkboxIndex = parseInt(event.target.getAttribute('data-checkbox-index') || '0', 10) + let currentCheckboxIndex = 0 + const newContent = source + .split('\n') + .map(line => { + if (line.startsWith('- [ ]') || line.startsWith('- [x]')) { + currentCheckboxIndex++ + + if (checkboxIndex === currentCheckboxIndex) { + return checked ? line.replace('- [ ]', '- [x]') : line.replace('- [x]', '- [ ]') + } + } + return line + }) + .join('\n') + onCheckboxChange(newContent) + } + }, + [onCheckboxChange, source] + ) + useEffect(() => { const container = ref.current @@ -173,11 +211,14 @@ export function MarkdownViewer({ input: ({ type, checked, ...props }) => { // checkbox inputs if (type === 'checkbox') { + checkboxCounter.current++ return ( <input type="checkbox" defaultChecked={checked} {...props} + data-checkbox-index={checkboxCounter.current} + onChange={handleCheckboxChange} // Removed disabled to make checkbox interactive disabled={undefined} /> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx index d62ddbdebf..a27ce3ee00 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx @@ -123,7 +123,15 @@ const PullRequestDescBox: FC<PullRequestDescBoxProps> = ({ ) : description ? ( /** View mode */ <Text className="flex-1" color="foreground-1"> - {description && <MarkdownViewer source={description} />} + {description && ( + <MarkdownViewer + source={comment} + onCheckboxChange={updatedDescription => { + setComment(updatedDescription) + handleUpdateDescription(title || '', updatedDescription) + }} + /> + )} </Text> ) : ( /** No description */ From 3e377a9adbe8c12f6967f4c2e8df9da74bcdcb45 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Thu, 7 Aug 2025 16:08:05 +0000 Subject: [PATCH 023/180] feat: design fixes webhook executions list page, execution details and webhook create (#10140) * c67458 fix: wodth * 229ba0 fix: webhook create page * 005957 feat: design fixes executions and list pages --- packages/ui/locales/en/views.json | 2 +- .../components/create-webhooks-form-fields.tsx | 6 ++++-- .../webhooks/webhook-create/repo-webhook-create-page.tsx | 4 ++-- .../components/webhook-executions-editor-control-bar.tsx | 4 ++-- .../repo-webhook-execution-details-page.tsx | 7 +++++-- .../repo-webhook-executions-list-page.tsx | 4 ++-- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index c1aa9a2e72..d9da6b5a57 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -11,7 +11,7 @@ "branches": "Branches", "search": "Search", "settings": "Settings", - "descriptionPlaceholder": "Enter a short description for the label", + "descriptionPlaceholder": "Enter a description", "description": "Description", "error": "Error:", "saving": "Saving…", diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-fields.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-fields.tsx index 771d9be7cd..396eed2586 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-fields.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-fields.tsx @@ -39,6 +39,8 @@ export const WebhookDescriptionField: FC<WebhookFormFieldProps> = ({ register }) {...register('description')} placeholder={t('views:repos.descriptionPlaceholder', 'Enter a description')} label={t('views:repos.description', 'Description')} + resizable + className="min-h-[136px]" /> ) } @@ -78,7 +80,7 @@ export const WebhookSSLVerificationField: FC<WebhookFormFieldProps> = ({ registe label={t('views:repos.sslVerification', 'SSL Verification')} id="insecure" {...register('insecure')} - className="gap-y-5" + className="gap-y-3" > <Radio.Item id="enable-ssl" value="1" label={t('views:repos.sslVerificationLabel', 'Enable SSL Verification')} /> <Radio.Item @@ -98,7 +100,7 @@ export const WebhookTriggerField: FC<WebhookFormFieldProps> = ({ register }) => label={t('views:repos.evenTriggerLabel', 'Which events would you like to use to trigger this webhook?')} id="trigger" {...register('trigger')} - className="gap-y-5" + className="gap-y-3" > <Radio.Item id="all-events" value="1" label={t('views:repos.evenTriggerAllLabel', 'Send me everything')} /> <Radio.Item diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx index 310c3250ce..be4dfd3789 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx @@ -92,8 +92,8 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ } return ( - <SandboxLayout.Content className="max-w-[570px] ml-3"> - <Text as="h1" variant="heading-section" className="mb-4"> + <SandboxLayout.Content className="max-w-[632px]"> + <Text as="h1" variant="heading-section" className="mb-2"> {preSetWebhookData ? t('views:repos.editWebhookTitle', 'Order Status Update Webhook') : t('views:repos.createWebhookTitle', 'Create a webhook')} diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/components/webhook-executions-editor-control-bar.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/components/webhook-executions-editor-control-bar.tsx index 45cad450f0..b8813b13fa 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/components/webhook-executions-editor-control-bar.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/components/webhook-executions-editor-control-bar.tsx @@ -19,8 +19,8 @@ export interface FileEditorControlBarProps { export const WebhookExecutionEditorControlBar: FC<FileEditorControlBarProps> = ({ view, onChangeView }) => { return ( - <StackedList.Root onlyTopRounded borderBackground> - <StackedList.Item disableHover isHeader className="px-3 py-1 "> + <StackedList.Root onlyTopRounded borderBackground className="border-cn-borders-3"> + <StackedList.Item disableHover isHeader className="px-4 py-1"> <Tabs.Root defaultValue={view} onValueChange={onChangeView}> <Tabs.List variant="ghost"> <Tabs.Trigger value={WebhookExecutionView.PAYLOAD}>{TAB_LABELS[WebhookExecutionView.PAYLOAD]}</Tabs.Trigger> diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx index 821eab5114..5b6397079d 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx @@ -86,8 +86,8 @@ export const RepoWebhookExecutionDetailsPage: FC<RepoWebhookExecutionDetailsPage } return ( - <SandboxLayout.Main className="ml-3"> - <SandboxLayout.Content> + <SandboxLayout.Main> + <SandboxLayout.Content maxWidth="5xl" className="ml-0.5"> <ListActions.Root> <ListActions.Left> <Text variant="heading-section">#{executionId}</Text> @@ -143,6 +143,7 @@ export const RepoWebhookExecutionDetailsPage: FC<RepoWebhookExecutionDetailsPage </div> <Spacer size={8} /> <WebhookExecutionEditorControlBar view={view} onChangeView={onChangeView} /> + {/* <div className="rounded-b-3 border-cn-borders-3 border-x border-b"> */} <CodeEditor height="500px" language={view === 'payload' ? 'json' : 'html'} @@ -153,7 +154,9 @@ export const RepoWebhookExecutionDetailsPage: FC<RepoWebhookExecutionDetailsPage options={{ readOnly: true }} + className="rounded-b-3 border-cn-borders-3 max-w-full" /> + {/* </div> */} </SandboxLayout.Content> </SandboxLayout.Main> ) diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx index eee91edeaf..d6fe6e86fe 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx @@ -33,7 +33,7 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ }, [t]) return ( - <SandboxLayout.Main className="ml-3"> + <SandboxLayout.Main> <SandboxLayout.Content> <Text as="h1" variant="heading-section" className="mb-2"> Order Status Update Webhook @@ -50,7 +50,7 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ <Skeleton.List /> ) : executions && executions.length > 0 ? ( <> - <Table.Root disableHighlightOnHover> + <Table.Root disableHighlightOnHover size="compact"> <Table.Header> <Table.Row> <Table.Head className="w-[136px]"> From 04628f163232d4da2b6e4573e4c38bff5b080856 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Thu, 7 Aug 2025 17:29:50 +0000 Subject: [PATCH 024/180] Merge page - Reviewers and closed pr design fixes (#10147) * 2d6635 Merge design fixes * d5ed0a Merge remote-tracking branch 'origin/main' into merge-page-design * 23279c Reverted label * c847f5 Fixed padding issues * 3c2fac Merge page design fixed --- packages/ui/src/components/tag.tsx | 2 +- .../components/labels/pull-request-labels-list.tsx | 1 + .../components/conversation/pull-request-panel.tsx | 12 +++++++----- .../sections/pull-request-comment-section.tsx | 1 + 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/tag.tsx b/packages/ui/src/components/tag.tsx index cc7612a4c1..984b422eae 100644 --- a/packages/ui/src/components/tag.tsx +++ b/packages/ui/src/components/tag.tsx @@ -123,7 +123,7 @@ function Tag({ </span> {showReset && !disabled && ( <button onClick={onReset}> - <IconV2 size="xs" name="xmark" className="cn-tag-icon" /> + <IconV2 size="2xs" name="xmark" className="cn-tag-icon" /> </button> )} {showCopyButton ? ( diff --git a/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx b/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx index e5058aa3b3..cecaf8d97e 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx @@ -24,6 +24,7 @@ export const LabelsList: FC<LabelsListProps> = ({ labels, className, showReset, <Tag key={label.key} variant="secondary" + size="sm" label={label.key} value={label.value || ''} theme={label.color} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 55067f2856..9ffde4db7b 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -566,7 +566,7 @@ const PullRequestPanel = ({ ) : null })()} {actions && pullReqMetadata?.closed ? ( - <Button variant="primary" theme="default" onClick={actions[0].action}> + <Button variant="primary" theme="default" size="sm" onClick={actions[0].action}> {actions[0].title} </Button> ) : null} @@ -607,7 +607,7 @@ const PullRequestPanel = ({ <Layout.Vertical className="w-full gap-1"> <TextInput id="merge-title" - label="Commit Message" + label="Commit message" className="w-full bg-cn-background-1" value={mergeTitle} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMergeTitle(e.target.value)} @@ -616,7 +616,7 @@ const PullRequestPanel = ({ /> <Textarea id="merge-message" - label="Commit Description" + label="Commit description" className="w-full" value={mergeMessage} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMergeMessage(e.target.value)} @@ -691,12 +691,14 @@ const PullRequestPanel = ({ <span className="text-2 text-cn-foreground-1"> branch has unmerged changes.</span> </Layout.Horizontal> {showDeleteBranchButton && ( - <Button theme="danger" onClick={onDeleteBranch}> + <Button theme="danger" size="sm" onClick={onDeleteBranch}> Delete Branch </Button> )} {!showDeleteBranchButton && showRestoreBranchButton && ( - <Button onClick={onRestoreBranch}>Restore Branch</Button> + <Button size="sm" onClick={onRestoreBranch}> + Restore Branch + </Button> )} </Layout.Horizontal> )} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx index 4190d9ccf5..e57d8b4c7e 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx @@ -20,6 +20,7 @@ const PullRequestCommentSection = ({ commentsInfo, handleAction }: PullRequestMe text={commentsInfo.header} icon={ <IconV2 + size="md" className={isSuccess ? 'text-cn-foreground-success' : 'text-cn-foreground-danger'} name={isSuccess ? 'check-circle-solid' : 'warning-triangle'} /> From a08e7f18715415fe148b303720d590c64a68da59 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Thu, 7 Aug 2025 19:13:19 +0000 Subject: [PATCH 025/180] fix: p2/p3 bugs across the app (#10149) * af0e47 fix: minor bug * beb353 feat: add split buttoin to no-data, fix: nodata on rules * c07716 fix: empty state for tags * bb991e chore: rebase * 0c8d0c fix: delete branch name displayed --- .../src/pages-v2/repo/repo-branch-list.tsx | 2 +- packages/ui/locales/en/views.json | 4 + packages/ui/locales/fr/views.json | 4 + .../dialogs/delete-alert-dialog.tsx | 4 +- packages/ui/src/components/no-data.tsx | 28 +++- .../layouts/webhooks-settings-layout.tsx | 2 +- .../repo-settings-general-rules.tsx | 136 ++++++++++-------- 7 files changed, 111 insertions(+), 69 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx index b6a6e506ca..9665d92541 100644 --- a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx @@ -86,8 +86,8 @@ export function RepoBranchesListPage() { } const handleResetDeleteBranch = () => { - setDeleteBranchName(null) setIsDeleteDialogOpen(false) + setDeleteBranchName(null) if (deleteBranchError) { resetDeleteBranch() } diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index d9da6b5a57..b47c1a89e6 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -215,6 +215,8 @@ "tagRules": "Tag Rules", "newTagRule": "New tag rule", "createBranchRule": "New branch rule", + "createBranchRuleButton": "Create Branch rule", + "createTagRuleButton": "Create tag rule", "security": "Security", "secretScanning": "Secret scanning", "secretScanningDescription": "Block commits containing secrets like passwords, API keys and tokens.", @@ -430,6 +432,8 @@ "createSecret": "Create new secret.", "noUsers": "No Users Found", "noUsersDescription": "There are no users in this scope. Click on the button below to start adding them.", + "createTagRuleButton": "Create Tag rule", + "createTagRule": "Create Tag rule", "createBranch": "Create new branch", "createConnector": "Create new connector.", "noConnectorsProject": "There are no connectors in this project yet.", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index e72e5495cb..7605d88c44 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -215,6 +215,8 @@ "tagRules": "Tag Rules", "newTagRule": "New tag rule", "createBranchRule": "New branch rule", + "createBranchRuleButton": "Create Branch rule", + "createTagRuleButton": "Create tag rule", "security": "Sécurité", "secretScanning": "Analyse des secrets", "secretScanningDescription": "Empêcher les commits contenant des secrets comme les mots de passe ou jetons API.", @@ -422,6 +424,8 @@ "createSecret": "Créer un nouveau secret.", "noUsers": "Aucun utilisateur trouvé", "noUsersDescription": "Il n'y a aucun utilisateur dans ce périmètre. Cliquez sur le bouton ci-dessous pour commencer à les ajouter.", + "createTagRuleButton": "Create Tag rule", + "createTagRule": "Create Tag rule", "createBranch": "Créer une nouvelle branche", "noPullRequests": "Il n'y a pas encore de demandes de tirage dans ce projet.", "createConnector": "Créez un nouveau connecteur.", diff --git a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx index c70699ea5c..a8b03449c8 100644 --- a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx +++ b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx @@ -67,7 +67,7 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ return ( <AlertDialog.Root theme="danger" open={open} onOpenChange={onClose} onConfirm={handleDelete} loading={isLoading}> <AlertDialog.Content title={t('component:deleteDialog.title', 'Are you sure?')}> - {displayMessageContent} + <div className="overflow-hidden break-words text-wrap">{displayMessageContent}</div> {withForm && ( <Fieldset> <Input @@ -107,7 +107,7 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ <AlertDialog.Cancel /> <AlertDialog.Confirm disabled={violation && !bypassable}> - {violation && bypassable ? `Bypass rules and delete ${type}` : `Yes, delete ${type}`} + {violation && bypassable ? `Bypass rules and delete` : `Yes`} </AlertDialog.Confirm> </AlertDialog.Content> </AlertDialog.Root> diff --git a/packages/ui/src/components/no-data.tsx b/packages/ui/src/components/no-data.tsx index 108509757e..d5c02e89f1 100644 --- a/packages/ui/src/components/no-data.tsx +++ b/packages/ui/src/components/no-data.tsx @@ -1,6 +1,6 @@ import { Dispatch, FC, ReactNode, SetStateAction } from 'react' -import { Button, ButtonProps, Illustration, IllustrationsNameType, Layout, Text } from '@/components' +import { Button, ButtonProps, Illustration, IllustrationsNameType, Layout, SplitButton, Text } from '@/components' import { useRouterContext } from '@/context' import { cn } from '@utils/cn' @@ -26,6 +26,13 @@ export interface NoDataProps { setLoadState?: Dispatch<SetStateAction<string>> textWrapperClassName?: string className?: string + splitButton?: { + label: ReactNode | string + options: { value: string; label: string }[] + handleOptionChange: (option: string) => void + handleButtonClick: () => void + props?: ButtonProps + } } export const NoData: FC<NoDataProps> = ({ @@ -38,7 +45,8 @@ export const NoData: FC<NoDataProps> = ({ secondaryButton, withBorder = false, textWrapperClassName, - className + className, + splitButton }) => { const { NavLink } = useRouterContext() @@ -64,7 +72,7 @@ export const NoData: FC<NoDataProps> = ({ </Text> ))} </Layout.Vertical> - {(primaryButton || secondaryButton) && ( + {(primaryButton || secondaryButton || splitButton) && ( <Layout.Horizontal gap="sm"> {primaryButton && (primaryButton.to ? ( @@ -91,6 +99,20 @@ export const NoData: FC<NoDataProps> = ({ {secondaryButton.label} </Button> ))} + {splitButton && ( + <SplitButton<string> + dropdownContentClassName="mt-0 min-w-[170px]" + handleButtonClick={() => splitButton.handleButtonClick()} + handleOptionChange={option => { + if (option === 'tag-rule') { + splitButton.handleOptionChange(option) + } + }} + options={splitButton.options} + > + {splitButton.label} + </SplitButton> + )} </Layout.Horizontal> )} </Layout.Vertical> diff --git a/packages/ui/src/views/layouts/webhooks-settings-layout.tsx b/packages/ui/src/views/layouts/webhooks-settings-layout.tsx index 99e83d5694..e939ee9cd9 100644 --- a/packages/ui/src/views/layouts/webhooks-settings-layout.tsx +++ b/packages/ui/src/views/layouts/webhooks-settings-layout.tsx @@ -4,7 +4,7 @@ import { ContentLayoutWithSidebar } from '@/views' const getNavItems = (t: TFunctionWithFallback) => [ { groupId: 0, - title: t('views:repos.webhookSettings', 'Webhook Settings'), + // title: t('views:repos.webhookSettings', 'Webhook Settings'), items: [ { id: 0, title: t('views:repos.details', 'Details'), to: 'details' }, { id: 1, title: t('views:repos.executions', 'Executions'), to: 'executions' } diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index 1e5c679615..6bb962d894 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -122,53 +122,51 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ return ( <> + <ListActions.Root className="mt-1"> + <ListActions.Left> + <SearchInput + id="search" + defaultValue={rulesSearchQuery} + inputContainerClassName="max-w-80" + placeholder={t('views:repos.search', 'Search')} + onChange={handleSearchChange} + /> + </ListActions.Left> + <ListActions.Right> + <Select + options={[ + { label: t('views:repos.allRules', 'All Rules'), value: null }, + { label: t('views:repos.branchRules', 'Branch Rules'), value: 'branch' }, + { label: t('views:repos.tagRules', 'Tag Rules'), value: 'tag' } + ]} + value={ruleTypeFilter} + onChange={value => setRuleTypeFilter?.(value as 'branch' | 'tag' | 'push' | null)} + size="md" + triggerClassName="min-w-[150px]" + /> + <SplitButton<string> + dropdownContentClassName="mt-0 min-w-[170px]" + handleButtonClick={() => navigate(toRepoBranchRuleCreate?.() || '')} + handleOptionChange={option => { + if (option === 'tag-rule') { + navigate(toRepoTagRuleCreate?.() || '') + } + }} + options={[ + { + value: 'tag-rule', + label: t('views:repos.newTagRule', 'New tag rule') + } + ]} + > + {t('views:repos.createBranchRule', 'New branch rule')} + </SplitButton> + </ListActions.Right> + </ListActions.Root> + {isShowRulesContent ? ( <> - <> - <ListActions.Root className="mt-1"> - <ListActions.Left> - <SearchInput - id="search" - defaultValue={rulesSearchQuery} - inputContainerClassName="max-w-80" - placeholder={t('views:repos.search', 'Search')} - onChange={handleSearchChange} - /> - </ListActions.Left> - <ListActions.Right> - <Select - options={[ - { label: t('views:repos.allRules', 'All Rules'), value: null }, - { label: t('views:repos.branchRules', 'Branch Rules'), value: 'branch' }, - { label: t('views:repos.tagRules', 'Tag Rules'), value: 'tag' } - ]} - value={ruleTypeFilter} - onChange={value => setRuleTypeFilter?.(value as 'branch' | 'tag' | 'push' | null)} - size="md" - triggerClassName="min-w-[150px]" - /> - <SplitButton<string> - dropdownContentClassName="mt-0 min-w-[170px]" - handleButtonClick={() => navigate(toRepoBranchRuleCreate?.() || '')} - handleOptionChange={option => { - if (option === 'tag-rule') { - navigate(toRepoTagRuleCreate?.() || '') - } - }} - options={[ - { - value: 'tag-rule', - label: t('views:repos.newTagRule', 'New tag rule') - } - ]} - > - {t('views:repos.createBranchRule', 'New branch rule')} - </SplitButton> - </ListActions.Right> - </ListActions.Root> - - <Spacer size={3} /> - </> + <Spacer size={3} /> {isLoading ? ( <> <Spacer size={7} /> @@ -269,23 +267,37 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ )} </> ) : ( - <NoData - withBorder - className="py-cn-3xl min-h-0" - textWrapperClassName="max-w-[350px]" - imageName={'no-data-cog'} - title={t('views:noData.noRules', 'No rules yet')} - description={[ - t( - 'views:noData.noRulesDescription', - 'There are no rules in this project. Click on the button below to start adding rules.' - ) - ]} - primaryButton={{ - label: t('views:repos.createRuleButton', 'Create rule'), - to: toRepoBranchRuleCreate?.() ?? '' - }} - /> + <> + <Spacer size={3} /> + <NoData + withBorder + className="py-cn-3xl min-h-0" + textWrapperClassName="max-w-[350px]" + imageName={'no-data-cog'} + title={t('views:noData.noRules', 'No rules yet')} + description={[ + t( + 'views:noData.noRulesDescription', + 'There are no rules in this project. Click on the button below to start adding rules.' + ) + ]} + splitButton={{ + label: t('views:repos.createBranchRuleButton', 'Create Branch rule'), + options: [ + { + value: 'tag-rule', + label: t('views:repos.createTagRuleButton', 'Create tag rule') + } + ], + handleOptionChange: option => { + if (option === 'tag-rule') { + navigate(toRepoTagRuleCreate?.() || '') + } + }, + handleButtonClick: () => navigate(toRepoBranchRuleCreate?.() || '') + }} + /> + </> )} </> ) From 3c13ab47710e9266702d6b72ff0a115b1974fe9a Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Thu, 7 Aug 2025 20:24:41 +0000 Subject: [PATCH 026/180] fix comment box to insert uploaded images and videos at the text selection location within comment (#10151) * 1b1172 fix arg type * 4e1266 add logic to not add a newline when not needed * 04b581 fix comment box to insert uploaded images and videos at the text selection location within comment * 55c544 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 3d07a1 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * d70666 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 50f0dc Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 38093f Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 15873a Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 39c940 Merge branch 'main' of https://git0.harness.io --- .../hooks/usePRCommonInteractions.ts | 31 ++++++++++++++----- .../conversation/pull-request-comment-box.tsx | 8 ++--- .../repo/pull-request/pull-request.types.ts | 12 ++++++- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts b/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts index f4113697bc..7b2a2f02e0 100644 --- a/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts +++ b/apps/gitness/src/pages-v2/pull-request/hooks/usePRCommonInteractions.ts @@ -2,7 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react' import { commentCreatePullReq, commentDeletePullReq, commentUpdatePullReq } from '@harnessio/code-service-client' import { generateAlphaNumericHash } from '@harnessio/ui/utils' -import { CommitSuggestion } from '@harnessio/ui/views' +import { CommitSuggestion, TextSelection } from '@harnessio/ui/views' import { useAPIPath } from '../../../hooks/useAPIPath' import { getErrorMessage } from '../pull-request-utils' @@ -68,19 +68,34 @@ export function usePRCommonInteractions({ ) const handleUpload = useCallback( - (blob: File, setMarkdownContent: (data: string) => void, currentComment?: string) => { + ( + blob: File, + setMarkdownContent: (data: string) => void, + currentComment: string = '', + textSelection: TextSelection = { start: currentComment.length, end: currentComment.length } + ) => { const reader = new FileReader() + const isImageFile = (): boolean => blob.type.startsWith('image/') + const isVideoFile = (): boolean => blob.type.startsWith('video/') + // Set up a function to be called when the load event is triggered const handleLoad = async function () { - if (blob.type.startsWith('image/') || blob.type.startsWith('video/')) { + if (isImageFile() || isVideoFile()) { const markdown = await uploadImage(reader.result) - if (blob.type.startsWith('image/')) { - setMarkdownContent(`${currentComment} \n ![image](${markdown})`) // Set the markdown content - } else { - setMarkdownContent(`${currentComment} \n ${markdown}`) // Set the markdown content - } + const commentBeforeSelection = currentComment.slice(0, textSelection.start) + const commentSelection = currentComment.slice(textSelection.start, textSelection.end) + const commentAfterSelection = currentComment.slice(textSelection.end) + + const alreadyHasNewline = commentBeforeSelection.endsWith('\n') + const hasSelection = commentSelection.length > 0 + + const markdownSyntax = isImageFile() + ? `${alreadyHasNewline ? '' : '\n'}![${hasSelection ? commentSelection : 'image'}](${markdown})` + : `${alreadyHasNewline ? '' : '\n'}${hasSelection ? `[${commentSelection}](${markdown})` : `${markdown}`}` + + setMarkdownContent(`${commentBeforeSelection}${markdownSyntax}${commentAfterSelection}`) } // Clean up event handlers after processing diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 2dd43a4cb3..3190948dde 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -28,6 +28,7 @@ import { handlePaste, HandleUploadType, PrincipalsMentionMap, + TextSelection, ToolbarAction, type PrincipalPropsType } from '@/views' @@ -38,11 +39,6 @@ import { isEmpty, isUndefined } from 'lodash-es' import { PullRequestCommentTextarea } from './pull-request-comment-textarea' import { replaceMentionEmailWithDisplayName, replaceMentionEmailWithId } from './utils' -interface TextSelection { - start: number - end: number -} - interface ParsedSelection extends TextSelection { text: string textLines: string[] @@ -207,7 +203,7 @@ export const PullRequestCommentBox = ({ const handleUploadCallback = (file: File) => { setFile(file) - handleUpload?.(file, setComment, comment) + handleUpload?.(file, setComment, comment, textSelection) } const handleFileSelect = () => { diff --git a/packages/ui/src/views/repo/pull-request/pull-request.types.ts b/packages/ui/src/views/repo/pull-request/pull-request.types.ts index c26ab24735..4abb850be2 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request.types.ts +++ b/packages/ui/src/views/repo/pull-request/pull-request.types.ts @@ -297,5 +297,15 @@ export enum PRFilterGroupTogglerOptions { ReviewRequested = 'ReviewRequested' } -export type HandleUploadType = (blob: File, setMarkdownContent: (data: string) => void, currentComment?: string) => void +export interface TextSelection { + start: number + end: number +} + +export type HandleUploadType = ( + blob: File, + setMarkdownContent: (data: string) => void, + currentComment?: string, + textSelection?: TextSelection +) => void export type HandleAiPullRequestSummaryType = () => Promise<{ summary: string }> From fcd01cc7887f6a242f181a7d31aea3461ae67b3d Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Thu, 7 Aug 2025 22:50:03 +0000 Subject: [PATCH 027/180] fix: design fixes across pages (#10152) * fd7bc2 fix: tags list loading state * b15e01 fix: tags list design 2 * ae1d85 fix: tags list design * 97d537 fix: branch list pr tag colors * df550f fix: user select none on table headers --- .../repo-branches/repo-branches-store.ts | 14 +++++- packages/ui/src/components/table.tsx | 4 +- .../repo-branch/components/branch-list.tsx | 7 --- .../repo-tags/components/repo-tags-list.tsx | 22 ++++----- .../repo/repo-tags/repo-tags-list-page.tsx | 48 +++++++++---------- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-branches/repo-branches-store.ts b/apps/design-system/src/subjects/views/repo-branches/repo-branches-store.ts index c7312144fa..e2e653c436 100644 --- a/apps/design-system/src/subjects/views/repo-branches/repo-branches-store.ts +++ b/apps/design-system/src/subjects/views/repo-branches/repo-branches-store.ts @@ -73,7 +73,19 @@ export const repoBranchesStore: IBranchSelectorStore = { running: 1, success: 1 } - } + }, + pullRequests: [ + { + number: 3, + updated: 1737045212396, + state: 'merged', + is_draft: false, + merged: 1737045212397, + timestamp: 'Jan 13, 2025', + reviewRequired: false, + labels: [] + } + ] }, { id: 2, diff --git a/packages/ui/src/components/table.tsx b/packages/ui/src/components/table.tsx index 79f905a3e9..d8f75de385 100644 --- a/packages/ui/src/components/table.tsx +++ b/packages/ui/src/components/table.tsx @@ -54,7 +54,9 @@ const TableRoot = forwardRef<HTMLTableElement, TableRootV2Props>( TableRoot.displayName = 'TableRoot' const TableHeader = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>( - ({ className, ...props }, ref) => <thead ref={ref} className={cn('cn-table-v2-header', className)} {...props} /> + ({ className, ...props }, ref) => ( + <thead ref={ref} className={cn('cn-table-v2-header pointer-events-none select-none', className)} {...props} /> + ) ) TableHeader.displayName = 'TableHeader' diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index bc9ced7d4b..586cba4d48 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -216,13 +216,6 @@ export const BranchesList: FC<BranchListPageProps> = ({ ).icon } size="xs" - className={cn({ - 'text-icons-success': - branch.pullRequests[0].state === 'open' && !branch.pullRequests[0].is_draft, - 'text-icons-1': branch.pullRequests[0].state === 'open' && branch.pullRequests[0].is_draft, - 'text-icons-danger': branch.pullRequests[0].state === 'closed', - 'text-icons-merged': branch.pullRequests[0].merged - })} /> #{branch.pullRequests[0].number} </Link> diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index f03d41b942..63361f79bb 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -94,25 +94,25 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ } if (isLoading) { - return <Skeleton.Table countRows={12} countColumns={5} /> + return <Skeleton.Table countRows={10} countColumns={5} /> } return ( - <Table.Root className="[&_td]:py-3.5" tableClassName="table-fixed" disableHighlightOnHover> + <Table.Root tableClassName="table-fixed"> <Table.Header> - <Table.Row className="pointer-events-none select-none"> - <Table.Head className="w-[17%]">{t('views:repos.tag', 'Tag')}</Table.Head> - <Table.Head className="w-[30%]">{t('views:repos.description', 'Description')}</Table.Head> - <Table.Head className="w-[15%]">{t('views:repos.commit', 'Commit')}</Table.Head> - <Table.Head className="w-[15%]">{t('views:repos.tagger', 'Tagger')}</Table.Head> + <Table.Row> + <Table.Head className="w-[15%]">{t('views:repos.tag', 'Tag')}</Table.Head> + <Table.Head className="w-[32%]">{t('views:repos.description', 'Description')}</Table.Head> + <Table.Head className="w-[10%]">{t('views:repos.commit', 'Commit')}</Table.Head> + <Table.Head className="w-1/5">{t('views:repos.tagger', 'Tagger')}</Table.Head> <Table.Head className="w-[16%]">{t('views:repos.creationDate', 'Creation date')}</Table.Head> - <Table.Head className="w-[7%]" /> + <Table.Head className="w-[46px]" /> </Table.Row> </Table.Header> <Table.Body> {tagsList.map(tag => ( - <Table.Row key={tag.sha}> + <Table.Row key={tag.sha} to={`../summary/refs/tags/${tag.name}`}> <Table.Cell> <Tag value={tag.name} theme="violet" size="md" variant="secondary" showCopyButton /> </Table.Cell> @@ -121,7 +121,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ {tag?.message} </Text> </Table.Cell> - <Table.Cell> + <Table.Cell disableLink> <CommitCopyActions sha={tag.commit?.sha ?? ''} toCommitDetails={toCommitDetails} /> </Table.Cell> <Table.Cell> @@ -145,7 +145,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ /> ) : null} </Table.Cell> - <Table.Cell className="w-[46px] text-right"> + <Table.Cell className="w-[46px] text-right" disableLink> <MoreActionsTooltip actions={getTableActions(tag).map(action => ({ ...action, diff --git a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx index 9c516e5a49..f77408f050 100644 --- a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx +++ b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx @@ -53,32 +53,30 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ 'h-full': !isLoading && !tagsList.length && !searchQuery })} > - {!isLoading && (!!tagsList.length || isDirtyList) && ( - <> - <Text as="h1" variant="heading-section"> - {t('views:repos.tags', 'Tags')} - </Text> - <Spacer size={6} /> - <ListActions.Root> - <ListActions.Left> - <SearchInput - inputContainerClassName="max-w-80" - defaultValue={searchQuery || ''} - onChange={handleSearchChange} - placeholder={t('views:repos.search', 'Search')} - autoFocus - /> - </ListActions.Left> - <ListActions.Right> - <Button onClick={openCreateTagDialog}> - <span>{t('views:repos.createTag', 'Create Tag')}</span> - </Button> - </ListActions.Right> - </ListActions.Root> + <Text as="h1" variant="heading-section"> + {t('views:repos.tags', 'Tags')} + </Text> - <Spacer size={4.5} /> - </> - )} + <Spacer size={6} /> + + <ListActions.Root> + <ListActions.Left> + <SearchInput + inputContainerClassName="max-w-80" + defaultValue={searchQuery || ''} + onChange={handleSearchChange} + placeholder={t('views:repos.search', 'Search')} + autoFocus + /> + </ListActions.Left> + <ListActions.Right> + <Button onClick={openCreateTagDialog}> + <span>{t('views:repos.createTag', 'Create Tag')}</span> + </Button> + </ListActions.Right> + </ListActions.Root> + + <Spacer size={5} /> <RepoTagsList onDeleteTag={onDeleteTag} From 697e50d1ca351dc22c8673a959e65e7ed90a3fbe Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Thu, 7 Aug 2025 23:17:55 +0000 Subject: [PATCH 028/180] fix: minor webhook design fixes (#10153) * ff90f9 fix: tsc * 038003 fix: webhook designs --- .../repo/components/CommitTitleWithPRLink.tsx | 12 +++--------- .../ui/src/views/repo/components/commits-list.tsx | 1 - .../sections/pull-request-changes-section.tsx | 2 +- .../repo-commit-details-view.tsx | 5 ++--- .../webhook-create/repo-webhook-create-page.tsx | 15 +++++++++++++-- .../repo-webhook-executions-list-page.tsx | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx index bd326b45d8..7f13880813 100644 --- a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx +++ b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx @@ -1,16 +1,15 @@ -import { Layout, LinkProps, Text, TextProps } from '@/components' +import { Layout, Link, Text, TextProps } from '@/components' interface CommitTitleWithPRLinkProps { commitMessage?: string title?: string toPullRequest?: ({ pullRequestId }: { pullRequestId: number }) => string - Link: React.ComponentType<LinkProps> textVariant?: TextProps['variant'] textClassName?: string } export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { - const { Link, textVariant, commitMessage, textClassName, title, toPullRequest } = props + const { textVariant, commitMessage, textClassName, title, toPullRequest } = props if (!commitMessage) return null @@ -35,12 +34,7 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { <Text variant={textVariant} className={textClassName}> <Layout.Flex>  ( - <Link - // className="hover:underline" - title={title} - to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`} - variant="secondary" - > + <Link title={title} to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`}> #{pullRequestId} </Link> )  diff --git a/packages/ui/src/views/repo/components/commits-list.tsx b/packages/ui/src/views/repo/components/commits-list.tsx index 04789ff5d3..5fc7345736 100644 --- a/packages/ui/src/views/repo/components/commits-list.tsx +++ b/packages/ui/src/views/repo/components/commits-list.tsx @@ -69,7 +69,6 @@ export const CommitsList: FC<CommitProps> = ({ data, toCommitDetails, toPullRequ <Layout.Grid flow="column" className="w-full pl-cn-md" columns="1fr auto" gap="md"> <Layout.Vertical gap="2xs" className="truncate"> <CommitTitleWithPRLink - Link={Link} toPullRequest={toPullRequest} commitMessage={commit.title} title={commit.message || commit.title} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx index b2df60f4b2..e6ccec0e2f 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx @@ -64,7 +64,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ > <Layout.Flex> <StackedList.Field - className="flex gap-y-1" + className="flex" title={<LineTitle text={changesInfo.header} icon={getStatusIcon(changesInfo.status)} />} description={<LineDescription text={changesInfo.content} />} /> diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index f39eed2991..9354548a52 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -47,12 +47,11 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ <div className="mt-4 rounded-md border border-cn-borders-2"> <div className="flex items-center justify-between rounded-t-md border-b border-cn-borders-2 bg-cn-background-2 px-4 py-3"> <CommitTitleWithPRLink - Link={Link} toPullRequest={toPullRequest} commitMessage={commitData?.title} title={commitData?.title} - textClassName={'text-14 font-mono font-medium leading-snug text-cn-foreground-1'} - textVariant={'body-normal'} + textClassName={'text-14 font-medium leading-snug'} + // textVariant={'body-normal'} /> <Button variant="outline" asChild> <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx index be4dfd3789..2bebb61f0a 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx @@ -1,7 +1,7 @@ import { FC, useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' -import { Button, ButtonLayout, Fieldset, FormSeparator, FormWrapper, Text } from '@/components' +import { Alert, Button, ButtonLayout, Fieldset, FormSeparator, FormWrapper, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout, WebhookStore } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' @@ -88,6 +88,13 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ ] const onSubmit: SubmitHandler<CreateWebhookFormFields> = data => { + if (data.trigger === TriggerEventsEnum.SELECTED_EVENTS && (!data.triggers || data.triggers.length === 0)) { + formMethods.setError('triggers', { + type: 'manual', + message: 'At least one event must be selected' + }) + return + } onFormSubmit(data) } @@ -158,7 +165,11 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ </ButtonLayout> </Fieldset> - {!!apiError && <span className="text-2 text-cn-foreground-danger">{apiError?.toString()}</span>} + {!!apiError && ( + <Alert.Root theme="danger"> + <Alert.Title>{apiError?.toString()}</Alert.Title> + </Alert.Root> + )} </FormWrapper> </SandboxLayout.Content> ) diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx index d6fe6e86fe..8daf90d732 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx @@ -50,7 +50,7 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ <Skeleton.List /> ) : executions && executions.length > 0 ? ( <> - <Table.Root disableHighlightOnHover size="compact"> + <Table.Root size="compact"> <Table.Header> <Table.Row> <Table.Head className="w-[136px]"> From 57e8e0646c2f46e3f1b0640524309312af301339 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Thu, 7 Aug 2025 23:26:10 +0000 Subject: [PATCH 029/180] fix: pr list order by number (#10154) * 470898 fix: pr list order by number --- apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx index 2e71c29b20..7575dcfac3 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx @@ -53,7 +53,7 @@ export default function PullRequestListPage() { state: prState, query: query ?? '', exclude_description: true, - sort: 'updated', + sort: 'number', order: 'desc', ...filterValues }, From 4951622ed00b0ef0ea1b58fa56ec1ccf130114ce Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 8 Aug 2025 00:13:42 +0000 Subject: [PATCH 030/180] fix: webhook schema, commit-dialog (#10155) * 724b85 fix: error display * aea720 fix: pr title efit * 69e08e fix: current branch name * ad0fe1 chore: clean up * a33922 fix: webhook schema, commit-dialog --- .../src/components-v2/git-commit-dialog.tsx | 2 +- .../pages-v2/webhooks/webhook-executions.tsx | 2 -- packages/ui/locales/en/component.json | 2 +- packages/ui/locales/en/views.json | 4 +-- packages/ui/locales/fr/component.json | 2 +- packages/ui/locales/fr/views.json | 4 +-- .../git-commit-dialog/git-commit-dialog.tsx | 3 +- .../components/pull-request-header.tsx | 2 +- .../ui/src/views/repo/repo-create/index.tsx | 4 +-- .../create-webhooks-form-schema.tsx | 33 +++++++++++++------ .../repo-webhook-create-page.tsx | 31 ++++++++++------- .../repo-webhook-executions-list-page.tsx | 8 ----- 12 files changed, 53 insertions(+), 44 deletions(-) diff --git a/apps/gitness/src/components-v2/git-commit-dialog.tsx b/apps/gitness/src/components-v2/git-commit-dialog.tsx index 151c97d6cd..230fafc3d1 100644 --- a/apps/gitness/src/components-v2/git-commit-dialog.tsx +++ b/apps/gitness/src/components-v2/git-commit-dialog.tsx @@ -182,7 +182,7 @@ export default function GitCommitDialog({ dryRun={dryRun} violation={violation} bypassable={bypassable} - currentBranch={currentBranch || 'Master'} + currentBranch={currentBranch.replace('refs/heads/', '')} isFileNameRequired={isNew && resourcePath?.length < 1} setAllStates={setAllStates} // TODO: Add a loading state for submission diff --git a/apps/gitness/src/pages-v2/webhooks/webhook-executions.tsx b/apps/gitness/src/pages-v2/webhooks/webhook-executions.tsx index 827eeeb573..b6cb1281f0 100644 --- a/apps/gitness/src/pages-v2/webhooks/webhook-executions.tsx +++ b/apps/gitness/src/pages-v2/webhooks/webhook-executions.tsx @@ -42,8 +42,6 @@ export const WebhookExecutionsContainer = () => { return ( <RepoWebhookExecutionsPage useWebhookStore={useWebhookStore} - toRepoWebhooks={() => routes.toRepoWebhooks({ webhookId })} - repo_ref={repo_ref} isLoading={isLoading} toRepoWebhookExecutionDetails={(executionId: string) => routes.toRepoWebhookExecutionDetails({ spaceId, repoId, webhookId, executionId }) diff --git a/packages/ui/locales/en/component.json b/packages/ui/locales/en/component.json index b48cebd150..a4485a623f 100644 --- a/packages/ui/locales/en/component.json +++ b/packages/ui/locales/en/component.json @@ -144,7 +144,7 @@ }, "radioGroup": { "directly": { - "labelFirst": "Commit directly to the", + "labelFirst": "Commit directly to", "labelSecond": "branch" }, "new": { diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index b47c1a89e6..83abed1445 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -165,9 +165,9 @@ "label": "Description" }, "gitignore": { - "placeholder": "Select", "label": "Add a .gitignore", - "caption": "Choose which files not to track from a list of templates." + "caption": "Choose which files not to track from a list of templates.", + "placeholder": "Select" }, "defaultBranchDialog": { "startLabel": "Your repository will be initialized with a", diff --git a/packages/ui/locales/fr/component.json b/packages/ui/locales/fr/component.json index 5784154e1b..327dc5d173 100644 --- a/packages/ui/locales/fr/component.json +++ b/packages/ui/locales/fr/component.json @@ -143,7 +143,7 @@ }, "radioGroup": { "directly": { - "labelFirst": "Commit directly to the", + "labelFirst": "Commit directly to", "labelSecond": "branch" }, "new": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 7605d88c44..66251fc6e6 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -165,9 +165,9 @@ "label": "Description" }, "gitignore": { - "placeholder": "Select", "label": "Add a .gitignore", - "caption": "Choose which files not to track from a list of templates." + "caption": "Choose which files not to track from a list of templates.", + "placeholder": "Select" }, "defaultBranchDialog": { "startLabel": "Your repository will be initialized with a", diff --git a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx index 65cc042471..0b67152e84 100644 --- a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx +++ b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx @@ -171,7 +171,7 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ value={CommitToGitRefOption.DIRECTLY} label={ <> - {t('component:commitDialog.form.radioGroup.directly.labelFirst', 'Commit directly to the')} + {t('component:commitDialog.form.radioGroup.directly.labelFirst', 'Commit directly to')} <Tag className="-mt-0.5 mx-1.5 align-sub" variant="secondary" @@ -181,7 +181,6 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ icon="git-branch" showIcon /> - {t('component:commitDialog.form.radioGroup.directly.labelSecond', 'branch')} </> } /> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index 31679e598b..ee71192632 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -65,7 +65,7 @@ export const PullRequestHeader: React.FC<PullRequestTitleProps> = ({ return ( <> <Layout.Vertical gap="md" className={cn(className)}> - <Layout.Horizontal gap="3xl" align="end"> + <Layout.Horizontal gap="sm" align="end"> <Text as="h1" variant="heading-section" className="[&>*]:ml-cn-xs"> {title} <Text as="span" variant="heading-section" color="foreground-2"> diff --git a/packages/ui/src/views/repo/repo-create/index.tsx b/packages/ui/src/views/repo/repo-create/index.tsx index e97c22e904..7524421edc 100644 --- a/packages/ui/src/views/repo/repo-create/index.tsx +++ b/packages/ui/src/views/repo/repo-create/index.tsx @@ -171,7 +171,7 @@ export function RepoCreatePage({ value={gitignoreValue} options={gitIgnoreOptions} onChange={value => handleSelectChange('gitignore', value)} - placeholder={t('views:repos.createNewRepoForm.gitignore.placeholder', 'Select')} + placeholder="None" label={t('views:repos.createNewRepoForm.gitignore.label', 'Add a .gitignore')} error={errors.gitignore?.message?.toString()} caption={t( @@ -187,7 +187,7 @@ export function RepoCreatePage({ value={licenseValue} options={licenseOptions} onChange={value => handleSelectChange('license', value)} - placeholder="Select" + placeholder="None" label="Choose a license" error={errors.license?.message?.toString()} caption="A license tells others what they can and can't do with your code." diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-schema.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-schema.tsx index 0aec901d4a..fe96e21a3b 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-schema.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/components/create-webhooks-form-schema.tsx @@ -2,13 +2,26 @@ import { z } from 'zod' import { SSLVerificationEnum, TriggerEventsEnum, WebhookTriggerEnum } from '../types' -export const createWebhookFormSchema = z.object({ - enabled: z.boolean(), - identifier: z.string().min(1, 'Name is required'), - description: z.string().optional(), - url: z.string().url('Please enter a valid URL'), - secret: z.string().optional(), - insecure: z.string(z.nativeEnum(SSLVerificationEnum)), - trigger: z.string(z.nativeEnum(TriggerEventsEnum)), - triggers: z.array(z.nativeEnum(WebhookTriggerEnum)).optional() -}) +export const createWebhookFormSchema = z + .object({ + enabled: z.boolean(), + identifier: z.string().min(1, 'Name is required'), + description: z.string().optional(), + url: z.string().url('Please enter a valid URL'), + secret: z.string().optional(), + insecure: z.string(z.nativeEnum(SSLVerificationEnum)), + trigger: z.string(z.nativeEnum(TriggerEventsEnum)), + triggers: z.array(z.nativeEnum(WebhookTriggerEnum)).optional() + }) + .refine( + data => { + if (data.trigger === TriggerEventsEnum.SELECTED_EVENTS) { + return data.triggers && data.triggers.length > 0 + } + return true + }, + { + message: 'At least one event must be selected', + path: ['triggers'] + } + ) diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx index 2bebb61f0a..bfa1fa52d1 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx @@ -133,18 +133,25 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ <Fieldset className="mt-5"> <WebhookTriggerField register={register} /> {triggerValue === TriggerEventsEnum.SELECTED_EVENTS && ( - <div className="flex justify-between"> - {eventSettingsComponents.map(component => ( - <div key={component.fieldName} className="flex flex-col"> - <WebhookEventSettingsFieldset - register={register} - setValue={setValue} - watch={watch} - eventList={component.events} - /> - </div> - ))} - </div> + <> + <div className="flex justify-between"> + {eventSettingsComponents.map(component => ( + <div key={component.fieldName} className="flex flex-col"> + <WebhookEventSettingsFieldset + register={register} + setValue={setValue} + watch={watch} + eventList={component.events} + /> + </div> + ))} + </div> + {formMethods.formState.errors.triggers && ( + <Alert.Root theme="danger"> + <Alert.Title>{formMethods.formState.errors.triggers.message}</Alert.Title> + </Alert.Root> + )} + </> )} </Fieldset> diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx index 8daf90d732..547a43c116 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx @@ -12,16 +12,12 @@ import { interface RepoWebhookExecutionsPageProps { useWebhookStore: () => WebhookStore - toRepoWebhooks: (repoRef?: string) => string - repo_ref: string isLoading: boolean toRepoWebhookExecutionDetails: (executionId: string) => string } const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ useWebhookStore, - toRepoWebhooks, - repo_ref, isLoading, toRepoWebhookExecutionDetails }) => { @@ -124,10 +120,6 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ "Your webhook executions will appear here once they're completed. Trigger your webhook to see results." ) ]} - primaryButton={{ - label: t('views:webhookData.create', 'Create webhook'), - to: `${toRepoWebhooks(repo_ref)}/create` - }} /> )} </SandboxLayout.Content> From 7f2bf25240cb0300692cebf73685045a5078b18c Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 8 Aug 2025 00:47:09 +0000 Subject: [PATCH 031/180] Fast-Forward error display when rebase available (#10156) * 1ed4e9 PR check error * 4ce3b4 Removed unwanted line * 1579a8 Removed unwanted code * 32e06a Confirm ready for review * c77bfd Fixed alignment issues * 2876be Fast-Forward error display when rebase available --- .../conversation/pull-request-panel.tsx | 86 +++++- .../sections/pull-request-merge-section.tsx | 276 +++++++++++------- 2 files changed, 249 insertions(+), 113 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 9ffde4db7b..640795f24b 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -67,6 +67,8 @@ interface HeaderProps { headerMsg?: string spaceId?: string repoId?: string + actions?: PullRequestAction[] + mergeButtonValue?: string } interface ButtonStateProps { @@ -94,10 +96,27 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { unchecked, mergeable, isOpen, - ruleViolation + ruleViolation, + actions, + mergeButtonValue } = props const areRulesBypassed = pullReqMetadata?.merge_violations_bypassed const mergeMethod = getMergeMethodDisplay(pullReqMetadata?.merge_method as MergeStrategy) + const isRebasable = pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged + + // Get the current selected merge method + const getCurrentMergeMethod = () => { + const selectedAction = actions?.[parseInt(mergeButtonValue || '0')] + if (selectedAction?.title === 'Fast-forward merge') return 'fast-forward' + if (selectedAction?.title === 'Squash and merge') return 'squash' + if (selectedAction?.title === 'Merge pull request') return 'merge' + if (selectedAction?.title === 'Rebase and merge') return 'rebase' + return undefined + } + + const currentMergeMethod = getCurrentMergeMethod() + const isFastForwardNotPossible = isRebasable && currentMergeMethod === 'fast-forward' + if (pullReqMetadata?.state === PullRequestFilterOption.MERGED) { return ( <> @@ -156,7 +175,9 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { ? 'Cannot merge pull request' : ruleViolation ? 'Cannot merge pull request' - : `Pull request can be merged`} + : isFastForwardNotPossible + ? 'Cannot merge pull request' + : `Pull request can be merged`} </Text> </div> ) @@ -369,15 +390,45 @@ const PullRequestPanel = ({ } const handleConfirmMerge = () => { + const selectedAction = actions[parseInt(mergeButtonValue || '0')] + // Check if this is a merge action + const isMergeAction = ['Squash and merge', 'Merge pull request', 'Rebase and merge', 'Fast-forward merge'].includes( + selectedAction?.title || '' + ) + setShowMergeInputs(false) setShowActionBtn(false) - setMergeInitiated(true) + + // Only set mergeInitiated for actual merge actions + if (isMergeAction) { + setMergeInitiated(true) + } + const actionIdx = actions.findIndex(action => action.id === mergeButtonValue) if (actionIdx !== -1) { actions[actionIdx]?.action?.() } } + // Check if Fast-Forward merge should be disabled + const shouldDisableFastForwardMerge = () => { + const selectedAction = actions[parseInt(mergeButtonValue || '0')] + const isRebasable = + pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged + const isFastForwardSelected = selectedAction?.title === 'Fast-forward merge' + return isFastForwardSelected && isRebasable + } + + // To get the selected merge method + const getSelectedMergeMethod = () => { + const selectedAction = actions[parseInt(mergeButtonValue || '0')] + if (selectedAction?.title === 'Fast-forward merge') return 'fast-forward' + if (selectedAction?.title === 'Squash and merge') return 'squash' + if (selectedAction?.title === 'Merge pull request') return 'merge' + if (selectedAction?.title === 'Rebase and merge') return 'rebase' + return undefined + } + const handleAccordionValuesChange = useCallback((data: string | string[]) => { if (typeof data === 'string') return @@ -431,6 +482,15 @@ const PullRequestPanel = ({ } }, [error, mergeInitiated]) + // Reset mergeInitiated when PR state changes (e.g., from draft to open) + useEffect(() => { + if (mergeInitiated && !isMerging && !pullReqMetadata?.is_draft) { + // Reset loading state when PR changes from draft to open (API success) + setMergeInitiated(false) + setShowActionBtn(false) + } + }, [mergeInitiated, isMerging, pullReqMetadata?.is_draft]) + const buttonState = getButtonState({ isMergeable, ruleViolation: prPanelData.ruleViolation, @@ -468,6 +528,8 @@ const PullRequestPanel = ({ headerMsg={headerMsg} spaceId={spaceId} repoId={repoId} + actions={actions} + mergeButtonValue={mergeButtonValue} /> } /> @@ -522,12 +584,14 @@ const PullRequestPanel = ({ loading={actions[parseInt(mergeButtonValue)]?.loading} selectedValue={mergeButtonValue} handleOptionChange={handleMergeTypeSelect} - options={actions.map(action => ({ - value: action.id, - label: action.title, - description: action.description, - disabled: action.disabled - }))} + options={actions.map(action => { + return { + value: action.id, + label: action.title, + description: action.description, + disabled: action.disabled + } + })} handleButtonClick={() => { const selectedAction = actions[parseInt(mergeButtonValue)] if (!selectedAction.disabled) { @@ -558,7 +622,7 @@ const PullRequestPanel = ({ theme="success" onClick={handleConfirmMerge} loading={isMerging || mergeInitiated} - disabled={cancelInitiated} + disabled={cancelInitiated || shouldDisableFastForwardMerge()} > Confirm {actions[parseInt(mergeButtonValue || '0')]?.title || 'Merge'} </Button> @@ -673,6 +737,8 @@ const PullRequestPanel = ({ conflictingFiles={prPanelData.conflictingFiles} accordionValues={accordionValues} setAccordionValues={setAccordionValues} + handleRebaseBranch={handleRebaseBranch} + selectedMergeMethod={getSelectedMergeMethod()} /> )} </Accordion.Root> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx index a194a79d0c..c2619261fd 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx @@ -1,6 +1,6 @@ -import { Dispatch, FC, MouseEvent, SetStateAction, useState } from 'react' +import { Dispatch, FC, MouseEvent, SetStateAction, useMemo, useState } from 'react' -import { Accordion, Button, CopyButton, IconV2, Layout, StackedList, Text } from '@/components' +import { Accordion, Button, CopyButton, IconV2, Layout, StackedList, Tag, Text } from '@/components' import { cn } from '@utils/cn' import { PanelAccordionShowButton } from '@views/repo/pull-request/details/components/conversation/sections/panel-accordion-show-button' import { isEmpty } from 'lodash-es' @@ -47,10 +47,20 @@ const ACCORDION_VALUE = 'item-4' interface PullRequestMergeSectionProps { unchecked: boolean mergeable: boolean - pullReqMetadata: { target_branch?: string | undefined; source_branch?: string | undefined } | undefined + pullReqMetadata: + | { + target_branch?: string | undefined + source_branch?: string | undefined + merge_target_sha?: string | null + merge_base_sha?: string + merged?: number | null + } + | undefined conflictingFiles?: string[] accordionValues: string[] setAccordionValues: Dispatch<SetStateAction<string[]>> + handleRebaseBranch?: () => void + selectedMergeMethod?: string } const PullRequestMergeSection = ({ unchecked, @@ -58,12 +68,28 @@ const PullRequestMergeSection = ({ pullReqMetadata, conflictingFiles, accordionValues, - setAccordionValues + setAccordionValues, + handleRebaseBranch, + selectedMergeMethod }: PullRequestMergeSectionProps) => { const [showCommandLineInfo, setShowCommandLineInfo] = useState(false) const isConflicted = !mergeable && !unchecked + // Memoized Fast-Forward logic calculations + const fastForwardState = useMemo(() => { + const isRebasable = + pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged + const isFastForwardSelected = selectedMergeMethod === 'fast-forward' + const isFastForwardNotPossible = isRebasable && isFastForwardSelected + + return { + isRebasable, + isFastForwardSelected, + isFastForwardNotPossible + } + }, [pullReqMetadata?.merge_target_sha, pullReqMetadata?.merge_base_sha, pullReqMetadata?.merged, selectedMergeMethod]) + const stepMap = [ { step: 'Step 1', @@ -100,114 +126,158 @@ const PullRequestMergeSection = ({ setShowCommandLineInfo(prevState => !prevState) } + // Helper function to render branch tags + const renderBranchTags = () => ( + <span className="inline-flex items-center gap-1"> + <span>Merge the latest changes from</span> + <Tag + variant="secondary" + theme="blue" + icon="git-branch" + value={pullReqMetadata?.target_branch || ''} + showIcon + showCopyButton + /> + <span>into</span> + <Tag + variant="secondary" + theme="blue" + icon="git-branch" + value={pullReqMetadata?.source_branch || ''} + showIcon + showCopyButton + /> + </span> + ) + return ( - <Accordion.Item value={ACCORDION_VALUE} className="border-0"> - <Accordion.Trigger - className={cn('py-3', { '[&>.cn-accordion-trigger-indicator]:hidden': mergeable || unchecked })} - > - <Layout.Flex> + <> + <Accordion.Item value={ACCORDION_VALUE} className="border-0"> + <Accordion.Trigger + className={cn('py-3', { '[&>.cn-accordion-trigger-indicator]:hidden': mergeable || unchecked })} + > + <Layout.Flex> + <StackedList.Field + className="flex gap-y-1" + title={ + <LineTitle + textClassName={isConflicted ? 'text-cn-foreground-danger' : ''} + text={ + unchecked + ? 'Merge check in progress...' + : !mergeable + ? 'Conflicts found in this branch' + : `This branch has no conflicts with ${pullReqMetadata?.target_branch} branch` + } + icon={ + unchecked ? ( + <IconV2 size="md" name="clock-solid" className="text-cn-foreground-warning" /> + ) : ( + <IconV2 + size="md" + className={mergeable ? 'text-cn-icon-success' : 'text-cn-foreground-danger'} + name={mergeable ? 'check-circle-solid' : 'warning-triangle-solid'} + /> + ) + } + /> + } + description={ + <> + {unchecked && <LineDescription text={'Checking for ability to merge automatically...'} />} + {isConflicted && ( + <LineDescription + text={ + <> + Use the  + <Button variant="link" onClick={handleCommandLineClick} asChild className="h-4"> + <span + role="button" + tabIndex={0} + aria-label="Open command line" + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation() + handleCommandLineClick() + } + }} + > + command line + </span> + </Button> +  to resolve conflicts + </> + } + /> + )} + </> + } + /> + <PanelAccordionShowButton + isShowButton={isConflicted} + value={ACCORDION_VALUE} + accordionValues={accordionValues} + /> + </Layout.Flex> + </Accordion.Trigger> + {isConflicted && ( + <Accordion.Content className="ml-7"> + <> + {showCommandLineInfo && ( + <div className="mb-3.5 rounded-md border border-cn-borders-2 p-1 px-4 py-2"> + <Text variant="heading-small" color="foreground-1"> + Resolve conflicts via command line + </Text> + <ol className="flex flex-col gap-y-3"> + {stepMap.map(item => ( + <StepInfo key={item.step} {...item} /> + ))} + </ol> + </div> + )} + <Text as="span"> + Conflicting files <Text as="span">{conflictingFiles?.length || 0}</Text> + </Text> + + {!isEmpty(conflictingFiles) && ( + <div className="mt-1"> + {conflictingFiles?.map(file => ( + <div className="flex items-center gap-x-2 py-1.5" key={file}> + <IconV2 size="md" className="text-icons-1" name="page" /> + <Text as="span" color="foreground-1"> + {file} + </Text> + </div> + ))} + </div> + )} + </> + </Accordion.Content> + )} + </Accordion.Item> + + {/* Fast-Forward merge error section - Using proper StackedList.Item */} + {fastForwardState.isFastForwardNotPossible && ( + <StackedList.Item disableHover className="border-t border-cn-borders-3 py-3 -ml-4"> <StackedList.Field className="flex gap-y-1" title={ <LineTitle - textClassName={isConflicted ? 'text-cn-foreground-danger' : ''} - text={ - unchecked - ? 'Merge check in progress...' - : !mergeable - ? 'Conflicts found in this branch' - : `This branch has no conflicts with ${pullReqMetadata?.target_branch} branch` - } - icon={ - unchecked ? ( - <IconV2 size="md" name="clock-solid" className="text-cn-foreground-warning" /> - ) : ( - <IconV2 - size="md" - className={mergeable ? 'text-cn-icon-success' : 'text-cn-foreground-danger'} - name={mergeable ? 'check-circle-solid' : 'warning-triangle-solid'} - /> - ) - } + textClassName="text-cn-foreground-danger" + text="This branch is out-of-date with the base branch" + icon={<IconV2 size="md" className="text-cn-foreground-danger" name="warning-triangle-solid" />} /> } - description={ - <> - {unchecked && <LineDescription text={'Checking for ability to merge automatically...'} />} - {isConflicted && ( - <LineDescription - text={ - <> - Use the  - <Button variant="link" onClick={handleCommandLineClick} asChild className="h-4"> - <span - role="button" - tabIndex={0} - aria-label="Open command line" - onKeyDown={e => { - if (e.key === 'Enter' || e.key === ' ') { - e.stopPropagation() - handleCommandLineClick() - } - }} - > - command line - </span> - </Button> -  to resolve conflicts - </> - } - /> - )} - </> - } - /> - <PanelAccordionShowButton - isShowButton={isConflicted} - value={ACCORDION_VALUE} - accordionValues={accordionValues} + description={<LineDescription text={renderBranchTags()} />} /> - </Layout.Flex> - </Accordion.Trigger> - {isConflicted && ( - <Accordion.Content className="ml-7"> - <> - {showCommandLineInfo && ( - <div className="mb-3.5 rounded-md border border-cn-borders-2 p-1 px-4 py-2"> - <Text as="h3" color="foreground-1"> - Resolve conflicts via command line - </Text> - <p className="pb-4 pt-1 text-2 text-cn-foreground-2"> - If the conflicts on this branch are too complex to resolve in the web editor, you can check it out via - command line to resolve the conflicts - </p> - <ol className="flex flex-col gap-y-3"> - {stepMap.map(item => ( - <StepInfo key={item.step} {...item} /> - ))} - </ol> - </div> - )} - <Text as="span"> - Conflicting files <Text as="span">{conflictingFiles?.length || 0}</Text> - </Text> - - {!isEmpty(conflictingFiles) && ( - <div className="mt-1"> - {conflictingFiles?.map(file => ( - <div className="flex items-center gap-x-2 py-1.5" key={file}> - <IconV2 size="md" className="text-icons-1" name="page" /> - <Text as="span" color="foreground-1"> - {file} - </Text> - </div> - ))} - </div> - )} - </> - </Accordion.Content> + {handleRebaseBranch && ( + <Button theme="default" variant="primary" onClick={handleRebaseBranch} size="md"> + Update with rebase + </Button> + )} + </StackedList.Item> )} - </Accordion.Item> + </> ) } From 3cb78672d07d5edbecaf6add329656fd80a4bba5 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 8 Aug 2025 04:46:42 +0000 Subject: [PATCH 032/180] add icons to header and empty state buttons and make all buttons title case (#10157) * 1d7e28 add icons to header and empty state buttons and make all buttons title case * b1eb1b Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b42931 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 55c544 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 3d07a1 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * d70666 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 50f0dc Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 38093f Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 15873a Merge branch 'main' of https://git0.harne --- packages/ui/locales/en/views.json | 130 +++++++++--------- packages/ui/locales/fr/views.json | 26 ++-- .../src/components/file-additions-trigger.tsx | 7 +- packages/ui/src/components/repo-subheader.tsx | 2 +- .../connector-details-activities.tsx | 9 +- .../connector-details-references.tsx | 9 +- .../connectors-list/connectors-list-page.tsx | 16 ++- .../labels/components/labels-list-view.tsx | 22 ++- .../ui/src/views/labels/labels-list-page.tsx | 7 +- packages/ui/src/views/not-found-page.tsx | 5 +- .../profile-settings-token-create-dialog.tsx | 6 +- .../profile-settings-general-page.tsx | 2 +- .../project-general/project-general-page.tsx | 2 +- .../views/repo/components/path-action-bar.tsx | 14 +- .../pull-request-compare-button.tsx | 5 +- .../components/pull-request-compare-form.tsx | 2 +- .../components/pull-request-list.tsx | 15 +- .../pull-request/pull-request-list-page.tsx | 10 +- .../repo-branch-settings-rules-page.tsx | 10 +- .../repo-branch/components/branch-list.tsx | 16 ++- .../repo-branch/repo-branch-list-view.tsx | 5 +- .../repo-commit-details-view.tsx | 5 +- .../repo/repo-commits/repo-commits-view.tsx | 9 +- .../ui/src/views/repo/repo-create/index.tsx | 2 +- .../views/repo/repo-files/repo-files-view.tsx | 9 +- .../repo/repo-import/repo-import-mulitple.tsx | 2 +- .../views/repo/repo-import/repo-import.tsx | 2 +- .../views/repo/repo-list/repo-list-page.tsx | 16 ++- .../ui/src/views/repo/repo-list/repo-list.tsx | 9 +- .../repo-settings-general-delete.tsx | 6 +- .../repo-settings-general-rules.tsx | 18 ++- .../repo/repo-summary/repo-empty-view.tsx | 4 +- .../views/repo/repo-summary/repo-summary.tsx | 3 +- .../repo-tag-settings-rules-page.tsx | 10 +- .../create-tag/create-tag-dialog.tsx | 4 +- .../repo-tags/components/repo-tags-list.tsx | 19 ++- .../repo/repo-tags/repo-tags-list-page.tsx | 3 +- .../repo-webhook-create-page.tsx | 8 +- .../components/repo-webhook-list.tsx | 31 ++++- .../webhook-list/repo-webhook-list-page.tsx | 7 +- .../secrets-list/secrets-list-page.tsx | 14 +- .../components/empty-state/empty-state.tsx | 9 +- .../page-components/actions/actions.tsx | 5 +- .../components/error-state/error-state.tsx | 9 +- .../no-search-results/no-search-results.tsx | 9 +- 45 files changed, 344 insertions(+), 189 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 83abed1445..e4c7cdf66b 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -1,13 +1,13 @@ { "repos": { - "create-file": "Create file", - "upload-files": "Upload files", + "createFile": "Create File", + "uploadFiles": "Upload Files", "summary": "Summary", "files": "Files", "pipelines": "Pipelines", "commits": "Commits", "tags": "Tags", - "pull-requests": "Pull requests", + "pullRequests": "Pull requests", "branches": "Branches", "search": "Search", "settings": "Settings", @@ -70,7 +70,7 @@ "label": "Label by" } }, - "createPullReq": "Create Pull request", + "createPullReq": "Create Pull Request", "BlockBranchCreation": "Block branch creation", "BlockBranchCreationDescription": "Only allow users with bypass permission to create matching branches", "BlockBranchDeletion": "Block branch deletion", @@ -128,12 +128,12 @@ "enterMinDefaultReviewers": "Enter minimum number of default reviewers", "enterMinReviewers": "Enter minimum number of reviewers", "updateBranchRule": "Update branch rule", - "CreateRule": "Create a branch rule", - "updateRule": "Update rule", - "createRuleButton": "Create rule", - "updatingRule": "Updating rule...", - "creatingRuleButton": "Creating rule...", - "createBranch": "Create branch", + "createBranchRule": "Create branch rule", + "updateRuleButton": "Update Rule", + "createRuleButton": "Create Rule", + "updatingRuleButton": "Updating Rule...", + "creatingRuleButton": "Creating Rule...", + "createBranch": "Create Branch", "branch": "Branch", "update": "Updated", "checkStatus": "Check status", @@ -181,9 +181,9 @@ } }, "repositories": "Repositories", - "createRepository": "Create repository", - "import-repository": "Import repository", - "import-repositories": "Import repositories", + "createRepository": "Create Repository", + "importRepository": "Import Repository", + "importRepositories": "Import Repositories", "importing": "Importing…", "updated": "Updated", "unarchiveRepo": "Unarchive this repository", @@ -192,11 +192,11 @@ "archiveRepoDescription": "Set this repository to archived and restrict it to read-only access.", "unarchiving": "Unarchiving...", "archiving": "Archiving...", - "unarchiveRepoButton": "Unarchive repository", - "archiveRepoButton": "Archive repository", + "unarchiveRepoButton": "Unarchive Repository", + "archiveRepoButton": "Archive Repository", "deleteRepo": "Delete this repository", "deleteRepoDescription": "This will permanently delete this repository, and everything contained in it.", - "deleteRepoButton": "Delete repository", + "deleteRepoButton": "Delete Repository", "settingsToolTip": "Cannot change settings while loading or updating.", "features": "Features", "gitLfs": "Git LFS", @@ -213,10 +213,8 @@ "allRules": "All Rules", "branchRules": "Branch Rules", "tagRules": "Tag Rules", - "newTagRule": "New tag rule", - "createBranchRule": "New branch rule", - "createBranchRuleButton": "Create Branch rule", - "createTagRuleButton": "Create tag rule", + "createTagRuleButton": "Create Tag Rule", + "createBranchRuleButton": "Create Branch Rule", "security": "Security", "secretScanning": "Secret scanning", "secretScanningDescription": "Block commits containing secrets like passwords, API keys and tokens.", @@ -244,14 +242,14 @@ "0": "We recommend every repository include a", "1": "README, LICENSE, and .gitignrore." }, - "createFile": "New file" + "createFile": "Create file" }, "cloneInstructions": { "title": "Please generate git credentials if it’s your first time cloning the repository", "subTitle": "Git clone URL", "http": "HTTP", "ssh": "SSH", - "generateButton": "Generate clone credential", + "generateButton": "Generate Clone Credential", "manageCredentials": { "0": "You can also manage your git credential", "1": "here" @@ -277,11 +275,11 @@ "BlockTagUpdate": "Block tag update", "BlockTagUpdateDescription": "Only allow users with bypass permission to update matching tags", "updateTagRule": "Update tag rule", - "CreateTagRule": "Create a tag rule", + "createTagRule": "Create tag rule", "createTagTitle": "Create a tag", "repoTagDescriptionPlaceholder": "Enter tag description here", - "creatingTagButton": "Creating tag...", - "createTagButton": "Create tag", + "creatingTagButton": "Creating Tag...", + "createTagButton": "Create Tag", "createTagDialog": { "validation": { "name": "Tag name is required", @@ -317,11 +315,11 @@ "editWebhookTitle": "Order Status Update Webhook", "createWebhookTitle": "Create a webhook", "webhookDetails": "Details", - "updatingWebhook": "Updating webhook...", - "creatingWebhook": "Creating webhook...", - "updateWebhook": "Update webhook", - "createWebhook": "Create webhook", - "createBranchButton": "Create branch", + "updatingWebhook": "Updating Webhook...", + "creatingWebhook": "Creating Webhook...", + "updateWebhook": "Update Webhook", + "createWebhook": "Create Webhook", + "createBranchButton": "Create Branch", "newBranch": "New branch", "emptyRepo": "This repository is empty.", "afterComparingOpenPullRequest": "After comparing changes, you may open a pull request to contribute your changes upstream.", @@ -358,7 +356,7 @@ "configuration": "Configuration", "references": "References", "activityHistory": "Activity history", - "createNew": "Create new connector", + "createNew": "Create New Connector", "errorEncountered": "Error Encountered", "viewDetails": "View Details", "id": "Connector ID", @@ -407,9 +405,9 @@ "noClosedPullRequests": "There are no closed pull requests in this project yet.", "noMergedPullRequests": "There are no merged pull requests in this project yet.", "button": { - "createPullRequest": "Create pull request" + "createPullRequest": "Create Pull Request" }, - "clearFilters": "Clear filters", + "clearFilters": "Clear Filters", "noPullRequestsInRepo": "Start your contribution journey by creating a new pull request draft.", "noPullRequestsInProject": "There are no pull requests in this project yet.", "noBranches": "No branches yet", @@ -423,7 +421,7 @@ "noRulesDescription": "There are no rules in this project. Click on the button below to start adding rules.", "noTags": "No tags yet", "noTagsDescription": "Your tags will appear here once they're created. Start creating tags to see your work organized.", - "createNewTag": "Create tag", + "createNewTag": "Create Tag", "noWebhookExecution": "No webhook executions yet", "noWebhookExecutionsDescription": "Your webhook executions will appear here once they're completed. Trigger your webhook to see the results.", "noWebhooks": "No webhooks yet", @@ -432,10 +430,10 @@ "createSecret": "Create new secret.", "noUsers": "No Users Found", "noUsersDescription": "There are no users in this scope. Click on the button below to start adding them.", - "createTagRuleButton": "Create Tag rule", - "createTagRule": "Create Tag rule", - "createBranch": "Create new branch", - "createConnector": "Create new connector.", + "createTagRuleButton": "Create Tag Rule", + "createTagRule": "Create Tag Rule", + "createBranch": "Create New Branch", + "createConnector": "Create New Connector.", "noConnectorsProject": "There are no connectors in this project yet.", "noSecretsProject": "There are no secrets in this project yet.", "commit": "Commit", @@ -443,7 +441,7 @@ "noLabelsDescription": "Use labels to organize, prioritize, and categorize tasks efficiently." }, "notFound": { - "button": "Reload page", + "button": "Reload Page", "title": "Something went wrong…", "descriptionWithType": "The requested page is not found. You can go back to view all {{type}} and manage your settings.", "description": "The requested page is not found." @@ -523,15 +521,15 @@ "created": "Created in", "description": "Description" }, - "edit": "Edit label", - "delete": "Delete label", + "edit": "Edit Label", + "delete": "Delete Label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", - "newLabel": "New label", - "create": "Create labels" + "newLabel": "New Label", + "create": "Create Labels" }, "projectSettings": { - "newLabels": "Create label", + "newLabels": "Create Label", "general": { "mainTitle": "General settings", "projectNamePlaceholder": "Enter project name", @@ -540,13 +538,13 @@ "projectDescriptionLabel": "Description", "formSubmitButton": { "savingState": "Saving...", - "defaultState": "Save changes", + "defaultState": "Save Changes", "savedState": "Saved" }, "formCancelButton": "Cancel", "deleteProjectTitle": "Delete project", "deleteProjectDescription": "This will permanently delete this project, and everything contained in it. All repositories in it will also be deleted.", - "deleteProjectButton": "Delete project" + "deleteProjectButton": "Delete Project" }, "newMember": "New member", "member": "Member", @@ -583,11 +581,11 @@ "changes": "Changes", "noCommitsYet": "No commits yet", "noCommitDataDescription": "There are no commits yet.", - "compareChangesCreateTitle": "Create pull request", + "compareChangesCreateTitle": "Create Pull Request", "compareChangesCreateDescription": "Open pull request that is ready for review.", - "compareChangesDraftTitle": "Create draft pull request", + "compareChangesDraftTitle": "Create Draft Pull request", "compareChangesDraftDescription": "Does not request code reviews and cannot be merged.", - "compareChangesCreatedButton": "Pull request created", + "compareChangesCreatedButton": "Pull Request Created", "compareChangesFormTitleLabel": "Add a title", "compareChangesFormTitlePlaceholder": "Enter pull request title", "compareChangesFormDescriptionHeading": "Add a description", @@ -639,10 +637,10 @@ "compareChangesDiffLink": "learn more about diff comparisons", "compareChangesFormTitle": "Add a title", "compareChangesFormDescription": "Add a description", - "compareChangesCreateButton": "Create pull request", - "compareChangesCreateButtonLoading": "Creating pull request...", - "compareChangesDraftButton": "Draft pull request", - "compareChangesDraftButtonLoading": "Drafting pull request...", + "compareChangesCreateButton": "Create Pull Request", + "compareChangesCreateButtonLoading": "Creating Pull Request...", + "compareChangesDraftButton": "Draft Pull Request", + "compareChangesDraftButtonLoading": "Drafting Pull Request...", "replyHere": "Reply here", "showDiff": "Show Diff", "deletedFileDiff": "This file was deleted.", @@ -669,9 +667,9 @@ "select": "Select", "tokenExpiryNone": "Token will never expire", "tokenExpiryDate": " Token will expire on", - "gotItButton": "Got it", - "generateTokenButton": "Generate token", - "generatingTokenButton": "Generating token...", + "gotItButton": "Got It", + "generateTokenButton": "Generate Token", + "generatingTokenButton": "Generating Token...", "tokenTableHeader": "Token", "statusTableHeader": "Status", "expirationDateTableHeader": "Expiration date", @@ -698,7 +696,7 @@ "enterUsernameCaption": "This username will be shown across the platform.", "accountEmail": "Account email", "updatingProfileButton": "Updating...", - "updateProfileButton": "Update profile", + "updateProfileButton": "Update Profile", "updatedButton": "Updated", "passwordSettingsTitle": "Password settings", "passwordSettingsDesc": "Minimum of 6 characters long containing at least one number and a mixture of uppercase and lowercase letters.", @@ -746,8 +744,8 @@ }, "rules": { "showParentRules": "Show rules from parent scopes", - "edit": "Edit rule", - "delete": "Delete rule", + "edit": "Edit Rule", + "delete": "Delete Rule", "exclude": "Exclude", "include": "Include" }, @@ -765,8 +763,8 @@ "commitDetailsDiffAdditionsAnd": "additions and", "commitDetailsDiffDeletions": "deletions", "commitDetailsTitle": "Commit", - "browseFiles": "Browse files", - "createNewCommit": "Create new commit", + "browseFiles": "Browse Files", + "createNewCommit": "Create New Commit", "commitDetailsAuthored": "authored", "verified": "Verified" }, @@ -793,12 +791,12 @@ "prCommentUpdated": "PR comment updated", "prReviewSubmitted": "PR review submitted", "prLabelAssigned": "PR label assigned", - "create": "Create webhook", - "edit": "Edit webhook", - "delete": "Delete webhook" + "create": "Create Webhook", + "edit": "Edit Webhook", + "delete": "Delete Webhook" }, "secrets": { - "createNew": "Create new secret", + "createNew": "Create New Secret", "delete": "Delete Secret", "secretsTitle": "Secrets" }, @@ -862,7 +860,7 @@ "confirm": "Confirm" }, "close": "Close", - "newUserButton": "New user", + "newUserButton": "New User", "searchPlaceholder": "Search", "usersHeader": "Users", "tabs": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 66251fc6e6..f8be2fe3fe 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -1,13 +1,13 @@ { "repos": { - "create-file": "Create file", - "upload-files": "Télécharger des fichiers", + "createFile": "Create file", + "uploadFiles": "Télécharger des fichiers", "summary": "Résumé", "files": "Fichiers", "pipelines": "Pipelines", "commits": "Commits", "tags": "Étiquettes", - "pull-requests": "Requêtes de tirage", + "pullRequests": "Requêtes de tirage", "branches": "Branches", "search": "Rechercher", "settings": "Paramètres", @@ -128,10 +128,10 @@ "enterMinDefaultReviewers": "Entrer le nombre minimal de réviseurs par défaut", "enterMinReviewers": "Entrer le nombre minimal de réviseurs", "updateBranchRule": "Mettre à jour la règle de branche", - "CreateRule": "Créer une règle", - "updateRule": "Mettre à jour la règle", - "createRuleButton": "Créer la règle", - "updatingRule": "Mise à jour de la règle...", + "createBranchRule": "Create branch rule", + "updateRuleButton": "Mettre à jour la règle", + "createRuleButton": "Créer une règle de branche", + "updatingRuleButton": "Mise à jour de la règle...", "creatingRuleButton": "Création de la règle...", "createBranch": "Create branch", "branch": "Branche", @@ -182,8 +182,8 @@ }, "repositories": "Dépôts", "createRepository": "Créer un dépôt", - "import-repository": "Importer un dépôt", - "import-repositories": "Import repositories", + "importRepository": "Importer un dépôt", + "importRepositories": "Import repositories", "importing": "Importing…", "updated": "Mis à jour", "unarchiveRepo": "Unarchive Repository", @@ -213,10 +213,8 @@ "allRules": "All Rules", "branchRules": "Branch Rules", "tagRules": "Tag Rules", - "newTagRule": "New tag rule", - "createBranchRule": "New branch rule", - "createBranchRuleButton": "Create Branch rule", "createTagRuleButton": "Create tag rule", + "createBranchRuleButton": "Create Branch rule", "security": "Sécurité", "secretScanning": "Analyse des secrets", "secretScanningDescription": "Empêcher les commits contenant des secrets comme les mots de passe ou jetons API.", @@ -244,7 +242,7 @@ "0": "We recommend every repository include a", "1": "README, LICENSE, and .gitignrore." }, - "createFile": "New file" + "createFile": "Create file" }, "cloneInstructions": { "title": "Please generate git credentials if it’s your first time cloning the repository", @@ -277,7 +275,7 @@ "BlockTagUpdate": "Block tag update", "BlockTagUpdateDescription": "Only allow users with bypass permission to update matching tags", "updateTagRule": "Update tag rule", - "CreateTagRule": "Create a tag rule", + "createTagRule": "Create tag rule", "createTagTitle": "Create a tag", "repoTagDescriptionPlaceholder": "Enter a description of this tag...", "creatingTagButton": "Creating tag...", diff --git a/packages/ui/src/components/file-additions-trigger.tsx b/packages/ui/src/components/file-additions-trigger.tsx index 2d351430a1..425b320c05 100644 --- a/packages/ui/src/components/file-additions-trigger.tsx +++ b/packages/ui/src/components/file-additions-trigger.tsx @@ -16,7 +16,8 @@ export const FileAdditionsTrigger: FC<FileAdditionsTriggerProps> = ({ pathNewFil <DropdownMenu.Root> <DropdownMenu.Trigger asChild ref={triggerRef}> <Button className="relative overflow-hidden pl-4 pr-8" variant="outline"> - <span className="border-r pr-2.5">{t('views:repos.create-file', 'Create file')}</span> + <IconV2 name="plus" /> + <span className="border-r pr-2.5">{t('views:repos.createFile', 'Create File')}</span> <span className="absolute right-0 top-0 flex h-full w-8 items-center justify-center text-icons-7 transition-colors group-data-[state=open]:bg-cn-background-3 group-data-[state=open]:text-icons-9"> <IconV2 name="nav-arrow-down" size="2xs" /> </span> @@ -26,14 +27,14 @@ export const FileAdditionsTrigger: FC<FileAdditionsTriggerProps> = ({ pathNewFil <DropdownMenu.Item title={ <Link variant="secondary" to={pathNewFile} prefixIcon="check"> - <span className="truncate">{t('views:repos.create-file', 'Create file')}</span> + <span className="truncate">{t('views:repos.createFile', 'Create File')}</span> </Link> } /> <DropdownMenu.Item title={ <Link variant="secondary" to={pathUploadFiles} prefixIcon="upload"> - <span className="truncate">{t('views:repos.upload-files', 'Upload files')}</span> + <span className="truncate">{t('views:repos.uploadFiles', 'Upload Files')}</span> </Link> } /> diff --git a/packages/ui/src/components/repo-subheader.tsx b/packages/ui/src/components/repo-subheader.tsx index 86b186e0e7..3b8cffe07d 100644 --- a/packages/ui/src/components/repo-subheader.tsx +++ b/packages/ui/src/components/repo-subheader.tsx @@ -55,7 +55,7 @@ export const RepoSubheader = ({ {t('views:repos.tags', 'Tags')} </Tabs.Trigger> <Tabs.Trigger value={RepoTabsKeys.PULLS} disabled={isRepoEmpty}> - {t('views:repos.pull-requests', 'Pull requests')} + {t('views:repos.pullRequests', 'Pull requests')} </Tabs.Trigger> <Tabs.Trigger value={RepoTabsKeys.BRANCHES} disabled={isRepoEmpty}> {t('views:repos.branches', 'Branches')} diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-activities.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-activities.tsx index b2e779ff9a..fd5507497a 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-activities.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-activities.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { useRouterContext, useTranslation } from '@/context' -import { NoData, Pagination } from '@components/index' +import { IconV2, NoData, Pagination } from '@components/index' import { Spacer } from '@components/spacer' import ConnectorDetailsActivitiesList from './connector-details-activities-list' @@ -36,7 +36,12 @@ const ConnectorDetailsActivities: FC<ConnectorDetailsActivityProps> = ({ ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { navigate(0) // Reload the page } diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-references.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-references.tsx index 6bec28e762..26a0b7254e 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-references.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-references.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { useRouterContext, useTranslation } from '@/context' -import { ListActions, NoData, Pagination, SearchBox } from '@components/index' +import { IconV2, ListActions, NoData, Pagination, SearchBox } from '@components/index' import { Spacer } from '@components/spacer' import { useDebounceSearch } from '@hooks/use-debounce-search' @@ -46,7 +46,12 @@ const ConnectorDetailsReference: FC<ConnectorDetailsReferenceProps> = ({ ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { navigate(0) // Reload the page } diff --git a/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx b/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx index cfe5a67bdd..4b2884bf9f 100644 --- a/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx +++ b/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react' -import { Button, NoData, Pagination, Spacer, Text } from '@/components' +import { Button, IconV2, NoData, Pagination, Spacer, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -58,7 +58,12 @@ const ConnectorsListPage: FC<ConnectorListPageProps> = ({ ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { navigate(0) // Reload the page } @@ -89,7 +94,12 @@ const ConnectorsListPage: FC<ConnectorListPageProps> = ({ onFilterSelectionChange={onFilterSelectionChange} onFilterValueChange={onFilterValueChange} handleInputChange={(value: string) => setSearchQuery(value)} - headerAction={<Button onClick={onCreate}>{t('views:connectors.createNew', 'New connector')}</Button>} + headerAction={ + <Button onClick={onCreate}> + <IconV2 name="plus" /> + {t('views:connectors.createNew', 'New Connector')} + </Button> + } filterOptions={CONNECTOR_FILTER_OPTIONS} /> <Spacer size={4.5} /> diff --git a/packages/ui/src/views/labels/components/labels-list-view.tsx b/packages/ui/src/views/labels/components/labels-list-view.tsx index 1284ea1641..4fbf28f377 100644 --- a/packages/ui/src/views/labels/components/labels-list-view.tsx +++ b/packages/ui/src/views/labels/components/labels-list-view.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { MoreActionsTooltip, NoData, Table, Tag, Text } from '@/components' +import { IconV2, MoreActionsTooltip, NoData, Table, Tag, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@/utils' import { ILabelType, LabelValuesType } from '@/views' @@ -64,7 +64,15 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ t('views:noData.checkSpelling', 'Check your spelling and filter options,'), t('views:noData.changeSearch', 'or search for a different keyword.') ]} - primaryButton={{ label: t('views:noData.clearSearch', 'Clear search'), onClick: handleResetQueryAndPages }} + primaryButton={{ + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearSearch', 'Clear search')} + </> + ), + onClick: handleResetQueryAndPages + }} /> ) } @@ -76,7 +84,15 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ imageName="no-data-branches" title={t('views:noData.labels', 'No labels yet')} description={[t('views:noData.createLabel', 'Create a new label to get started.')]} - primaryButton={{ label: t('views:projectSettings.newLabels', 'Create label'), to: 'create' }} + primaryButton={{ + label: ( + <> + <IconV2 name="plus" /> + {t('views:projectSettings.newLabels', 'Create Label')} + </> + ), + to: 'create' + }} /> ) } diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index 4c97f0a3e9..ae2693281c 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, Checkbox, ListActions, Pagination, SearchInput, Skeleton, Text } from '@/components' +import { Button, Checkbox, IconV2, ListActions, Pagination, SearchInput, Skeleton, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { ILabelsStore, SandboxLayout } from '@/views' @@ -83,7 +83,10 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ </ListActions.Left> <ListActions.Right> <Button asChild> - <Link to="create">{t('views:labelData.newLabel', 'New label')}</Link> + <Link to="create"> + <IconV2 name="plus" /> + {t('views:labelData.newLabel', 'New Label')} + </Link> </Button> </ListActions.Right> </ListActions.Root> diff --git a/packages/ui/src/views/not-found-page.tsx b/packages/ui/src/views/not-found-page.tsx index b7a06daa22..cbd6e6abec 100644 --- a/packages/ui/src/views/not-found-page.tsx +++ b/packages/ui/src/views/not-found-page.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Button } from '@/components' +import { Button, IconV2 } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -32,7 +32,8 @@ export const NotFoundPage: FC<NotFoundPageProps> = ({ pageTypeText }) => { : t('views:notFound.description', 'The requested page is not found.')} </span> <Button variant="outline" type="button" onClick={handleReload}> - {t('views:notFound.button', 'Reload page')} + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} </Button> </div> </SandboxLayout.Main> diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx index 421a97c116..588ea2c4b7 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx @@ -172,14 +172,14 @@ export const ProfileSettingsTokenCreateDialog: FC<ProfileSettingsTokenCreateDial <ButtonLayout> <Dialog.Close onClick={onClose}> {createdTokenData - ? t('views:profileSettings.gotItButton', 'Got it') + ? t('views:profileSettings.gotItButton', 'Got It') : t('views:profileSettings.cancel', 'Cancel')} </Dialog.Close> {!createdTokenData && ( <Button type="submit" disabled={isLoading}> {!isLoading - ? t('views:profileSettings.generateTokenButton', 'Generate token') - : t('views:profileSettings.generatingTokenButton', 'Generating token...')} + ? t('views:profileSettings.generateTokenButton', 'Generate Token') + : t('views:profileSettings.generatingTokenButton', 'Generating Token...')} </Button> )} </ButtonLayout> diff --git a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx index 3d7c0a4fe3..641dde9e0c 100644 --- a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx +++ b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx @@ -236,7 +236,7 @@ export const SettingsAccountGeneralPage: FC<SettingsAccountGeneralPageProps> = ( > {isUpdatingUser ? t('views:profileSettings.updatingProfileButton', 'Updating...') - : t('views:profileSettings.updateProfileButton', 'Update profile')} + : t('views:profileSettings.updateProfileButton', 'Update Profile')} </Button> ) : ( <Button className="pointer-events-none" variant="ghost" type="button" theme="success"> diff --git a/packages/ui/src/views/project/project-general/project-general-page.tsx b/packages/ui/src/views/project/project-general/project-general-page.tsx index b6c86aa86b..c444d5dc7a 100644 --- a/packages/ui/src/views/project/project-general/project-general-page.tsx +++ b/packages/ui/src/views/project/project-general/project-general-page.tsx @@ -145,7 +145,7 @@ export const ProjectSettingsGeneralPage = ({ <Button type="submit"> {isUpdating ? t('views:projectSettings.general.formSubmitButton.savingState', 'Saving...') - : t('views:projectSettings.general.formSubmitButton.defaultState', 'Save changes')} + : t('views:projectSettings.general.formSubmitButton.defaultState', 'Save Changes')} </Button> <Button variant="outline" type="button" onClick={handleReset}> {t('views:projectSettings.general.formCancelButton', 'Cancel')} diff --git a/packages/ui/src/views/repo/components/path-action-bar.tsx b/packages/ui/src/views/repo/components/path-action-bar.tsx index 9e60764598..53c2413748 100644 --- a/packages/ui/src/views/repo/components/path-action-bar.tsx +++ b/packages/ui/src/views/repo/components/path-action-bar.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Button, Layout, PathBreadcrumbs, PathParts } from '@/components' +import { Button, IconV2, Layout, PathBreadcrumbs, PathParts } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { BranchSelectorTab, CodeModes } from '@/views' @@ -60,7 +60,8 @@ export const PathActionBar: FC<PathActionBarProps> = ({ selectedRefType === BranchSelectorTab.BRANCHES && ( <Button asChild> <Link className="relative grid grid-cols-[auto_1fr] items-center gap-1.5" to={pathNewFile}> - <span className="truncate">{t('views:repos.create-file', 'Create file')}</span> + <IconV2 name="plus" /> + <span className="truncate">{t('views:repos.createFile', 'Create File')}</span> </Link> </Button> )} @@ -68,10 +69,15 @@ export const PathActionBar: FC<PathActionBarProps> = ({ <div className="flex gap-2.5"> {!!handleCancelFileEdit && ( <Button variant="outline" onClick={handleCancelFileEdit}> - Cancel changes + Cancel Changes + </Button> + )} + {!!handleOpenCommitDialog && ( + <Button onClick={handleOpenCommitDialog}> + <IconV2 name="git-commit" /> + Commit Changes </Button> )} - {!!handleOpenCommitDialog && <Button onClick={handleOpenCommitDialog}>Commit changes</Button>} </div> )} </Layout.Horizontal> diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-button.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-button.tsx index 33a01f50d7..a72a508d18 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-button.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-button.tsx @@ -62,7 +62,7 @@ const PullRequestCompareButton: FC<PullRequestCompareButtonProps> = ({ options={[ { value: PR_TYPE.CREATE, - label: t(`views:pullRequests.compareChangesCreateTitle`, 'Create pull request'), + label: t(`views:pullRequests.compareChangesCreateTitle`, 'Create Pull Request'), description: t( `views:pullRequests.compareChangesCreateDescription`, 'Open pull request that is ready for review.' @@ -70,7 +70,7 @@ const PullRequestCompareButton: FC<PullRequestCompareButtonProps> = ({ }, { value: PR_TYPE.DRAFT, - label: t(`views:pullRequests.compareChangesDraftTitle`, 'Create draft pull request'), + label: t(`views:pullRequests.compareChangesDraftTitle`, 'Create Draft Pull Request'), description: t( `views:pullRequests.compareChangesDraftDescription`, 'Does not request code reviews and cannot be merged.' @@ -78,6 +78,7 @@ const PullRequestCompareButton: FC<PullRequestCompareButtonProps> = ({ } ]} > + <IconV2 name="plus" /> {t( `views:pullRequests.compareChanges${prType}Button${isLoading ? 'Loading' : ''}`, `${prType}${isLoading ? 'ing' : ''} pull request${isLoading ? '...' : ''}` diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-form.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-form.tsx index f9f192d509..ba8455a30d 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-form.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-form.tsx @@ -76,7 +76,7 @@ const PullRequestCompareForm = forwardRef<HTMLFormElement, PullRequestFormProps> preserveCommentOnSave allowEmptyValue isLoading={isLoading} - buttonTitle="Create pull request" + buttonTitle="Create Pull Request" onSaveComment={newComment => { if (isEmpty(errors)) { onFormSubmit({ title: formMethods.getValues('title'), description: newComment }) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx index 7defc1ffcc..38d8ad0593 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { NoData, StackedList } from '@/components' +import { IconV2, NoData, StackedList } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { PRState, PullRequestListProps } from '@/views' import { noop } from 'lodash-es' @@ -53,7 +53,7 @@ const EmptyStateView: FC<EmptyStateProps> = ({ 'views:noData.noOpenPullRequests', `There are no open pull requests in this ${repoId ? 'repo' : 'project'} yet.` ), - t('views:repos.createPullReq', 'Create Pull request.') + t('views:repos.createPullReq', 'Create Pull Request.') ] } case PR_STATE.CLOSED: @@ -61,7 +61,7 @@ const EmptyStateView: FC<EmptyStateProps> = ({ title: t('views:noData.title.noClosedPullRequests', 'No closed pull requests yet'), description: [ t('views:noData.noClosedPullRequests', 'There are no closed pull requests in this project yet.'), - t('views:repos.createPullReq', 'Create Pull request.') + t('views:repos.createPullReq', 'Create Pull Request.') ] } case PR_STATE.MERGED: @@ -69,7 +69,7 @@ const EmptyStateView: FC<EmptyStateProps> = ({ title: t('views:noData.title.noMergedPullRequests', 'No merged pull requests yet'), description: [ t('views:noData.noMergedPullRequests', 'There are no merged pull requests in this project yet.'), - t('views:repos.createPullReq', 'Create Pull request.') + t('views:repos.createPullReq', 'Create Pull Request.') ] } default: @@ -106,7 +106,12 @@ const EmptyStateView: FC<EmptyStateProps> = ({ primaryButton={ repoId ? { - label: t('views:noData.button.createPullRequest', 'Create pull request'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:noData.button.createPullRequest', 'Create Pull Request')} + </> + ), to: `${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/` } : undefined diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index 029504c3ae..795f19fdce 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -273,7 +273,12 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ primaryButton={ repoId ? { - label: 'Create pull request', + label: ( + <> + <IconV2 name="plus" /> + {t('views:noData.button.createPullRequest', 'Create Pull Request')} + </> + ), to: `${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/` } : undefined @@ -459,7 +464,8 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ {repoId ? ( <Button asChild> <Link to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/`}> - {t('views:repos.createPullReq', 'Create Pull request.')} + <IconV2 name="plus" /> + {t('views:repos.createPullReq', 'Create Pull Request.')} </Link> </Button> ) : null} diff --git a/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx index 4fa6dd797a..fecef95a76 100644 --- a/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx @@ -139,7 +139,7 @@ export const RepoBranchSettingsRulesPage: FC<RepoBranchSettingsRulesPageProps> = <Text as="h1" variant="heading-section" className="mb-4"> {presetRuleData ? t('views:repos.updateBranchRule', 'Update branch rule') - : t('views:repos.CreateRule', 'Create a branch rule')} + : t('views:repos.createBranchRule', 'Create a branch rule')} </Text> <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> @@ -187,11 +187,11 @@ export const RepoBranchSettingsRulesPage: FC<RepoBranchSettingsRulesPageProps> = <Button type="submit" disabled={isLoading}> {!isLoading ? presetRuleData - ? t('views:repos.updateRule', 'Update rule') - : t('views:repos.createRuleButton', 'Create rule') + ? t('views:repos.updateRuleButton', 'Update Rule') + : t('views:repos.createRuleButton', 'Create Rule') : presetRuleData - ? t('views:repos.updatingRule', 'Updating rule...') - : t('views:repos.creatingRuleButton', 'Creating rule...')} + ? t('views:repos.updatingRuleButton', 'Updating Rule...') + : t('views:repos.creatingRuleButton', 'Creating Rule...')} </Button> <Button type="button" variant="outline" asChild> <NavLink to="..">{t('views:repos.cancel', 'Cancel')}</NavLink> diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 586cba4d48..484ab07d3e 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -65,14 +65,24 @@ export const BranchesList: FC<BranchListPageProps> = ({ t('views:noData.startBranchDescription', 'Start branching to see your work organized.') ] } - secondaryButton={ + primaryButton={ isDirtyList ? { - label: t('views:noData.clearSearch', 'Clear search'), + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearSearch', 'Clear Search')} + </> + ), onClick: handleResetFiltersAndPages } : { - label: t('views:repos.createBranch', 'Create branch'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:repos.createBranch', 'Create Branch')} + </> + ), onClick: () => { setCreateBranchDialogOpen(true) } diff --git a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx index 80428abad1..49f430f133 100644 --- a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx +++ b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' +import { Button, IconV2, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -73,7 +73,8 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ variant="primary" theme="default" > - {t('views:repos.createBranch', 'Create branch')} + <IconV2 name="plus" /> + {t('views:repos.createBranch', 'Create Branch')} </Button> </ListActions.Right> </ListActions.Root> diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index 9354548a52..56a4475483 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Avatar, Button, CommitCopyActions, Layout, Skeleton, Text, TimeAgoCard } from '@/components' +import { Avatar, Button, CommitCopyActions, IconV2, Layout, Skeleton, Text, TimeAgoCard } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { ICommitDetailsStore, SandboxLayout } from '@/views' @@ -55,7 +55,8 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ /> <Button variant="outline" asChild> <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> - {t('views:commits.browseFiles', 'Browse files')} + <IconV2 name="folder" /> + {t('views:commits.browseFiles', 'Browse Files')} </Link> </Button> </div> diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index bbb991045c..a86b891d89 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -86,7 +86,12 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ primaryButton={ isDirtyList ? { - label: t('views:noData.clearFilters', 'Clear filters'), + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearFilters', 'Clear Filters')} + </> + ), onClick: handleResetFiltersAndPages } : // TODO: add onClick for Creating new commit @@ -94,7 +99,7 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ label: ( <> <IconV2 name="plus" /> - {t('views:commits.createNewCommit', 'Make commit')} + {t('views:commits.createNewCommit', 'Create New Commit')} </> ) } diff --git a/packages/ui/src/views/repo/repo-create/index.tsx b/packages/ui/src/views/repo/repo-create/index.tsx index 7524421edc..2dd39c8090 100644 --- a/packages/ui/src/views/repo/repo-create/index.tsx +++ b/packages/ui/src/views/repo/repo-create/index.tsx @@ -259,7 +259,7 @@ export function RepoCreatePage({ <ButtonLayout horizontalAlign="start"> {/* TODO: Improve loading state to avoid flickering */} <Button type="submit" disabled={isLoading || !isEmpty(errors)}> - {!isLoading ? 'Create repository' : 'Creating repository...'} + {!isLoading ? 'Create Repository' : 'Creating Repository...'} </Button> <Button type="button" variant="secondary" onClick={onFormCancel}> Cancel diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 40e433c016..4855e9555c 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode, useMemo } from 'react' -import { NoData, PathParts, Skeleton } from '@/components' +import { IconV2, NoData, PathParts, Skeleton } from '@/components' import { useTranslation } from '@/context' import { BranchInfoBar, @@ -118,7 +118,12 @@ export const RepoFiles: FC<RepoFilesProps> = ({ title="No files yet" description={['There are no files in this repository yet.']} primaryButton={{ - label: 'Create file', + label: ( + <> + <IconV2 name="plus" /> + {t('views:repos.createFile', 'Create File')} + </> + ), to: `${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/files/new/${gitRef}/~/` }} /> diff --git a/packages/ui/src/views/repo/repo-import/repo-import-mulitple.tsx b/packages/ui/src/views/repo/repo-import/repo-import-mulitple.tsx index 884de224c8..59ac5e3408 100644 --- a/packages/ui/src/views/repo/repo-import/repo-import-mulitple.tsx +++ b/packages/ui/src/views/repo/repo-import/repo-import-mulitple.tsx @@ -269,7 +269,7 @@ export function RepoImportMultiplePage({ <ButtonLayout horizontalAlign="start"> {/* TODO: Improve loading state to avoid flickering */} <Button type="submit" disabled={isLoading}> - {!isLoading ? 'Import repositories' : 'Importing repositories...'} + {!isLoading ? 'Import Repositories' : 'Importing Repositories...'} </Button> <Button type="button" variant="outline" onClick={handleCancel}> Cancel diff --git a/packages/ui/src/views/repo/repo-import/repo-import.tsx b/packages/ui/src/views/repo/repo-import/repo-import.tsx index 9d8600a034..26cc9eb926 100644 --- a/packages/ui/src/views/repo/repo-import/repo-import.tsx +++ b/packages/ui/src/views/repo/repo-import/repo-import.tsx @@ -252,7 +252,7 @@ export function RepoImportPage({ onFormSubmit, onFormCancel, isLoading, apiError <ButtonLayout horizontalAlign="start"> {/* TODO: Improve loading state to avoid flickering */} <Button type="submit" disabled={isLoading}> - {!isLoading ? 'Import repository' : 'Importing repository...'} + {!isLoading ? 'Import Repository' : 'Importing Repository...'} </Button> <Button type="button" variant="outline" onClick={handleCancel}> Cancel diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index 4cb57d33cd..39836263c1 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -68,7 +68,12 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { navigate(0) // Reload the page } @@ -187,19 +192,20 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ options={[ { value: 'new', - label: t('views:repos.createRepository', 'Create repository') + label: t('views:repos.createRepository', 'Create Repository') }, { value: 'import', - label: t('views:repos.import-repository', 'Import repository') + label: t('views:repos.importRepository', 'Import Repository') }, { value: 'import-multiple', - label: t('views:repos.import-repositories', 'Import repositories') + label: t('views:repos.importRepositories', 'Import Repositories') } ]} > - {t('views:repos.createRepository', 'Create repository')} + <IconV2 name="plus" /> + {t('views:repos.createRepository', 'Create Repository')} </SplitButton> } filterOptions={filterOptions} diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index e9b957bd47..cc42c51b5b 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -123,14 +123,19 @@ export function RepoList({ t('views:noData.createOrImportRepos', 'Create new or import an existing repository.') ]} primaryButton={{ - label: <>{t('views:repos.createRepository', 'Create repository')}</>, + label: ( + <> + <IconV2 name="plus" /> + {t('views:repos.createRepository', 'Create Repository')} + </> + ), to: toCreateRepo?.() }} secondaryButton={{ label: ( <> <IconV2 name="import" /> - {t('views:repos.import-repository', 'Import Repository')} + {t('views:repos.importRepository', 'Import Repository')} </> ), to: toImportRepo?.(), diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx index 55a2c7b2e5..9a15866dcc 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx @@ -45,8 +45,8 @@ export const RepoSettingsGeneralDelete: FC<{ ? t('views:repos.unarchiving', 'Unarchiving...') : t('views:repos.archiving', 'Archiving...') : archived - ? t('views:repos.unarchiveRepoButton', 'Unarchive repository') - : t('views:repos.archiveRepoButton', 'Archive repository')} + ? t('views:repos.unarchiveRepoButton', 'Unarchive Repository') + : t('views:repos.archiveRepoButton', 'Archive Repository')} </Button> </ButtonLayout> {apiError && apiError.type === ErrorTypes.ARCHIVE_REPO && ( @@ -69,7 +69,7 @@ export const RepoSettingsGeneralDelete: FC<{ </Layout.Vertical> <ButtonLayout horizontalAlign="start"> <Button type="button" variant="primary" theme="danger" onClick={openRepoAlertDeleteDialog}> - {t('views:repos.deleteRepoButton', 'Delete this repository')} + {t('views:repos.deleteRepoButton', 'Delete Repository')} </Button> </ButtonLayout> {apiError && apiError.type === ErrorTypes.DELETE_REPO && ( diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index 6bb962d894..ce397cc79d 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -155,11 +155,12 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ options={[ { value: 'tag-rule', - label: t('views:repos.newTagRule', 'New tag rule') + label: t('views:repos.createTagRuleButton', 'Create Tag Rule') } ]} > - {t('views:repos.createBranchRule', 'New branch rule')} + <IconV2 name="plus" /> + {t('views:repos.createBranchRuleButton', 'Create Branch Rule')} </SplitButton> </ListActions.Right> </ListActions.Root> @@ -219,13 +220,13 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ <MoreActionsTooltip actions={[ { - title: t('views:rules.edit', 'Edit rule'), + title: t('views:rules.edit', 'Edit Rule'), iconName: 'edit-pencil', onClick: () => handleRuleClick(rule.identifier!) }, { isDanger: true, - title: t('views:rules.delete', 'Delete rule'), + title: t('views:rules.delete', 'Delete Rule'), iconName: 'trash', onClick: () => openRulesAlertDeleteDialog(rule.identifier!) } @@ -282,11 +283,16 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ ) ]} splitButton={{ - label: t('views:repos.createBranchRuleButton', 'Create Branch rule'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:repos.createBranchRuleButton', 'Create Branch Rule')} + </> + ), options: [ { value: 'tag-rule', - label: t('views:repos.createTagRuleButton', 'Create tag rule') + label: t('views:repos.createTagRuleButton', 'Create Tag Rule') } ], handleOptionChange: option => { diff --git a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx index 66ddeefff6..64e1fbcc9a 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx @@ -86,7 +86,7 @@ ${sshUrl} label: ( <> <IconV2 name="plus" /> - {t('views:repos.emptyRepoPage.noData.createFile', 'New file')} + {t('views:repos.emptyRepoPage.noData.createFile', 'Create File')} </> ), to: `${projName ? `/${projName}` : ''}/repos/${repoName}/files/new/${gitRef}/~/` @@ -114,7 +114,7 @@ ${sshUrl} <Layout.Grid gapY="sm" as="section"> <Button onClick={handleCreateToken} className="w-fit"> - {t('views:repos.emptyRepoPage.cloneInstructions.generateButton', 'Generate clone credential')} + {t('views:repos.emptyRepoPage.cloneInstructions.generateButton', 'Generate Clone Credential')} </Button> {tokenGenerationError && ( diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index cd2c5e2a42..57391596de 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -204,7 +204,8 @@ export function RepoSummaryView({ className="relative grid grid-cols-[auto_1fr] items-center gap-1.5" to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/files/new/${gitRef || selectedBranchOrTag?.name || ''}/~/`} > - <span className="truncate">{t('views:repos.create-file', 'Create file')}</span> + <IconV2 name="plus" /> + <span className="truncate">{t('views:repos.createFile', 'Create File')}</span> </Link> </Button> ) : null} diff --git a/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx index 3dda2d7c84..d661649c64 100644 --- a/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx @@ -122,7 +122,7 @@ export const RepoTagSettingsRulesPage: FC<RepoTagSettingsRulesPageProps> = ({ <Text as="h1" variant="heading-section" color="foreground-1" className="mb-4"> {presetRuleData ? t('views:repos.updateTagRule', 'Update tag rule') - : t('views:repos.CreateTagRule', 'Create a tag rule')} + : t('views:repos.createTagRule', 'Create tag rule')} </Text> <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> @@ -155,11 +155,11 @@ export const RepoTagSettingsRulesPage: FC<RepoTagSettingsRulesPageProps> = ({ <Button type="submit" disabled={isLoading}> {!isLoading ? presetRuleData - ? t('views:repos.updateRule', 'Update rule') - : t('views:repos.createRuleButton', 'Create rule') + ? t('views:repos.updateRuleButton', 'Update Rule') + : t('views:repos.createRuleButton', 'Create Rule') : presetRuleData - ? t('views:repos.updatingRule', 'Updating rule...') - : t('views:repos.creatingRuleButton', 'Creating rule...')} + ? t('views:repos.updatingRuleButton', 'Updating Rule...') + : t('views:repos.creatingRuleButton', 'Creating Rule...')} </Button> <Button type="button" variant="outline" asChild> <NavLink to="..">{t('views:repos.cancel', 'Cancel')}</NavLink> diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 6f455bc41f..81a483f873 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -109,8 +109,8 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ </Dialog.Close> <Button type="submit" disabled={isLoading} loading={isLoading}> {isLoading - ? t('views:repos.creatingTagButton', 'Creating tag...') - : t('views:repos.createTagButton', 'Create tag')} + ? t('views:repos.creatingTagButton', 'Creating Tag...') + : t('views:repos.createTagButton', 'Create Tag')} </Button> </ButtonLayout> </Dialog.Footer> diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index 63361f79bb..c5a8c9c60d 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -4,6 +4,7 @@ import { ActionData, Avatar, CommitCopyActions, + IconV2, MoreActionsTooltip, NoData, Skeleton, @@ -42,7 +43,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ const getTableActions = (tag: CommitTagType): ActionData[] => [ { iconName: 'git-branch', - title: t('views:repos.createBranch', 'Create branch'), + title: t('views:repos.createBranch', 'Create Branch'), onClick: () => onOpenCreateBranchDialog(tag) }, { @@ -53,7 +54,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ { iconName: 'trash', isDanger: true, - title: t('views:repos.deleteTag', 'Delete tag'), + title: t('views:repos.deleteTag', 'Delete Tag'), onClick: () => onDeleteTag(tag.name) } ] @@ -81,11 +82,21 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ primaryButton={ isDirtyList ? { - label: t('views:noData.clearSearch', 'Clear search'), + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearSearch', 'Clear Search')} + </> + ), onClick: onResetFiltersAndPages } : { - label: t('views:noData.createNewTag', 'Create tag'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:noData.createNewTag', 'Create Tag')} + </> + ), onClick: onOpenCreateTagDialog } } diff --git a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx index f77408f050..3b90b9947f 100644 --- a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx +++ b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' +import { Button, IconV2, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' import { useTranslation } from '@/context' import { RepoTagsListViewProps, SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -71,6 +71,7 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ </ListActions.Left> <ListActions.Right> <Button onClick={openCreateTagDialog}> + <IconV2 name="plus" /> <span>{t('views:repos.createTag', 'Create Tag')}</span> </Button> </ListActions.Right> diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx index bfa1fa52d1..034682ce73 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx @@ -160,11 +160,11 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ <Button type="submit" disabled={isLoading}> {isLoading ? preSetWebhookData - ? t('views:repos.updatingWebhook', 'Updating webhook...') - : t('views:repos.creatingWebhook', 'Creating webhook...') + ? t('views:repos.updatingWebhook', 'Updating Webhook...') + : t('views:repos.creatingWebhook', 'Creating Webhook...') : preSetWebhookData - ? t('views:repos.updateWebhook', 'Update webhook') - : t('views:repos.createWebhook', 'Create webhook')} + ? t('views:repos.updateWebhook', 'Update Webhook') + : t('views:repos.createWebhook', 'Create Webhook')} </Button> <Button type="button" variant="outline" onClick={onFormCancel}> {t('views:repos.cancel', 'Cancel')} diff --git a/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx b/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx index 5f880639b7..eb75324a34 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx @@ -1,4 +1,15 @@ -import { Layout, MoreActionsTooltip, NoData, Pagination, Spacer, StatusBadge, Switch, Table, Text } from '@/components' +import { + IconV2, + Layout, + MoreActionsTooltip, + NoData, + Pagination, + Spacer, + StatusBadge, + Switch, + Table, + Text +} from '@/components' import { useRouterContext, useTranslation } from '@/context' import { WebhookType } from '@/views' @@ -80,11 +91,21 @@ export function RepoWebhookList({ primaryButton={ isDirtyList ? { - label: t('views:noData.clearFilters', 'Clear filters'), + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearFilters', 'Clear Filters')} + </> + ), onClick: handleReset } : { - label: t('views:webhookData.create', 'Create webhook'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:webhookData.create', 'Create Webhook')} + </> + ), onClick: handleNavigate } } @@ -160,7 +181,7 @@ export function RepoWebhookList({ iconName="more-horizontal" actions={[ { - title: t('views:webhookData.edit', 'Edit webhook'), + title: t('views:webhookData.edit', 'Edit Webhook'), iconName: 'edit-pencil', onClick: () => navigate( @@ -170,7 +191,7 @@ export function RepoWebhookList({ { isDanger: true, iconName: 'trash', - title: t('views:webhookData.delete', 'Delete webhook'), + title: t('views:webhookData.delete', 'Delete Webhook'), onClick: () => openDeleteWebhookDialog(webhook.id) } ]} diff --git a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx index 4bfcca0434..651c3c0310 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, ListActions, SearchInput, Skeleton, Spacer, Text } from '@/components' +import { Button, IconV2, ListActions, SearchInput, Skeleton, Spacer, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -66,7 +66,10 @@ const RepoWebhookListPage: FC<RepoWebhookListPageProps> = ({ </ListActions.Left> <ListActions.Right> <Button asChild> - <Link to="create">Create webhook</Link> + <Link to="create"> + <IconV2 name="plus" /> + {t('views:webhookData.create', 'Create Webhook')} + </Link> </Button> </ListActions.Right> </ListActions.Root> diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx index 37f036057f..2745b62a7b 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Button, ListActions, NoData, Pagination, SearchBox, Spacer, Text } from '@/components' +import { Button, IconV2, ListActions, NoData, Pagination, SearchBox, Spacer, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { useDebounceSearch } from '@/hooks' import { SandboxLayout } from '@/views' @@ -48,7 +48,12 @@ const SecretListPage: FC<SecretListPageProps> = ({ ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { navigate(0) // Reload the page } @@ -75,7 +80,10 @@ const SecretListPage: FC<SecretListPageProps> = ({ /> </ListActions.Left> <ListActions.Right> - <Button onClick={onCreate}>{t('views:secrets.createNew', 'New secret')}</Button> + <Button onClick={onCreate}> + <IconV2 name="plus" /> + {t('views:secrets.createNew', 'New Secret')} + </Button> </ListActions.Right> </ListActions.Root> <Spacer size={4} /> diff --git a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx index 121d834ecd..608165acdb 100644 --- a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx +++ b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx @@ -1,4 +1,4 @@ -import { NoData } from '@/components' +import { IconV2, NoData } from '@/components' import { useTranslation } from '@/context' import { DialogLabels } from '@/views/user-management' import { useDialogData } from '@/views/user-management/components/dialogs/hooks/use-dialog-data' @@ -20,7 +20,12 @@ export const EmptyState = () => { ) ]} primaryButton={{ - label: t('views:userManagement.newUserButton', 'New user'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:userManagement.newUserButton', 'New User')} + </> + ), onClick: () => handleDialogOpen(null, DialogLabels.CREATE_USER) }} /> diff --git a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx index 664b77a39a..715b516267 100644 --- a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx +++ b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx @@ -1,4 +1,4 @@ -import { Button, ListActions, SearchBox } from '@/components' +import { Button, IconV2, ListActions, SearchBox } from '@/components' import { useTranslation } from '@/context' import { DialogLabels } from '@/views/user-management/components/dialogs' import { useDialogData } from '@/views/user-management/components/dialogs/hooks/use-dialog-data' @@ -23,7 +23,8 @@ export const Actions = () => { </ListActions.Left> <ListActions.Right> <Button onClick={() => handleDialogOpen(null, DialogLabels.CREATE_USER)}> - {t('views:userManagement.newUserButton', 'New user')} + <IconV2 name="plus" /> + {t('views:userManagement.newUserButton', 'New User')} </Button> </ListActions.Right> </ListActions.Root> diff --git a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/error-state/error-state.tsx b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/error-state/error-state.tsx index 26b8ce0136..832bb51b5f 100644 --- a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/error-state/error-state.tsx +++ b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/error-state/error-state.tsx @@ -1,4 +1,4 @@ -import { NoData } from '@/components' +import { IconV2, NoData } from '@/components' import { useTranslation } from '@/context' import { useStates } from '@/views/user-management/providers/state-provider' @@ -21,7 +21,12 @@ export const ErrorState = () => { ) ]} primaryButton={{ - label: t('views:notFound.button', 'Reload page'), + label: ( + <> + <IconV2 name="refresh" /> + {t('views:notFound.button', 'Reload Page')} + </> + ), onClick: () => { window.location.reload() } diff --git a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx index 6fe8325c82..e4e2c2c7d5 100644 --- a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx +++ b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx @@ -1,4 +1,4 @@ -import { NoData } from '@/components' +import { IconV2, NoData } from '@/components' import { useTranslation } from '@/context' import { useSearch } from '@/views/user-management/providers/search-provider' @@ -19,7 +19,12 @@ export const NoSearchResults = () => { }) ]} primaryButton={{ - label: t('views:noData.clearFilters', 'Clear filters'), + label: ( + <> + <IconV2 name="trash" /> + {t('views:noData.clearFilters', 'Clear Filters')} + </> + ), onClick: handleResetSearch }} /> From f78a385ca1190fe7d812dd4982e4912f73198ea8 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Fri, 8 Aug 2025 10:04:38 +0000 Subject: [PATCH 033/180] fix: redirect legacy url for edit files (#10158) * 42e0a5 fix: handle not found paths for different branch * 6d9982 fix: redirect from edit to files * 642e1f fix: redirect legacy url for edit files --- apps/gitness/src/pages-v2/repo/repo-code.tsx | 4 ++- .../src/pages-v2/repo/repo-sidebar.tsx | 8 +++-- apps/gitness/src/routes.tsx | 29 ++++++++++++++++++- packages/ui/src/views/not-found-page.tsx | 5 ++-- .../views/repo/repo-files/repo-files-view.tsx | 14 +++++++-- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-code.tsx b/apps/gitness/src/pages-v2/repo/repo-code.tsx index 461fec02fd..2dba51883a 100644 --- a/apps/gitness/src/pages-v2/repo/repo-code.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-code.tsx @@ -50,7 +50,8 @@ export const RepoCode = () => { const { data: { body: repoDetails } = {}, refetch: refetchRepoContent, - isLoading: isLoadingRepoDetails + isLoading: isLoadingRepoDetails, + error: repoDetailsError } = useGetContentQuery({ path: fullResourcePath || '', repo_ref: repoRef, @@ -172,6 +173,7 @@ export const RepoCode = () => { isLoadingRepoDetails={isLoadingRepoDetails} toRepoFileDetails={({ path }: { path: string }) => `../${path}`} showContributeBtn={showContributeBtn} + repoDetailsError={repoDetailsError} > {renderCodeView} </RepoFiles> diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index ebf76390ea..fbbf232620 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -143,10 +143,14 @@ export const RepoSidebar = () => { (branchTagName: BranchSelectorListItem, type: BranchSelectorTab) => { if (type === BranchSelectorTab.BRANCHES) { setPreSelectedTab(type) - navigate(`${routes.toRepoFiles({ spaceId, repoId })}/${REFS_BRANCH_PREFIX + branchTagName.name}`) + navigate( + `${routes.toRepoFiles({ spaceId, repoId })}/${REFS_BRANCH_PREFIX + branchTagName.name}/~/${fullResourcePath}` + ) } else if (type === BranchSelectorTab.TAGS) { setPreSelectedTab(type) - navigate(`${routes.toRepoFiles({ spaceId, repoId })}/${REFS_TAGS_PREFIX + branchTagName.name}`) + navigate( + `${routes.toRepoFiles({ spaceId, repoId })}/${REFS_TAGS_PREFIX + branchTagName.name}/~/${fullResourcePath}` + ) } }, [navigate, repoId, spaceId] diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 683f9a777a..30678bf1c1 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -1,4 +1,4 @@ -import { Navigate } from 'react-router-dom' +import { Navigate, redirect } from 'react-router-dom' import { Breadcrumb, Layout, Sidebar } from '@harnessio/ui/components' import { @@ -215,6 +215,17 @@ export const repoRoutes: CustomRouteObject[] = [ }, { path: 'summary', + loader: ({ params }) => { + const wildcard = params['*'] || '' + if (wildcard.startsWith('edit/')) { + const cleanPath = wildcard.substring(5) + return redirect(`./${cleanPath}`) + } else if (wildcard.startsWith('new/')) { + const cleanPath = wildcard.substring(4) + return redirect(`./${cleanPath}`) + } + return null + }, element: <RepoSummaryPage />, handle: { breadcrumb: () => <span>{Page.Summary}</span>, @@ -291,6 +302,22 @@ export const repoRoutes: CustomRouteObject[] = [ pageTitle: Page.Branches } }, + { + path: 'edit/*', + loader: ({ params }) => { + const wildcard = params['*'] || '' + return redirect(`../files/edit/${wildcard}`) + }, + element: ( + <ExplorerPathsProvider> + <RepoSidebar /> + </ExplorerPathsProvider> + ), + handle: { + breadcrumb: () => <span>{Page.Files}</span>, + routeName: RouteConstants.toRepoFiles + } + }, { path: 'files', element: ( diff --git a/packages/ui/src/views/not-found-page.tsx b/packages/ui/src/views/not-found-page.tsx index cbd6e6abec..c94629a5ed 100644 --- a/packages/ui/src/views/not-found-page.tsx +++ b/packages/ui/src/views/not-found-page.tsx @@ -6,9 +6,10 @@ import { SandboxLayout } from '@/views' export interface NotFoundPageProps { pageTypeText?: string + errorMessage?: string } -export const NotFoundPage: FC<NotFoundPageProps> = ({ pageTypeText }) => { +export const NotFoundPage: FC<NotFoundPageProps> = ({ pageTypeText, errorMessage }) => { const { t } = useTranslation() const handleReload = () => { @@ -29,7 +30,7 @@ export const NotFoundPage: FC<NotFoundPageProps> = ({ pageTypeText }) => { `The requested page is not found. You can go back to view all ${pageTypeText} and manage your settings.`, { type: pageTypeText } ) - : t('views:notFound.description', 'The requested page is not found.')} + : errorMessage || t('views:notFound.description', 'The requested page is not found.')} </span> <Button variant="outline" type="button" onClick={handleReload}> <IconV2 name="refresh" /> diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 4855e9555c..c8b1e0fcfa 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -2,6 +2,7 @@ import { FC, ReactNode, useMemo } from 'react' import { IconV2, NoData, PathParts, Skeleton } from '@/components' import { useTranslation } from '@/context' +import { UsererrorError } from '@/types' import { BranchInfoBar, BranchSelectorListItem, @@ -10,6 +11,7 @@ import { CommitDivergenceType, FileLastChangeBar, LatestFileTypes, + NotFoundPage, PathActionBar, RepoFile, SandboxLayout, @@ -40,6 +42,7 @@ interface RepoFilesProps { selectedRefType: BranchSelectorTab fullResourcePath?: string showContributeBtn?: boolean + repoDetailsError?: UsererrorError | null } export const RepoFiles: FC<RepoFilesProps> = ({ @@ -65,7 +68,8 @@ export const RepoFiles: FC<RepoFilesProps> = ({ gitRef, selectedRefType, fullResourcePath, - showContributeBtn + showContributeBtn, + repoDetailsError }) => { const { t } = useTranslation() @@ -76,7 +80,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ if (!isView) return children - if (!isRepoEmpty && !isDir) { + if (!isRepoEmpty && !isDir && !repoDetailsError) { return ( <> {!isLoadingRepoDetails && <FileLastChangeBar toCommitDetails={toCommitDetails} {...latestFile} />} @@ -85,7 +89,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ ) } - if (isShowSummary && files.length) + if (isShowSummary && files.length && !repoDetailsError) return ( <> {selectedBranchTag?.name !== defaultBranchName && ( @@ -111,6 +115,10 @@ export const RepoFiles: FC<RepoFilesProps> = ({ </> ) + if (repoDetailsError) { + return <NotFoundPage errorMessage={repoDetailsError.message} /> + } + return ( <NoData withBorder From 3c36e65d2571d6affbf20471a66ca914fac67d64 Mon Sep 17 00:00:00 2001 From: Pranesh TG <pranesh.g@harness.io> Date: Fri, 8 Aug 2025 10:51:51 +0000 Subject: [PATCH 034/180] Update tag, tag icon sizes and Add new button sizes (#10148) * 6fd8a2 Update branch name tag to gray theme * 01add1 Fix Type issues * 8c1cc1 Fix tag component in all references * 80ca04 Update tag, tag icon sizes and Add new button sizes --- .../src/content/docs/components/button.mdx | 6 ++ .../src/content/docs/components/tag.mdx | 68 ++++++------- packages/ui/src/components/branch-tag.tsx | 27 ++++++ packages/ui/src/components/button.tsx | 6 +- packages/ui/src/components/copy-tag.tsx | 36 +++++++ .../git-commit-dialog/git-commit-dialog.tsx | 4 +- packages/ui/src/components/index.ts | 3 + packages/ui/src/components/multi-select.tsx | 5 +- .../ui/src/components/path-breadcrumbs.tsx | 2 +- packages/ui/src/components/reset-tag.tsx | 11 +++ .../ui/src/components/scope/scope-tag.tsx | 1 - packages/ui/src/components/tag.tsx | 96 ++++++++++--------- packages/ui/src/components/toggle-group.tsx | 13 +-- .../labels/components/labels-list-view.tsx | 2 - .../views/repo/components/branch-info-bar.tsx | 14 +-- .../labels/label-value-selector.tsx | 4 +- .../labels/pull-request-labels-list.tsx | 18 ++-- .../components/pull-request-header.tsx | 7 +- .../pull-request-item-description.tsx | 7 +- .../components/pull-request-item-title.tsx | 2 +- .../pull-request-branch-badge.tsx | 18 ---- .../conversation/pull-request-panel.tsx | 14 +-- .../pull-request-system-comments.tsx | 19 +--- .../sections/pull-request-merge-section.tsx | 20 +--- .../repo-branch/components/branch-list.tsx | 11 +-- .../repo-settings-general-rules.tsx | 1 - .../repo-tags/components/repo-tags-list.tsx | 4 +- .../search/components/search-results-list.tsx | 2 +- .../components/button.ts | 56 ++++++++--- .../tailwind-utils-config/components/tag.ts | 16 +++- 30 files changed, 277 insertions(+), 216 deletions(-) create mode 100644 packages/ui/src/components/branch-tag.tsx create mode 100644 packages/ui/src/components/copy-tag.tsx create mode 100644 packages/ui/src/components/reset-tag.tsx delete mode 100644 packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-branch-badge.tsx diff --git a/apps/portal/src/content/docs/components/button.mdx b/apps/portal/src/content/docs/components/button.mdx index 7ca9293b0e..38696ea27c 100644 --- a/apps/portal/src/content/docs/components/button.mdx +++ b/apps/portal/src/content/docs/components/button.mdx @@ -377,6 +377,12 @@ Transparent buttons have no background or border and don't accept theme props. <Button variant="transparent" size="sm" iconOnly> <IconV2 name="xmark" skipSize /> </Button> + <Button variant="outline" size="2xs" iconOnly> + <IconV2 name="xmark" skipSize /> + </Button> + <Button variant="outline" size="3xs" iconOnly> + <IconV2 name="xmark" skipSize /> + </Button> </div> <hr /> diff --git a/apps/portal/src/content/docs/components/tag.mdx b/apps/portal/src/content/docs/components/tag.mdx index 6812a41ced..b7fc555ac6 100644 --- a/apps/portal/src/content/docs/components/tag.mdx +++ b/apps/portal/src/content/docs/components/tag.mdx @@ -59,6 +59,15 @@ import { DocsPage } from "@/components/docs-page"; <div className="flex flex-wrap items-end gap-3"> <Tag value="Default Size" theme="blue" /> <Tag value="Small Size" size="sm" theme="blue" /> + <Tag value="Small Size" size="sm" theme="blue" icon="label" /> + <Tag + value="Small Size" + size="sm" + theme="blue" + icon="label" + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} + /> </div> </div> @@ -90,8 +99,8 @@ import { DocsPage } from "@/components/docs-page"; value="Reset" variant="secondary" theme="orange" - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} /> </div> </div> @@ -166,31 +175,32 @@ import { DocsPage } from "@/components/docs-page"; <Tag value="Outline Reset" variant="outline" - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="blue" /> <Tag value="Secondary Reset" variant="secondary" - showReset - onReset={() => alert("Reset clicked")} + icon="label" + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="green" /> <Tag value="Rounded Outline Reset" variant="outline" rounded - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="purple" /> <Tag value="Rounded Secondary Reset" variant="secondary" rounded - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="red" /> </div> @@ -205,16 +215,16 @@ import { DocsPage } from "@/components/docs-page"; value="Outline Icon Reset" variant="outline" showIcon - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="yellow" /> <Tag value="Secondary Icon Reset" variant="secondary" showIcon - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="violet" /> <Tag @@ -222,8 +232,8 @@ import { DocsPage } from "@/components/docs-page"; variant="outline" rounded showIcon - showReset - onReset={() => alert("Reset clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="brown" /> <Tag @@ -231,8 +241,9 @@ import { DocsPage } from "@/components/docs-page"; variant="secondary" rounded showIcon - showReset - onReset={() => alert("Reset clicked")} + onClick={() => alert("Tag clicked")} + actionIcon="xmark" + onActionClick={() => alert("Reset clicked")} theme="indigo" /> </div> @@ -291,8 +302,8 @@ import { Tag } from '@harnessio/ui/components' <Tag value="Outline Reset" variant="outline" - showReset - onReset={() => alert('Reset clicked')} + actionIcon="xmark" + onActionClick={() => alert('Reset clicked')} theme="blue" /> @@ -301,8 +312,8 @@ import { Tag } from '@harnessio/ui/components' value="Outline Icon Reset" variant="outline" showIcon - showReset - onReset={() => alert('Reset clicked')} + actionIcon="xmark" + onActionClick={() => alert('Reset clicked')} theme="yellow" /> ``` @@ -346,21 +357,14 @@ import { Tag } from '@harnessio/ui/components' value: "string", }, { - name: "showIcon", - description: "If true, displays the icon.", - required: false, - value: "boolean", - defaultValue: "false", - }, - { - name: "showReset", + name: "actionIcon", description: "If true, displays a reset (close) icon.", required: false, - value: "boolean", + value: "IconV2Name", defaultValue: "false", }, { - name: "onReset", + name: "onActionClick", description: "Callback function triggered when the reset icon is clicked.", required: false, diff --git a/packages/ui/src/components/branch-tag.tsx b/packages/ui/src/components/branch-tag.tsx new file mode 100644 index 0000000000..1fca8975d3 --- /dev/null +++ b/packages/ui/src/components/branch-tag.tsx @@ -0,0 +1,27 @@ +import { CopyTag } from '@/components' +import { useRouterContext } from '@/context' + +interface BranchTagProps { + branchName: string + spaceId?: string + repoId?: string + hideBranchIcon?: boolean +} + +const BranchTag: React.FC<BranchTagProps> = ({ branchName, spaceId, repoId, hideBranchIcon }) => { + const { Link } = useRouterContext() + + return ( + <Link to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/files/${branchName}`}> + <CopyTag + variant="secondary" + theme="gray" + icon={hideBranchIcon ? undefined : 'git-branch'} + value={branchName || ''} + /> + </Link> + ) +} + +BranchTag.displayName = 'BranchTag' +export { BranchTag } diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index 99bcfc03e0..7a03c08957 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -18,9 +18,11 @@ const buttonVariants = cva('cn-button', { transparent: 'cn-button-transparent' }, size: { - md: '', + '3xs': 'cn-button-3xs', + '2xs': 'cn-button-2xs', xs: 'cn-button-xs', - sm: 'cn-button-sm' + sm: 'cn-button-sm', + md: '' }, rounded: { true: 'cn-button-rounded' diff --git a/packages/ui/src/components/copy-tag.tsx b/packages/ui/src/components/copy-tag.tsx new file mode 100644 index 0000000000..11005d22f0 --- /dev/null +++ b/packages/ui/src/components/copy-tag.tsx @@ -0,0 +1,36 @@ +import { useCallback, useEffect, useState } from 'react' + +import copy from 'clipboard-copy' + +import { Tag, TagProps } from './tag' + +type CopyTagProps = TagProps & { + actionIcon?: never + onActionClick?: never +} + +export function CopyTag(props: CopyTagProps) { + const { value } = props + + const [copied, setCopied] = useState(false) + + useEffect(() => { + let timeoutId: number + + if (copied) { + timeoutId = window.setTimeout(() => setCopied(false), 1000) + } + + return () => { + clearTimeout(timeoutId) + } + }, [copied]) + + const handleCopy = useCallback(() => { + copy(value).then(() => { + setCopied(true) + }) + }, [value]) + + return <Tag {...props} actionIcon={copied ? 'check' : 'copy'} onActionClick={handleCopy} /> +} diff --git a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx index 0b67152e84..7efc4f186b 100644 --- a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx +++ b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx @@ -175,11 +175,9 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ <Tag className="-mt-0.5 mx-1.5 align-sub" variant="secondary" - theme="blue" - size="md" + theme="gray" value={currentBranch} icon="git-branch" - showIcon /> </> } diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 3da1ed0618..73347814f5 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -70,6 +70,9 @@ export * from './resizable' export * from './time-ago-card' export * from './informer' export * from './tag' +export * from './copy-tag' +export * from './reset-tag' +export * from './branch-tag' export * from './sorts' export * from './entity-form-layout' export * from './multi-select' diff --git a/packages/ui/src/components/multi-select.tsx b/packages/ui/src/components/multi-select.tsx index e7a5fb1768..ad817662d6 100644 --- a/packages/ui/src/components/multi-select.tsx +++ b/packages/ui/src/components/multi-select.tsx @@ -335,12 +335,11 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>( theme={option?.theme} label={option.key} value={option?.value || ''} - showReset={!disabled} - onReset={() => handleUnselect(option)} + actionIcon={disabled ? undefined : 'xmark'} + onActionClick={() => handleUnselect(option)} disabled={disabled} title={option.title} icon={option.icon} - showIcon={Boolean(option.icon)} /> ) })} diff --git a/packages/ui/src/components/path-breadcrumbs.tsx b/packages/ui/src/components/path-breadcrumbs.tsx index ba6aae740f..094d8117ad 100644 --- a/packages/ui/src/components/path-breadcrumbs.tsx +++ b/packages/ui/src/components/path-breadcrumbs.tsx @@ -44,7 +44,7 @@ const InputPathBreadcrumbItem = ({ <Text variant="body-single-line-normal" color="foreground-3"> in </Text> - <Tag value={gitRefName} icon="git-branch" showIcon /> + <Tag variant="secondary" theme="gray" value={gitRefName} icon="git-branch" /> </Layout.Flex> ) } diff --git a/packages/ui/src/components/reset-tag.tsx b/packages/ui/src/components/reset-tag.tsx new file mode 100644 index 0000000000..6be208228a --- /dev/null +++ b/packages/ui/src/components/reset-tag.tsx @@ -0,0 +1,11 @@ +import { Tag, TagProps } from './tag' + +interface ResetTagProps extends TagProps { + onReset: () => void + actionIcon?: never + onActionClick?: never +} + +export function ResetTag({ onReset, ...props }: ResetTagProps) { + return <Tag {...props} actionIcon="xmark" onActionClick={onReset} /> +} diff --git a/packages/ui/src/components/scope/scope-tag.tsx b/packages/ui/src/components/scope/scope-tag.tsx index d02818e582..d3e2ef9a6e 100644 --- a/packages/ui/src/components/scope/scope-tag.tsx +++ b/packages/ui/src/components/scope/scope-tag.tsx @@ -13,7 +13,6 @@ const ScopeTag: React.FC<ScopeTagProps> = ({ scopeType, scopedPath, size, classN theme: 'gray', size, value: scopedPath || '', - showIcon: true, className } diff --git a/packages/ui/src/components/tag.tsx b/packages/ui/src/components/tag.tsx index 984b422eae..ce89d4890b 100644 --- a/packages/ui/src/components/tag.tsx +++ b/packages/ui/src/components/tag.tsx @@ -1,8 +1,8 @@ import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' -import { CopyButton, CopyButtonProps } from './copy-button' -import { IconNameMapV2, IconV2 } from './icon-v2' +import { Button } from './button' +import { IconV2, IconV2NamesType } from './icon-v2' const tagVariants = cva('cn-tag', { variants: { @@ -46,16 +46,14 @@ type TagProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'role' | 'tabIndex'> size?: VariantProps<typeof tagVariants>['size'] theme?: VariantProps<typeof tagVariants>['theme'] rounded?: boolean - icon?: keyof typeof IconNameMapV2 - showIcon?: boolean - showReset?: boolean - onReset?: () => void + icon?: IconV2NamesType label?: string value: string disabled?: boolean - showCopyButton?: boolean title?: string enableHover?: boolean + actionIcon?: IconV2NamesType + onActionClick?: () => void } function Tag({ @@ -64,16 +62,14 @@ function Tag({ theme, rounded, icon, - onReset, label, value, className, - showReset = false, - showIcon = false, disabled = false, - showCopyButton = false, enableHover = false, title, + actionIcon, + onActionClick, ...props }: TagProps) { if (label && value) { @@ -85,13 +81,13 @@ function Tag({ theme, rounded, icon, - showIcon, - showReset, - onReset, + actionIcon, + onActionClick, label: label, value, disabled, - enableHover: enableHover || !!onReset + enableHover: Boolean(props.onClick), + onClick: props.onClick }} /> ) @@ -104,40 +100,41 @@ function Tag({ tagVariants({ variant, size, theme, rounded }), { 'text-cn-foreground-disabled cursor-not-allowed': disabled, - 'cn-tag-hoverable': !disabled && (enableHover || !!onReset), - 'pr-0': showCopyButton + 'cn-tag-hoverable': !disabled && (enableHover || props.onClick), + 'cursor-pointer': !disabled && props.onClick }, className )} {...props} > - {showIcon && ( - <IconV2 - size="sm" - name={icon || 'label'} - className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} - /> + {icon && ( + <IconV2 size="xs" name={icon} className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} /> )} <span className={cn('cn-tag-text', { 'text-cn-foreground-disabled': disabled })} title={title || value || label}> {value || label} </span> - {showReset && !disabled && ( - <button onClick={onReset}> - <IconV2 size="2xs" name="xmark" className="cn-tag-icon" /> - </button> - )} - {showCopyButton ? ( - <CopyButton - name={value || label || ''} + + {actionIcon && ( + <Button iconOnly - buttonVariant="transparent" - className="cn-tag-icon" - // @TODO: sync with design team to get the righ tokens for the copy button - color={theme as CopyButtonProps['color']} - size="xs" - iconSize="2xs" - /> - ) : null} + disabled={disabled} + rounded={rounded} + className="cn-tag-action-icon-button" + variant="transparent" + size={size === 'sm' ? '3xs' : '2xs'} + onClick={e => { + e.stopPropagation() + e.preventDefault() + onActionClick?.() + }} + > + <IconV2 + skipSize + name={actionIcon} + className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} + /> + </Button> + )} </div> ) } @@ -148,15 +145,15 @@ function TagSplit({ theme, rounded, icon, - showIcon, - showReset, + actionIcon, + onActionClick, value, label = '', - onReset, disabled = false, - enableHover = false + enableHover = false, + onClick }: TagProps) { - const sharedProps = { variant, size, theme, rounded, icon, disabled } + const sharedProps = { variant, size, theme, rounded, icon, disabled, onClick } return ( <div @@ -166,10 +163,17 @@ function TagSplit({ })} > {/* LEFT TAG - should never have a Reset Icon */} - <Tag {...sharedProps} showIcon={showIcon} value={label} className="cn-tag-split-left" /> + <Tag {...sharedProps} icon={icon} value={label} className="cn-tag-split-left" /> {/* RIGHT TAG - should never have a tag Icon */} - <Tag {...sharedProps} showReset={showReset} onReset={onReset} value={value} className="cn-tag-split-right" /> + <Tag + {...sharedProps} + icon={icon} + actionIcon={actionIcon} + onActionClick={onActionClick} + value={value} + className="cn-tag-split-right" + /> </div> ) } diff --git a/packages/ui/src/components/toggle-group.tsx b/packages/ui/src/components/toggle-group.tsx index 91344efb6e..713369b38e 100644 --- a/packages/ui/src/components/toggle-group.tsx +++ b/packages/ui/src/components/toggle-group.tsx @@ -12,16 +12,7 @@ import { useState } from 'react' -import { - Button, - ButtonSizes, - IconPropsV2, - IconV2, - IconV2NamesType, - toggleVariants, - Tooltip, - TooltipProps -} from '@/components' +import { Button, IconPropsV2, IconV2, IconV2NamesType, toggleVariants, Tooltip, TooltipProps } from '@/components' import { cn } from '@/utils' import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group' import { VariantProps } from 'class-variance-authority' @@ -53,7 +44,7 @@ export interface ToggleGroupProps type?: ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>['type'] variant?: ToggleGroupVariant selectedVariant?: ToggleGroupSelectedVariant - size?: ButtonSizes + size?: VariantProps<typeof toggleVariants>['size'] disabled?: boolean className?: string unselectable?: boolean diff --git a/packages/ui/src/views/labels/components/labels-list-view.tsx b/packages/ui/src/views/labels/components/labels-list-view.tsx index 4fbf28f377..99302e5e71 100644 --- a/packages/ui/src/views/labels/components/labels-list-view.tsx +++ b/packages/ui/src/views/labels/components/labels-list-view.tsx @@ -126,11 +126,9 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ </Table.Cell> <Table.Cell className="w-[240px] !py-3.5 leading-none"> <Tag - size="md" theme="gray" variant="secondary" value={getDisplayPath(label.scope, createdIn)} - showIcon icon={label.scope === 0 ? 'repository' : 'folder'} /> </Table.Cell> diff --git a/packages/ui/src/views/repo/components/branch-info-bar.tsx b/packages/ui/src/views/repo/components/branch-info-bar.tsx index a7ab3afbfb..3cf5c73b7e 100644 --- a/packages/ui/src/views/repo/components/branch-info-bar.tsx +++ b/packages/ui/src/views/repo/components/branch-info-bar.tsx @@ -58,15 +58,7 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ {!hasAhead && !hasBehind && 'up to date with'} </span> - <Tag - variant="secondary" - theme="blue" - size="md" - icon="git-branch" - showIcon - value={defaultBranchName} - className="align-middle" - /> + <Tag variant="secondary" theme="gray" icon="git-branch" value={defaultBranchName} className="align-middle" /> </Text> {showContributeBtn && ( @@ -98,11 +90,9 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ <Tag className="mt-0.5 align-sub" variant="secondary" - theme="blue" - size="md" + theme="gray" value={defaultBranchName} icon="git-branch" - showIcon /> </Text> diff --git a/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx b/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx index 88b74491b8..d96b0e7210 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx @@ -99,8 +99,8 @@ export const LabelValueSelector: FC<LabelValueSelectorProps> = ({ label, handleA hasSearchIcon={false} {...wrapConditionalObjectElement({ maxLength: 50 }, !!label?.isCustom)} > - <div className="max-w-20 pr-2"> - <Tag variant="secondary" size="sm" theme={label.color} value={label.key ?? ''} /> + <div className="pr-2"> + <Tag className="max-w-20" variant="secondary" size="sm" theme={label.color} value={label.key ?? ''} /> </div> </SearchBox.Root> diff --git a/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx b/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx index cecaf8d97e..1d6ee1ad74 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/pull-request-labels-list.tsx @@ -28,13 +28,17 @@ export const LabelsList: FC<LabelsListProps> = ({ labels, className, showReset, label={label.key} value={label.value || ''} theme={label.color} - onReset={label.onDelete} - showReset={showReset} - onClick={e => { - e.stopPropagation() - e.preventDefault() - onClick?.(label) - }} + onActionClick={label.onDelete} + actionIcon={showReset ? 'xmark' : undefined} + onClick={ + onClick + ? e => { + e.stopPropagation() + e.preventDefault() + onClick?.(label) + } + : undefined + } /> ))} </div> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index ee71192632..71b32f0755 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -1,10 +1,9 @@ import { useCallback, useState } from 'react' -import { Button, IconV2, Layout, StatusBadge, Text, TimeAgoCard } from '@/components' +import { BranchTag, Button, IconV2, Layout, StatusBadge, Text, TimeAgoCard } from '@/components' import { cn } from '@utils/cn' import { BranchSelectorContainerProps } from '@views/repo' -import PullRequestBranchBadge from '../details/components/conversation/pull-request-branch-badge' import { getPrState } from '../utils' import { PullRequestHeaderEditDialog } from './pull-request-header-edit-dialog' @@ -100,11 +99,11 @@ export const PullRequestHeader: React.FC<PullRequestTitleProps> = ({ into </Text> - <PullRequestBranchBadge branchName={target_branch || ''} spaceId={spaceId || ''} repoId={repoId || ''} /> + <BranchTag branchName={target_branch || ''} spaceId={spaceId || ''} repoId={repoId || ''} /> <Text variant="body-single-line-normal" color="foreground-2"> from </Text> - <PullRequestBranchBadge branchName={source_branch || ''} spaceId={spaceId || ''} repoId={repoId || ''} /> + <BranchTag branchName={source_branch || ''} spaceId={spaceId || ''} repoId={repoId || ''} /> <span className="mx-1.5 h-4 w-px bg-cn-borders-3" /> <TimeAgoCard timestamp={created} /> </Layout.Horizontal> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx index bf532540f8..4236aedc37 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx @@ -29,9 +29,8 @@ export const PullRequestItemDescription: FC<PullRequestItemDescriptionProps> = ( const branchTagProps: Omit<TagProps, 'value'> = { variant: 'secondary', icon: 'git-branch', - showIcon: true, enableHover: true, - theme: 'blue' + theme: 'gray' } return ( @@ -57,12 +56,12 @@ export const PullRequestItemDescription: FC<PullRequestItemDescriptionProps> = ( <Separator orientation="vertical" className="h-3.5" /> {sourceBranch && ( - <Layout.Horizontal gap="2xs"> + <Layout.Horizontal align="center" gap="2xs"> <Link to={`${relativePath}/files/${targetBranch}`}> <Tag value={targetBranch} {...branchTagProps} /> </Link> - <span>←</span> + <IconV2 className="text-cn-foreground-3" name="arrow-long-left" /> <Link to={`${relativePath}/files/${sourceBranch}`}> <Tag value={sourceBranch} {...branchTagProps} /> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx index 455ac0673d..709d15b489 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx @@ -41,7 +41,7 @@ export const PullRequestItemTitle: FC<PullRequestItemTitleProps> = ({ /> <div className="[&>*:not(:last-child)]:mr-cn-xs"> - {repoId && <Tag className="align-bottom" value={repoId} showIcon icon="repository" theme="gray" />} + {repoId && <Tag className="align-bottom" value={repoId} icon="repository" theme="gray" />} <Text as="span" variant="heading-base"> {name} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-branch-badge.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-branch-badge.tsx deleted file mode 100644 index dcc4eaef75..0000000000 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-branch-badge.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Tag } from '@/components' -import { useRouterContext } from '@/context' - -interface BranchBadgeProps { - branchName: string - spaceId?: string - repoId?: string -} -const PullRequestBranchBadge: React.FC<BranchBadgeProps> = ({ branchName, spaceId, repoId }) => { - const { Link } = useRouterContext() - return ( - <Link to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/files/${branchName}`}> - <Tag variant="secondary" theme="blue" icon="git-branch" value={branchName || ''} showIcon showCopyButton /> - </Link> - ) -} - -export default PullRequestBranchBadge diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 640795f24b..8b0a012bee 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react' import { Accordion, Alert, + BranchTag, Button, ButtonLayout, Checkbox, @@ -41,7 +42,6 @@ import { MergeStrategy, PullRequestRoutingProps } from '../../pull-request-details-types' -import PullRequestBranchBadge from './pull-request-branch-badge' import PullRequestChangesSection from './sections/pull-request-changes-section' import PullRequestCheckSection from './sections/pull-request-checks-section' import PullRequestCommentSection from './sections/pull-request-comment-section' @@ -125,17 +125,9 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { <span>{pullReqMetadata?.merger?.display_name}</span> <span>{areRulesBypassed ? `bypassed rules and ${mergeMethod}` : `${mergeMethod}`}</span> <span>into</span> - <PullRequestBranchBadge - branchName={pullReqMetadata?.target_branch || ''} - spaceId={spaceId} - repoId={repoId} - /> + <BranchTag branchName={pullReqMetadata?.target_branch || ''} spaceId={spaceId} repoId={repoId} /> <span>from</span> - <PullRequestBranchBadge - branchName={pullReqMetadata?.source_branch || ''} - spaceId={spaceId} - repoId={repoId} - /> + <BranchTag branchName={pullReqMetadata?.source_branch || ''} spaceId={spaceId} repoId={repoId} /> <TimeAgoCard timestamp={pullReqMetadata?.merged} /> </Text> <Layout.Horizontal> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx index 42c99203db..b57611e86a 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx @@ -1,6 +1,6 @@ import { FC, useMemo } from 'react' -import { Avatar, CommitCopyActions, IconPropsV2, IconV2, Tag, Text, TimeAgoCard } from '@/components' +import { Avatar, BranchTag, CommitCopyActions, IconPropsV2, IconV2, Tag, Text, TimeAgoCard } from '@/components' import { useRouterContext } from '@/context' import { ColorsEnum, @@ -16,7 +16,6 @@ import { import { TypesPullReq } from '@views/repo/pull-request/pull-request.types' import { noop } from 'lodash-es' -import PullRequestBranchBadge from './pull-request-branch-badge' import PullRequestTimelineItem, { TimelineItemProps } from './pull-request-timeline-item' const labelActivityToTitleDict: Record<LabelActivity, string> = { @@ -148,19 +147,11 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ <Text variant="body-single-line-normal" color="foreground-3"> {merge_method === MergeStrategy.REBASE ? 'rebased changes from branch' : 'merged changes from'} </Text> - <PullRequestBranchBadge - branchName={pullReqMetadata?.source_branch as string} - spaceId={spaceId} - repoId={repoId} - /> + <BranchTag branchName={pullReqMetadata?.source_branch as string} spaceId={spaceId} repoId={repoId} /> <Text variant="body-single-line-normal" color="foreground-3"> into </Text> - <PullRequestBranchBadge - branchName={pullReqMetadata?.target_branch as string} - spaceId={spaceId} - repoId={repoId} - /> + <BranchTag branchName={pullReqMetadata?.target_branch as string} spaceId={spaceId} repoId={repoId} /> <Text variant="body-single-line-normal" color="foreground-3"> {merge_method === MergeStrategy.REBASE ? ', now at ' : 'by commit'} </Text> @@ -235,9 +226,7 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ <Text variant="body-single-line-normal" color="foreground-3"> {isSourceBranchDeleted ? 'deleted the' : 'restored the'} </Text> - {!!sourceBranch && ( - <PullRequestBranchBadge branchName={sourceBranch} spaceId={spaceId} repoId={repoId} /> - )} + {!!sourceBranch && <BranchTag branchName={sourceBranch} spaceId={spaceId} repoId={repoId} />} <Text variant="body-single-line-normal" color="foreground-3"> branch </Text> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx index c2619261fd..d59e7e41df 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx @@ -1,6 +1,6 @@ import { Dispatch, FC, MouseEvent, SetStateAction, useMemo, useState } from 'react' -import { Accordion, Button, CopyButton, IconV2, Layout, StackedList, Tag, Text } from '@/components' +import { Accordion, Button, CopyButton, CopyTag, IconV2, Layout, StackedList, Text } from '@/components' import { cn } from '@utils/cn' import { PanelAccordionShowButton } from '@views/repo/pull-request/details/components/conversation/sections/panel-accordion-show-button' import { isEmpty } from 'lodash-es' @@ -130,23 +130,9 @@ const PullRequestMergeSection = ({ const renderBranchTags = () => ( <span className="inline-flex items-center gap-1"> <span>Merge the latest changes from</span> - <Tag - variant="secondary" - theme="blue" - icon="git-branch" - value={pullReqMetadata?.target_branch || ''} - showIcon - showCopyButton - /> + <CopyTag variant="secondary" theme="gray" icon="git-branch" value={pullReqMetadata?.target_branch || ''} /> <span>into</span> - <Tag - variant="secondary" - theme="blue" - icon="git-branch" - value={pullReqMetadata?.source_branch || ''} - showIcon - showCopyButton - /> + <CopyTag variant="secondary" theme="gray" icon="git-branch" value={pullReqMetadata?.source_branch || ''} /> </span> ) diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 484ab07d3e..1756a9787e 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -3,6 +3,7 @@ import { FC } from 'react' import { ActionData, Avatar, + CopyTag, IconPropsV2, IconV2, MoreActionsTooltip, @@ -136,16 +137,12 @@ export const BranchesList: FC<BranchListPageProps> = ({ {/* branch name */} <Table.Cell className="content-center"> <div className="flex items-center"> - <Tag + <CopyTag variant="secondary" - size="md" value={branch?.name} - icon="lock" - theme="blue" - showIcon={defaultBranch === branch?.name} - showCopyButton + icon={defaultBranch === branch?.name ? 'lock' : undefined} + theme="gray" /> - {/* <CopyButton buttonVariant="ghost" color="gray" name={branch?.name} /> */} </div> </Table.Cell> {/* user avatar and timestamp */} diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index ce397cc79d..bfa9bf9687 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -193,7 +193,6 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ </Text> {rule.type && ( <Tag - showIcon variant="outline" size="sm" theme={rule.type === 'branch' ? 'blue' : 'purple'} diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index c5a8c9c60d..a78a016d99 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -4,12 +4,12 @@ import { ActionData, Avatar, CommitCopyActions, + CopyTag, IconV2, MoreActionsTooltip, NoData, Skeleton, Table, - Tag, Text, TimeAgoCard } from '@/components' @@ -125,7 +125,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ {tagsList.map(tag => ( <Table.Row key={tag.sha} to={`../summary/refs/tags/${tag.name}`}> <Table.Cell> - <Tag value={tag.name} theme="violet" size="md" variant="secondary" showCopyButton /> + <CopyTag value={tag.name} theme="violet" size="md" variant="secondary" /> </Table.Cell> <Table.Cell> <Text variant="body-normal" className="line-clamp-3"> diff --git a/packages/ui/src/views/search/components/search-results-list.tsx b/packages/ui/src/views/search/components/search-results-list.tsx index e993336a2a..ca58fe3579 100644 --- a/packages/ui/src/views/search/components/search-results-list.tsx +++ b/packages/ui/src/views/search/components/search-results-list.tsx @@ -85,7 +85,7 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ gap="xs" className={cn('py-3 px-5', { 'border-b border-cn-border-2': item.matches && item.matches.length > 1 })} > - {!isRepoScope ? <Tag value={item.repo_path} icon="repository" showIcon={true} /> : null} + {!isRepoScope ? <Tag value={item.repo_path} icon="repository" /> : null} <Link to={toRepoFileDetails({ repoPath: item.repo_path, diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index 4b34149dc2..6d0add0a3c 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -5,6 +5,8 @@ const variants = ['solid', 'soft', 'surface', 'ghost'] as const const themes = ['success', 'danger', 'muted', 'primary'] as const +const sizes = ['3xs', '2xs', 'xs', 'sm', 'md'] as const + const themeStyleMapper: Record<(typeof themes)[number], string> = { success: 'green', danger: 'red', @@ -78,6 +80,21 @@ function createButtonVariantStyles() { return { ...combinationStyles, ...separatorStyles } } +function createButtonSizeStyles() { + const styles: CSSRuleObject = {} + + sizes.forEach(size => { + styles[`&:where(.cn-button-${size})`] = { + height: `var(--cn-btn-size-${size})`, + paddingBlock: `var(--cn-btn-py-${size})`, + paddingInline: `var(--cn-btn-px-${size})`, + gap: `var(--cn-btn-gap-${size})` + } + }) + + return styles +} + export default { '.cn-button': { transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke', @@ -107,16 +124,8 @@ export default { }, // sizes - '&:where(.cn-button-sm)': { - height: 'var(--cn-btn-size-sm)', - padding: 'var(--cn-btn-py-sm) var(--cn-btn-px-sm)', - gap: 'var(--cn-btn-gap-sm)' - }, - '&:where(.cn-button-xs)': { - height: 'var(--cn-btn-size-xs)', - paddingBlock: 'var(--cn-btn-py-xs)', - paddingInline: 'var(--cn-btn-px-xs)', - gap: 'var(--cn-btn-gap-xs)', + ...createButtonSizeStyles(), + '&:where(.cn-button-xs, .cn-button-3xs, .cn-button-2xs)': { '@apply font-caption-single-line-normal': '' }, @@ -149,6 +158,7 @@ export default { } }, + // variant styles ...createButtonVariantStyles(), // Rounded @@ -170,14 +180,36 @@ export default { // Icon Only sizing '&:where(.cn-button-icon-only.cn-button-sm)': { width: 'var(--cn-btn-size-sm)', - height: 'var(--cn-btn-size-sm)' + height: 'var(--cn-btn-size-sm)', + '& > svg': { + strokeWidth: 'var(--cn-icon-stroke-width-sm)' + } }, '&:where(.cn-button-icon-only.cn-button-xs)': { width: 'var(--cn-btn-size-xs)', height: 'var(--cn-btn-size-xs)', '& > svg': { width: 'var(--cn-icon-size-sm)', - height: 'var(--cn-icon-size-sm)' + height: 'var(--cn-icon-size-sm)', + strokeWidth: 'var(--cn-icon-stroke-width-xs)' + } + }, + '&:where(.cn-button-icon-only.cn-button-2xs)': { + width: 'var(--cn-btn-size-2xs)', + height: 'var(--cn-btn-size-2xs)', + '& > svg': { + width: 'var(--cn-icon-size-2xs)', + height: 'var(--cn-icon-size-2xs)', + strokeWidth: 'var(--cn-icon-stroke-width-2xs)' + } + }, + '&:where(.cn-button-icon-only.cn-button-3xs)': { + width: 'var(--cn-btn-size-3xs)', + height: 'var(--cn-btn-size-3xs)', + '& > svg': { + width: 'var(--cn-icon-size-2xs)', + height: 'var(--cn-icon-size-2xs)', + strokeWidth: 'var(--cn-icon-stroke-width-2xs)' } }, diff --git a/packages/ui/tailwind-utils-config/components/tag.ts b/packages/ui/tailwind-utils-config/components/tag.ts index 521906d151..a156b90e58 100644 --- a/packages/ui/tailwind-utils-config/components/tag.ts +++ b/packages/ui/tailwind-utils-config/components/tag.ts @@ -74,9 +74,23 @@ export default { height: `var(--cn-tag-size-md)`, '@apply w-fit items-center transition-colors select-none font-body-single-line-normal': '', + ':where(.cn-tag-action-icon-button)': { + marginRight: 'calc(-1 * var(--cn-tag-px))', + marginLeft: 'calc(-1 * var(--cn-tag-gap))', + opacity: 'var(--cn-opacity-70)', + + '&:hover': { + opacity: 'var(--cn-opacity-100)' + } + }, + '&:where(.cn-tag-sm)': { height: `var(--cn-tag-size-sm)`, - '@apply font-caption-single-line-normal': '' + '@apply font-caption-single-line-normal': '', + + '.cn-tag-action-icon-button': { + marginRight: 'calc(-0.5 * var(--cn-tag-px))' + } }, '&:where(.cn-tag-rounded)': { From 23003d91f8b9ee131419b07846ed89590ebd8097 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 8 Aug 2025 14:09:07 +0300 Subject: [PATCH 035/180] Update comments markdown typography styles (#2056) --- .../src/components/markdown-viewer/style.css | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 1c57a683bf..2a5f870e51 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -202,7 +202,7 @@ } li { - @apply marker:text-cn-foreground-1 my-cn-2xs ps-0; + @apply marker:text-cn-foreground-2 my-cn-2xs ps-0; } ol { @@ -228,12 +228,14 @@ } code { - @apply font-body-code border-cn-borders-2 text-nowrap rounded border bg-[var(--cn-set-brand-surface-bg)] px-1 !text-[var(--cn-set-brand-surface-text)] before:hidden after:hidden; + @apply font-body-code px-cn-2xs py-cn-4xs text-nowrap rounded border-0 bg-[var(--cn-set-brand-surface-bg)] !text-[var(--cn-set-blue-soft-text)] before:hidden after:hidden; + font-size: inherit; + line-height: inherit; } strong { font: var(--cn-comp-markdown-content-emphasized); - @apply text-cn-foreground-1; + @apply text-cn-foreground-2; } pre { @@ -245,7 +247,8 @@ } blockquote { - @apply border-cn-borders-1 my-cn-md border-l-4 pl-4 pr-0; + font: var(--cn-comp-markdown-content-paragraph); + @apply text-cn-foreground-3 my-cn-md relative border-0 pl-4 pr-0 before:absolute before:inset-y-0 before:left-0 before:w-1 before:rounded-sm before:bg-[var(--cn-border-2)]; } table { @@ -286,8 +289,13 @@ @apply bg-transparent; } + a, blockquote { - @apply before:bg-cn-background-softgray relative border-0 before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:rounded-sm; + @apply font-body-normal; + } + + strong { + @apply font-body-strong; } } } From 6bedaadf5a5db03b178153f7dbbdb51c95825d86 Mon Sep 17 00:00:00 2001 From: Pavel <pavel@pixelpoint.io> Date: Fri, 8 Aug 2025 15:26:18 +0400 Subject: [PATCH 036/180] refactor: update Skeleton.Table usage across multiple components to improve loading state representation (#2055) --- .../src/content/docs/components/skeleton.mdx | 4 +- .../components/skeletons/skeleton-table.tsx | 6 +- .../connector-details-activities-list.tsx | 55 ++++---- .../connector-details-references-list.tsx | 69 +++++----- .../connectors-list/connectors-list.tsx | 118 ++++++++---------- .../components/delegate-connectivity-list.tsx | 93 ++++++-------- .../ui/src/views/labels/labels-list-page.tsx | 2 +- .../components/profile-settings-keys-list.tsx | 2 +- .../profile-settings-tokens-list.tsx | 2 +- .../views/repo/repo-files/repo-files-view.tsx | 2 +- .../repo-settings-general-features.tsx | 2 +- .../views/repo/repo-summary/repo-summary.tsx | 2 +- .../secrets/secrets-list/secrets-list.tsx | 68 +++++----- 13 files changed, 198 insertions(+), 227 deletions(-) diff --git a/apps/portal/src/content/docs/components/skeleton.mdx b/apps/portal/src/content/docs/components/skeleton.mdx index b3a4a89709..dcc8ddd738 100644 --- a/apps/portal/src/content/docs/components/skeleton.mdx +++ b/apps/portal/src/content/docs/components/skeleton.mdx @@ -201,7 +201,7 @@ The `Skeleton.Table` component is a placeholder for table elements. It can be us client:only code={`<div className="w-[600px] grid gap-4"> <div> - <Skeleton.Table countColumns={5} countRows={5} showHeader /> + <Skeleton.Table countColumns={5} countRows={5} /> </div> </div>`} /> @@ -392,7 +392,7 @@ The `Skeleton.Table` component is a placeholder for table elements. It can be us defaultValue: 5, }, { - name: "showHeader", + name: "hideHeader", description: "Defines whether the skeleton table has a header row", required: false, value: "boolean", diff --git a/packages/ui/src/components/skeletons/skeleton-table.tsx b/packages/ui/src/components/skeletons/skeleton-table.tsx index 4dc1476a51..d27f90f087 100644 --- a/packages/ui/src/components/skeletons/skeleton-table.tsx +++ b/packages/ui/src/components/skeletons/skeleton-table.tsx @@ -7,7 +7,7 @@ export interface SkeletonTableProps { classNameBody?: string countRows?: number countColumns?: number - showHeader?: boolean + hideHeader?: boolean } export const SkeletonTable = ({ @@ -16,11 +16,11 @@ export const SkeletonTable = ({ classNameBody, countRows = 12, countColumns = 5, - showHeader = false + hideHeader = false }: SkeletonTableProps) => { return ( <Table.Root className={cn('cn-skeleton-table', className)} disableHighlightOnHover> - {showHeader && ( + {!hideHeader && ( <Table.Header className={classNameHeader}> <Table.Row> {Array.from({ length: countColumns }).map((_, columnIndex) => ( diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx index 4f1482df62..ec08b47fd7 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-activities-list.tsx @@ -34,7 +34,7 @@ const ConnectorDetailsActivitiesList = ({ isLoading, activities }: ConnectorDeta const { t } = useTranslation() const content = activities?.content if (isLoading) { - return <Skeleton.List /> + return <Skeleton.Table countRows={12} countColumns={3} /> } if (!content.length) { @@ -59,35 +59,32 @@ const ConnectorDetailsActivitiesList = ({ isLoading, activities }: ConnectorDeta <Table.Head className="w-44 text-cn-foreground-4">{t('views:connectors.status', 'Status')}</Table.Head> </Table.Row> </Table.Header> - {isLoading ? ( - <Skeleton.Table countRows={12} countColumns={5} /> - ) : ( - <Table.Body> - {content.map(({ referredEntity, activityTime, description, activityStatus }: ConnectorActivityItem) => { - const { name, entityRef } = referredEntity - const identifier = entityRef?.identifier || name - return ( - <Table.Row key={identifier} className="cursor-pointer"> - <Table.Cell className="max-w-80 content-center items-center truncate"> - <Activity activity={description} /> - </Table.Cell> - <Table.Cell className="my-2 block max-w-80 content-center items-center p-2.5"> - {activityTime ? ( - <TimeAgoCard - timestamp={activityTime} - textProps={{ variant: 'body-strong', color: 'foreground-3', truncate: true }} - /> - ) : null} - </Table.Cell> - <Table.Cell className="max-w-full content-center truncate p-2.5 text-left text-sm font-normal leading-tight tracking-tight text-cn-foreground-4"> - <ConnectivityStatus status={activityStatus.toLowerCase()} /> - </Table.Cell> - </Table.Row> - ) - })} - </Table.Body> - )} + <Table.Body> + {content.map(({ referredEntity, activityTime, description, activityStatus }: ConnectorActivityItem) => { + const { name, entityRef } = referredEntity + const identifier = entityRef?.identifier || name + return ( + <Table.Row key={identifier} className="cursor-pointer"> + <Table.Cell className="max-w-80 content-center items-center truncate"> + <Activity activity={description} /> + </Table.Cell> + <Table.Cell className="my-2 block max-w-80 content-center items-center p-2.5"> + {activityTime ? ( + <TimeAgoCard + timestamp={activityTime} + textProps={{ variant: 'body-strong', color: 'foreground-3', truncate: true }} + /> + ) : null} + </Table.Cell> + + <Table.Cell className="max-w-full content-center truncate p-2.5 text-left text-sm font-normal leading-tight tracking-tight text-cn-foreground-4"> + <ConnectivityStatus status={activityStatus.toLowerCase()} /> + </Table.Cell> + </Table.Row> + ) + })} + </Table.Body> </Table.Root> ) } diff --git a/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx b/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx index 206e3f80b9..13eebe624c 100644 --- a/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx +++ b/packages/ui/src/views/connectors/connector-details/connector-details-references-list.tsx @@ -4,12 +4,12 @@ import { useTranslation } from '@/context' import { ConnectorReferenceItem, ConnectorReferenceListProps } from './types' const Title = ({ title }: { title: string }): JSX.Element => ( - <span className="max-w-full truncate text-sm font-medium leading-tight tracking-tight text-cn-foreground-1"> + <span className="text-cn-foreground-1 max-w-full truncate text-sm font-medium leading-tight tracking-tight"> {title} </span> ) const Description = ({ description }: { description: string }): JSX.Element => ( - <span className="max-w-full truncate text-1 font-normal leading-none tracking-tight text-cn-foreground-4"> + <span className="text-1 text-cn-foreground-4 max-w-full truncate font-normal leading-none tracking-tight"> {description} </span> ) @@ -23,7 +23,7 @@ const ConnectorDetailsReferenceList = ({ const { t } = useTranslation() const content = entities?.content if (isLoading) { - return <Skeleton.List /> + return <Skeleton.Table countRows={12} countColumns={3} /> } if (!content.length) { @@ -46,42 +46,39 @@ const ConnectorDetailsReferenceList = ({ > <Table.Header> <Table.Row> - <Table.Head className="w-96 text-cn-foreground-4">Entity</Table.Head> - <Table.Head className="w-96 text-cn-foreground-4">Scope</Table.Head> - <Table.Head className="w-44 text-right text-cn-foreground-4">Created</Table.Head> + <Table.Head className="text-cn-foreground-4 w-96">Entity</Table.Head> + <Table.Head className="text-cn-foreground-4 w-96">Scope</Table.Head> + <Table.Head className="text-cn-foreground-4 w-44 text-right">Created</Table.Head> </Table.Row> </Table.Header> - {isLoading ? ( - <Skeleton.Table countRows={12} countColumns={5} /> - ) : ( - <Table.Body> - {content.map(({ referredByEntity, createdAt }: ConnectorReferenceItem) => { - const { name, type, entityRef } = referredByEntity - const { scope } = entityRef - const identifier = entityRef?.identifier || name - return ( - <Table.Row key={identifier} className="cursor-pointer"> - <Table.Cell onClick={() => toEntity?.(identifier)} className="max-w-80 self-start truncate"> - <div className="flex flex-col gap-2.5"> - <Title title={identifier} /> - <Description description={`Type: ${type}`} /> - </div> - </Table.Cell> - <Table.Cell - onClick={() => toScope?.(scope)} - className="max-w-80 content-center truncate font-medium text-cn-foreground-accent" - > - {scope} - </Table.Cell> - <Table.Cell onClick={() => toEntity?.(identifier)}> - {createdAt ? <TimeAgoCard timestamp={createdAt} /> : null} - </Table.Cell> - </Table.Row> - ) - })} - </Table.Body> - )} + <Table.Body> + {content.map(({ referredByEntity, createdAt }: ConnectorReferenceItem) => { + const { name, type, entityRef } = referredByEntity + const { scope } = entityRef + const identifier = entityRef?.identifier || name + return ( + <Table.Row key={identifier} className="cursor-pointer"> + <Table.Cell onClick={() => toEntity?.(identifier)} className="max-w-80 self-start truncate"> + <div className="flex flex-col gap-2.5"> + <Title title={identifier} /> + <Description description={`Type: ${type}`} /> + </div> + </Table.Cell> + <Table.Cell + onClick={() => toScope?.(scope)} + className="text-cn-foreground-accent max-w-80 content-center truncate font-medium" + > + {scope} + </Table.Cell> + + <Table.Cell onClick={() => toEntity?.(identifier)}> + {createdAt ? <TimeAgoCard timestamp={createdAt} /> : null} + </Table.Cell> + </Table.Row> + ) + })} + </Table.Body> </Table.Root> ) } diff --git a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx index bbafb0013c..46a3855c4b 100644 --- a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx +++ b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx @@ -21,7 +21,7 @@ import { ConnectorListItem, ConnectorListProps } from './types' import { ConnectorTypeToLogoNameMap } from './utils' const Title = ({ title }: { title: string }): JSX.Element => ( - <span className="max-w-full truncate font-medium text-cn-foreground-1" title={title}> + <span className="text-cn-foreground-1 max-w-full truncate font-medium" title={title}> {title} </span> ) @@ -34,7 +34,7 @@ const ConnectivityStatus = ({ item }: { item: ConnectorListItem; connectorDetail return isSuccess ? ( <div className="flex items-center gap-2"> <IconV2 name="circle" size="2xs" className="text-icons-success" /> - <Text className="transition-colors duration-200 group-hover:text-cn-foreground-1"> + <Text className="group-hover:text-cn-foreground-1 transition-colors duration-200"> {t('views:connectors.success', 'Success')} </Text> </div> @@ -54,7 +54,7 @@ const ConnectivityStatus = ({ item }: { item: ConnectorListItem; connectorDetail > <Button className="group h-auto gap-2 p-0 font-normal hover:!bg-transparent" variant="ghost"> <IconV2 name="circle" size="2xs" className="text-icons-danger" /> - <Text className="transition-colors duration-200 group-hover:text-cn-foreground-1"> + <Text className="group-hover:text-cn-foreground-1 transition-colors duration-200"> {t('views:connectors.failure', 'Failed')} </Text> </Button> @@ -83,7 +83,7 @@ export function ConnectorsList({ const { t } = useTranslation() if (isLoading) { - return <Skeleton.List /> + return <Skeleton.Table countRows={12} countColumns={6} /> } if (!connectors.length) { @@ -117,68 +117,60 @@ export function ConnectorsList({ <Table.Head className="w-10" /> </Table.Row> </Table.Header> - {isLoading ? ( - <Skeleton.Table countRows={12} countColumns={5} /> - ) : ( - <Table.Body> - {connectors.map(({ name, identifier, type, spec, status, lastModifiedAt, isFavorite }) => { - const connectorLogo = type ? ConnectorTypeToLogoNameMap.get(type) : undefined - const connectorDetailUrl = toConnectorDetails?.({ identifier, type, spec, status, lastModifiedAt }) || '' + <Table.Body> + {connectors.map(({ name, identifier, type, spec, status, lastModifiedAt, isFavorite }) => { + const connectorLogo = type ? ConnectorTypeToLogoNameMap.get(type) : undefined + const connectorDetailUrl = toConnectorDetails?.({ identifier, type, spec, status, lastModifiedAt }) || '' - return ( - <Table.Row className="[&_td]:py-5" key={identifier} to={connectorDetailUrl}> - <Table.Cell className="content-center truncate"> - <div className="flex items-center gap-2.5"> - <div className="flex w-full max-w-8 items-center justify-center"> - {connectorLogo ? ( - <LogoV2 name={connectorLogo} size="lg" /> - ) : ( - <IconV2 name="connectors" size="lg" /> - )} - </div> - <Title title={identifier} /> + return ( + <Table.Row className="[&_td]:py-5" key={identifier} to={connectorDetailUrl}> + <Table.Cell className="content-center truncate"> + <div className="flex items-center gap-2.5"> + <div className="flex w-full max-w-8 items-center justify-center"> + {connectorLogo ? <LogoV2 name={connectorLogo} size="lg" /> : <IconV2 name="connectors" size="lg" />} </div> - </Table.Cell> - <Table.Cell className="content-center truncate" title={spec?.url}> - {spec?.url} - </Table.Cell> - <Table.Cell className="content-center whitespace-nowrap"> - {status ? ( - <ConnectivityStatus - item={{ name, identifier, type, spec, status, lastModifiedAt }} - connectorDetailUrl={connectorDetailUrl} - /> - ) : null} - </Table.Cell> - <Table.Cell className="content-center"> - {lastModifiedAt ? <TimeAgoCard timestamp={lastModifiedAt} /> : null} - </Table.Cell> - <Table.Cell className="content-center !p-1.5" disableLink> - <Favorite - isFavorite={isFavorite} - onFavoriteToggle={(favorite: boolean) => onToggleFavoriteConnector(identifier, !favorite)} + <Title title={identifier} /> + </div> + </Table.Cell> + <Table.Cell className="content-center truncate" title={spec?.url}> + {spec?.url} + </Table.Cell> + <Table.Cell className="content-center whitespace-nowrap"> + {status ? ( + <ConnectivityStatus + item={{ name, identifier, type, spec, status, lastModifiedAt }} + connectorDetailUrl={connectorDetailUrl} /> - </Table.Cell> - <Table.Cell className="content-center !p-0" disableLink> - <MoreActionsTooltip - actions={[ - { - title: t('views:connectors.viewDetails', 'View Details'), - to: connectorDetailUrl - }, - { - isDanger: true, - title: t('views:connectors.delete', 'Delete'), - onClick: () => onDeleteConnector(identifier) - } - ]} - /> - </Table.Cell> - </Table.Row> - ) - })} - </Table.Body> - )} + ) : null} + </Table.Cell> + <Table.Cell className="content-center"> + {lastModifiedAt ? <TimeAgoCard timestamp={lastModifiedAt} /> : null} + </Table.Cell> + <Table.Cell className="content-center !p-1.5" disableLink> + <Favorite + isFavorite={isFavorite} + onFavoriteToggle={(favorite: boolean) => onToggleFavoriteConnector(identifier, !favorite)} + /> + </Table.Cell> + <Table.Cell className="content-center !p-0" disableLink> + <MoreActionsTooltip + actions={[ + { + title: t('views:connectors.viewDetails', 'View Details'), + to: connectorDetailUrl + }, + { + isDanger: true, + title: t('views:connectors.delete', 'Delete'), + onClick: () => onDeleteConnector(identifier) + } + ]} + /> + </Table.Cell> + </Table.Row> + ) + })} + </Table.Body> </Table.Root> ) } diff --git a/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx b/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx index 296c83e88c..d4131e9eec 100644 --- a/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx +++ b/packages/ui/src/views/delegates/components/delegate-connectivity-list.tsx @@ -18,7 +18,7 @@ export function DelegateConnectivityList({ const { t } = useTranslation() if (isLoading) { - return <Skeleton.List /> + return <Skeleton.Table countRows={12} countColumns={4} /> } if (!delegates.length) { @@ -46,57 +46,46 @@ export function DelegateConnectivityList({ <Table.Head className="w-24">{t('views:delegates.selected', 'Selected')}</Table.Head> </Table.Row> </Table.Header> - {isLoading ? ( - <Skeleton.Table countRows={12} countColumns={5} /> - ) : ( - <Table.Body> - {delegates.map( - ({ - groupId, - groupName, - activelyConnected, - lastHeartBeat, - groupCustomSelectors, - groupImplicitSelectors - }) => { - return ( - <Table.Row key={groupId}> - <Table.Cell className="max-w-80 content-center truncate whitespace-nowrap"> - <div className="flex items-center gap-2.5"> - <Title title={groupName} /> - </div> - </Table.Cell> - <Table.Cell className="content-center truncate whitespace-nowrap"> - <div className="inline-flex items-center gap-2"> - <IconV2 - name="circle" - size="2xs" - className={cn(activelyConnected ? 'text-icons-success' : 'text-icons-danger')} - /> - {lastHeartBeat ? <TimeAgoCard timestamp={lastHeartBeat} /> : null} - </div> - </Table.Cell> - <Table.Cell className="max-w-96 whitespace-normal break-words"> - {groupCustomSelectors.map((selector: string) => ( - <StatusBadge variant="secondary" theme="merged" key={selector} className="mb-1 mr-2"> - {selector} - </StatusBadge> - ))} - </Table.Cell> - <Table.Cell className="min-w-8 content-center whitespace-nowrap"> - <div className="flex items-center justify-center"> - {isDelegateSelected( - [...defaultTo(groupImplicitSelectors, []), ...defaultTo(groupCustomSelectors, [])], - selectedTags || [] - ) && <IconV2 name="check" size="2xs" className="text-icons-success" />} - </div> - </Table.Cell> - </Table.Row> - ) - } - )} - </Table.Body> - )} + <Table.Body> + {delegates.map( + ({ groupId, groupName, activelyConnected, lastHeartBeat, groupCustomSelectors, groupImplicitSelectors }) => { + return ( + <Table.Row key={groupId}> + <Table.Cell className="max-w-80 content-center truncate whitespace-nowrap"> + <div className="flex items-center gap-2.5"> + <Title title={groupName} /> + </div> + </Table.Cell> + <Table.Cell className="content-center truncate whitespace-nowrap"> + <div className="inline-flex items-center gap-2"> + <IconV2 + name="circle" + size="2xs" + className={cn(activelyConnected ? 'text-icons-success' : 'text-icons-danger')} + /> + {lastHeartBeat ? <TimeAgoCard timestamp={lastHeartBeat} /> : null} + </div> + </Table.Cell> + <Table.Cell className="max-w-96 whitespace-normal break-words"> + {groupCustomSelectors.map((selector: string) => ( + <StatusBadge variant="secondary" theme="merged" key={selector} className="mb-1 mr-2"> + {selector} + </StatusBadge> + ))} + </Table.Cell> + <Table.Cell className="min-w-8 content-center whitespace-nowrap"> + <div className="flex items-center justify-center"> + {isDelegateSelected( + [...defaultTo(groupImplicitSelectors, []), ...defaultTo(groupCustomSelectors, [])], + selectedTags || [] + ) && <IconV2 name="check" size="2xs" className="text-icons-success" />} + </div> + </Table.Cell> + </Table.Row> + ) + } + )} + </Table.Body> </Table.Root> ) } diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index ae2693281c..83d95b86d6 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -92,7 +92,7 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ </ListActions.Root> )} - {isLoading && <Skeleton.List className="mb-8 mt-5" />} + {isLoading && <Skeleton.Table countRows={5} countColumns={3} />} {!isLoading && ( <LabelsListView diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx index 41d6abddd7..ec422cb844 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx @@ -29,7 +29,7 @@ export const ProfileKeysList: FC<ProfileKeysListProps> = ({ publicKeys, isLoadin </Table.Header> {isLoading ? ( - <Skeleton.Table countRows={4} countColumns={4} /> + <Skeleton.Table countRows={4} countColumns={4} hideHeader /> ) : ( <Table.Body> {publicKeys.length ? ( diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx index ee7d220a11..7ede21e266 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-tokens-list.tsx @@ -29,7 +29,7 @@ export const ProfileTokensList: FC<ProfileTokensListProps> = ({ tokens, isLoadin </Table.Row> </Table.Header> {isLoading ? ( - <Skeleton.Table countRows={5} /> + <Skeleton.Table countRows={5} countColumns={5} hideHeader /> ) : ( <Table.Body> {tokens.length ? ( diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index c8b1e0fcfa..e349371031 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -76,7 +76,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ const isView = useMemo(() => codeMode === CodeModes.VIEW, [codeMode]) const content = useMemo(() => { - if (loading || isLoadingRepoDetails) return <Skeleton.Table countColumns={3} countRows={8} showHeader /> + if (loading || isLoadingRepoDetails) return <Skeleton.Table countColumns={3} countRows={8} /> if (!isView) return children diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx index 9cb3e3b4b4..3a6dbc5b44 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx @@ -66,7 +66,7 @@ export const RepoSettingsFeaturesForm: FC<RepoSettingsFeaturesFormProps> = ({ <Layout.Vertical gap="xl"> <Text variant="heading-subsection">{t('views:repos.features', 'Features')}</Text> {isLoadingFeaturesSettings ? ( - <Skeleton.Form linesCount={2} /> + <Skeleton.Form /> ) : ( <ControlGroup> <Checkbox diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 57391596de..0d924bee5f 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -128,7 +128,7 @@ export function RepoSummaryView({ return ( <SandboxLayout.Main fullWidth> <SandboxLayout.Content> - <Skeleton.Table countColumns={3} countRows={10} showHeader /> + <Skeleton.Table countColumns={3} countRows={10} /> </SandboxLayout.Content> </SandboxLayout.Main> ) diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx index e1f67a6d51..2296819169 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx @@ -4,14 +4,14 @@ import { useTranslation } from '@/context' import { SecretListProps } from './types' const Title = ({ title }: { title: string }): JSX.Element => ( - <span className="max-w-full truncate font-medium text-cn-foreground-1">{title}</span> + <span className="text-cn-foreground-1 max-w-full truncate font-medium">{title}</span> ) export function SecretList({ secrets, isLoading, toSecretDetails, onDeleteSecret }: SecretListProps): JSX.Element { const { t } = useTranslation() if (isLoading) { - return <Skeleton.List /> + return <Skeleton.Table countRows={12} countColumns={5} /> } if (!secrets.length) { @@ -38,40 +38,36 @@ export function SecretList({ secrets, isLoading, toSecretDetails, onDeleteSecret <Table.Head></Table.Head> </Table.Row> </Table.Header> - {isLoading ? ( - <Skeleton.Table countRows={12} countColumns={5} /> - ) : ( - <Table.Body> - {secrets.map(secret => ( - <Table.Row key={secret.identifier} className="cursor-pointer" to={toSecretDetails?.(secret)}> - <Table.Cell className="w-[361px] content-center truncate"> - <div className="flex items-center gap-2.5"> - <IconV2 name="ssh-key" size="md" /> - <Title title={secret.identifier} /> - </div> - </Table.Cell> - <Table.Cell className="w-[350px] content-center truncate"> - {secret.spec?.secretManagerIdentifier} - </Table.Cell> - <Table.Cell className="content-center"> - {secret?.updatedAt ? <TimeAgoCard timestamp={secret.updatedAt} /> : null} - </Table.Cell> - <Table.Cell className="content-center text-right"> - <MoreActionsTooltip - isInTable - actions={[ - { - isDanger: true, - title: t('views:secrets.delete', 'Delete Secret'), - onClick: () => onDeleteSecret(secret.identifier) - } - ]} - /> - </Table.Cell> - </Table.Row> - ))} - </Table.Body> - )} + <Table.Body> + {secrets.map(secret => ( + <Table.Row key={secret.identifier} className="cursor-pointer" to={toSecretDetails?.(secret)}> + <Table.Cell className="w-[361px] content-center truncate"> + <div className="flex items-center gap-2.5"> + <IconV2 name="ssh-key" size="md" /> + <Title title={secret.identifier} /> + </div> + </Table.Cell> + <Table.Cell className="w-[350px] content-center truncate"> + {secret.spec?.secretManagerIdentifier} + </Table.Cell> + <Table.Cell className="content-center"> + {secret?.updatedAt ? <TimeAgoCard timestamp={secret.updatedAt} /> : null} + </Table.Cell> + <Table.Cell className="content-center text-right"> + <MoreActionsTooltip + isInTable + actions={[ + { + isDanger: true, + title: t('views:secrets.delete', 'Delete Secret'), + onClick: () => onDeleteSecret(secret.identifier) + } + ]} + /> + </Table.Cell> + </Table.Row> + ))} + </Table.Body> </Table.Root> ) } From 9a7e5c44c585a569590f92781562f4d38b6abc3e Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 8 Aug 2025 15:03:16 +0300 Subject: [PATCH 037/180] Add focus state for Radio and Checkbox (#2057) --- packages/ui/tailwind-utils-config/components/checkbox.ts | 5 +++++ packages/ui/tailwind-utils-config/components/radio.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/ui/tailwind-utils-config/components/checkbox.ts b/packages/ui/tailwind-utils-config/components/checkbox.ts index 5c39c2f484..458f2b9621 100644 --- a/packages/ui/tailwind-utils-config/components/checkbox.ts +++ b/packages/ui/tailwind-utils-config/components/checkbox.ts @@ -31,6 +31,11 @@ export default { border: 'var(--cn-border-width-1) solid var(--cn-comp-selection-unselected-border)', borderRadius: 'var(--cn-rounded-1)', backgroundColor: 'var(--cn-comp-selection-unselected-bg)', + + '&:where(:not([disabled])):focus': { + boxShadow: 'var(--cn-ring-focus)' + }, + '&:where(:not([disabled])):hover': { backgroundColor: 'var(--cn-comp-selection-unselected-bg-hover)', borderColor: 'var(--cn-comp-selection-unselected-border-hover)' diff --git a/packages/ui/tailwind-utils-config/components/radio.ts b/packages/ui/tailwind-utils-config/components/radio.ts index 9d2bbfc78b..43a01cba6d 100644 --- a/packages/ui/tailwind-utils-config/components/radio.ts +++ b/packages/ui/tailwind-utils-config/components/radio.ts @@ -32,6 +32,11 @@ export default { border: 'var(--cn-border-width-1) solid var(--cn-comp-selection-unselected-border)', borderRadius: '50%', backgroundColor: 'var(--cn-comp-selection-unselected-bg)', + + '&:where(:not([disabled])):focus': { + boxShadow: 'var(--cn-ring-focus)' + }, + '&:where(:not([disabled])):hover': { backgroundColor: 'var(--cn-comp-selection-unselected-bg-hover)', borderColor: 'var(--cn-comp-selection-unselected-border-hover)' From 19d9735e56945f6144c1f6e9f730a4653393e974 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 8 Aug 2025 15:46:21 +0300 Subject: [PATCH 038/180] Fix md scroll style (#2058) --- .../ui/src/components/markdown-viewer/style.css | 13 ++++++++++--- packages/ui/src/styles/styles.css | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 2a5f870e51..00f7df4dbb 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -6,7 +6,7 @@ color-scheme: dark; --color-fg-default: #dedede; - --color-fg-muted: #969896; + --color-fg-muted: var(--cn-text-3); --color-fg-subtle: #7aa6da; --color-canvas-default: var(--cn-bg-1); @@ -72,7 +72,7 @@ color-scheme: light; --color-fg-default: #353535; - --color-fg-muted: #10a567; + --color-fg-muted: var(--cn-text-3); --color-fg-subtle: #386ac3; --color-canvas-default: var(--cn-bg-1); @@ -154,6 +154,10 @@ font: var(--cn-comp-markdown-content-paragraph); @apply text-cn-foreground-2 !max-w-full; + &::-webkit-scrollbar-thumb { + background-color: var(--cn-comp-scrollbar-thumb); + } + h1, h2, h3, @@ -229,6 +233,9 @@ code { @apply font-body-code px-cn-2xs py-cn-4xs text-nowrap rounded border-0 bg-[var(--cn-set-brand-surface-bg)] !text-[var(--cn-set-blue-soft-text)] before:hidden after:hidden; + } + + :not(p) > code { font-size: inherit; line-height: inherit; } @@ -242,7 +249,7 @@ @apply font-body-code bg-cn-background-1 border-cn-borders-3 text-cn-foreground-1 mb-0 rounded border py-3 pl-4 pr-3; code { - @apply bg-cn-background-1 !text-cn-foreground-1 mr-7 p-0; + @apply bg-cn-background-1 !text-cn-foreground-1 mr-cn-3xl p-0; } } diff --git a/packages/ui/src/styles/styles.css b/packages/ui/src/styles/styles.css index 084356a68c..eafa4cdf1e 100644 --- a/packages/ui/src/styles/styles.css +++ b/packages/ui/src/styles/styles.css @@ -529,7 +529,7 @@ mark { } [data-radix-select-viewport]::-webkit-scrollbar-thumb { - background-color: var(--cn-bg-2); + background-color: var(--cn-comp-scrollbar-thumb); border: 4px solid transparent; border-radius: 7px; background-clip: content-box; From 1d0dc7fc0ea2e9943d59310e37b2e4e9ce201262 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 8 Aug 2025 16:33:46 +0300 Subject: [PATCH 039/180] Fix dropdown content height on small screen heights (#2059) --- packages/ui/tailwind-utils-config/components/dropdown-menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/tailwind-utils-config/components/dropdown-menu.ts b/packages/ui/tailwind-utils-config/components/dropdown-menu.ts index b4f52d807b..775186f6bf 100644 --- a/packages/ui/tailwind-utils-config/components/dropdown-menu.ts +++ b/packages/ui/tailwind-utils-config/components/dropdown-menu.ts @@ -38,7 +38,7 @@ export default { borderRadius: 'var(--cn-dropdown-radius)', backgroundColor: 'var(--cn-bg-3)', boxShadow: 'var(--cn-shadow-4)', - '@apply data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2': + '@apply flex flex-col data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2': '', '&-container': { From 831ccdf5e0a16efee30d4d392132c5b6e5cad291 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 8 Aug 2025 17:55:17 +0300 Subject: [PATCH 040/180] Fix some design diffs and bugs at Summary page + add focus to Switch (#2061) --- packages/ui/src/components/dialog.tsx | 7 +++++- .../components/form-primitives/textarea.tsx | 25 ++++++++++++++++++- .../repo/components/file-last-change-bar.tsx | 1 + .../components/edit-repo-details-dialog.tsx | 11 ++++++-- .../components/switch.ts | 5 ++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index f8ab45cad3..0b32921d06 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -56,7 +56,12 @@ const Content = forwardRef<HTMLDivElement, ContentProps>( return ( <DialogPrimitive.Portal container={portalContainer}> <DialogPrimitive.Overlay className="cn-modal-dialog-overlay" /> - <DialogPrimitive.Content ref={ref} className={cn(contentVariants({ size }), className)} {...props}> + <DialogPrimitive.Content + ref={ref} + className={cn(contentVariants({ size }), className)} + onOpenAutoFocus={event => event.preventDefault()} + {...props} + > {!hideClose && ( <DialogPrimitive.Close asChild> <Button variant="transparent" className="cn-modal-dialog-close" iconOnly> diff --git a/packages/ui/src/components/form-primitives/textarea.tsx b/packages/ui/src/components/form-primitives/textarea.tsx index b59623cfeb..28bae12713 100644 --- a/packages/ui/src/components/form-primitives/textarea.tsx +++ b/packages/ui/src/components/form-primitives/textarea.tsx @@ -1,4 +1,13 @@ -import { ChangeEvent, forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useState } from 'react' +import { + ChangeEvent, + forwardRef, + TextareaHTMLAttributes, + useCallback, + useEffect, + useMemo, + useRef, + useState +} from 'react' import { CommonInputsProp, ControlGroup, FormCaption, Label } from '@/components' import { cn, generateAlphaNumericHash, isAnyTypeOf, useMergeRefs } from '@/utils' @@ -54,10 +63,12 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>( informerProps, informerContent, wrapperClassName, + autoFocus, ...props }, ref ) => { + const textareaRef = useRef<HTMLTextAreaElement | null>(null) const [counter, setCounter] = useState(0) const isHorizontal = orientation === 'horizontal' @@ -84,6 +95,8 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>( node => { if (!node) return + textareaRef.current = node + if (isAnyTypeOf(node.value, ['number', 'string'])) { setCharactersCount(String(node.value)) } @@ -91,6 +104,16 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>( ref ]) + useEffect(() => { + if (autoFocus && textareaRef.current) { + const t = setTimeout(() => { + textareaRef.current?.focus() + }, 0) + + return () => clearTimeout(t) + } + }, [autoFocus]) + return ( <ControlGroup.Root className={wrapperClassName} orientation={orientation}> {(!!label || maxCharacters || (isHorizontal && !!caption)) && ( diff --git a/packages/ui/src/views/repo/components/file-last-change-bar.tsx b/packages/ui/src/views/repo/components/file-last-change-bar.tsx index 17af5c9226..6e130574a1 100644 --- a/packages/ui/src/views/repo/components/file-last-change-bar.tsx +++ b/packages/ui/src/views/repo/components/file-last-change-bar.tsx @@ -57,6 +57,7 @@ export const FileLastChangeBar: FC<FileLastChangeBarProps> = ({ className="flex-none" right title={<TopDetails toCommitDetails={toCommitDetails} {...props} />} + disableTruncate /> </> ) : ( diff --git a/packages/ui/src/views/repo/repo-summary/components/edit-repo-details-dialog.tsx b/packages/ui/src/views/repo/repo-summary/components/edit-repo-details-dialog.tsx index 0328835cdc..4263a8514e 100644 --- a/packages/ui/src/views/repo/repo-summary/components/edit-repo-details-dialog.tsx +++ b/packages/ui/src/views/repo/repo-summary/components/edit-repo-details-dialog.tsx @@ -18,15 +18,23 @@ export const EditRepoDetails = ({ updateRepoError }: EditRepoDetailsDialog) => { const [newDesc, setNewDesc] = useState<string>(description) + const handleClose = () => { setNewDesc(description || '') onClose() } + useEffect(() => { setNewDesc(description) }, [description]) + + const onOpenChangeHandler = () => { + setNewDesc(description || '') + onClose() + } + return ( - <Dialog.Root open={showEditRepoDetails} onOpenChange={onClose}> + <Dialog.Root open={showEditRepoDetails} onOpenChange={onOpenChangeHandler}> <Dialog.Content> <Dialog.Header> <Dialog.Title>Repository Description</Dialog.Title> @@ -38,7 +46,6 @@ export const EditRepoDetails = ({ resizable rows={6} autoFocus - defaultValue={description} onChange={(e: ChangeEvent<HTMLTextAreaElement>) => { setNewDesc(e?.target?.value) }} diff --git a/packages/ui/tailwind-utils-config/components/switch.ts b/packages/ui/tailwind-utils-config/components/switch.ts index 8619e37514..e702da2405 100644 --- a/packages/ui/tailwind-utils-config/components/switch.ts +++ b/packages/ui/tailwind-utils-config/components/switch.ts @@ -22,6 +22,11 @@ export default { backgroundColor: `var(--cn-comp-selection-unselected-bg)`, borderColor: `var(--cn-comp-selection-unselected-border)`, + + '&:where(:not([disabled])):focus': { + boxShadow: 'var(--cn-ring-focus)' + }, + '&:where(:not([disabled])):hover': { backgroundColor: `var(--cn-comp-selection-unselected-bg-hover)`, borderColor: `var(--cn-comp-selection-unselected-border-hover)`, From 322f58affa502a9b31177fe97b4194bbc45c7b14 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Fri, 8 Aug 2025 15:00:59 +0000 Subject: [PATCH 041/180] fix: show files list instantly and lazy load file details in summary when scrolled (#10159) * 70902c fix: show files list instantly and lazy load file details in summary when scrolled * a94828 fix: show files list instantly and lazy load file details in summary when scrolled * 82728b fix: design fixes for repo files * 36f96e fix: branch info bar design fix * a7cab6 fix: min sidebar with 264px * 5a24dc fix: show files list instantly and lazy load file details in summary when scrolled --- .../src/hooks/useRepoFileContentDetails.ts | 169 ++++++++---------- apps/gitness/src/pages-v2/repo/repo-code.tsx | 4 +- .../src/pages-v2/repo/repo-sidebar.tsx | 2 +- .../src/pages-v2/repo/repo-summary.tsx | 4 +- packages/ui/locales/en/views.json | 1 + packages/ui/locales/fr/views.json | 1 + .../views/repo/components/branch-info-bar.tsx | 39 ++-- .../views/repo/components/summary/summary.tsx | 108 ++++++++++- .../views/repo/repo-files/repo-files-view.tsx | 7 +- .../views/repo/repo-summary/repo-summary.tsx | 3 + 10 files changed, 210 insertions(+), 128 deletions(-) diff --git a/apps/gitness/src/hooks/useRepoFileContentDetails.ts b/apps/gitness/src/hooks/useRepoFileContentDetails.ts index 1e712ee719..cc5f0bbf0f 100644 --- a/apps/gitness/src/hooks/useRepoFileContentDetails.ts +++ b/apps/gitness/src/hooks/useRepoFileContentDetails.ts @@ -10,6 +10,7 @@ import { getTrimmedSha, normalizeGitRef } from '../utils/git-utils' interface UseRepoContentDetailsProps { repoRef: string fullGitRef: string + fullResourcePath: string pathToTypeMap: Map<string, OpenapiGetContentOutput['type']> spaceId?: string repoId?: string @@ -20,29 +21,26 @@ interface UseRepoContentDetailsProps { interface UseRepoContentDetailsResult { files: RepoFile[] loading: boolean + loadMetadataForPaths: (paths: string[]) => Promise<void> } /** - * fetch and process repository content details using batch processing + * show files immediately and load metadata lazily */ export const useRepoFileContentDetails = ({ repoRef, fullGitRef, + fullResourcePath, pathToTypeMap, spaceId, - repoId, - batchSize = 20, - throttleDelay = 300 + repoId }: UseRepoContentDetailsProps): UseRepoContentDetailsResult => { const [files, setFiles] = useState<RepoFile[]>([]) const [loading, setLoading] = useState(false) const routes = useRoutes() - // Track whether this is the first render to avoid unnecessary API calls on remounts - const isFirstRender = useRef(true) - - // Previous props to detect actual changes - const prevPropsRef = useRef({ repoRef, fullGitRef, pathMapSize: pathToTypeMap.size }) + // files that already have metadata loaded + const metadataLoadedRef = useRef(new Set<string>()) // convert content type to summary item type const getSummaryItemType = useCallback((type: OpenapiGetContentOutput['type']): SummaryItemType => { @@ -76,61 +74,45 @@ export const useRepoFileContentDetails = ({ return sortPathsByType(Array.from(pathToTypeMap.keys()), filteredMap) }, [pathToTypeMap, filteredMap]) - const createFileObject = useCallback( - (path: string, item: TypesPathDetails): RepoFile => ({ + // Creates file object with basic info (no metadata) + const createBasicFileObject = useCallback( + (path: string): RepoFile => ({ id: path, type: getSummaryItemType(pathToTypeMap.get(path)), name: getLastPathSegment(path) || path, - lastCommitMessage: item?.last_commit?.message || '', - timestamp: item?.last_commit?.author?.when ?? '', - user: { name: item?.last_commit?.author?.identity?.name || '' }, - sha: item?.last_commit?.sha && getTrimmedSha(item.last_commit.sha), + lastCommitMessage: '', + timestamp: '', + user: undefined, + sha: undefined, path: routes.toRepoFiles({ spaceId, repoId, '*': `${fullGitRef || ''}/~/${path}` }) }), [pathToTypeMap, getSummaryItemType, getLastPathSegment, routes, spaceId, repoId, fullGitRef] ) - useEffect(() => { - // If there are no paths to process, skip processing - if (!pathToTypeMap.size) { - return - } - - // Check if props actually changed - const prevProps = prevPropsRef.current - const propsChanged = - prevProps.repoRef !== repoRef || - prevProps.fullGitRef !== fullGitRef || - prevProps.pathMapSize !== pathToTypeMap.size - - prevPropsRef.current = { repoRef, fullGitRef, pathMapSize: pathToTypeMap.size } - - // Skip API call if this is a re-render with the same props - if (!isFirstRender.current && !propsChanged) { - return - } - - isFirstRender.current = false - - setLoading(true) - - // Track processed files to avoid duplicates - const processedFilesSet = new Set<string>() - let timeoutId: ReturnType<typeof setTimeout> | null = null + // Creates file object with metadata + const createFileObjectWithMetadata = useCallback( + (item: TypesPathDetails): Partial<RepoFile> => ({ + lastCommitMessage: item?.last_commit?.message || '', + timestamp: item?.last_commit?.author?.when ?? '', + user: { name: item?.last_commit?.author?.identity?.name || '' }, + sha: item?.last_commit?.sha && getTrimmedSha(item.last_commit.sha) + }), + [] + ) - const processBatch = async (startIndex: number, isFirstBatch: boolean = false) => { - // If we've processed all paths, we're done - if (startIndex >= allPaths.length) { - return - } + // load metadata for specific file paths + const loadMetadataForPaths = useCallback( + async (paths: string[]) => { + if (!paths.length || !repoRef || !fullGitRef) return - // Get the next batch of paths - const batchPaths = allPaths.slice(startIndex, startIndex + batchSize) + // Filter out paths that already have metadata + const pathsToLoad = paths.filter(path => !metadataLoadedRef.current.has(path)) + if (!pathsToLoad.length) return try { const { body: response } = await pathDetails({ queryParams: { git_ref: normalizeGitRef(fullGitRef || '') }, - body: { paths: batchPaths }, + body: { paths: pathsToLoad }, repo_ref: repoRef }) @@ -140,64 +122,55 @@ export const useRepoFileContentDetails = ({ response.details.forEach(detail => { if (detail.path) { detailsMap.set(detail.path, detail) + metadataLoadedRef.current.add(detail.path) } }) - // Process paths in order from the pre-sorted batch - const newFiles: RepoFile[] = [] - for (const path of batchPaths) { - // Skip if already processed or not found in response - if (processedFilesSet.has(path) || !detailsMap.has(path)) continue - - // Mark as processed - processedFilesSet.add(path) - - // Get the details for this path - const item = detailsMap.get(path)! - newFiles.push(createFileObject(path, item)) - } - - // Update the files state - setFiles(prevFiles => [...prevFiles, ...newFiles]) - - // Set loading to false after the first batch is processed - if (isFirstBatch) { - setLoading(false) - } - } else if (isFirstBatch) { - // If first batch has no results, still set loading to false - setLoading(false) + // Update files with metadata + setFiles(prevFiles => + prevFiles.map(file => { + if (pathsToLoad.includes(file.id) && detailsMap.has(file.id)) { + const metadata = createFileObjectWithMetadata(detailsMap.get(file.id)!) + return { ...file, ...metadata } + } + return file + }) + ) } - - // Process the next batch with throttling - timeoutId = setTimeout(() => { - processBatch(startIndex + batchSize, false) - }, throttleDelay) } catch (error) { - console.error('Error fetching path details:', error) + console.error('Error loading metadata for paths:', paths, error) + } + }, + [repoRef, fullGitRef, createFileObjectWithMetadata] + ) - // If this was the first batch, set loading to false even on error - if (isFirstBatch) { - setLoading(false) - } + // Initialize files immediately when pathToTypeMap changes + useEffect(() => { + const shouldUpdateFiles = pathToTypeMap.size > 0 - // Even if there's an error, try to process the next batch (with throttling) - timeoutId = setTimeout(() => { - processBatch(startIndex + batchSize, false) - }, throttleDelay) - } - } + if (shouldUpdateFiles) { + setLoading(true) - setFiles([]) - processBatch(0, true) + // Create basic file objects immediately + const basicFiles = allPaths.map(path => createBasicFileObject(path)) + setFiles(basicFiles) - return () => { - if (timeoutId) { - clearTimeout(timeoutId) - } + // Clear metadata cache for new repo/ref + metadataLoadedRef.current.clear() + + // Set loading to false immediately since we're showing files + setLoading(false) + } else if (pathToTypeMap.size === 0) { + // No files to show + setFiles([]) setLoading(false) + metadataLoadedRef.current.clear() } - }, [createFileObject, allPaths, repoRef, fullGitRef]) + }, [pathToTypeMap, allPaths, createBasicFileObject, repoRef, fullGitRef, fullResourcePath]) - return { files, loading } + return { + files, + loading, + loadMetadataForPaths + } } diff --git a/apps/gitness/src/pages-v2/repo/repo-code.tsx b/apps/gitness/src/pages-v2/repo/repo-code.tsx index 2dba51883a..743ffe2a89 100644 --- a/apps/gitness/src/pages-v2/repo/repo-code.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-code.tsx @@ -79,9 +79,10 @@ export const RepoCode = () => { return new Map(nonEmptyPathEntries.map((entry: OpenapiContentInfo) => [entry?.path ? entry.path : '', entry.type])) }, [repoDetails]) - const { files, loading } = useRepoFileContentDetails({ + const { files, loading, loadMetadataForPaths } = useRepoFileContentDetails({ repoRef, fullGitRef, + fullResourcePath, pathToTypeMap: repoEntryPathToFileTypeMap, spaceId, repoId @@ -174,6 +175,7 @@ export const RepoCode = () => { toRepoFileDetails={({ path }: { path: string }) => `../${path}`} showContributeBtn={showContributeBtn} repoDetailsError={repoDetailsError} + loadMetadataForPaths={loadMetadataForPaths} > {renderCodeView} </RepoFiles> diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index fbbf232620..d184dbafce 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -22,7 +22,7 @@ import { PathParams } from '../../RouteDefinitions' import { FILE_SEPERATOR, normalizeGitRef, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../../utils/git-utils' import { transformBranchList } from './transform-utils/branch-transform' -const SIDEBAR_MIN_WIDTH = 210 +const SIDEBAR_MIN_WIDTH = 264 const SIDEBAR_MAX_WIDTH = 700 /** diff --git a/apps/gitness/src/pages-v2/repo/repo-summary.tsx b/apps/gitness/src/pages-v2/repo/repo-summary.tsx index 9965d015ea..455e7c6a8d 100644 --- a/apps/gitness/src/pages-v2/repo/repo-summary.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-summary.tsx @@ -236,9 +236,10 @@ export default function RepoSummaryPage() { return new Map(nonEmtpyPathEntries.map((entry: OpenapiContentInfo) => [entry.path, entry.type])) }, [repoDetails?.content?.entries]) - const { files, loading } = useRepoFileContentDetails({ + const { files, loading, loadMetadataForPaths } = useRepoFileContentDetails({ repoRef, fullGitRef, + fullResourcePath: '', pathToTypeMap: repoEntryPathToFileTypeMap, spaceId, repoId @@ -324,6 +325,7 @@ export default function RepoSummaryPage() { toRepoTags={() => routes.toRepoTags({ spaceId, repoId })} toRepoPullRequests={() => routes.toRepoPullRequests({ spaceId, repoId })} showContributeBtn={showContributeBtn} + loadMetadataForPaths={loadMetadataForPaths} /> {showTokenDialog && createdTokenData && ( <CloneCredentialDialog diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index e4c7cdf66b..1906be245c 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -26,6 +26,7 @@ "compareAndPullRequest": "Compare & pull request", "dismiss": "Dismiss", "compareBranchesToSeeChanges": "Compare branches to see your changes.", + "noNewCommits": "No new commits yet", "default": "Default", "viewAll": "View all {{type}}", "branchesLowercase": "branches", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index f8be2fe3fe..17c45d4146 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -26,6 +26,7 @@ "compareAndPullRequest": "Comparez et ouvrez une requête de tirage", "dismiss": "Dismiss", "compareBranchesToSeeChanges": "Comparez les branches pour voir vos changements.", + "noNewCommits": "Pas encore de nouveaux commits", "default": "Par défaut", "viewAll": "Voir tous les {{type}}", "branchesLowercase": "branches", diff --git a/packages/ui/src/views/repo/components/branch-info-bar.tsx b/packages/ui/src/views/repo/components/branch-info-bar.tsx index 3cf5c73b7e..3bed528833 100644 --- a/packages/ui/src/views/repo/components/branch-info-bar.tsx +++ b/packages/ui/src/views/repo/components/branch-info-bar.tsx @@ -34,7 +34,7 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ return ( <Layout.Flex - className="border-cn-borders-2 bg-cn-background-2 min-h-9 rounded-md border px-4 py-2" + className="border-cn-borders-2 bg-cn-background-2 min-h-[3.25rem] rounded-md border px-4 py-2" align="center" justify="between" gapX="xs" @@ -65,7 +65,7 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ <DropdownMenu.Root> <DropdownMenu.Trigger asChild> <Button - className="group/contribute data-[state=open]:border-cn-borders-9 data-[state=open]:text-cn-foreground-1" + className="group/contribute data-[state=open]:border-cn-borders-9 data-[state=open]:text-cn-foreground-1 py-2" variant="outline" > <IconV2 name="git-pull-request" size="xs" /> @@ -86,7 +86,10 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ </div> <Layout.Grid gapY="xs"> <Text variant="body-single-line-strong" color="foreground-1"> - This branch is {ahead} {easyPluralize(ahead, 'commit', 'commits')} ahead of{' '} + {hasAhead + ? `This branch is ${ahead} ${easyPluralize(ahead, 'commit', 'commits')} ahead of` + : 'This branch is not ahead of'} +   <Tag className="mt-0.5 align-sub" variant="secondary" @@ -97,23 +100,27 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ </Text> <Text color="foreground-3"> - {t( - 'views:repos.compareBranchesToSeeChanges', - 'Open a pull request to contribute your changes upstream.' - )} + {hasAhead + ? t( + 'views:repos.compareBranchesToSeeChanges', + 'Open a pull request to contribute your changes upstream.' + ) + : t('views:repos.noNewCommits', 'No new commits yet.')} </Text> </Layout.Grid> </Layout.Grid> - <ButtonLayout> - <Button className="w-full" variant="outline" asChild> - <Link - to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/${defaultBranchName}...${selectedBranchTag?.name}`} - > - Compare - </Link> - </Button> - </ButtonLayout> + {hasAhead && ( + <ButtonLayout> + <Button className="w-full" variant="outline" asChild> + <Link + to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/${defaultBranchName}...${selectedBranchTag?.name}`} + > + Compare + </Link> + </Button> + </ButtonLayout> + )} </Layout.Grid> </DropdownMenu.Slot> </DropdownMenu.Content> diff --git a/packages/ui/src/views/repo/components/summary/summary.tsx b/packages/ui/src/views/repo/components/summary/summary.tsx index 09f37105ba..71b8c698f7 100644 --- a/packages/ui/src/views/repo/components/summary/summary.tsx +++ b/packages/ui/src/views/repo/components/summary/summary.tsx @@ -1,4 +1,6 @@ -import { IconV2, Table, Text, TimeAgoCard } from '@/components' +import { useEffect, useRef } from 'react' + +import { IconV2, Skeleton, Table, Text, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { FileStatus, LatestFileTypes, RepoFile, SummaryItemType } from '@/views' import { FileLastChangeBar } from '@views/repo/components' @@ -6,12 +8,14 @@ import { FileLastChangeBar } from '@views/repo/components' interface RoutingProps { toCommitDetails?: ({ sha }: { sha: string }) => string } + interface SummaryProps extends RoutingProps { latestFile: LatestFileTypes files: RepoFile[] hideHeader?: boolean toCommitDetails?: ({ sha }: { sha: string }) => string toRepoFileDetails?: ({ path }: { path: string }) => string + loadMetadataForPaths?: (paths: string[]) => Promise<void> } export const Summary = ({ @@ -19,10 +23,74 @@ export const Summary = ({ files, hideHeader = false, toCommitDetails, - toRepoFileDetails + toRepoFileDetails, + loadMetadataForPaths }: SummaryProps) => { const { t } = useTranslation() + // files that have been observed and metadata requested + const observedFilesRef = useRef(new Set<string>()) + const intersectionObserverRef = useRef<IntersectionObserver | null>(null) + const tableRowsRef = useRef<Map<string, HTMLTableRowElement>>(new Map()) + + // Initialize intersection observer for lazy metadata loading + useEffect(() => { + if (!loadMetadataForPaths) return + + // Disconnect existing observer + if (intersectionObserverRef.current) { + intersectionObserverRef.current.disconnect() + } + + // Create new intersection observer + intersectionObserverRef.current = new IntersectionObserver( + entries => { + const visiblePaths: string[] = [] + + entries.forEach(entry => { + if (entry.isIntersecting) { + const fileId = entry.target.getAttribute('data-file-id') + if (fileId && !observedFilesRef.current.has(fileId)) { + observedFilesRef.current.add(fileId) + visiblePaths.push(fileId) + } + } + }) + + // Load metadata for newly visible files + if (visiblePaths.length > 0) { + loadMetadataForPaths(visiblePaths).catch(error => { + console.error('Failed to load metadata for visible files:', error) + }) + } + }, + { + // Load metadata when file is 200px from viewport + rootMargin: '200px', + threshold: 0 + } + ) + + // Observe all current table rows + tableRowsRef.current.forEach(element => { + if (element && intersectionObserverRef.current) { + intersectionObserverRef.current.observe(element) + } + }) + + return () => { + if (intersectionObserverRef.current) { + intersectionObserverRef.current.disconnect() + } + } + }, [loadMetadataForPaths, files]) + + // Reset observed files and table row refs when files list changes + useEffect(() => { + observedFilesRef.current.clear() + tableRowsRef.current.clear() + }, [files]) + return ( <> {!hideHeader && <FileLastChangeBar toCommitDetails={toCommitDetails} {...latestFile} />} @@ -46,7 +114,21 @@ export const Summary = ({ </Table.Row> )} {files.map(file => ( - <Table.Row key={file.id} to={toRepoFileDetails?.({ path: file.path }) ?? ''}> + <Table.Row + key={file.id} + to={toRepoFileDetails?.({ path: file.path }) ?? ''} + data-file-id={file.id} + ref={el => { + // Store table row ref + if (el) { + tableRowsRef.current.set(file.id, el) + } + // Observe each table row for intersection + if (el && intersectionObserverRef.current) { + intersectionObserverRef.current.observe(el) + } + }} + > <Table.Cell className="relative"> <div className={`flex cursor-pointer items-center gap-1.5 ${ @@ -89,14 +171,22 @@ export const Summary = ({ </div> </Table.Cell> <Table.Cell> - <Text className="line-clamp-1">{file.lastCommitMessage}</Text> + {file.lastCommitMessage ? ( + <Text className="line-clamp-1">{file.lastCommitMessage}</Text> + ) : ( + <Skeleton.Box className="w-full h-5" /> + )} </Table.Cell> <Table.Cell className="text-right" disableLink> - <TimeAgoCard - timestamp={file?.timestamp} - dateTimeFormatOptions={{ dateStyle: 'medium' }} - textProps={{ color: 'foreground-3', wrap: 'nowrap', align: 'right' }} - /> + {file.timestamp ? ( + <TimeAgoCard + timestamp={file.timestamp} + dateTimeFormatOptions={{ dateStyle: 'medium' }} + textProps={{ color: 'foreground-3', wrap: 'nowrap', align: 'right' }} + /> + ) : ( + <Skeleton.Box className="w-full h-5" /> + )} </Table.Cell> </Table.Row> ))} diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index e349371031..8985529dcb 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -43,6 +43,7 @@ interface RepoFilesProps { fullResourcePath?: string showContributeBtn?: boolean repoDetailsError?: UsererrorError | null + loadMetadataForPaths?: (paths: string[]) => Promise<void> } export const RepoFiles: FC<RepoFilesProps> = ({ @@ -69,7 +70,8 @@ export const RepoFiles: FC<RepoFilesProps> = ({ selectedRefType, fullResourcePath, showContributeBtn, - repoDetailsError + repoDetailsError, + loadMetadataForPaths }) => { const { t } = useTranslation() @@ -111,6 +113,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ latestFile={latestFile} files={files} toRepoFileDetails={toRepoFileDetails} + loadMetadataForPaths={loadMetadataForPaths} /> </> ) @@ -155,7 +158,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ ]) return ( - <SandboxLayout.Main className="repo-files-height bg-transparent" fullWidth> + <SandboxLayout.Main className="repo-files-height bg-transparent"> <SandboxLayout.Content className="flex h-full flex-col pl-cn-xl gap-y-cn-md"> {isView && !isRepoEmpty && ( <PathActionBar diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 0d924bee5f..4f47d696e3 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -87,6 +87,7 @@ export interface RepoSummaryViewProps extends Partial<RoutingProps> { refType?: BranchSelectorTab prCandidateBranches?: TypesBranchTable[] showContributeBtn?: boolean + loadMetadataForPaths?: (paths: string[]) => Promise<void> } export function RepoSummaryView({ @@ -119,6 +120,7 @@ export function RepoSummaryView({ tokenGenerationError, refType = BranchSelectorTab.BRANCHES, showContributeBtn, + loadMetadataForPaths, ...props }: RepoSummaryViewProps) { const { Link } = useRouterContext() @@ -230,6 +232,7 @@ export function RepoSummaryView({ files={files} toRepoFileDetails={toRepoFileDetails} hideHeader + loadMetadataForPaths={loadMetadataForPaths} /> <Spacer size={5} /> <StackedList.Root onlyTopRounded borderBackground> From a689b771392f35452d44ca98403352c26d6d8590 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Fri, 8 Aug 2025 15:53:00 +0000 Subject: [PATCH 042/180] fix: add outdated timestamp (#10161) * a611a7 refactor: address comments * 854617 refactor: self review * 33f3cf fix: add outdated timestamp --- .../components/pull-request-diff-viewer.tsx | 15 +++++++++-- .../common/pull-request-comment-view.tsx | 6 ++--- .../conversation/regular-and-code-comment.tsx | 26 ++++++++++++------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index c815a0d3cf..912bce3bd0 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { Avatar, Text, TextInput, TimeAgoCard } from '@/components' +import { Avatar, Layout, Separator, Tag, Text, TextInput, TimeAgoCard } from '@/components' import { useTheme, useTranslation } from '@/context' import { activitiesToDiffCommentItems, @@ -491,7 +491,18 @@ const PullRequestDiffViewer = ({ header={[ { name: parent.author, - description: <TimeAgoCard timestamp={parent?.created} /> + description: ( + <Layout.Horizontal align="center" gap="xs"> + <TimeAgoCard timestamp={parent?.created} /> + + {parent?.payload?.payload?.code_comment?.outdated && ( + <> + <Separator orientation="vertical" className="h-3.5" /> + <Tag key={'outdated'} value="OUTDATED" theme="orange" /> + </> + )} + </Layout.Horizontal> + ) } ]} content={ diff --git a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx index 874e12b8e8..f27e283ab2 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx @@ -2,7 +2,7 @@ import { FC } from 'react' -import { Button, CounterBadge, MarkdownViewer } from '@/components' +import { Button, CounterBadge, Layout, MarkdownViewer } from '@/components' import { CommitSuggestion } from '@views/repo/pull-request/pull-request.types' import { CommentItem, TypesPullReqActivity } from '../../pull-request-details-types' @@ -61,7 +61,7 @@ const PRCommentView: FC<PRCommentViewProps> = ({ {/* Only show the suggestion buttons if the suggestion is not yet applied */} {isSuggestion && !isApplied && ( - <div className="flex justify-end gap-x-2.5"> + <Layout.Horizontal align="center" justify="end" gap="sm" className="pt-4"> <Button className="gap-x-2" variant="outline" @@ -92,7 +92,7 @@ const PRCommentView: FC<PRCommentViewProps> = ({ Add suggestion to batch </Button> )} - </div> + </Layout.Horizontal> )} </> ) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx index 1d0067c1bb..e12dbbd2c3 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx @@ -1,6 +1,6 @@ import { FC, memo, useCallback, useState } from 'react' -import { Avatar, CopyButton, IconV2, Layout, TextInput, TimeAgoCard } from '@/components' +import { Avatar, CopyButton, IconV2, Layout, Separator, Tag, Text, TextInput, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { activitiesToDiffCommentItems, @@ -313,9 +313,16 @@ const PullRequestRegularAndCodeCommentInternal: FC<PullRequestRegularAndCodeComm payload?.created ? { description: ( - <div className="flex gap-x-1"> - reviewed <TimeAgoCard timestamp={payload.created} /> - </div> + <Layout.Horizontal align="center" gap="3xs"> + <Text variant="body-single-line-normal">reviewed</Text> + <TimeAgoCard timestamp={payload.created} /> + {payload?.code_comment?.outdated && ( + <> + <Separator orientation="vertical" className="h-3.5 mx-1" /> + <Tag key={'outdated'} value="OUTDATED" theme="orange" /> + </> + )} + </Layout.Horizontal> ) } : {} @@ -327,8 +334,8 @@ const PullRequestRegularAndCodeCommentInternal: FC<PullRequestRegularAndCodeComm handleSaveComment, isNotCodeComment: true, contentHeader: ( - <Layout.Horizontal gap="sm"> - <span className="font-medium text-cn-foreground-1">{payload?.code_comment?.path}</span> + <Layout.Horizontal gap="sm" align="center"> + <Text variant="body-single-line-normal">{payload?.code_comment?.path}</Text> <CopyButton name={payload?.code_comment?.path || ''} size="xs" color="gray" /> </Layout.Horizontal> ), @@ -376,9 +383,10 @@ const PullRequestRegularAndCodeCommentInternal: FC<PullRequestRegularAndCodeComm payload?.created ? { description: ( - <div className="flex gap-x-1"> - commented <TimeAgoCard timestamp={payload.created} /> - </div> + <Layout.Horizontal align="center" gap="3xs"> + <Text variant="body-normal">commented</Text> + <TimeAgoCard timestamp={payload.created} /> + </Layout.Horizontal> ) } : {} From dd930f71e4874a78b038992456bb56964409aab9 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 8 Aug 2025 16:21:31 +0000 Subject: [PATCH 043/180] fix a couple of remaining 'Create' button titles (#10162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * d170a1 Add User * 8271e5 Créer * a91505 create user * 044da8 remove 'New' for consistency * 677e2e update create new titles * e472b1 fix a couple of remaining 'Create' button titles * 5eca58 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b1eb1b Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b42931 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 55c544 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 3d07a1 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * d70666 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 50f0dc Merge branch 'main' of https://git0.harness.i --- packages/ui/locales/en/views.json | 8 ++++---- packages/ui/locales/fr/views.json | 8 ++++---- .../connectors/connectors-list/connectors-list-page.tsx | 2 +- .../views/connectors/connectors-list/connectors-list.tsx | 2 +- packages/ui/src/views/labels/labels-list-page.tsx | 2 +- .../src/views/secrets/secrets-list/secrets-list-page.tsx | 2 +- .../components/empty-state/empty-state.tsx | 2 +- .../components/page-components/actions/actions.tsx | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 1906be245c..e3e504622f 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -357,7 +357,7 @@ "configuration": "Configuration", "references": "References", "activityHistory": "Activity history", - "createNew": "Create New Connector", + "createNew": "Create Connector", "errorEncountered": "Error Encountered", "viewDetails": "View Details", "id": "Connector ID", @@ -526,7 +526,7 @@ "delete": "Delete Label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", - "newLabel": "New Label", + "createLabel": "Create Label", "create": "Create Labels" }, "projectSettings": { @@ -797,7 +797,7 @@ "delete": "Delete Webhook" }, "secrets": { - "createNew": "Create New Secret", + "createNew": "Create Secret", "delete": "Delete Secret", "secretsTitle": "Secrets" }, @@ -861,7 +861,7 @@ "confirm": "Confirm" }, "close": "Close", - "newUserButton": "New User", + "newUserButton": "Add User", "searchPlaceholder": "Search", "usersHeader": "Users", "tabs": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 17c45d4146..b6766703fb 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -349,7 +349,7 @@ "configuration": "Configuration", "references": "Références", "activityHistory": "Historique des activités", - "createNew": "Créer un nouveau connecteur", + "createNew": "Créer un connecteur", "errorEncountered": "Erreur rencontrée", "viewDetails": "Voir les détails", "id": "ID du connecteur", @@ -518,7 +518,7 @@ "delete": "Delete label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", - "newLabel": "New label", + "createLabel": "Create label", "create": "Create labels" }, "projectSettings": { @@ -784,7 +784,7 @@ "delete": "Delete webhook" }, "secrets": { - "createNew": "Créer un nouveau secret", + "createNew": "Créer un secret", "delete": "Supprimer le secret", "secretsTitle": "Secrets" }, @@ -848,7 +848,7 @@ "confirm": "Confirmer" }, "close": "Fermer", - "newUserButton": "Nouvel utilisateur", + "newUserButton": "Ajouter utilisateur", "searchPlaceholder": "Rechercher", "usersHeader": "Utilisateurs", "tabs": { diff --git a/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx b/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx index 4b2884bf9f..797cc0d254 100644 --- a/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx +++ b/packages/ui/src/views/connectors/connectors-list/connectors-list-page.tsx @@ -97,7 +97,7 @@ const ConnectorsListPage: FC<ConnectorListPageProps> = ({ headerAction={ <Button onClick={onCreate}> <IconV2 name="plus" /> - {t('views:connectors.createNew', 'New Connector')} + {t('views:connectors.createNew', 'Create Connector')} </Button> } filterOptions={CONNECTOR_FILTER_OPTIONS} diff --git a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx index 46a3855c4b..ac61538ecf 100644 --- a/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx +++ b/packages/ui/src/views/connectors/connectors-list/connectors-list.tsx @@ -94,7 +94,7 @@ export function ConnectorsList({ title={t('views:noData.noConnectors', 'No connectors yet')} description={[ t('views:noData.noConnectors', 'There are no connectors in this project yet.'), - t('views:connectors.createNew', 'New connector') + t('views:connectors.createNew', 'Create Connector') ]} /> ) diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index 83d95b86d6..32072158dd 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -85,7 +85,7 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ <Button asChild> <Link to="create"> <IconV2 name="plus" /> - {t('views:labelData.newLabel', 'New Label')} + {t('views:labelData.newLabel', 'Create Label')} </Link> </Button> </ListActions.Right> diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx index 2745b62a7b..6bc314ebe6 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx @@ -82,7 +82,7 @@ const SecretListPage: FC<SecretListPageProps> = ({ <ListActions.Right> <Button onClick={onCreate}> <IconV2 name="plus" /> - {t('views:secrets.createNew', 'New Secret')} + {t('views:secrets.createNew', 'Create Secret')} </Button> </ListActions.Right> </ListActions.Root> diff --git a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx index 608165acdb..b123fa410a 100644 --- a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx +++ b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx @@ -23,7 +23,7 @@ export const EmptyState = () => { label: ( <> <IconV2 name="plus" /> - {t('views:userManagement.newUserButton', 'New User')} + {t('views:userManagement.newUserButton', 'Add User')} </> ), onClick: () => handleDialogOpen(null, DialogLabels.CREATE_USER) diff --git a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx index 715b516267..a80d9b2694 100644 --- a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx +++ b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx @@ -24,7 +24,7 @@ export const Actions = () => { <ListActions.Right> <Button onClick={() => handleDialogOpen(null, DialogLabels.CREATE_USER)}> <IconV2 name="plus" /> - {t('views:userManagement.newUserButton', 'New User')} + {t('views:userManagement.newUserButton', 'Add User')} </Button> </ListActions.Right> </ListActions.Root> From 6d8e7b297835a1e9f820f4b27bc2f42accc24e0e Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Fri, 8 Aug 2025 17:16:03 +0000 Subject: [PATCH 044/180] fix: Fix few P3 bugs (#10150) * 5d19b5 remove unused string * 426743 cleanup strings * be07a1 review suggestion * 170738 cleanup * a43fae Merge branch 'main' into fix-p3-bugs-7-aug * 86704e handle long branch names * 759f7c cleanup * 1179b4 remove unused import * 20c070 improve comment * bde108 handle "Make Commit" onclick * 5cbd24 fix button labels * d68dd3 fix letter casing --- .../src/pages-v2/repo/repo-commits.tsx | 1 + packages/ui/locales/en/views.json | 4 +-- packages/ui/locales/fr/views.json | 6 ++--- .../branch-banner/branch-compare-banner.tsx | 4 ++- .../repo/repo-commits/repo-commits-view.tsx | 26 +++++++++++-------- .../repo/repo-summary/repo-empty-view.tsx | 9 ++----- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-commits.tsx b/apps/gitness/src/pages-v2/repo/repo-commits.tsx index d672c7f9e4..71cd5640a2 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commits.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commits.tsx @@ -78,6 +78,7 @@ export default function RepoCommitsPage() { className="min-w-[120px]" /> )} + toFiles={() => routes.toRepoFiles({ spaceId, repoId, '*': gitRefName })} /> ) } diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index e3e504622f..7e5c8912c7 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -258,7 +258,7 @@ "pushRepository": { "0": "You might need to", "1": "create an API token", - "2": "In order to pull from or push into this repository" + "2": "in order to pull from or push into this repository" } }, "initialCommit": "Then push some content into it", @@ -765,7 +765,7 @@ "commitDetailsDiffDeletions": "deletions", "commitDetailsTitle": "Commit", "browseFiles": "Browse Files", - "createNewCommit": "Create New Commit", + "createCommit": "Create commit", "commitDetailsAuthored": "authored", "verified": "Verified" }, diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index b6766703fb..cc1711931c 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -243,7 +243,7 @@ "0": "We recommend every repository include a", "1": "README, LICENSE, and .gitignrore." }, - "createFile": "Create file" + "createFile": "Créer un fichier" }, "cloneInstructions": { "title": "Please generate git credentials if it’s your first time cloning the repository", @@ -258,7 +258,7 @@ "pushRepository": { "0": "You might need to", "1": "create an API token", - "2": "In order to pull from or push into this repository" + "2": "pour pouvoir tirer ou pousser dans ce dépôt" } }, "initialCommit": "Then push some content into it", @@ -752,7 +752,7 @@ "commitDetailsDiffDeletions": "suppressions", "commitDetailsTitle": "Commit", "browseFiles": "Parcourir les fichiers", - "createNewCommit": "Créer un nouveau commit", + "createCommit": "Créer un commit", "commitDetailsAuthored": "créé par", "verified": "Vérifié" }, diff --git a/packages/ui/src/views/repo/components/branch-banner/branch-compare-banner.tsx b/packages/ui/src/views/repo/components/branch-banner/branch-compare-banner.tsx index a9f57e2b29..bcab18090a 100644 --- a/packages/ui/src/views/repo/components/branch-banner/branch-compare-banner.tsx +++ b/packages/ui/src/views/repo/components/branch-banner/branch-compare-banner.tsx @@ -39,7 +39,9 @@ const BranchCompareBanner: FC<BranchCompareBannerProps> = ({ <IconV2 name="git-branch" size="sm" className="text-icons-success" /> <Text variant="body-strong" color="foreground-1"> <Link to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/summary/refs/heads/${branch.name}`}> - {branch.name} + <Text lineClamp={1} variant="body-single-line-strong" className="max-w-96"> + {branch.name} + </Text> </Link> </Text> <Text variant="body-single-line-normal" color="foreground-2"> diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index a86b891d89..fac0c96cc9 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -4,16 +4,20 @@ import { IconV2, Layout, NoData, Pagination, Skeleton, Text } from '@/components import { useTranslation } from '@/context' import { CommitsList, SandboxLayout, TypesCommit } from '@/views' -export interface RepoCommitsViewProps { +interface RoutingProps { + toCommitDetails?: ({ sha }: { sha: string }) => string + toPullRequest?: ({ pullRequestId }: { pullRequestId: number }) => string + toFiles?: () => string + toCode?: ({ sha }: { sha: string }) => string +} + +export interface RepoCommitsViewProps extends Partial<RoutingProps> { isFetchingCommits: boolean commitsList?: TypesCommit[] | null xNextPage: number xPrevPage: number page: number setPage: (page: number) => void - toCommitDetails?: ({ sha }: { sha: string }) => string - toPullRequest?: ({ pullRequestId }: { pullRequestId: number }) => string - toCode?: ({ sha }: { sha: string }) => string renderProp: () => JSX.Element | null } @@ -27,7 +31,8 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ toCommitDetails, toCode, renderProp: BranchSelectorContainer, - toPullRequest + toPullRequest, + toFiles }) => { const { t } = useTranslation() @@ -96,12 +101,11 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ } : // TODO: add onClick for Creating new commit { - label: ( - <> - <IconV2 name="plus" /> - {t('views:commits.createNewCommit', 'Create New Commit')} - </> - ) + label: t('views:commits.createNewCommit', 'Create commit'), + /** + * To make the first commit, redirect to the files page so a new file can be created. + */ + to: toFiles?.() || '' } } /> diff --git a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx index 64e1fbcc9a..9ca6ec4f38 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, IconV2, Layout, MarkdownViewer, NoData, Text } from '@/components' +import { Alert, Button, Layout, MarkdownViewer, NoData, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -83,12 +83,7 @@ ${sshUrl} t('views:repos.emptyRepoPage.noData.description.1', 'README, LICENSE, and .gitignrore') ]} primaryButton={{ - label: ( - <> - <IconV2 name="plus" /> - {t('views:repos.emptyRepoPage.noData.createFile', 'Create File')} - </> - ), + label: t('views:repos.emptyRepoPage.noData.createFile', 'Create file'), to: `${projName ? `/${projName}` : ''}/repos/${repoName}/files/new/${gitRef}/~/` }} className="py-cn-3xl" From 3d27f2fef80507e4b554c27220ff4966064a80d5 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 8 Aug 2025 17:21:46 +0000 Subject: [PATCH 045/180] change Add User to Create User (#10165) * 18ebd9 change Add User to Create User * 4cb0d2 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 5eca58 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b1eb1b Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b42931 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 55c544 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 3d07a1 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * d70666 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 50f0dc Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Com --- packages/ui/locales/en/views.json | 2 +- packages/ui/locales/fr/views.json | 2 +- .../user-management/components/empty-state/empty-state.tsx | 2 +- .../components/page-components/actions/actions.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 7e5c8912c7..b863013fbd 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -861,7 +861,7 @@ "confirm": "Confirm" }, "close": "Close", - "newUserButton": "Add User", + "newUserButton": "Create User", "searchPlaceholder": "Search", "usersHeader": "Users", "tabs": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index cc1711931c..6b43477606 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -848,7 +848,7 @@ "confirm": "Confirmer" }, "close": "Fermer", - "newUserButton": "Ajouter utilisateur", + "newUserButton": "Créer utilisateur", "searchPlaceholder": "Rechercher", "usersHeader": "Utilisateurs", "tabs": { diff --git a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx index b123fa410a..a44cc72719 100644 --- a/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx +++ b/packages/ui/src/views/user-management/components/empty-state/empty-state.tsx @@ -23,7 +23,7 @@ export const EmptyState = () => { label: ( <> <IconV2 name="plus" /> - {t('views:userManagement.newUserButton', 'Add User')} + {t('views:userManagement.newUserButton', 'Create User')} </> ), onClick: () => handleDialogOpen(null, DialogLabels.CREATE_USER) diff --git a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx index a80d9b2694..b3556aa609 100644 --- a/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx +++ b/packages/ui/src/views/user-management/components/page-components/actions/actions.tsx @@ -24,7 +24,7 @@ export const Actions = () => { <ListActions.Right> <Button onClick={() => handleDialogOpen(null, DialogLabels.CREATE_USER)}> <IconV2 name="plus" /> - {t('views:userManagement.newUserButton', 'Add User')} + {t('views:userManagement.newUserButton', 'Create User')} </Button> </ListActions.Right> </ListActions.Root> From 2dca3a8d9d566e371b69c988b1f801f774390878 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Fri, 8 Aug 2025 20:12:56 +0000 Subject: [PATCH 046/180] chore: update to title case from 'outdated' to 'Outdated' (#10168) * e8cf4b chore: update title case from 'outdated' to 'Outdated' --- .../repo/pull-request/components/pull-request-diff-viewer.tsx | 2 +- .../components/conversation/regular-and-code-comment.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index 912bce3bd0..8535f887da 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -498,7 +498,7 @@ const PullRequestDiffViewer = ({ {parent?.payload?.payload?.code_comment?.outdated && ( <> <Separator orientation="vertical" className="h-3.5" /> - <Tag key={'outdated'} value="OUTDATED" theme="orange" /> + <Tag key={'outdated'} value="Outdated" theme="orange" /> </> )} </Layout.Horizontal> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx index e12dbbd2c3..95a00f4db3 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx @@ -319,7 +319,7 @@ const PullRequestRegularAndCodeCommentInternal: FC<PullRequestRegularAndCodeComm {payload?.code_comment?.outdated && ( <> <Separator orientation="vertical" className="h-3.5 mx-1" /> - <Tag key={'outdated'} value="OUTDATED" theme="orange" /> + <Tag key={'outdated'} value="Outdated" theme="orange" /> </> )} </Layout.Horizontal> From 160c28de37590e539fd6e6683db576eeb4ba6849 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 8 Aug 2025 22:13:09 +0000 Subject: [PATCH 047/180] unhide comment box grab handle so it can be resized (#10167) * f6a5d2 unhide comment box grab handle so it can be resized * 73ac01 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 4cb0d2 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 5eca58 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b1eb1b Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * b42931 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 55c544 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * 3d07a1 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * d70666 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary * f6d180 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PS --- .../components/conversation/pull-request-comment-box.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 3190948dde..f564d3deb0 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -605,7 +605,7 @@ export const PullRequestCommentBox = ({ <Layout.Flex align="center" - className="bg-cn-background-1 absolute bottom-[1px] left-[1px] w-[calc(100%-2px)] rounded" + className="bg-cn-background-1 absolute bottom-[1px] left-[1px] w-[calc(100%-20px)] rounded" > {toolbar.map((item, index) => { const isFirst = index === 0 From 0e366af456431b7400ca6dba8d864cee45faef22 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Sun, 10 Aug 2025 16:37:06 -0700 Subject: [PATCH 048/180] fix: make required badges consistent --- .../conversation/sections/pull-request-changes-section.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx index e6ccec0e2f..d4c0dd8b8d 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx @@ -125,7 +125,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ </span> </div> )} - <StatusBadge variant="secondary">Required</StatusBadge> + <StatusBadge variant="outline">Required</StatusBadge> </div> )} @@ -142,7 +142,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ /> <span className="text-2 text-cn-foreground-1">{`${changeReqReviewer} requested changes to the pull request`}</span> </div> - {reqNoChangeReq && <StatusBadge variant="secondary">Required</StatusBadge>} + {reqNoChangeReq && <StatusBadge variant="outline">Required</StatusBadge>} </div> )} From 7eeaee773acd2f0432735ffbc68962f197773c43 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Mon, 11 Aug 2025 00:19:16 +0000 Subject: [PATCH 049/180] Delete file navigation to default branch fix (#10171) * afe598 Fixed prettier check * 186307 fixed typecheck * 5ea015 File delete navigation fix --- .../src/components-v2/file-content-viewer.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index 14f43dc1a7..ae25849828 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -26,14 +26,7 @@ import { useGitRef } from '../hooks/useGitRef' import { useRepoBranchesStore } from '../pages-v2/repo/stores/repo-branches-store' import { PathParams } from '../RouteDefinitions' import { PageResponseHeader } from '../types' -import { - decodeGitContent, - FILE_SEPERATOR, - filenameToLanguage, - formatBytes, - GitCommitAction, - normalizeGitRef -} from '../utils/git-utils' +import { decodeGitContent, filenameToLanguage, formatBytes, GitCommitAction, normalizeGitRef } from '../utils/git-utils' import GitBlame from './GitBlame' const getDefaultView = (language?: string): ViewTypeValue => { @@ -55,7 +48,6 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp const fileContent = decodeGitContent(repoContent?.content?.data) const repoRef = useGetRepoRef() const { fullGitRef, fullResourcePath } = useCodePathDetails() - const parentPath = fullResourcePath?.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR) const downloadFile = useDownloadRawFile() const navigate = useNavigate() const apiPath = useAPIPath() @@ -148,7 +140,8 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp resourcePath={fullResourcePath || ''} onSuccess={(_commitInfo, isNewBranch, newBranchName) => { if (!isNewBranch) { - navigate(`${routes.toRepoFiles({ spaceId, repoId })}${parentPath ? `/~/${parentPath}` : ''}`) + // Navigate to files view in the same branch after deletion + navigate(`${routes.toRepoFiles({ spaceId, repoId })}/${fullGitRef}`) } else { navigate( routes.toPullRequestCompare({ From 9567e25a6b86b8a0bb5131082a1426cdbaa526ff Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Mon, 11 Aug 2025 13:18:32 +0300 Subject: [PATCH 050/180] Fix dropdown checkbox icon (#2062) --- packages/ui/locales/en/views.json | 2 ++ packages/ui/locales/fr/views.json | 2 ++ packages/ui/tailwind-utils-config/components/checkbox.ts | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index b863013fbd..a731fc5c2c 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -526,6 +526,7 @@ "delete": "Delete Label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", + "newLabel": "Create Label", "createLabel": "Create Label", "create": "Create Labels" }, @@ -765,6 +766,7 @@ "commitDetailsDiffDeletions": "deletions", "commitDetailsTitle": "Commit", "browseFiles": "Browse Files", + "createNewCommit": "Create commit", "createCommit": "Create commit", "commitDetailsAuthored": "authored", "verified": "Verified" diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 6b43477606..c86b77d16d 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -518,6 +518,7 @@ "delete": "Delete label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", + "newLabel": "Create Label", "createLabel": "Create label", "create": "Create labels" }, @@ -752,6 +753,7 @@ "commitDetailsDiffDeletions": "suppressions", "commitDetailsTitle": "Commit", "browseFiles": "Parcourir les fichiers", + "createNewCommit": "Create commit", "createCommit": "Créer un commit", "commitDetailsAuthored": "créé par", "verified": "Vérifié" diff --git a/packages/ui/tailwind-utils-config/components/checkbox.ts b/packages/ui/tailwind-utils-config/components/checkbox.ts index 458f2b9621..5b13cf8233 100644 --- a/packages/ui/tailwind-utils-config/components/checkbox.ts +++ b/packages/ui/tailwind-utils-config/components/checkbox.ts @@ -117,6 +117,11 @@ export default { color: `var(--cn-set-red-solid-text)` }, + '.cn-checkbox-icon': { + width: 'var(--cn-icon-size-2xs)', + height: 'var(--cn-icon-size-2xs)' + }, + '.cn-checkbox-label': { font: 'var(--cn-body-strong) !important', color: 'var(--cn-text-1) !important', From eacb51139c18c90703452992e4df9b10f32178f0 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Mon, 11 Aug 2025 13:40:22 +0300 Subject: [PATCH 051/180] Fix checkbox icon size (#2063) --- packages/ui/src/components/checkbox.tsx | 4 ++-- packages/ui/src/components/dropdown-menu.tsx | 4 ++-- packages/ui/tailwind-utils-config/components/checkbox.ts | 5 ----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/checkbox.tsx b/packages/ui/src/components/checkbox.tsx index 42368c6f5f..18f5f9fbf5 100644 --- a/packages/ui/src/components/checkbox.tsx +++ b/packages/ui/src/components/checkbox.tsx @@ -37,9 +37,9 @@ const Checkbox = forwardRef<ElementRef<typeof CheckboxPrimitive.Root>, Omit<Chec <CheckboxPrimitive.Root id={checkboxId} ref={ref} className={checkboxVariants({ error })} {...props}> <CheckboxPrimitive.Indicator className="cn-checkbox-indicator"> {props.checked === 'indeterminate' ? ( - <IconV2 name="minus" className="cn-icon cn-icon-2xs" skipSize /> + <IconV2 name="minus" className="cn-icon" size="2xs" /> ) : ( - <IconV2 name="check" className="cn-icon cn-icon-2xs" skipSize /> + <IconV2 name="check" className="cn-icon" size="2xs" /> )} </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index b7b8de04a3..58a7104f77 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -227,9 +227,9 @@ const DropdownMenuCheckboxItem = forwardRef< {checked && ( <div className="cn-checkbox-indicator" {...{ 'data-state': checkedDataState }}> {checked === 'indeterminate' ? ( - <IconV2 name="minus" className="cn-checkbox-icon" skipSize /> + <IconV2 name="minus" className="cn-checkbox-icon" size="2xs" /> ) : ( - <IconV2 name="check" className="cn-checkbox-icon" skipSize /> + <IconV2 name="check" className="cn-checkbox-icon" size="2xs" /> )} </div> )} diff --git a/packages/ui/tailwind-utils-config/components/checkbox.ts b/packages/ui/tailwind-utils-config/components/checkbox.ts index 5b13cf8233..458f2b9621 100644 --- a/packages/ui/tailwind-utils-config/components/checkbox.ts +++ b/packages/ui/tailwind-utils-config/components/checkbox.ts @@ -117,11 +117,6 @@ export default { color: `var(--cn-set-red-solid-text)` }, - '.cn-checkbox-icon': { - width: 'var(--cn-icon-size-2xs)', - height: 'var(--cn-icon-size-2xs)' - }, - '.cn-checkbox-label': { font: 'var(--cn-body-strong) !important', color: 'var(--cn-text-1) !important', From 9917b25963ba839cf01a1068d7bcd87312025608 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:13:18 +0400 Subject: [PATCH 052/180] fix multiple issues (#2050) --- .../components-v2/breadcrumbs/breadcrumbs.tsx | 11 +--- .../src/components-v2/project-dropdown.tsx | 2 +- .../components-v2/standalone/app-shell.tsx | 2 +- packages/ui/src/components/topbar.tsx | 2 +- .../ui/src/styles/shared-style-variables.css | 64 ------------------- .../repo/components/CommitTitleWithPRLink.tsx | 23 +++---- .../views/repo/components/commits-list.tsx | 10 +-- .../repo-commit-details-view.tsx | 3 +- packages/ui/tailwind-design-system.ts | 22 ------- 9 files changed, 22 insertions(+), 117 deletions(-) diff --git a/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx b/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx index 6cf145d347..e7075767a7 100644 --- a/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx +++ b/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx @@ -2,7 +2,6 @@ import { UIMatch } from 'react-router-dom' import { Breadcrumb, IconV2, Separator, Sidebar, Topbar } from '@harnessio/ui/components' import { useRouterContext } from '@harnessio/ui/context' -import { cn } from '@harnessio/ui/utils' import { CustomHandle } from '../../framework/routing/types' @@ -24,16 +23,12 @@ export const Breadcrumbs = ({ if (!breadcrumbs.length) return null return ( - <Topbar.Root - className={cn('bg-cn-background-1 border-b border-cn-borders-2 sticky top-0 left-0 z-20', { - 'pl-1.5': !isMobile - })} - > + <Topbar.Root className="bg-cn-background-1 sticky left-0 top-0 z-20 border-b"> <Topbar.Left> {withMobileSidebarToggle && isMobile && ( <> - <Sidebar.Trigger className="-ml-1 text-topbar-foreground-2 hover:bg-topbar-background-1 hover:text-topbar-foreground-1" /> - <Separator orientation="vertical" className="ml-1 mr-2 h-4 bg-cn-background-0" /> + <Sidebar.Trigger className="-ml-1" /> + <Separator orientation="vertical" className="bg-cn-background-0 ml-1 mr-2 h-4" /> </> )} <Breadcrumb.Root className={breadcrumbClassName} size="sm" separator={<IconV2 name="nav-arrow-right" />}> diff --git a/apps/gitness/src/components-v2/project-dropdown.tsx b/apps/gitness/src/components-v2/project-dropdown.tsx index ea063fba94..97c158a9db 100644 --- a/apps/gitness/src/components-v2/project-dropdown.tsx +++ b/apps/gitness/src/components-v2/project-dropdown.tsx @@ -16,7 +16,7 @@ function ProjectDropdown(): JSX.Element { <DropdownMenu.Root> <DropdownMenu.Trigger className="flex items-center gap-x-1.5" disabled={!spaces.length}> {spaceId ?? 'Select project'} - <IconV2 className="chevron-down text-topbar-icon-1" name="nav-solid-arrow-down" size="2xs" /> + <IconV2 className="text-cn-foreground-2" name="nav-arrow-down" size="xs" /> </DropdownMenu.Trigger> <DropdownMenu.Content className="w-[300px]"> {spaces.map(({ identifier }) => ( diff --git a/apps/gitness/src/components-v2/standalone/app-shell.tsx b/apps/gitness/src/components-v2/standalone/app-shell.tsx index 94bd3e4830..3ca7941151 100644 --- a/apps/gitness/src/components-v2/standalone/app-shell.tsx +++ b/apps/gitness/src/components-v2/standalone/app-shell.tsx @@ -70,7 +70,7 @@ export const AppShell: FC = () => { <> <AppSideBar> <Breadcrumbs breadcrumbs={breadcrumbs} isMobile={isMobile} withMobileSidebarToggle /> - <MainContentLayout useSidebar={useSidebar} withBreadcrumbs={breadcrumbs.length > 0} enableInset> + <MainContentLayout useSidebar={useSidebar} withBreadcrumbs={breadcrumbs.length > 0}> <Outlet /> </MainContentLayout> </AppSideBar> diff --git a/packages/ui/src/components/topbar.tsx b/packages/ui/src/components/topbar.tsx index d56eb632f8..83d7ebf8d3 100644 --- a/packages/ui/src/components/topbar.tsx +++ b/packages/ui/src/components/topbar.tsx @@ -15,7 +15,7 @@ const Topbar = { return ( <div className={cn( - `grid w-full grid-cols-[1fr_auto] font-regular h-[var(--cn-breadcrumbs-height)] items-center gap-6 px-5 text-sm`, + `grid w-full grid-cols-[1fr_auto] font-regular h-[var(--cn-breadcrumbs-height)] items-center gap-6 px-cn-2xl text-sm`, { 'grid-cols-[auto_1fr_auto]': hasCenter }, className )} diff --git a/packages/ui/src/styles/shared-style-variables.css b/packages/ui/src/styles/shared-style-variables.css index 63d670631e..0d0ed94f43 100644 --- a/packages/ui/src/styles/shared-style-variables.css +++ b/packages/ui/src/styles/shared-style-variables.css @@ -564,14 +564,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: var(--canary-pure-white); - --canary-topbar-foreground-01: var(--canary-base-chrome-1000); - --canary-topbar-foreground-02: 240 5% 15%; - --canary-topbar-foreground-03: 240 4% 40%; - --canary-topbar-foreground-04: 240 5% 25%; - --canary-topbar-icon-01: 240 4% 40%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: var(--canary-base-chrome-800); @@ -846,14 +838,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: var(--canary-pure-white); - --canary-topbar-foreground-01: var(--canary-base-chrome-1000); - --canary-topbar-foreground-02: 240 5% 15%; - --canary-topbar-foreground-03: 240 4% 40%; - --canary-topbar-foreground-04: 240 5% 25%; - --canary-topbar-icon-01: 240 4% 40%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: 240 5% 25%; @@ -1127,14 +1111,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: var(--canary-pure-white); - --canary-topbar-foreground-01: var(--canary-base-chrome-1000); - --canary-topbar-foreground-02: 240 5% 15%; - --canary-topbar-foreground-03: 240 4% 40%; - --canary-topbar-foreground-04: 240 5% 25%; - --canary-topbar-icon-01: 240 4% 40%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: 240 5% 25%; @@ -1408,14 +1384,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: var(--canary-pure-white); - --canary-topbar-foreground-01: var(--canary-base-chrome-1000); - --canary-topbar-foreground-02: 240 5% 15%; - --canary-topbar-foreground-03: 240 4% 40%; - --canary-topbar-foreground-04: 240 5% 25%; - --canary-topbar-icon-01: 240 4% 40%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: 240 5% 25%; @@ -1709,14 +1677,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: 240 6% 6%; - --canary-topbar-foreground-01: 240 6% 90%; - --canary-topbar-foreground-02: var(--canary-grey-80); - --canary-topbar-foreground-03: 240 6% 60%; - --canary-topbar-foreground-04: var(--canary-grey-94); - --canary-topbar-icon-01: 240 6% 60%; - /* Tags */ /* --canary-gray */ --canary-tag-foreground-gray-01: var(--canary-grey-70); @@ -1972,14 +1932,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: 240 6% 6%; - --canary-topbar-foreground-01: 240 6% 90%; - --canary-topbar-foreground-02: var(--canary-grey-80); - --canary-topbar-foreground-03: 240 6% 60%; - --canary-topbar-foreground-04: var(--canary-grey-94); - --canary-topbar-icon-01: 240 6% 60%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: var(--canary-base-chrome-200); @@ -2254,14 +2206,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: 240 6% 6%; - --canary-topbar-foreground-01: 240 6% 90%; - --canary-topbar-foreground-02: var(--canary-grey-80); - --canary-topbar-foreground-03: 240 6% 60%; - --canary-topbar-foreground-04: var(--canary-grey-94); - --canary-topbar-icon-01: 240 6% 60%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: var(--canary-base-chrome-200); @@ -2535,14 +2479,6 @@ --canary-toast-icon-danger-default: var(--canary-pure-white); --canary-toast-icon-danger-hover: var(--canary-pure-white) / 0.65; - /* Topbar */ - --canary-topbar-background-01: 240 6% 6%; - --canary-topbar-foreground-01: 240 6% 90%; - --canary-topbar-foreground-02: var(--canary-grey-80); - --canary-topbar-foreground-03: 240 6% 60%; - --canary-topbar-foreground-04: var(--canary-grey-94); - --canary-topbar-icon-01: 240 6% 60%; - /* Tags */ /* --gray */ --canary-tag-foreground-gray-01: var(--canary-base-chrome-200); diff --git a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx index 7f13880813..00f2753e8b 100644 --- a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx +++ b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx @@ -4,12 +4,11 @@ interface CommitTitleWithPRLinkProps { commitMessage?: string title?: string toPullRequest?: ({ pullRequestId }: { pullRequestId: number }) => string - textVariant?: TextProps['variant'] - textClassName?: string + textProps?: Omit<TextProps, 'ref'> } export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { - const { textVariant, commitMessage, textClassName, title, toPullRequest } = props + const { textProps, commitMessage, title, toPullRequest } = props if (!commitMessage) return null @@ -23,7 +22,7 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { const pieces = commitMessage.split(match[0]) const piecesEls = pieces.map(piece => { return ( - <Text variant={textVariant} className={textClassName} truncate title={title} key={piece}> + <Text {...textProps} truncate title={title} key={piece}> {piece} </Text> ) @@ -31,14 +30,12 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { piecesEls.splice( 1, 0, - <Text variant={textVariant} className={textClassName}> - <Layout.Flex> -  ( - <Link title={title} to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`}> - #{pullRequestId} - </Link> - )  - </Layout.Flex> + <Text {...textProps}> +  ( + <Link title={title} to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`} className="[font:inherit]"> + #{pullRequestId} + </Link> + )  </Text> ) @@ -47,7 +44,7 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { } return ( - <Text variant={textVariant} className={textClassName} truncate title={title}> + <Text {...textProps} truncate title={title}> {commitMessage} </Text> ) diff --git a/packages/ui/src/views/repo/components/commits-list.tsx b/packages/ui/src/views/repo/components/commits-list.tsx index 5fc7345736..0a590389b3 100644 --- a/packages/ui/src/views/repo/components/commits-list.tsx +++ b/packages/ui/src/views/repo/components/commits-list.tsx @@ -47,7 +47,7 @@ export const CommitsList: FC<CommitProps> = ({ data, toCommitDetails, toPullRequ return ( <div className={className}> {entries.map(([date, commitData]) => ( - <NodeGroup.Root className="grid-cols-[9px_1fr] gap-4 pb-cn-xl last:pb-0" key={date}> + <NodeGroup.Root className="pb-cn-xl grid-cols-[9px_1fr] gap-4 last:pb-0" key={date}> <NodeGroup.Icon simpleNodeIcon /> <NodeGroup.Title>{date && <Text variant="body-single-line-normal">Commits on {date}</Text>}</NodeGroup.Title> <NodeGroup.Content className="overflow-hidden"> @@ -60,21 +60,21 @@ export const CommitsList: FC<CommitProps> = ({ data, toCommitDetails, toPullRequ return ( <StackedList.Item - className="flex items-start p-cn-sm pl-cn-xs" + className="p-cn-sm pl-cn-xs flex items-start" key={commit?.sha || idx} isLast={commitData.length - 1 === idx} asChild > <Link className="grow overflow-hidden" to={`${toCommitDetails?.({ sha: commit?.sha || '' })}`}> - <Layout.Grid flow="column" className="w-full pl-cn-md" columns="1fr auto" gap="md"> + <Layout.Grid flow="column" className="pl-cn-md w-full" columns="1fr auto" gap="md"> <Layout.Vertical gap="2xs" className="truncate"> <CommitTitleWithPRLink toPullRequest={toPullRequest} commitMessage={commit.title} title={commit.message || commit.title} - textVariant={'heading-base'} + textProps={{ variant: 'heading-base' }} /> - <div className="flex items-center gap-cn-2xs"> + <div className="gap-cn-2xs flex items-center"> {authorName && <Avatar name={authorName} src={avatarUrl} size="md" rounded />} <Text variant="body-single-line-strong">{authorName || ''}</Text> <Text variant="body-single-line-normal" color="foreground-3"> diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index 56a4475483..026d907ae5 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -50,8 +50,7 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ toPullRequest={toPullRequest} commitMessage={commitData?.title} title={commitData?.title} - textClassName={'text-14 font-medium leading-snug'} - // textVariant={'body-normal'} + textProps={{ variant: 'body-code' }} /> <Button variant="outline" asChild> <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> diff --git a/packages/ui/tailwind-design-system.ts b/packages/ui/tailwind-design-system.ts index 170bb21574..8f00a193b8 100644 --- a/packages/ui/tailwind-design-system.ts +++ b/packages/ui/tailwind-design-system.ts @@ -276,20 +276,6 @@ export default { lime: 'var(--canary-label-background-lime-01)' } }, - topbar: { - background: { - 1: 'hsl(var(--canary-topbar-background-01))' - }, - foreground: { - 1: 'hsl(var(--canary-topbar-foreground-01))', - 2: 'hsl(var(--canary-topbar-foreground-02))', - 3: 'hsl(var(--canary-topbar-foreground-03))', - 4: 'hsl(var(--canary-topbar-foreground-04))' - }, - icon: { - 1: 'hsl(var(--canary-topbar-icon-01))' - } - }, graph: { background: { 1: 'hsl(var(--canary-graph-background-1))', @@ -493,10 +479,6 @@ export default { { pattern: /^bg-label-background-/ }, { pattern: /^bg-label-foreground-/ }, // this is essential for the color select in the LabelFormColorAndNameGroup component { pattern: /^text-label-foreground-/ }, - // topbar classes - { pattern: /^bg-topbar-background-/ }, - { pattern: /^text-topbar-foreground-/ }, - { pattern: /^text-topbar-icon-/ }, // Hover classes { pattern: /^hover:bg-graph-/ }, @@ -510,10 +492,6 @@ export default { // label classes { pattern: /^hover:bg-label-background-/ }, { pattern: /^hover:text-label-foreground-/ }, - // topbar classes - { pattern: /^hover:bg-topbar-background-/ }, - { pattern: /^hover:text-topbar-foreground-/ }, - { pattern: /^hover:text-topbar-icon-/ }, 'stroke-borders-2', { pattern: /rounded-./ }, { pattern: /border-./ }, From bd09b4a364c23f603d5cf0b7b92de978d815ad26 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Mon, 11 Aug 2025 14:15:27 +0000 Subject: [PATCH 053/180] fix: open paths lost in MFE, file input rebuildPaths (#10174) * 832931 fix: open paths lost in MFE, file input rebuildPaths --- apps/gitness/package.json | 2 +- .../gitness/src/components-v2/file-editor.tsx | 22 +++---- .../context/ExplorerPathsContext.tsx | 47 +++++++++++--- .../ui/src/components/path-breadcrumbs.tsx | 27 +++++--- pnpm-lock.yaml | 64 +------------------ 5 files changed, 69 insertions(+), 93 deletions(-) diff --git a/apps/gitness/package.json b/apps/gitness/package.json index 666f23bd50..d342a31c3a 100644 --- a/apps/gitness/package.json +++ b/apps/gitness/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@eslint/js": "^9.9.0", - "@git-diff-view/react": "^0.0.16", + "@git-diff-view/react": "^0.0.30", "@swc/core": "^1.12.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^12.1.5", diff --git a/apps/gitness/src/components-v2/file-editor.tsx b/apps/gitness/src/components-v2/file-editor.tsx index 9f06cd33ba..bfac1ce421 100644 --- a/apps/gitness/src/components-v2/file-editor.tsx +++ b/apps/gitness/src/components-v2/file-editor.tsx @@ -62,17 +62,18 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) const pathToSplit = useMemo(() => { if (isNew) { - // When in new file mode, use fullResourcePath to ensure we maintain the directory structure - if (fullResourcePath && parentPath !== fullResourcePath) { - // Update parentPath to match fullResourcePath when in new file mode - setParentPath(fullResourcePath) - } return fullResourcePath || parentPath } else if (parentPath?.length && fileName.length) { return [parentPath, fileName].join(FILE_SEPERATOR) } return parentPath?.length ? parentPath : fileName - }, [isNew, parentPath, fileName, fullResourcePath, setParentPath]) + }, [isNew, parentPath, fileName, fullResourcePath]) + + useEffect(() => { + if (isNew && fullResourcePath && parentPath !== fullResourcePath) { + setParentPath(fullResourcePath) + } + }, [isNew, fullResourcePath, parentPath]) const pathParts = useMemo( () => [ @@ -111,11 +112,10 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) } const rebuildPaths = useCallback(() => { - const _tokens = fileName?.split(FILE_SEPERATOR).filter(part => !!part.trim()) - const _fileName = ((_tokens?.pop() as string) || '').trim() - const _parentPath = parentPath - ?.split(FILE_SEPERATOR) - .concat(_tokens || '') + const _tokens = fileName?.split(FILE_SEPERATOR).filter(part => !!part.trim()) || [] + const _fileName = (_tokens.pop() || '').trim() + const _parentPath = (parentPath?.split(FILE_SEPERATOR) || []) + .concat(_tokens) .map(p => p.trim()) .filter(part => !!part.trim()) .join(FILE_SEPERATOR) diff --git a/apps/gitness/src/framework/context/ExplorerPathsContext.tsx b/apps/gitness/src/framework/context/ExplorerPathsContext.tsx index 9a89665af7..34870eaa02 100644 --- a/apps/gitness/src/framework/context/ExplorerPathsContext.tsx +++ b/apps/gitness/src/framework/context/ExplorerPathsContext.tsx @@ -8,9 +8,7 @@ interface ExplorerPathsContextType { openFolderPaths: string[] setOpenFolderPaths: React.Dispatch<React.SetStateAction<string[]>> } - const ExplorerPathsContext = createContext<ExplorerPathsContextType | undefined>(undefined) - export const useOpenFolderPaths = () => { const context = useContext(ExplorerPathsContext) if (!context) { @@ -18,27 +16,58 @@ export const useOpenFolderPaths = () => { } return context } - +export const STORAGE_KEY = '_explorer_paths' +// Helper functions for localStorage +const getStorageKey = (repoRef: string, gitRef: string | undefined) => + `${STORAGE_KEY}_${repoRef}_${gitRef || 'default'}` +const getPathsFromStorage = (key: string): string[] => { + try { + const stored = localStorage.getItem(key) + return stored ? JSON.parse(stored) : [] + } catch (error) { + console.error('Error reading from localStorage:', error) + return [] + } +} +const savePathsToStorage = (key: string, paths: string[]): void => { + try { + localStorage.setItem(key, JSON.stringify(paths)) + } catch (error) { + console.error('Error writing to localStorage:', error) + } +} interface ExplorerPathsProviderProps { children: React.ReactNode } - export const ExplorerPathsProvider: React.FC<ExplorerPathsProviderProps> = ({ children }) => { const repoRef = useGetRepoRef() const { gitRef } = useParams<PathParams>() - const [openFolderPaths, setOpenFolderPaths] = useState<string[]>([]) - + const storageKey = getStorageKey(repoRef, gitRef) + // Initialize from localStorage + const [openFolderPaths, setOpenFolderPathsState] = useState<string[]>(() => { + return getPathsFromStorage(storageKey) + }) const prevRepoRef = useRef(repoRef) const prevGitRef = useRef(gitRef) - + // Custom setter that updates both state and localStorage + const setOpenFolderPaths = (newPathsOrUpdater: React.SetStateAction<string[]>) => { + setOpenFolderPathsState(currentPaths => { + const newPaths = typeof newPathsOrUpdater === 'function' ? newPathsOrUpdater(currentPaths) : newPathsOrUpdater + // Save to localStorage + savePathsToStorage(storageKey, newPaths) + return newPaths + }) + } + // Reset paths when repo or git ref changes useEffect(() => { if (prevRepoRef.current !== repoRef || prevGitRef.current !== gitRef) { - setOpenFolderPaths([]) + const newKey = getStorageKey(repoRef, gitRef) + const savedPaths = getPathsFromStorage(newKey) + setOpenFolderPathsState(savedPaths) prevRepoRef.current = repoRef prevGitRef.current = gitRef } }, [repoRef, gitRef]) - return ( <ExplorerPathsContext.Provider value={{ openFolderPaths, setOpenFolderPaths }}> {children} diff --git a/packages/ui/src/components/path-breadcrumbs.tsx b/packages/ui/src/components/path-breadcrumbs.tsx index 094d8117ad..a134b406b5 100644 --- a/packages/ui/src/components/path-breadcrumbs.tsx +++ b/packages/ui/src/components/path-breadcrumbs.tsx @@ -34,10 +34,14 @@ const InputPathBreadcrumbItem = ({ changeFileName(event.currentTarget.value) }} onBlur={handleOnBlur} - onFocus={() => { + onFocus={({ target }) => { const value = (parentPath ? parentPath + '/' : '') + path changeFileName(value) setParentPath?.('') + setTimeout(() => { + target.setSelectionRange(value.length, value.length) + target.scrollLeft = Number.MAX_SAFE_INTEGER + }, 0) }} autoFocus={!!isNew} /> @@ -83,15 +87,18 @@ export const PathBreadcrumbs = ({ items, isEdit, isNew, ...props }: PathBreadcru } return ( - <InputPathBreadcrumbItem - path={fileName} - changeFileName={changeFileName} - gitRefName={gitRefName} - handleOnBlur={handleOnBlur} - isNew={isNew} - parentPath={parentPath} - setParentPath={setParentPath} - /> + <> + <Breadcrumb.Separator /> + <InputPathBreadcrumbItem + path={fileName} + changeFileName={changeFileName} + gitRefName={gitRefName} + handleOnBlur={handleOnBlur} + isNew={isNew} + parentPath={parentPath} + setParentPath={setParentPath} + /> + </> ) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58365d6926..a01085de83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,8 +242,8 @@ importers: specifier: ^9.9.0 version: 9.21.0 '@git-diff-view/react': - specifier: ^0.0.16 - version: 0.0.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + specifier: ^0.0.30 + version: 0.0.30(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@swc/core': specifier: ^1.12.1 version: 1.12.1 @@ -2399,24 +2399,12 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@git-diff-view/core@0.0.16': - resolution: {integrity: sha512-AmVDNZBGXYUSv15bBTryArR+pe4HWX3QziKp3RHHzHOswqcbVMYhp5o3hId8g8nbRmj3jXLKeL6JiSRaOQahaA==} - '@git-diff-view/core@0.0.30': resolution: {integrity: sha512-CyYi/y1q543aYeWQO9c+1awfRh47dPF7n4SNQQVl5I6U2NH4Ctg8eON6e2bIEd4dom/awZ1Hb8kTvaesBpfxJA==} - '@git-diff-view/lowlight@0.0.16': - resolution: {integrity: sha512-XL3rjMD7hvPCd97LDMhLw02qYzc1xGgUGmzjp1aJqnuh+QjO5opEbebXXp5rsmhinbSjYy1/kq3v5EB2YX6PhA==} - '@git-diff-view/lowlight@0.0.30': resolution: {integrity: sha512-TeWqLZLy3+gL4AjCs9kNFZs44Bt9MdLbXAqigDgEOnTwCWAU5Ll/iBlr84vL/QQlWF19pw9t7wYetonpo2TkaQ==} - '@git-diff-view/react@0.0.16': - resolution: {integrity: sha512-mBvPfNYDDY/D8ZOdfzZ6bR76uSJel1Ma3OAmmYzwzVDhj/KfMRAwUuE4KCFXVWMi5dR1ZC/apUnIJRw5pGc99g==} - peerDependencies: - react: 17.0.2 - react-dom: 17.0.2 - '@git-diff-view/react@0.0.30': resolution: {integrity: sha512-MtePI/ww+TTifdnFYxmz3ATlCWPg2uRQvmYj+FuhMUJBaUwCl4v74d9S7fSFk7gmHTjfGaNFP1th+CUdqlCOsA==} peerDependencies: @@ -4298,9 +4286,6 @@ packages: typescript: optional: true - '@vue/reactivity@3.5.13': - resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} - '@vue/reactivity@3.5.18': resolution: {integrity: sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==} @@ -7095,9 +7080,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsan@3.1.14: - resolution: {integrity: sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==} - jsdom@25.0.1: resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} engines: {node: '>=18'} @@ -8540,11 +8522,6 @@ packages: peerDependencies: react: 17.0.2 - reactivity-store@0.3.9: - resolution: {integrity: sha512-ADKscagTECPmc1zWkCbj6iuJgan3+DX8FcB3FwG4k00B+07kz+HWfp9hup5uyaElYnCrZsuvoClxzWV6AqP91A==} - peerDependencies: - react: 17.0.2 - read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -12068,12 +12045,6 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@git-diff-view/core@0.0.16': - dependencies: - '@git-diff-view/lowlight': 0.0.16 - highlight.js: 11.11.1 - lowlight: 3.3.0 - '@git-diff-view/core@0.0.30': dependencies: '@git-diff-view/lowlight': 0.0.30 @@ -12081,29 +12052,12 @@ snapshots: highlight.js: 11.11.1 lowlight: 3.3.0 - '@git-diff-view/lowlight@0.0.16': - dependencies: - '@types/hast': 3.0.4 - highlight.js: 11.11.1 - lowlight: 3.3.0 - '@git-diff-view/lowlight@0.0.30': dependencies: '@types/hast': 3.0.4 highlight.js: 11.11.1 lowlight: 3.3.0 - '@git-diff-view/react@0.0.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': - dependencies: - '@git-diff-view/core': 0.0.16 - '@types/hast': 3.0.4 - highlight.js: 11.11.1 - lowlight: 3.3.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - reactivity-store: 0.3.9(react@17.0.2) - use-sync-external-store: 1.4.0(react@17.0.2) - '@git-diff-view/react@0.0.30(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: '@git-diff-view/core': 0.0.30 @@ -14559,10 +14513,6 @@ snapshots: optionalDependencies: typescript: 5.8.2 - '@vue/reactivity@3.5.13': - dependencies: - '@vue/shared': 3.5.13 - '@vue/reactivity@3.5.18': dependencies: '@vue/shared': 3.5.18 @@ -18234,8 +18184,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsan@3.1.14: {} - jsdom@25.0.1: dependencies: cssstyle: 4.2.1 @@ -19963,14 +19911,6 @@ snapshots: react: 17.0.2 use-sync-external-store: 1.5.0(react@17.0.2) - reactivity-store@0.3.9(react@17.0.2): - dependencies: - '@vue/reactivity': 3.5.13 - '@vue/shared': 3.5.13 - jsan: 3.1.14 - react: 17.0.2 - use-sync-external-store: 1.4.0(react@17.0.2) - read-cache@1.0.0: dependencies: pify: 2.3.0 From 876dcdecdf51b9bdc22bd81cd9a4747380b35368 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Mon, 11 Aug 2025 14:35:20 +0000 Subject: [PATCH 054/180] Extended diff view improvement (#10175) * 4acf94 fix code selection * aff08b improve * 49d9ff extended diff view improvement --- .../extended-diff-view-style.css | 2 ++ .../extended-diff-view-utils.ts | 24 +++++++++++++++ .../extended-diff-view/extended-diff-view.tsx | 29 ++++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css index 263f33fcaf..8828d8399b 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css @@ -11,6 +11,7 @@ inset: 0; opacity: 0.1; background-color: var(--cn-yellow-300) !important; + pointer-events: none; } .ExtendedDiffView-RowCell-Selected.diff-line-new-num::before, @@ -33,6 +34,7 @@ inset: 0; opacity: 0.1; background-color: var(--cn-yellow-300) !important; + pointer-events: none; } .diff-add-widget-wrapper { diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-utils.ts b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-utils.ts index 9215955614..a212a4828e 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-utils.ts +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-utils.ts @@ -91,3 +91,27 @@ export function updateSelection( } }) } + +export function getNumberHolder(el: HTMLElement | null, inMouseDown = false): HTMLElement | null { + if (!el) return null + + let numberHolder: HTMLElement | null = null + + if (!inMouseDown || el.closest('.diff-add-widget-wrapper')) { + const newContentEL = el.closest('.diff-line-new-content') + const oldContentEL = el.closest('.diff-line-old-content') + + if (newContentEL) { + numberHolder = newContentEL.parentElement?.querySelector('.diff-line-new-num') ?? null + } + if (oldContentEL) { + numberHolder = oldContentEL.parentElement?.querySelector('.diff-line-old-num') ?? null + } + } + + if (!numberHolder) { + numberHolder = el.closest('.diff-line-new-num') || el.closest('.diff-line-old-num') + } + + return numberHolder +} diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx index 621be3d683..9b2e4765bd 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx @@ -5,7 +5,14 @@ import { DiffFile, DiffView, SplitSide } from '@git-diff-view/react' import './extended-diff-view-style.css' import { ExtendedDiffViewProps, LinesRange } from './extended-diff-view-types' -import { getLineFromEl, getPreselectState, getSide, orderRange, updateSelection } from './extended-diff-view-utils' +import { + getLineFromEl, + getNumberHolder, + getPreselectState, + getSide, + orderRange, + updateSelection +} from './extended-diff-view-utils' /** * ExtendedDiffView is a extended/patched version of DiffView. @@ -47,19 +54,18 @@ export const ExtendedDiffView = forwardRef( if (!container) return const handleMouseDown = (e: MouseEvent) => { - if (!(e.target instanceof HTMLElement)) return - if (!e.target.classList.contains('diff-line-new-num') && !e.target.classList.contains('diff-line-old-num')) - return + const numberHolder = getNumberHolder(e.target as HTMLElement, true) + if (!numberHolder) return - const line = getLineFromEl(e.target) - if (line == null) return + const line = getLineFromEl(numberHolder) + if (!line) return isSelectingRef.current = true selectedRangeRef.current = { start: line, end: line, - side: getSide(e.target) ?? 'new' + side: getSide(numberHolder) ?? 'new' } updateSelection(containerRef.current, selectedRangeRef.current, preselectedLinesRef.current) @@ -67,11 +73,12 @@ export const ExtendedDiffView = forwardRef( const handleMouseOver = (e: MouseEvent) => { if (!isSelectingRef.current) return - if (!(e.target instanceof HTMLElement)) return - if (!e.target.classList.contains('diff-line-new-num') && !e.target.classList.contains('diff-line-old-num')) - return - const line = getLineFromEl(e.target) + const numberHolder = getNumberHolder(e.target as HTMLElement) + if (!numberHolder) return + + const line = getLineFromEl(numberHolder) + if (!line) return if (line !== null && selectedRangeRef.current) { selectedRangeRef.current = { ...selectedRangeRef.current, end: line } From 831a2f24f3e0aa54551297d6d479fddfabd8efb4 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Mon, 11 Aug 2025 14:44:47 +0000 Subject: [PATCH 055/180] Blame editor v2 (#10163) * 5892f1 minor update * 7c30bd blame editor v2 --- apps/gitness/src/components-v2/GitBlame.tsx | 42 ++- .../src/components-v2/file-content-viewer.tsx | 10 +- packages/ui/src/utils/index.ts | 2 + .../src/components/BlameEditorV2.tsx | 275 ++++++++++++++++++ packages/yaml-editor/src/index.ts | 4 + packages/yaml-editor/src/types/blame.ts | 1 + 6 files changed, 325 insertions(+), 9 deletions(-) create mode 100644 packages/yaml-editor/src/components/BlameEditorV2.tsx diff --git a/apps/gitness/src/components-v2/GitBlame.tsx b/apps/gitness/src/components-v2/GitBlame.tsx index ce96200536..eb2fd5141e 100644 --- a/apps/gitness/src/components-v2/GitBlame.tsx +++ b/apps/gitness/src/components-v2/GitBlame.tsx @@ -3,10 +3,11 @@ import { useEffect, useState } from 'react' import { uniqWith } from 'lodash-es' import { useGetBlameQuery } from '@harnessio/code-service-client' -import { Layout } from '@harnessio/ui/components' -import { getInitials } from '@harnessio/ui/utils' +import { Avatar, Layout, Text } from '@harnessio/ui/components' +import { useRouterContext } from '@harnessio/ui/context' +import { formatDistanceToNow, getInitials } from '@harnessio/ui/utils' import { Contributors } from '@harnessio/ui/views' -import { BlameEditor, BlameEditorProps, ThemeDefinition } from '@harnessio/yaml-editor' +import { BlameEditorV2, BlameEditorV2Props, ThemeDefinition } from '@harnessio/yaml-editor' import { BlameItem } from '@harnessio/yaml-editor/dist/types/blame' import { useThemeStore } from '../framework/context/ThemeContext' @@ -18,11 +19,14 @@ interface GitBlameProps { themeConfig: { rootElementSelector?: string; defaultTheme?: string; themes?: ThemeDefinition[] } codeContent: string language: string - height?: BlameEditorProps['height'] + height?: BlameEditorV2Props['height'] + toCommitDetails: ({ sha }: { sha: string }) => string } -export default function GitBlame({ themeConfig, codeContent, language, height }: GitBlameProps) { +export default function GitBlame({ themeConfig, codeContent, language, height, toCommitDetails }: GitBlameProps) { const repoRef = useGetRepoRef() + const { navigate } = useRouterContext() + const { fullGitRef, fullResourcePath } = useCodePathDetails() const [blameBlocks, setBlameBlocks] = useState<BlameItem[]>([]) @@ -55,10 +59,32 @@ export default function GitBlame({ themeConfig, codeContent, language, height }: author: authorInfo || {} } + const { name, email } = commitInfo.author?.identity || { name: '', email: '' } + blameData.push({ fromLineNumber, toLineNumber, - commitInfo: commitInfo + commitInfo: commitInfo, + infoContent: ( + /* IMPORTANT: itemContent accepts only atomic component that are not depends on external state (e.g. context provider) */ + <div className="flex items-center gap-4 pl-4"> + <Text style={{ width: '125px' }} truncate> + {formatDistanceToNow(commitInfo.author?.when)} + </Text> + <Avatar name={name} rounded title={name + '\n' + email} /> + <Text + style={{ width: '250px' }} + className="cursor-pointer text-2 leading-snug hover:underline" + truncate + title={commitInfo.title} + onClick={() => { + navigate(toCommitDetails({ sha: commitInfo.sha })) + }} + > + {commitInfo.title} + </Text> + </div> + ) }) fromLineNumber = toLineNumber + 1 @@ -71,7 +97,7 @@ export default function GitBlame({ themeConfig, codeContent, language, height }: setBlameBlocks(blameData) setContributors(uniqWith(authors, (a, b) => a.email === b.email)) } - }, [gitBlame]) + }, [gitBlame, toCommitDetails, navigate]) const { theme } = useThemeStore() const monacoTheme = (theme ?? '').startsWith('dark') ? 'dark' : 'light' @@ -81,7 +107,7 @@ export default function GitBlame({ themeConfig, codeContent, language, height }: <div className="flex items-center border-x border-b px-cn-md py-cn-sm"> <Contributors contributors={contributors} /> </div> - <BlameEditor + <BlameEditorV2 code={codeContent} language={language} themeConfig={themeConfig} diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index ae25849828..6d350e3ad5 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -213,7 +213,15 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp </Tabs.Content> <Tabs.Content value="blame" className="grow"> - <GitBlame height="100%" themeConfig={themeConfig} codeContent={fileContent} language={language} /> + <GitBlame + height="100%" + themeConfig={themeConfig} + codeContent={fileContent} + language={language} + toCommitDetails={({ sha }: { sha: string }) => { + return routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha }) + }} + /> </Tabs.Content> <Tabs.Content value="history"> diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index 0dcda3df7e..c03e7c1763 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -5,3 +5,5 @@ export * from './typeUtils' export * from './mergeUtils' export * from './TimeUtils' export * from './getComponentDisplayName' + +export { formatDistanceToNow } from 'date-fns' diff --git a/packages/yaml-editor/src/components/BlameEditorV2.tsx b/packages/yaml-editor/src/components/BlameEditorV2.tsx new file mode 100644 index 0000000000..faf6729c0c --- /dev/null +++ b/packages/yaml-editor/src/components/BlameEditorV2.tsx @@ -0,0 +1,275 @@ +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' +import { render } from 'react-dom' + +import Editor, { EditorProps, loader, Monaco, useMonaco } from '@monaco-editor/react' +import * as monaco from 'monaco-editor' + +import { MonacoCommonDefaultOptions } from '../constants/monaco-common-default-options' +import { useTheme } from '../hooks/useTheme' +import { BlameItem } from '../types/blame' +import { ThemeDefinition } from '../types/themes' +import { createCommitMessage, getMonacoEditorCss } from '../utils/blame-editor-utils' +import { createRandomString } from '../utils/utils' + +loader.config({ monaco }) + +const BLAME_MESSAGE_WIDTH = 450 +const COMMIT_MESSAGE_LENGTH = 30 + +const defaultOptions: monaco.editor.IStandaloneEditorConstructionOptions = { + ...MonacoCommonDefaultOptions, + readOnly: true, + matchBrackets: 'never', + renderValidationDecorations: 'off', + guides: { indentation: false }, + folding: false, + stickyScroll: { enabled: false }, + renderWhitespace: 'none', + renderLineHighlight: 'none' +} + +export interface BlameEditorV2Props { + code: string + language: string + themeConfig?: { rootElementSelector?: string; defaultTheme?: string; themes?: ThemeDefinition[] } + theme?: string + lineNumbersPosition?: 'left' | 'center' + blameData: BlameItem[] + height?: EditorProps['height'] + options?: monaco.editor.IStandaloneEditorConstructionOptions + className?: string +} + +export function BlameEditorV2({ + code, + language, + themeConfig, + lineNumbersPosition = 'left', + blameData, + theme: themeFromProps, + height = '75vh', + options, + className +}: BlameEditorV2Props): JSX.Element { + const blameDataRef = useRef(blameData) + + const holderRef = useRef<HTMLDivElement>(null) + + const instanceId = useRef(createRandomString(5)) + const monaco = useMonaco() + const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor | undefined>() + + const monacoRef = useRef<typeof monaco | null>(null) + const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null) + + useEffect(() => { + blameDataRef.current = blameData + }, [blameData]) + + function setupBlameEditor() { + const editor = editorRef.current + const monaco = monacoRef.current + + if (!editor || !monaco) return + + editor.changeViewZones(function (changeAccessor) { + // space before first line + changeAccessor.addZone({ + afterLineNumber: 0, + heightInPx: 1, + domNode: document.createElement('div') + }) + + blameDataRef.current.forEach(blameItem => { + if (blameItem.infoContent) { + const holder = document.createElement('div') + holder.className = 'blame-editor-info-holder' + holder.style.position = 'relative' + holder.style.zIndex = '100' + holder.style.overflow = 'visible' + + optimizedRender(holder, blameItem.infoContent) + + changeAccessor.addZone({ + afterLineNumber: blameItem.fromLineNumber - 1, + heightInPx: 0, + domNode: holder + }) + } + + const domNode = document.createElement('div') + domNode.style.borderTop = '1px solid var(--cn-border-2)' + domNode.style.marginTop = '9px' + domNode.className = 'blame-editor-separator' + + changeAccessor.addZone({ + afterLineNumber: blameItem.toLineNumber, + heightInPx: 20, + domNode: domNode + }) + }) + }) + + const decoratorItems: monaco.editor.IModelDeltaDecoration[] = [] + blameDataRef.current.forEach(blameItem => { + for (let lineNo = blameItem.fromLineNumber; lineNo <= blameItem.toLineNumber; lineNo++) { + decoratorItems.push({ + range: new monaco.Range(lineNo, 0, lineNo + 1, 0), + options: { + before: { + content: createCommitMessage('', COMMIT_MESSAGE_LENGTH), + cursorStops: monaco.editor.InjectedTextCursorStops.None, + inlineClassName: `blame-editor-commit blame-editor-commit-${lineNo}` + } + } + }) + } + }) + + // TODO: on unmount clear decorators, on blameData change recreate + editor.createDecorationsCollection(decoratorItems) + } + + function handleEditorDidMount(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) { + editorRef.current = editor + monacoRef.current = monaco + + editor.setValue(code) + setEditor(editor) + + monaco.languages.typescript?.typescriptDefaults?.setDiagnosticsOptions?.({ + noSuggestionDiagnostics: true, + noSyntaxValidation: true, + noSemanticValidation: true + }) + monaco.languages.typescript?.javascriptDefaults?.setDiagnosticsOptions?.({ + noSuggestionDiagnostics: true, + noSyntaxValidation: true, + noSemanticValidation: true + }) + + setupBlameEditor() + } + + useEffect(() => { + editor?.setValue(code) + }, [code]) + + const { theme } = useTheme({ monacoRef, themeConfig, editor, theme: themeFromProps }) + + const monacoEditorCss = useMemo( + () => + getMonacoEditorCss({ + instanceId: instanceId.current, + lineNumbersPosition + }), + [blameData] + ) + + // prevent line numbers to appear initially at the wrong position + useLayoutEffect(() => { + if (holderRef.current) holderRef.current.style.setProperty('--line-number-display', `none`) + }, []) + + // set adjustment for lines numbers position + useEffect(() => { + if (lineNumbersPosition === 'center' && holderRef.current) { + const scrollableEl = holderRef.current.getElementsByClassName('lines-content')[0] + + if (scrollableEl) { + const config = { attributes: true } + + const callback = () => { + const left = parseInt(getComputedStyle(scrollableEl).left) + if (holderRef.current) { + holderRef.current.style.setProperty('--line-number-offset', `${BLAME_MESSAGE_WIDTH + 16 + left}px`) + holderRef.current.style.setProperty('--line-number-display', `block`) + } + } + + const observer = new MutationObserver(callback) + observer.observe(scrollableEl, config) + + callback() + + return () => { + observer.disconnect() + } + } + } + }) + + // adjust lines numbers position + const lineNumbersCss = useMemo(() => { + return ` + .monaco-editor-${instanceId.current} .margin { + display: var(--line-number-display); + left: var(--line-number-offset); + pointer-events: none; + }` + }, []) + + const clipSelection = useMemo(() => { + return `.monaco-editor-${instanceId.current} .view-overlays { + clip-path: polygon(${BLAME_MESSAGE_WIDTH + 16}px 0, 100% 0%, 100% 100%, ${BLAME_MESSAGE_WIDTH + 16}px 100%); + height:100% !important; + }` + }, []) + + const mergedOptions = useMemo( + () => ({ + ...defaultOptions, + ...options + }), + [options] + ) + + const styleEl = useMemo(() => { + return ( + <style + dangerouslySetInnerHTML={{ + __html: `${monacoEditorCss} ${lineNumbersCss} ${clipSelection}` + }} + /> + ) + }, [monacoEditorCss, clipSelection, lineNumbersCss]) + + return ( + <div className={className} ref={holderRef}> + {styleEl} + <Editor + className={`monaco-editor-${instanceId.current} overflow-hidden rounded-b-md`} + height={height} + language={language} + theme={theme} + options={mergedOptions} + onMount={handleEditorDidMount} + /> + </div> + ) +} + +function optimizedRender(el: HTMLDivElement, comp: JSX.Element) { + const observer = new IntersectionObserver( + ([entry], obs) => { + if (entry.isIntersecting) { + obs.disconnect() + + const holder = document.createElement('div') + holder.style.position = 'absolute' + holder.style.top = '-2px' + el.appendChild(holder) + + render(comp, holder) + } + }, + { + root: document.querySelector('.monaco-scrollable-element'), + threshold: 0.2 + } + ) + + observer.observe(el) + + return el +} diff --git a/packages/yaml-editor/src/index.ts b/packages/yaml-editor/src/index.ts index a56203a60e..8dc2f74f2e 100644 --- a/packages/yaml-editor/src/index.ts +++ b/packages/yaml-editor/src/index.ts @@ -1,4 +1,5 @@ import { BlameEditor, BlameEditorProps } from './components/BlameEditor' +import { BlameEditorV2, BlameEditorV2Props } from './components/BlameEditorV2' import { CodeEditor, CodeEditorProps } from './components/CodeEditor' import { CodeDiffEditor, DiffEditorProps } from './components/DiffEditor' import { YamlEditor, YamlEditorProps, type YamlRevision } from './components/YamlEditor' @@ -35,5 +36,8 @@ export type { CodeEditorProps } export { BlameEditor } export type { BlameEditorProps } +export { BlameEditorV2 } +export type { BlameEditorV2Props } + export { CodeDiffEditor } export type { DiffEditorProps } diff --git a/packages/yaml-editor/src/types/blame.ts b/packages/yaml-editor/src/types/blame.ts index 0b06566c7d..93a7739c78 100644 --- a/packages/yaml-editor/src/types/blame.ts +++ b/packages/yaml-editor/src/types/blame.ts @@ -2,6 +2,7 @@ export interface BlameItem { fromLineNumber: number toLineNumber: number commitInfo?: BlameItemCommit + infoContent?: JSX.Element } export interface BlameItemCommit { From 49f6edef18cb59b2139cf750098fe6b178a4bd72 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Mon, 11 Aug 2025 16:51:59 +0000 Subject: [PATCH 056/180] fallback added (#10176) * b3ae68 fix * ce40b3 fix * c208ad fix * 221a7a fix * 145957 fallback added --- .../design-tokens/core/typography.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core-design-system/design-tokens/core/typography.json b/packages/core-design-system/design-tokens/core/typography.json index 09a0ce9284..172ce3295e 100644 --- a/packages/core-design-system/design-tokens/core/typography.json +++ b/packages/core-design-system/design-tokens/core/typography.json @@ -176,11 +176,18 @@ "fontFamily": { "default": { "$type": "fontFamilies", - "$value": "Inter" + "$value": [ + "Inter", + "system-ui", + "sans-serif" + ] }, "mono": { "$type": "fontFamilies", - "$value": "JetBrains Mono" + "$value": [ + "JetBrains Mono", + "monospace" + ] } }, "textDecoration": { From 0aea792cb0e4665db627ec5ead325561f9d21735 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Mon, 11 Aug 2025 18:57:10 +0000 Subject: [PATCH 057/180] feat: Replace window location update with route utils from MFE (#10170) * 231aad Merge branch 'main' into use-route-utils-from-parent * cee750 cleanup * 67059d changes for backward compatiblity * ae5150 use route utils --- apps/gitness/src/AppMFE.tsx | 2 ++ .../src/framework/context/MFEContext.tsx | 5 ++++ .../pull-request/pull-request-list.tsx | 27 +++++++++---------- apps/gitness/src/pages-v2/repo/repo-list.tsx | 26 +++++++++--------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/apps/gitness/src/AppMFE.tsx b/apps/gitness/src/AppMFE.tsx index 59d298b709..fab720d6ad 100644 --- a/apps/gitness/src/AppMFE.tsx +++ b/apps/gitness/src/AppMFE.tsx @@ -91,6 +91,7 @@ export default function AppMFE({ customHooks, customUtils, routes, + routeUtils, hooks }: AppMFEProps) { new CodeServiceAPIClient({ @@ -163,6 +164,7 @@ export default function AppMFE({ customHooks, customUtils, routes, + routeUtils, hooks, setMFETheme }} diff --git a/apps/gitness/src/framework/context/MFEContext.tsx b/apps/gitness/src/framework/context/MFEContext.tsx index 3998c477a1..e3ad0375fd 100644 --- a/apps/gitness/src/framework/context/MFEContext.tsx +++ b/apps/gitness/src/framework/context/MFEContext.tsx @@ -80,6 +80,10 @@ export interface IMFEContext { toOrgSettings: () => string toProjectSettings: () => string }> + routeUtils: Partial<{ + toCODERepository: ({ repoPath }: { repoPath: string }) => void + toCODEPullRequest: ({ repoPath, pullRequestId }: { repoPath: string; pullRequestId: string }) => void + }> hooks: Hooks setMFETheme: (newTheme: string) => void } @@ -97,6 +101,7 @@ export const MFEContext = createContext<IMFEContext>({ customHooks: {}, customUtils: {}, routes: {}, + routeUtils: {}, hooks: {}, setMFETheme: noop }) diff --git a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx index e4ac6f91eb..2de5c2a09d 100644 --- a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx @@ -48,7 +48,7 @@ export default function PullRequestListPage() { const oldPageRef = useRef(page) const [lastUpdatedPRFilter, setLastUpdatedPRFilter] = useState<{ updated_lt?: number; updated_gt?: number }>({}) - const { scope, renderUrl } = useMFEContext() + const { scope, renderUrl, routeUtils } = useMFEContext() const basename = `/ng${renderUrl}` const isMFE = useIsMFE() const { navigate } = useRouterContext() @@ -208,19 +208,18 @@ export default function PullRequestListPage() { if (!isMFE || isSameScope) { navigate(pullRequestPath) } else { - const fullPath = `${basename}${getPullRequestUrl({ - repo, - scope: { - accountId, - orgIdentifier, - projectIdentifier - }, - pullRequestSubPath: pullRequestPath - })}` - - // TODO: Fix this properly to avoid full page refresh. - // Currently, not able to navigate properly with React Router. - window.location.href = fullPath + if (routeUtils?.toCODEPullRequest) { + // Navigate with parent app's React Router + routeUtils.toCODEPullRequest({ repoPath: repo.path, pullRequestId: prNumber?.toString() || '' }) + } else { + // TODO: Remove this fallback once the routeUtils is available in all release branches + const fullPath = `${basename}${getPullRequestUrl({ + repo, + scope: { accountId, orgIdentifier, projectIdentifier }, + pullRequestSubPath: pullRequestPath + })}` + window.location.href = fullPath + } } } diff --git a/apps/gitness/src/pages-v2/repo/repo-list.tsx b/apps/gitness/src/pages-v2/repo/repo-list.tsx index 03203f3711..19cf34a033 100644 --- a/apps/gitness/src/pages-v2/repo/repo-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-list.tsx @@ -39,8 +39,6 @@ export default function ReposListPage() { setImportToastId } = useRepoStore() const { toast, dismiss } = useToast() - const { renderUrl } = useMFEContext() - const basename = `/ng${renderUrl}` const isMFE = useIsMFE() const { navigate } = useRouterContext() @@ -48,7 +46,8 @@ export default function ReposListPage() { const { queryPage, setQueryPage } = usePaginationQueryStateWithStore({ page, setPage }) const [favorite, setFavorite] = useQueryState<boolean>('favorite') const [recursive, setRecursive] = useQueryState<boolean>('recursive') - const { scope } = useMFEContext() + const { scope, renderUrl, routeUtils } = useMFEContext() + const basename = `/ng${renderUrl}` const [sort, setSort] = useState<ListReposQueryQueryParams['sort']>('last_git_push') const [order, setOrder] = useState<ListReposQueryQueryParams['order']>('desc') @@ -163,15 +162,18 @@ export default function ReposListPage() { if (!isMFE || isSameScope) { navigate(repoSummaryPath) } else { - const fullPath = `${basename}${getRepoUrl({ - repo, - scope, - repoSubPath: repoSummaryPath - })}` - - // TODO: Fix this properly to avoid full page refresh. - // Currently, not able to navigate properly with React Router. - window.location.href = fullPath + if (routeUtils?.toCODERepository) { + // Navigate with parent app's React Router + routeUtils.toCODERepository?.({ repoPath: repo.path }) + } else { + // TODO: Remove this fallback once the routeUtils is available in all release branches + const fullPath = `${basename}${getRepoUrl({ + repo, + scope, + repoSubPath: repoSummaryPath + })}` + window.location.href = fullPath + } } } From 2d66bd83fdec467b570f358000657c4aefd0fa54 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Mon, 11 Aug 2025 19:35:54 +0000 Subject: [PATCH 058/180] feat: match search page to mocks (#10178) * ac3169 add underline on link * a9253f feat: match search page to mocks --- .../ui/src/components/accordion/accordion.tsx | 2 +- .../search/components/search-results-list.tsx | 184 +++++++++--------- packages/ui/src/views/search/search-page.tsx | 1 + 3 files changed, 98 insertions(+), 89 deletions(-) diff --git a/packages/ui/src/components/accordion/accordion.tsx b/packages/ui/src/components/accordion/accordion.tsx index 9c80d8528f..fa9dc45f38 100644 --- a/packages/ui/src/components/accordion/accordion.tsx +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -78,7 +78,7 @@ const AccordionItem = forwardRef<ElementRef<typeof AccordionPrimitive.Item>, Acc if (variant === 'card') { return ( - <Card.Root size={cardSize} className="mb-2 w-full"> + <Card.Root size={cardSize} className="mb-2 w-full" wrapperClassname="!p-0"> <AccordionPrimitive.Item ref={ref} className="w-full" {...props} /> </Card.Root> ) diff --git a/packages/ui/src/views/search/components/search-results-list.tsx b/packages/ui/src/views/search/components/search-results-list.tsx index ca58fe3579..6f2ccb27df 100644 --- a/packages/ui/src/views/search/components/search-results-list.tsx +++ b/packages/ui/src/views/search/components/search-results-list.tsx @@ -1,6 +1,6 @@ import { FC, useState } from 'react' -import { Alert, Card, Layout, Skeleton, Spacer, Tag, Text } from '@/components' +import { Accordion, Alert, Layout, Skeleton, Spacer, Tag, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' import { cn } from '@utils/cn' @@ -79,101 +79,109 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ ) } - const CardContent = ({ item }: { item: SearchResultItem }) => ( - <Card.Content> - <Layout.Horizontal - gap="xs" - className={cn('py-3 px-5', { 'border-b border-cn-border-2': item.matches && item.matches.length > 1 })} - > - {!isRepoScope ? <Tag value={item.repo_path} icon="repository" /> : null} - <Link - to={toRepoFileDetails({ - repoPath: item.repo_path, - filePath: item.file_name, - branch: item.repo_branch - })} - > - <Text variant="body-strong">{item.file_name}</Text> - </Link> - </Layout.Horizontal> - - {item.matches && item.matches.length > 1 && ( - <Layout.Vertical gap="none"> - {item.matches - .slice(0, expandedItems[`${item.repo_path}/${item.file_name}`] ? undefined : DEFAULT_NUM_ITEMS_TO_SHOW) - .map(match => ( - <div - key={`${match.before}-${match.fragments.map(frag => frag.pre + frag.match + frag.post).join('')}-${match.after}`} - > - <pre className={cn('bg-cn-background-1 px-4 py-1 border-b border-cn-border-2')}> - <code className="monospace"> - {match.before.trim().length > 0 && ( - <> - {match.line_num - 1} {match.before} - <br /> - </> - )} - {match.line_num}{' '} - {match.fragments?.map((segment, segIndex) => ( - <span key={`seg-${segIndex}`}> - {segment.pre} - <mark>{segment.match}</mark> - {segment.post} - </span> - ))} - {match.after.trim().length > 0 && ( - <> - <br /> - {match.line_num + 1} {match.after} - </> - )} - </code> - </pre> - </div> - ))} - {item.matches?.length > DEFAULT_NUM_ITEMS_TO_SHOW && ( - <Text - variant="body-normal" - className="text-cn-primary cursor-pointer px-4 py-2 hover:underline" - tabIndex={0} - onClick={() => { - const key = `${item.repo_path}/${item.file_name}` - setExpandedItems(prev => ({ - ...prev, - [key]: !prev[key] - })) - }} + const AccordionContent = ({ item }: { item: SearchResultItem }) => ( + <Accordion.Root + type="multiple" + indicatorPosition="left" + size="sm" + variant="card" + cardSize="sm" + defaultValue={[item.file_name]} + disabled={item.matches.length === 0} + > + <Accordion.Item value={item.file_name}> + <Accordion.Trigger className="py-3 px-4 gap-4"> + <Layout.Horizontal className="w-full" gap="md"> + {!isRepoScope ? <Tag value={item.repo_path} icon="repository" /> : null} + <Link + to={toRepoFileDetails({ + repoPath: item.repo_path, + filePath: item.file_name, + branch: item.repo_branch + })} + className="hover:underline" > - {expandedItems[`${item.repo_path}/${item.file_name}`] - ? t('views:search.showLess', '- Show Less') - : t('views:search.showMore', `+ Show ${item.matches.length - DEFAULT_NUM_ITEMS_TO_SHOW} more matches`)} - </Text> + <Text variant="body-strong">{item.file_name}</Text> + </Link> + </Layout.Horizontal> + </Accordion.Trigger> + <Accordion.Content className="pb-0 bg-cn-background-1 border-t"> + {item.matches && item.matches.length > 1 && ( + <Layout.Vertical gap="none" className="mt-1"> + {item.matches + .slice(0, expandedItems[`${item.repo_path}/${item.file_name}`] ? undefined : DEFAULT_NUM_ITEMS_TO_SHOW) + .map(match => ( + <div + key={`${match.before}-${match.fragments.map(frag => frag.pre + frag.match + frag.post).join('')}-${match.after}`} + className="px-4" + > + <pre className={cn('bg-cn-background-1')}> + <code className="monospace"> + {match.before.trim().length > 0 && ( + <> + {match.line_num - 1} {match.before} + <br /> + </> + )} + {match.line_num}{' '} + {match.fragments?.map((segment, segIndex) => ( + <span key={`seg-${segIndex}`}> + {segment.pre} + <mark>{segment.match}</mark> + {segment.post} + </span> + ))} + {match.after.trim().length > 0 && ( + <> + <br /> + {match.line_num + 1} {match.after} + </> + )} + </code> + </pre> + </div> + ))} + {item.matches?.length > DEFAULT_NUM_ITEMS_TO_SHOW && ( + <Text + variant="caption-single-line-normal" + className="hover:underline bg-cn-background-0 mt-2 pl-4 py-1.5 cursor-pointer" + tabIndex={0} + onMouseDown={e => { + e.preventDefault() + }} + onClick={e => { + e.stopPropagation() + e.preventDefault() + + const key = `${item.repo_path}/${item.file_name}` + setExpandedItems(prev => ({ + ...prev, + [key]: !prev[key] + })) + }} + > + {expandedItems[`${item.repo_path}/${item.file_name}`] + ? t('views:search.showLess', '- Show Less') + : t( + 'views:search.showMore', + `+ Show ${item.matches.length - DEFAULT_NUM_ITEMS_TO_SHOW} more matches` + )} + </Text> + )} + </Layout.Vertical> )} - </Layout.Vertical> - )} - </Card.Content> + </Accordion.Content> + </Accordion.Item> + </Accordion.Root> ) return ( - <Layout.Vertical gap="md"> + <Layout.Vertical gap="xs"> {results.map(item => item.matches.length == 0 ? ( - <Link - key={`${item.repo_path}/${item.file_name}`} - to={toRepoFileDetails({ - repoPath: item.repo_path, - filePath: item.file_name, - branch: item.repo_branch - })} - > - <Card.Root tabIndex={-1} wrapperClassname="!p-0"> - <CardContent item={item} /> - </Card.Root> - </Link> + <AccordionContent key={`${item.repo_path}/${item.file_name}`} item={item} /> ) : ( - <Card.Root key={`${item.repo_path}/${item.file_name}`} tabIndex={-1} wrapperClassname="!p-0"> - <CardContent item={item} /> - </Card.Root> + <AccordionContent key={`${item.repo_path}/${item.file_name}`} item={item} /> ) )} </Layout.Vertical> diff --git a/packages/ui/src/views/search/search-page.tsx b/packages/ui/src/views/search/search-page.tsx index 0b35e8172c..cbfb43c507 100644 --- a/packages/ui/src/views/search/search-page.tsx +++ b/packages/ui/src/views/search/search-page.tsx @@ -115,6 +115,7 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ 'mx-auto': isLoading || results?.length || searchQuery, 'h-full': !isLoading && !results?.length && !searchQuery })} + // maxWidth="7xl" > <div className="relative"> <SearchInput From 3780f8cd4d302950ae09e41c5859b8aaa18c8df9 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Mon, 11 Aug 2025 20:28:11 +0000 Subject: [PATCH 059/180] Conflict section - Alignment issues (#10169) * 1ce879 Merge remote-tracking branch 'origin/main' into rk-conflict-page-design * 889244 Error handling for Commit changes button * df0a85 trigger CI rebuild for typecheck * b0098c Fixed prettier check * 674d66 Fixed comments * 56579c Updated link and fixed copy button padding * 0d6940 Alignment issues for conflict section --- .../commit-suggestions-dialog.tsx | 9 +- .../pull-request-conversation.tsx | 27 ++++-- packages/ui/locales/en/views.json | 2 - packages/ui/locales/fr/views.json | 2 - .../commit-suggestions-dialog.tsx | 22 ++++- .../ui/src/views/labels/labels-list-page.tsx | 2 +- .../conversation/pull-request-panel.tsx | 97 ++++++++++++------- .../sections/pull-request-merge-section.tsx | 92 ++++++++++-------- .../repo/repo-commits/repo-commits-view.tsx | 2 +- 9 files changed, 159 insertions(+), 96 deletions(-) diff --git a/apps/gitness/src/components-v2/commit-suggestions-dialog.tsx b/apps/gitness/src/components-v2/commit-suggestions-dialog.tsx index f909b69dbc..da73791f3c 100644 --- a/apps/gitness/src/components-v2/commit-suggestions-dialog.tsx +++ b/apps/gitness/src/components-v2/commit-suggestions-dialog.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { CommentApplySuggestionsOkResponse, @@ -36,6 +36,13 @@ export default function CommitSuggestionsDialog({ const commitTitlePlaceholder = 'Apply suggestion from code review' + // Clear error when dialog closes + useEffect(() => { + if (!open) { + setError(undefined) + } + }, [open]) + const onSubmit = async (formValues: CommitSuggestionsFormType) => { const { message, title } = formValues const data = { diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index c47aacc8bb..06c6e8c810 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -11,7 +11,6 @@ import { mergePullReqOp, OpenapiMergePullReq, OpenapiStatePullReqRequest, - rebaseBranch, RebaseBranchRequestBody, reviewerAddPullReq, reviewerDeletePullReq, @@ -24,6 +23,7 @@ import { useGetBranchQuery, useListPrincipalsQuery, useListPullReqActivitiesQuery, + useRebaseBranchMutation, useRestorePullReqSourceBranchMutation, useRevertPullReqOpMutation, useReviewerListPullReqQuery, @@ -622,6 +622,18 @@ export default function PullRequestConversationPage() { const [mergeTitle, setMergeTitle] = useState(pullReqMetadata?.title || '') const [mergeMessage, setMergeMessage] = useState('') const [isMerging, setIsMerging] = useState(false) + const { mutateAsync: performRebase, isLoading: isRebasing } = useRebaseBranchMutation( + { repo_ref: repoRef }, + { + onSuccess: () => { + handleRefetchData() + setRuleViolationArr(undefined) + }, + onError: (error: any) => { + setRebaseErrorMessage(error.message || 'Failed to rebase branch. Please try again.') + } + } + ) const [selectedMergeMethod, setSelectedMergeMethod] = useState<EnumMergeMethod | null>(null) // Update merge title based on selected merge method @@ -728,7 +740,7 @@ export default function PullRequestConversationPage() { dryMerge }) - const handleRebaseBranch = useCallback(() => { + const handleRebaseBranch = useCallback(async () => { const payload: RebaseBranchRequestBody = { ...(pullReqMetadata?.target_branch ? { @@ -749,13 +761,8 @@ export default function PullRequestConversationPage() { : {}) } - rebaseBranch({ body: payload, repo_ref: repoRef }) - .then(() => { - handleRefetchData() - setRuleViolationArr(undefined) - }) - .catch(error => setRebaseErrorMessage(error.message)) - }, [pullReqMetadata, handleRefetchData, setRuleViolationArr, repoRef]) + await performRebase({ body: payload }) + }, [pullReqMetadata, performRebase]) /** * Memoize overviewProps @@ -866,6 +873,7 @@ export default function PullRequestConversationPage() { setMergeTitle, setMergeMessage, isMerging, + isRebasing, onMergeMethodSelect: handleMergeMethodSelect } }, [ @@ -905,6 +913,7 @@ export default function PullRequestConversationPage() { mergeMessage, routes, isMerging, + isRebasing, setSelectedMergeMethod, handleMergeMethodSelect ]) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index a731fc5c2c..b863013fbd 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -526,7 +526,6 @@ "delete": "Delete Label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", - "newLabel": "Create Label", "createLabel": "Create Label", "create": "Create Labels" }, @@ -766,7 +765,6 @@ "commitDetailsDiffDeletions": "deletions", "commitDetailsTitle": "Commit", "browseFiles": "Browse Files", - "createNewCommit": "Create commit", "createCommit": "Create commit", "commitDetailsAuthored": "authored", "verified": "Verified" diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index c86b77d16d..6b43477606 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -518,7 +518,6 @@ "delete": "Delete label", "title": "Labels", "showParentLabels": "Show labels from parent scopes", - "newLabel": "Create Label", "createLabel": "Create label", "create": "Create labels" }, @@ -753,7 +752,6 @@ "commitDetailsDiffDeletions": "suppressions", "commitDetailsTitle": "Commit", "browseFiles": "Parcourir les fichiers", - "createNewCommit": "Create commit", "createCommit": "Créer un commit", "commitDetailsAuthored": "créé par", "verified": "Vérifié" diff --git a/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx b/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx index 6828187fb3..a01fa7f989 100644 --- a/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx +++ b/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx @@ -1,7 +1,7 @@ -import { FC } from 'react' +import { FC, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { Button, ButtonLayout, Dialog, FormInput, FormWrapper } from '@/components' +import { Alert, Button, ButtonLayout, Dialog, FormInput, FormWrapper } from '@/components' import { UsererrorError } from '@/types' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -32,6 +32,7 @@ export const CommitSuggestionsDialog: FC<CommitSuggestionsDialogProps> = ({ isOpen, onClose, commitTitlePlaceHolder, + error, onFormSubmit, isSubmitting }) => { @@ -44,7 +45,14 @@ export const CommitSuggestionsDialog: FC<CommitSuggestionsDialogProps> = ({ } }) - const { register, handleSubmit } = formMethods + const { register, handleSubmit, reset } = formMethods + // Reset form when dialog opens or closes + useEffect(() => { + reset({ + message: '', + title: commitTitlePlaceHolder + }) + }, [isOpen, reset, commitTitlePlaceHolder]) return ( <Dialog.Root open={isOpen} onOpenChange={onClose}> @@ -71,6 +79,14 @@ export const CommitSuggestionsDialog: FC<CommitSuggestionsDialogProps> = ({ /> </Dialog.Body> + {error && ( + <Alert.Root theme="danger" className="mt-4"> + <Alert.Title> + {error.message || 'An error occurred while applying suggestions. Please try again.'} + </Alert.Title> + </Alert.Root> + )} + <Dialog.Footer> <ButtonLayout> <Dialog.Close onClick={onClose} disabled={isSubmitting}> diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index 32072158dd..c6ddf360cd 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -85,7 +85,7 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ <Button asChild> <Link to="create"> <IconV2 name="plus" /> - {t('views:labelData.newLabel', 'Create Label')} + {t('views:labelData.createLabel', 'Create Label')} </Link> </Button> </ListActions.Right> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 8b0a012bee..880b9aaeac 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -47,6 +47,19 @@ import PullRequestCheckSection from './sections/pull-request-checks-section' import PullRequestCommentSection from './sections/pull-request-comment-section' import PullRequestMergeSection from './sections/pull-request-merge-section' +const MERGE_METHOD_TITLES: Record<MergeStrategy, string> = { + [MergeStrategy.FAST_FORWARD]: 'Fast-forward merge', + [MergeStrategy.SQUASH]: 'Squash and merge', + [MergeStrategy.MERGE]: 'Merge pull request', + [MergeStrategy.REBASE]: 'Rebase and merge' +} + +const getMergeStrategyFromTitle = (title?: string): MergeStrategy | undefined => { + if (!title) return undefined + const entry = Object.entries(MERGE_METHOD_TITLES).find(([, display]) => display === title) + return entry ? (entry[0] as MergeStrategy) : undefined +} + export const getMergeMethodDisplay = (mergeMethodType: MergeStrategy): string => { return mergeMethodMapping[mergeMethodType] } @@ -107,15 +120,22 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { // Get the current selected merge method const getCurrentMergeMethod = () => { const selectedAction = actions?.[parseInt(mergeButtonValue || '0')] - if (selectedAction?.title === 'Fast-forward merge') return 'fast-forward' - if (selectedAction?.title === 'Squash and merge') return 'squash' - if (selectedAction?.title === 'Merge pull request') return 'merge' - if (selectedAction?.title === 'Rebase and merge') return 'rebase' - return undefined + switch (getMergeStrategyFromTitle(selectedAction?.title)) { + case MergeStrategy.FAST_FORWARD: + return MergeStrategy.FAST_FORWARD + case MergeStrategy.SQUASH: + return MergeStrategy.SQUASH + case MergeStrategy.MERGE: + return MergeStrategy.MERGE + case MergeStrategy.REBASE: + return MergeStrategy.REBASE + default: + return undefined + } } const currentMergeMethod = getCurrentMergeMethod() - const isFastForwardNotPossible = isRebasable && currentMergeMethod === 'fast-forward' + const isFastForwardNotPossible = isRebasable && currentMergeMethod === MergeStrategy.FAST_FORWARD if (pullReqMetadata?.state === PullRequestFilterOption.MERGED) { return ( @@ -288,6 +308,7 @@ export interface PullRequestPanelProps setMergeTitle: (title: string) => void setMergeMessage: (message: string) => void isMerging?: boolean + isRebasing?: boolean onMergeMethodSelect?: (method: string) => void } @@ -325,6 +346,7 @@ const PullRequestPanel = ({ setMergeTitle, setMergeMessage, isMerging, + isRebasing, onMergeMethodSelect, ...routingProps }: PullRequestPanelProps) => { @@ -346,32 +368,37 @@ const PullRequestPanel = ({ const handleMergeTypeSelect = (value: string) => { const selectedAction = actions[parseInt(value)] - if (selectedAction.title === 'Squash and merge') { - setMergeMessage( - pullReqCommits?.commits - ?.map(commit => `* ${commit?.sha?.substring(0, 6)} ${commit?.title}`) - .join('\n\n') - ?.slice(0, 1000) ?? '' - ) - onMergeMethodSelect?.('squash') - } else if (selectedAction.title === 'Merge pull request') { - setMergeMessage('') - onMergeMethodSelect?.('merge') - } else if (selectedAction.title === 'Rebase and merge') { - setMergeMessage('') - onMergeMethodSelect?.('rebase') - } else if (selectedAction.title === 'Fast-forward merge') { - setMergeMessage('') - onMergeMethodSelect?.('fast-forward') + const strategy = getMergeStrategyFromTitle(selectedAction.title) + switch (strategy) { + case MergeStrategy.SQUASH: + setMergeMessage( + pullReqCommits?.commits + ?.map(commit => `* ${commit?.sha?.substring(0, 6)} ${commit?.title}`) + .join('\n\n') + ?.slice(0, 1000) ?? '' + ) + onMergeMethodSelect?.(MergeStrategy.SQUASH) + break + case MergeStrategy.MERGE: + setMergeMessage('') + onMergeMethodSelect?.(MergeStrategy.MERGE) + break + case MergeStrategy.REBASE: + setMergeMessage('') + onMergeMethodSelect?.(MergeStrategy.REBASE) + break + case MergeStrategy.FAST_FORWARD: + setMergeMessage('') + onMergeMethodSelect?.(MergeStrategy.FAST_FORWARD) + break + default: + break } setShowActionBtn(true) setMergeButtonValue(value) - if (selectedAction.title === 'Merge pull request' || selectedAction.title === 'Squash and merge') { - setShowMergeInputs(true) - } else { - setShowMergeInputs(false) - } + const showInputs = strategy === MergeStrategy.MERGE || strategy === MergeStrategy.SQUASH + setShowMergeInputs(!!showInputs) } const handleCancelMerge = () => { @@ -384,9 +411,8 @@ const PullRequestPanel = ({ const handleConfirmMerge = () => { const selectedAction = actions[parseInt(mergeButtonValue || '0')] // Check if this is a merge action - const isMergeAction = ['Squash and merge', 'Merge pull request', 'Rebase and merge', 'Fast-forward merge'].includes( - selectedAction?.title || '' - ) + const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) + const isMergeAction = mergeActionTitles.has(selectedAction?.title || '') setShowMergeInputs(false) setShowActionBtn(false) @@ -407,18 +433,14 @@ const PullRequestPanel = ({ const selectedAction = actions[parseInt(mergeButtonValue || '0')] const isRebasable = pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged - const isFastForwardSelected = selectedAction?.title === 'Fast-forward merge' + const isFastForwardSelected = getMergeStrategyFromTitle(selectedAction?.title) === MergeStrategy.FAST_FORWARD return isFastForwardSelected && isRebasable } // To get the selected merge method const getSelectedMergeMethod = () => { const selectedAction = actions[parseInt(mergeButtonValue || '0')] - if (selectedAction?.title === 'Fast-forward merge') return 'fast-forward' - if (selectedAction?.title === 'Squash and merge') return 'squash' - if (selectedAction?.title === 'Merge pull request') return 'merge' - if (selectedAction?.title === 'Rebase and merge') return 'rebase' - return undefined + return getMergeStrategyFromTitle(selectedAction?.title) } const handleAccordionValuesChange = useCallback((data: string | string[]) => { @@ -730,6 +752,7 @@ const PullRequestPanel = ({ accordionValues={accordionValues} setAccordionValues={setAccordionValues} handleRebaseBranch={handleRebaseBranch} + isRebasing={isRebasing} selectedMergeMethod={getSelectedMergeMethod()} /> )} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx index d59e7e41df..5961e619b5 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx @@ -1,6 +1,6 @@ import { Dispatch, FC, MouseEvent, SetStateAction, useMemo, useState } from 'react' -import { Accordion, Button, CopyButton, CopyTag, IconV2, Layout, StackedList, Text } from '@/components' +import { Accordion, Button, Card, CopyButton, CopyTag, IconV2, Layout, Link, StackedList, Text } from '@/components' import { cn } from '@utils/cn' import { PanelAccordionShowButton } from '@views/repo/pull-request/details/components/conversation/sections/panel-accordion-show-button' import { isEmpty } from 'lodash-es' @@ -11,29 +11,34 @@ interface StepInfoProps { step: string description: string code?: string - comment?: string + comment?: string | React.ReactElement } const StepInfo: FC<StepInfoProps> = item => { return ( <li> - <Layout.Horizontal gap="3xs"> - <Text as="h3" variant="body-strong" color="foreground-1" className="flex-none"> + <Layout.Horizontal gap="2xs"> + <Text as="h3" variant="body-strong" className="flex-none"> {item.step} </Text> - <Layout.Vertical className="w-[90%] max-w-full"> - <Text>{item.description}</Text> - <div - className={cn('text-2 text-cn-foreground-2', { - 'border border-cn-borders-2 rounded-md px-2 py-1 !my-2': item.code, - '!my-1': item.comment - })} - > - <Layout.Horizontal align="center" justify="between"> - <Text>{item.code ? item.code : item.comment}</Text> - {!!item.code && <CopyButton name={item.code} />} + <Layout.Vertical className="w-[90%] max-w-full" gap="xs"> + <Text variant="body-normal">{item.description}</Text> + {item.code ? ( + <Layout.Horizontal + align="center" + justify="between" + className="border border-cn-borders-2 rounded-md px-1.5 py-1.5 mt-1 mb-3" + > + <Text variant="body-normal" className="font-mono"> + {item.code} + </Text> + <CopyButton name={item.code} size="xs" /> </Layout.Horizontal> - </div> + ) : item.comment ? ( + <Text variant="body-normal" className="my-1"> + {item.comment} + </Text> + ) : null} </Layout.Vertical> </Layout.Horizontal> </li> @@ -60,6 +65,7 @@ interface PullRequestMergeSectionProps { accordionValues: string[] setAccordionValues: Dispatch<SetStateAction<string[]>> handleRebaseBranch?: () => void + isRebasing?: boolean selectedMergeMethod?: string } const PullRequestMergeSection = ({ @@ -70,6 +76,7 @@ const PullRequestMergeSection = ({ accordionValues, setAccordionValues, handleRebaseBranch, + isRebasing, selectedMergeMethod }: PullRequestMergeSectionProps) => { const [showCommandLineInfo, setShowCommandLineInfo] = useState(false) @@ -108,9 +115,16 @@ const PullRequestMergeSection = ({ }, { step: 'Step 4', - description: ' Fix the conflicts and commit the result', - comment: - 'See Resolving a merge conflict using the command line for step-by-step instruction on resolving merge conflicts' + description: 'Fix the conflicts and commit the results', + comment: ( + <> + See{' '} + <Link to="https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging.html#_merge_conflicts" target="_blank"> + Resolving a merge conflict using the command line + </Link>{' '} + for step-by-step instructions on resolving merge conflicts + </> + ) }, { step: 'Step 5', @@ -210,32 +224,30 @@ const PullRequestMergeSection = ({ <Accordion.Content className="ml-7"> <> {showCommandLineInfo && ( - <div className="mb-3.5 rounded-md border border-cn-borders-2 p-1 px-4 py-2"> - <Text variant="heading-small" color="foreground-1"> - Resolve conflicts via command line - </Text> - <ol className="flex flex-col gap-y-3"> - {stepMap.map(item => ( - <StepInfo key={item.step} {...item} /> - ))} - </ol> - </div> + <Card.Root className="mb-3.5 bg-transparent border-cn-borders-3" size="sm"> + <Card.Content className="px-4 py-2"> + <Layout.Vertical gap="sm"> + <Text variant="heading-small">Resolve conflicts via command line</Text> + <ol className="flex flex-col gap-y-0.5"> + {stepMap.map(item => ( + <StepInfo key={item.step} {...item} /> + ))} + </ol> + </Layout.Vertical> + </Card.Content> + </Card.Root> )} - <Text as="span"> - Conflicting files <Text as="span">{conflictingFiles?.length || 0}</Text> - </Text> + <Text variant="body-normal">Conflicting files {conflictingFiles?.length || 0}</Text> {!isEmpty(conflictingFiles) && ( - <div className="mt-1"> + <Layout.Vertical gap="xs" className="mt-1"> {conflictingFiles?.map(file => ( - <div className="flex items-center gap-x-2 py-1.5" key={file}> + <Layout.Horizontal key={file} align="center" gap="xs" className="py-1.5"> <IconV2 size="md" className="text-icons-1" name="page" /> - <Text as="span" color="foreground-1"> - {file} - </Text> - </div> + <Text variant="body-normal">{file}</Text> + </Layout.Horizontal> ))} - </div> + </Layout.Vertical> )} </> </Accordion.Content> @@ -257,7 +269,7 @@ const PullRequestMergeSection = ({ description={<LineDescription text={renderBranchTags()} />} /> {handleRebaseBranch && ( - <Button theme="default" variant="primary" onClick={handleRebaseBranch} size="md"> + <Button theme="default" variant="primary" onClick={handleRebaseBranch} loading={isRebasing} size="md"> Update with rebase </Button> )} diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index fac0c96cc9..a7216b952b 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -101,7 +101,7 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ } : // TODO: add onClick for Creating new commit { - label: t('views:commits.createNewCommit', 'Create commit'), + label: t('views:commits.createCommit', 'Create commit'), /** * To make the first commit, redirect to the files page so a new file can be created. */ From f48f94b695ce817d3bbd3f5f7ac9b51706e3b328 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Mon, 11 Aug 2025 21:38:57 +0000 Subject: [PATCH 060/180] fix: Remove extra left margin from Breadcrumbs to align vertically (#10179) * 23b0b6 remove margin --- apps/gitness/src/components-v2/mfe/app-shell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gitness/src/components-v2/mfe/app-shell.tsx b/apps/gitness/src/components-v2/mfe/app-shell.tsx index 716380d619..67843553a8 100644 --- a/apps/gitness/src/components-v2/mfe/app-shell.tsx +++ b/apps/gitness/src/components-v2/mfe/app-shell.tsx @@ -14,7 +14,7 @@ export const AppShellMFE = memo(() => { return ( <> - <Breadcrumbs breadcrumbs={breadcrumbs} breadcrumbClassName="ml-6" /> + <Breadcrumbs breadcrumbs={breadcrumbs} /> <MainContentLayout className="text-cn-foreground-2" withBreadcrumbs={breadcrumbs.length > 0}> <Outlet /> </MainContentLayout> From 87d9e37467c90b6879ae42ce96cc36d98fd2b409 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Mon, 11 Aug 2025 23:08:03 +0000 Subject: [PATCH 061/180] fix: make required badges consistent (#10172) * 746656 fix: make required badges consistent From 026c166f1c2d25066095644db7a844d17abe5ccb Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Tue, 12 Aug 2025 04:14:37 +0000 Subject: [PATCH 062/180] feat: fix routing for parent scopes on rules page (#10180) * 259087 chore: rebase * 08e61c feat: fix routing for parent scopes on rules page --- .../src/framework/context/MFEContext.tsx | 10 +++ .../project/project-rules-list-container.tsx | 15 ++-- .../repo-settings-rules-list-container.tsx | 15 ++-- apps/gitness/src/utils/rule-url-utils.ts | 69 ++++++++++++++++--- .../project-rules/project-rules-page.tsx | 2 +- .../repo-settings-general-rules.tsx | 11 ++- .../repo-settings-rules-page.tsx | 2 +- .../search/components/search-results-list.tsx | 2 +- 8 files changed, 104 insertions(+), 22 deletions(-) diff --git a/apps/gitness/src/framework/context/MFEContext.tsx b/apps/gitness/src/framework/context/MFEContext.tsx index e3ad0375fd..7e132b90a5 100644 --- a/apps/gitness/src/framework/context/MFEContext.tsx +++ b/apps/gitness/src/framework/context/MFEContext.tsx @@ -83,6 +83,16 @@ export interface IMFEContext { routeUtils: Partial<{ toCODERepository: ({ repoPath }: { repoPath: string }) => void toCODEPullRequest: ({ repoPath, pullRequestId }: { repoPath: string; pullRequestId: string }) => void + toCODERule: ({ repoPath, ruleId }: { repoPath: string; ruleId: string }) => void + toCODEManageRepositories: ({ + space, + ruleId, + settingSection + }: { + space: string + ruleId: string + settingSection: string + }) => void }> hooks: Hooks setMFETheme: (newTheme: string) => void diff --git a/apps/gitness/src/pages-v2/project/project-rules-list-container.tsx b/apps/gitness/src/pages-v2/project/project-rules-list-container.tsx index 8264154bb7..44c27ecd4b 100644 --- a/apps/gitness/src/pages-v2/project/project-rules-list-container.tsx +++ b/apps/gitness/src/pages-v2/project/project-rules-list-container.tsx @@ -14,7 +14,7 @@ import { useMFEContext } from '../../framework/hooks/useMFEContext' import { useQueryState } from '../../framework/hooks/useQueryState' import usePaginationQueryStateWithStore from '../../hooks/use-pagination-query-state-with-store' import { getTotalRulesApplied } from '../../utils/repo-branch-rules-utils' -import { generateRuleDetailsUrl } from '../../utils/rule-url-utils' +import { getScopedRuleUrl } from '../../utils/rule-url-utils' import { useProjectRulesStore } from './stores/project-rules-store' export const ProjectRulesListContainer = () => { @@ -29,7 +29,9 @@ export const ProjectRulesListContainer = () => { const navigate = useNavigate() const { setRules } = useProjectRulesStore() const { - routes: { toAccountSettings, toOrgSettings, toProjectSettings } + routes: { toAccountSettings, toOrgSettings, toProjectSettings }, + routeUtils, + scope: { accountId, orgIdentifier, projectIdentifier } } = useMFEContext() const [isRuleAlertDeleteDialogOpen, setRuleIsAlertDeleteDialogOpen] = useState(false) @@ -117,12 +119,17 @@ export const ProjectRulesListContainer = () => { toProjectBranchRuleCreate={() => routes.toProjectBranchRuleCreate({ space_ref })} toProjectTagRuleCreate={() => routes.toProjectTagRuleCreate({ space_ref })} toProjectRuleDetails={(identifier, scope) => { - return generateRuleDetailsUrl({ + getScopedRuleUrl({ scope, identifier, + toCODEManageRepositories: routeUtils?.toCODEManageRepositories, + toCODERule: routeUtils?.toCODERule, toAccountSettings, toOrgSettings, - toProjectSettings + toProjectSettings, + accountId, + orgIdentifier, + projectIdentifier }) }} showParentScopeLabelsCheckbox={space_ref?.includes('/')} diff --git a/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx b/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx index bd46404711..1f75c6deb4 100644 --- a/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx @@ -19,7 +19,7 @@ import { useGetRepoId } from '../../framework/hooks/useGetRepoId' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useMFEContext } from '../../framework/hooks/useMFEContext' import { PathParams } from '../../RouteDefinitions' -import { generateRuleDetailsUrl } from '../../utils/rule-url-utils' +import { getScopedRuleUrl } from '../../utils/rule-url-utils' import { useRepoRulesStore } from './stores/repo-settings-store' export const RepoSettingsRulesListContainer = () => { @@ -46,7 +46,9 @@ export const RepoSettingsRulesListContainer = () => { } const { - routes: { toAccountSettings, toOrgSettings, toProjectSettings } + routes: { toAccountSettings, toOrgSettings, toProjectSettings }, + routeUtils, + scope: { accountId, orgIdentifier, projectIdentifier } } = useMFEContext() const { @@ -123,15 +125,20 @@ export const RepoSettingsRulesListContainer = () => { ruleTypeFilter={ruleTypeFilter} setRuleTypeFilter={setRuleTypeFilter} toProjectRuleDetails={(identifier, scope) => { - return generateRuleDetailsUrl({ + return getScopedRuleUrl({ scope, identifier, + toCODEManageRepositories: routeUtils.toCODEManageRepositories, + toCODERule: routeUtils.toCODERule, toAccountSettings, toOrgSettings, toProjectSettings, toRepoBranchRule: routes.toRepoBranchRule, spaceId, - repoId: repoName + repoId: repoName, + accountId, + orgIdentifier, + projectIdentifier }) }} /> diff --git a/apps/gitness/src/utils/rule-url-utils.ts b/apps/gitness/src/utils/rule-url-utils.ts index 6056fff885..32ed97b7ac 100644 --- a/apps/gitness/src/utils/rule-url-utils.ts +++ b/apps/gitness/src/utils/rule-url-utils.ts @@ -19,39 +19,90 @@ export function transformToRuleDetailsUrl(url?: string, ruleId?: string): string * @param params Parameters needed to generate the URL * @returns The URL for the rule details page */ -export function generateRuleDetailsUrl({ + +// @TODO: Remove navigate fallback once MFE route utils are available across all release branches + +export function getScopedRuleUrl({ scope, identifier, + toCODEManageRepositories, + toCODERule, toAccountSettings, toOrgSettings, toProjectSettings, toRepoBranchRule, spaceId, - repoId + repoId, + accountId, + orgIdentifier, + projectIdentifier }: { scope: number identifier: string + toCODEManageRepositories?: ({ + space, + ruleId, + settingSection + }: { + space: string + ruleId: string + settingSection: string + }) => void + toCODERule?: ({ + repoPath, + ruleId, + settingSection + }: { + repoPath: string + ruleId: string + settingSection: string + }) => void toAccountSettings?: () => string toOrgSettings?: () => string toProjectSettings?: () => string toRepoBranchRule?: (params: { spaceId: string; repoId: string; identifier: string }) => string spaceId?: string repoId?: string -}): string { + accountId?: string + orgIdentifier?: string + projectIdentifier?: string +}) { if (scope === 0) { - return toRepoBranchRule?.({ spaceId: spaceId ?? '', repoId: repoId ?? '', identifier }) || '' + const repoPath = [accountId, orgIdentifier, projectIdentifier, repoId].filter(Boolean).join('/') + + if (toCODERule) { + toCODERule({ + repoPath, + ruleId: identifier, + settingSection: 'rules' + }) + } else { + const url = toRepoBranchRule?.({ spaceId: spaceId ?? '', repoId: repoId ?? '', identifier }) ?? '' + window.location.href = transformToRuleDetailsUrl(url, identifier) + } } if (scope === 1) { - return transformToRuleDetailsUrl(toAccountSettings?.(), identifier) + if (!toCODEManageRepositories) + window.location.href = transformToRuleDetailsUrl(toAccountSettings?.() ?? '', identifier) + toCODEManageRepositories?.({ space: `${accountId ?? ''}`, ruleId: identifier, settingSection: 'rules' }) } if (scope === 2) { - return transformToRuleDetailsUrl(toOrgSettings?.(), identifier) + if (!toCODEManageRepositories) window.location.href = transformToRuleDetailsUrl(toOrgSettings?.() ?? '', identifier) + toCODEManageRepositories?.({ + space: `${accountId ?? ''}/${orgIdentifier ?? ''}`, + ruleId: identifier, + settingSection: 'rules' + }) } if (scope === 3) { - return transformToRuleDetailsUrl(toProjectSettings?.(), identifier) + if (!toCODEManageRepositories) + window.location.href = transformToRuleDetailsUrl(toProjectSettings?.() ?? '', identifier) + toCODEManageRepositories?.({ + space: `${accountId ?? ''}/${orgIdentifier ?? ''}/${projectIdentifier ?? ''}`, + ruleId: identifier, + settingSection: 'rules' + }) } - - return '' } diff --git a/packages/ui/src/views/project/project-rules/project-rules-page.tsx b/packages/ui/src/views/project/project-rules/project-rules-page.tsx index fb7a8a3604..88f7e8e826 100644 --- a/packages/ui/src/views/project/project-rules/project-rules-page.tsx +++ b/packages/ui/src/views/project/project-rules/project-rules-page.tsx @@ -22,7 +22,7 @@ export interface ProjectRulesPageProps { onParentScopeLabelsChange?: (checked: boolean) => void ruleTypeFilter?: 'branch' | 'tag' | 'push' | null setRuleTypeFilter?: (filter: 'branch' | 'tag' | 'push' | null) => void - toProjectRuleDetails?: (identifier: string, scope: number) => string + toProjectRuleDetails?: (identifier: string, scope: number) => void } export const ProjectRulesPage: FC<ProjectRulesPageProps> = ({ useProjectRulesStore, diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index bfa9bf9687..44c1fb8ad6 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -85,7 +85,7 @@ export interface RepoSettingsGeneralRulesProps { toRepoTagRuleCreate?: () => string ruleTypeFilter?: 'branch' | 'tag' | 'push' | null setRuleTypeFilter?: (filter: 'branch' | 'tag' | 'push' | null) => void - toProjectRuleDetails?: (identifier: string, scope: number) => string + toProjectRuleDetails?: (identifier: string, scope: number) => void } export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ @@ -178,7 +178,14 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ {rules?.map((rule, idx) => rule?.identifier ? ( <StackedList.Item className="py-4 pr-1.5" key={rule.identifier} asChild> - <Link to={toProjectRuleDetails?.(rule.identifier, rule.scope ?? 0) || ''} target="_blank"> + <Link + to="#" + onClick={e => { + e.preventDefault() + e.stopPropagation() + toProjectRuleDetails?.(rule.identifier ?? '', rule.scope ?? 0) + }} + > <StackedList.Field className="grid gap-1.5" title={ diff --git a/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx index 414d8fa227..40aa9a3c05 100644 --- a/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx @@ -21,7 +21,7 @@ interface RepoSettingsRulesPageProps { onParentScopeLabelsChange?: (checked: boolean) => void ruleTypeFilter?: 'branch' | 'tag' | 'push' | null setRuleTypeFilter?: (filter: 'branch' | 'tag' | 'push' | null) => void - toProjectRuleDetails?: (identifier: string, scope: number) => string + toProjectRuleDetails?: (identifier: string, scope: number) => void } export const RepoSettingsRulesPage: React.FC<RepoSettingsRulesPageProps> = ({ diff --git a/packages/ui/src/views/search/components/search-results-list.tsx b/packages/ui/src/views/search/components/search-results-list.tsx index 6f2ccb27df..27e9c14b2f 100644 --- a/packages/ui/src/views/search/components/search-results-list.tsx +++ b/packages/ui/src/views/search/components/search-results-list.tsx @@ -106,7 +106,7 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ </Layout.Horizontal> </Accordion.Trigger> <Accordion.Content className="pb-0 bg-cn-background-1 border-t"> - {item.matches && item.matches.length > 1 && ( + {item.matches && item.matches.length >= 1 && ( <Layout.Vertical gap="none" className="mt-1"> {item.matches .slice(0, expandedItems[`${item.repo_path}/${item.file_name}`] ? undefined : DEFAULT_NUM_ITEMS_TO_SHOW) From ee55cdb1a50e9577e3cfb67091ad68ce859ac1a8 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Tue, 12 Aug 2025 08:48:54 +0000 Subject: [PATCH 063/180] fix: sticky PR filters for PR compare page - changes tab (#10183) * 350f1b fix: sticky PR filters for PR compare page - changes tab --- .../compare/components/pull-request-compare-diff-list.tsx | 2 +- .../repo/pull-request/compare/pull-request-compare-page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index 32e79e966f..098c3e2e48 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -88,7 +88,7 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ return ( <> - <ListActions.Root> + <ListActions.Root className="sticky top-[100px] z-20 bg-cn-background-1 py-2"> <ListActions.Left> <DropdownMenu.Root> <Text as="p" variant="body-single-line-normal" className="text-2 leading-tight text-cn-foreground-2 pt-1.5"> diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 6c3409963f..f45236f0d8 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -447,7 +447,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ /> )} </Tabs.Content> - <Tabs.Content className="pt-7" value="changes"> + <Tabs.Content className="pt-5" value="changes"> {/* Content for Changes */} {(diffData ?? []).length > 0 ? ( <PullRequestCompareDiffList From 7aa040e40da9be5706ec2aaa44fcb3b7d2e9158f Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Tue, 12 Aug 2025 10:20:36 +0000 Subject: [PATCH 064/180] card-gradient-fix (#10177) * e29a46 fix * 64a230 card-gradient-fix --- .../core-design-system/design-tokens/mode/dark/default.json | 4 ++-- .../core-design-system/design-tokens/mode/light/default.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index f837d63453..1de91d31cf 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -2221,11 +2221,11 @@ "card": { "from": { "$type": "color", - "$value": "{chrome.900}" + "$value": "{chrome.950}" }, "to": { "$type": "color", - "$value": "{chrome.950}" + "$value": "{blue.1000}" } }, "dialog": { diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index 16157ba40f..f054bc8719 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -2239,11 +2239,11 @@ "card": { "from": { "$type": "color", - "$value": "{pure.white}" + "$value": "{blue.25}" }, "to": { "$type": "color", - "$value": "{chrome.25}" + "$value": "{blue.50}" } }, "dialog": { From e0c00e5838081bb247b764c65b0f8021c01e8e84 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:30:38 +0400 Subject: [PATCH 065/180] add commit details page design review (#2046) * add commit details page design review * fixes * add ChangedFilesShortInfo component * fixes * fixes * replace link with button --- .../repo/repo-commit-details-diff.tsx | 5 +- .../src/pages-v2/repo/repo-commit-details.tsx | 3 +- apps/gitness/src/routes.tsx | 8 +- packages/ui/locales/en/views.json | 18 ++- packages/ui/locales/fr/views.json | 18 ++- .../ui/src/components/accordion/accordion.tsx | 29 +++-- packages/ui/src/components/link.tsx | 64 +++++----- packages/ui/src/components/stacked-list.tsx | 5 +- packages/ui/src/components/text.tsx | 3 +- packages/ui/src/context/router-context.tsx | 9 +- .../repo/components/CommitTitleWithPRLink.tsx | 49 ++++---- .../changed-files-short-info.tsx | 60 ++++++++++ .../ui/src/views/repo/components/index.ts | 1 + .../pull-request-compare-diff-list.tsx | 81 ++----------- .../changes/pull-request-changes-filter.tsx | 54 +-------- .../components/commit-changes.tsx | 59 +++++----- .../components/commit-diff.tsx | 23 ++-- .../components/commit-sidebar.tsx | 29 ++--- .../repo-commit-details-view.tsx | 110 +++++++++--------- .../ui/src/views/repo/repo-sidebar/index.tsx | 7 +- .../components/accordion.ts | 5 + .../tailwind-utils-config/components/badge.ts | 1 + .../components/button.ts | 1 + 23 files changed, 304 insertions(+), 338 deletions(-) create mode 100644 packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx diff --git a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx index a6d9bfb97f..6e64a915c3 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx @@ -10,6 +10,7 @@ import { useGetContentQuery, useListPathsQuery } from '@harnessio/code-service-client' +import { Layout } from '@harnessio/ui/components' import { CommitDiff, CommitSidebar } from '@harnessio/ui/views' import Explorer from '../../components-v2/FileExplorer' @@ -98,7 +99,7 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool const filesList = filesData?.body?.files || [] return ( - <> + <Layout.Flex gapX="xl"> {showSidebar && ( <CommitSidebar navigateToFile={() => {}} filesList={filesList}> {!!repoDetails?.body?.content?.entries?.length && <Explorer repoDetails={repoDetails?.body} />} @@ -106,6 +107,6 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool )} <CommitDiff useCommitDetailsStore={useCommitDetailsStore} /> - </> + </Layout.Flex> ) } diff --git a/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx b/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx index 66b03621be..949edd2276 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commit-details.tsx @@ -9,7 +9,7 @@ import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { PathParams } from '../../RouteDefinitions' import { useCommitDetailsStore } from './stores/commit-details-store' -export default function RepoCommitDetailsPage({ showSidebar = true }: { showSidebar?: boolean }) { +export default function RepoCommitDetailsPage() { const repoRef = useGetRepoRef() const { commitSHA } = useParams<PathParams>() const { setCommitData } = useCommitDetailsStore() @@ -34,7 +34,6 @@ export default function RepoCommitDetailsPage({ showSidebar = true }: { showSide routes.toPullRequest({ spaceId, repoId, pullRequestId: pullRequestId.toString() }) } useCommitDetailsStore={useCommitDetailsStore} - showSidebar={showSidebar} loadingCommitDetails={loadingCommitDetails} /> ) diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 30678bf1c1..ae3e58db87 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -273,13 +273,9 @@ export const repoRoutes: CustomRouteObject[] = [ }, { path: 'commit/:commitSHA', - element: <RepoCommitDetailsPage showSidebar={false} />, + element: <RepoCommitDetailsPage />, handle: { - breadcrumb: ({ commitSHA }: { commitSHA: string }) => ( - <> - <span>{commitSHA.substring(0, 7)}</span> - </> - ), + breadcrumb: ({ commitSHA }: { commitSHA: string }) => <span>{commitSHA.substring(0, 7)}</span>, routeName: RouteConstants.toRepoCommitDetails }, children: [ diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index b863013fbd..381444d82b 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -757,16 +757,26 @@ "accountOnly": "Account only", "label": "Scope" }, + "repo": { + "components": { + "commitDetailsDiffShowing": "Showing", + "commitDetailsDiffChangedFiles": "changed files", + "commitDetailsDiffWith": "with", + "commitDetailsDiffAdditionsAnd": "additions and", + "commitDetailsDiffDeletions": "deletions" + } + }, "commits": { + "commitDetailsTitle": "Commit", + "browseFiles": "Browse Files", + "commitDetailsAuthored": "authored", + "createCommit": "Create commit", + "createNewCommit": "Create commit", "commitDetailsDiffShowing": "Showing", "commitDetailsDiffChangedFiles": "changed files", "commitDetailsDiffWith": "with", "commitDetailsDiffAdditionsAnd": "additions and", "commitDetailsDiffDeletions": "deletions", - "commitDetailsTitle": "Commit", - "browseFiles": "Browse Files", - "createCommit": "Create commit", - "commitDetailsAuthored": "authored", "verified": "Verified" }, "createTagDialog": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 6b43477606..1431fe9d2d 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -744,16 +744,26 @@ "label": "Périmètre", "account": "Compte" }, + "repo": { + "components": { + "commitDetailsDiffShowing": "Affichage de", + "commitDetailsDiffChangedFiles": "fichiers modifiés", + "commitDetailsDiffWith": "avec", + "commitDetailsDiffAdditionsAnd": "ajouts et", + "commitDetailsDiffDeletions": "suppressions" + } + }, "commits": { + "commitDetailsTitle": "Commit", + "browseFiles": "Parcourir les fichiers", + "commitDetailsAuthored": "créé par", + "createCommit": "Créer un commit", + "createNewCommit": "Créer un commit", "commitDetailsDiffShowing": "Affichage de", "commitDetailsDiffChangedFiles": "fichiers modifiés", "commitDetailsDiffWith": "avec", "commitDetailsDiffAdditionsAnd": "ajouts et", "commitDetailsDiffDeletions": "suppressions", - "commitDetailsTitle": "Commit", - "browseFiles": "Parcourir les fichiers", - "createCommit": "Créer un commit", - "commitDetailsAuthored": "créé par", "verified": "Vérifié" }, "createTagDialog": { diff --git a/packages/ui/src/components/accordion/accordion.tsx b/packages/ui/src/components/accordion/accordion.tsx index fa9dc45f38..247b2a0068 100644 --- a/packages/ui/src/components/accordion/accordion.tsx +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -1,4 +1,4 @@ -import { ComponentPropsWithoutRef, createContext, ElementRef, forwardRef, ReactNode, useContext } from 'react' +import { ComponentPropsWithoutRef, createContext, ElementRef, forwardRef, Fragment, ReactNode, useContext } from 'react' import { Card } from '@components/card' import { IconPropsV2, IconV2 } from '@components/icon-v2' @@ -98,7 +98,7 @@ type AccordionTriggerProps = ComponentPropsWithoutRef<typeof AccordionPrimitive. } const AccordionTrigger = forwardRef<ElementRef<typeof AccordionPrimitive.Trigger>, AccordionTriggerProps>( - ({ className, children, suffix, prefix, indicatorProps, headerClassName, ...props }, ref) => { + ({ className, children, suffix, prefix, indicatorProps, headerClassName, asChild, ...props }, ref) => { const { indicatorPosition } = useContext(AccordionContext) const Indicator = () => ( @@ -110,16 +110,25 @@ const AccordionTrigger = forwardRef<ElementRef<typeof AccordionPrimitive.Trigger /> ) + const Wrapper = asChild ? 'div' : Fragment + return ( <AccordionPrimitive.Header className={headerClassName}> - <AccordionPrimitive.Trigger ref={ref} className={cn('cn-accordion-trigger', className)} {...props}> - {indicatorPosition === 'left' && <Indicator />} - - {!!prefix && <span className="cn-accordion-trigger-prefix">{prefix}</span>} - <span className="cn-accordion-trigger-text">{children}</span> - {!!suffix && <span className="cn-accordion-trigger-suffix">{suffix}</span>} - - {indicatorPosition === 'right' && <Indicator />} + <AccordionPrimitive.Trigger + ref={ref} + className={cn('cn-accordion-trigger', className)} + asChild={asChild} + {...props} + > + <Wrapper> + {indicatorPosition === 'left' && <Indicator />} + + {!!prefix && <span className="cn-accordion-trigger-prefix">{prefix}</span>} + <span className="cn-accordion-trigger-text">{children}</span> + {!!suffix && <span className="cn-accordion-trigger-suffix">{suffix}</span>} + + {indicatorPosition === 'right' && <Indicator />} + </Wrapper> </AccordionPrimitive.Trigger> </AccordionPrimitive.Header> ) diff --git a/packages/ui/src/components/link.tsx b/packages/ui/src/components/link.tsx index c117d970e2..173a39d9d2 100644 --- a/packages/ui/src/components/link.tsx +++ b/packages/ui/src/components/link.tsx @@ -1,4 +1,4 @@ -import { MouseEvent, RefAttributes } from 'react' +import { forwardRef, MouseEvent, RefAttributes } from 'react' import type { LinkProps as LinkBaseProps } from 'react-router-dom' import { useRouterContext } from '@/context' @@ -42,42 +42,38 @@ interface LinkProps extends LinkBaseProps, RefAttributes<HTMLAnchorElement>, Var disabled?: boolean } -const Link = ({ - className, - variant = 'default', - children, - prefixIcon, - suffixIcon, - disabled = false, - size, - onClick, - ...props -}: LinkProps) => { - const { Link: LinkBase } = useRouterContext() +const Link = forwardRef<HTMLAnchorElement, LinkProps>( + ( + { className, variant = 'default', children, prefixIcon, suffixIcon, disabled = false, size, onClick, ...props }, + ref + ) => { + const { Link: LinkBase } = useRouterContext() - const handleClick = (e: MouseEvent<HTMLAnchorElement>) => { - if (disabled) { - e.preventDefault() - e.stopPropagation() - } else { - onClick?.(e) + const handleClick = (e: MouseEvent<HTMLAnchorElement>) => { + if (disabled) { + e.preventDefault() + e.stopPropagation() + } else { + onClick?.(e) + } } - } - return ( - <LinkBase - {...props} - className={cn(linkVariants({ variant, size }), className)} - onClick={handleClick} - data-disabled={disabled} - aria-disabled={disabled} - > - {!!prefixIcon && <IconV2 name={typeof prefixIcon === 'string' ? prefixIcon : 'nav-arrow-left'} size="2xs" />} - {children} - {!!suffixIcon && <IconV2 name={typeof suffixIcon === 'string' ? suffixIcon : 'arrow-up-right'} size="2xs" />} - </LinkBase> - ) -} + return ( + <LinkBase + {...props} + className={cn(linkVariants({ variant, size }), className)} + onClick={handleClick} + data-disabled={disabled} + aria-disabled={disabled} + ref={ref} + > + {!!prefixIcon && <IconV2 name={typeof prefixIcon === 'string' ? prefixIcon : 'nav-arrow-left'} size="2xs" />} + {children} + {!!suffixIcon && <IconV2 name={typeof suffixIcon === 'string' ? suffixIcon : 'arrow-up-right'} size="2xs" />} + </LinkBase> + ) + } +) Link.displayName = 'Link' diff --git a/packages/ui/src/components/stacked-list.tsx b/packages/ui/src/components/stacked-list.tsx index 1b44c06624..67ff9e9c70 100644 --- a/packages/ui/src/components/stacked-list.tsx +++ b/packages/ui/src/components/stacked-list.tsx @@ -21,10 +21,10 @@ const listItemVariants = cva( } ) -const listFieldVariants = cva('flex flex-1 flex-col items-stretch gap-cn-2xs justify-start', { +const listFieldVariants = cva('gap-cn-2xs flex flex-1 flex-col items-stretch justify-start', { variants: { right: { - true: 'justify-end items-end' + true: 'items-end justify-end' } } }) @@ -135,6 +135,7 @@ const ListField = ({ variant={primary ? 'heading-base' : 'body-normal'} color={label ? 'foreground-2' : 'foreground-1'} truncate={!disableTruncate} + className="min-w-0" > {title} </Text> diff --git a/packages/ui/src/components/text.tsx b/packages/ui/src/components/text.tsx index 93742f954e..fca4f2a9c9 100644 --- a/packages/ui/src/components/text.tsx +++ b/packages/ui/src/components/text.tsx @@ -73,7 +73,8 @@ export const textVariants = cva('', { disabled: 'text-cn-foreground-disabled', success: 'text-cn-foreground-success', warning: 'text-cn-foreground-warning', - danger: 'text-cn-foreground-danger' + danger: 'text-cn-foreground-danger', + accent: 'text-cn-foreground-accent' }, lineClamp: { default: '', diff --git a/packages/ui/src/context/router-context.tsx b/packages/ui/src/context/router-context.tsx index b5ea2f661d..5d7707f76b 100644 --- a/packages/ui/src/context/router-context.tsx +++ b/packages/ui/src/context/router-context.tsx @@ -13,14 +13,15 @@ import { RouterContextProvider as FiltersRouterContextProvider } from '@harnessi const resolveTo = (to: LinkProps['to']) => (typeof to === 'string' ? to : to.pathname || '/') -const LinkDefault = ({ to, children, ...props }: LinkProps) => { +const LinkDefault = forwardRef<HTMLAnchorElement, LinkProps>(({ to, children, ...props }, ref) => { const href = resolveTo(to) return ( - <a href={href} {...props}> + <a href={href} {...props} ref={ref}> {children} </a> ) -} +}) +LinkDefault.displayName = 'LinkDefault' const NavLinkDefault = forwardRef<HTMLAnchorElement, NavLinkProps>( ({ to, children, className, style, ...props }, ref) => { @@ -68,7 +69,7 @@ const useParamsDefault = (): Params => { const defaultLocation: Location = { ...window.location, state: {}, key: '' } interface RouterContextType { - Link: ComponentType<LinkProps> + Link: ComponentType<ComponentPropsWithRef<typeof LinkDefault>> NavLink: ComponentType<ComponentPropsWithRef<typeof NavLinkDefault>> Outlet: ComponentType<OutletProps> location: Location diff --git a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx index 00f2753e8b..cd2555945c 100644 --- a/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx +++ b/packages/ui/src/views/repo/components/CommitTitleWithPRLink.tsx @@ -19,27 +19,34 @@ export const CommitTitleWithPRLink = (props: CommitTitleWithPRLinkProps) => { const pullRequestIdInt = parseInt(pullRequestId) if (!isNaN(pullRequestIdInt)) { - const pieces = commitMessage.split(match[0]) - const piecesEls = pieces.map(piece => { - return ( - <Text {...textProps} truncate title={title} key={piece}> - {piece} - </Text> - ) - }) - piecesEls.splice( - 1, - 0, - <Text {...textProps}> -  ( - <Link title={title} to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`} className="[font:inherit]"> - #{pullRequestId} - </Link> - )  - </Text> - ) - - return <Layout.Flex>{piecesEls}</Layout.Flex> + const pieces = commitMessage + .split(match[0]) + .filter(el => el.trim() !== '') + .map((piece, index) => { + if (index === 0) { + return ( + <Text {...textProps} truncate title={title} key={piece}> + {piece} + </Text> + ) + } + + return ( + <Text {...textProps} key={piece}> +  ( + <Link + title={title} + to={`${toPullRequest?.({ pullRequestId: pullRequestIdInt })}`} + className="[font:inherit]" + > + #{pullRequestId} + </Link> + )  + </Text> + ) + }) + + return <Layout.Flex className="min-w-0">{pieces}</Layout.Flex> } } diff --git a/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx b/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx new file mode 100644 index 0000000000..bddfe16a93 --- /dev/null +++ b/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx @@ -0,0 +1,60 @@ +import { useTranslation } from '@/context' +import { DiffFileEntry, TypesDiffStats } from '@/views' +import { Button } from '@components/button' +import { DropdownMenu } from '@components/dropdown-menu' +import { Layout } from '@components/layout' +import { StatusBadge } from '@components/status-badge/status-badge' +import { Text } from '@components/text' +import { formatNumber } from '@utils/TimeUtils' + +interface ChangedFilesShortInfoProps { + diffData?: Partial<DiffFileEntry>[] + diffStats?: TypesDiffStats | null + goToDiff: (fileName: string) => void +} + +export const ChangedFilesShortInfo = ({ diffData, diffStats, goToDiff }: ChangedFilesShortInfoProps) => { + const { t } = useTranslation() + + return ( + <DropdownMenu.Root> + <Text variant="body-single-line-normal"> + {t('views:repo.components.commitDetailsDiffShowing', 'Showing')}{' '} + <DropdownMenu.Trigger asChild> + <Button variant="link" className="inline-flex h-auto p-0"> + {formatNumber(diffStats?.files_changed ?? 0)}{' '} + {t('views:repo.components.commitDetailsDiffChangedFiles', 'changed files')} + </Button> + </DropdownMenu.Trigger>{' '} + {t('views:repo.components.commitDetailsDiffWith', 'with')} {formatNumber(diffStats?.additions ?? 0)}{' '} + {t('views:repo.components.commitDetailsDiffAdditionsAnd', 'additions and')}{' '} + {formatNumber(diffStats?.deletions ?? 0)} {t('views:repo.components.commitDetailsDiffDeletions', 'deletions')} + </Text> + + <DropdownMenu.Content className="max-h-[360px] max-w-[800px]" align="start"> + {diffData?.map(diff => ( + <DropdownMenu.IconItem + key={diff.filePath} + onClick={() => goToDiff(diff.filePath ?? '')} + icon="page" + title={diff.filePath} + label={ + <Layout.Horizontal gap="2xs" align="center"> + {diff.addedLines != null && diff.addedLines > 0 && ( + <StatusBadge variant="outline" size="sm" theme="success"> + +{diff.addedLines} + </StatusBadge> + )} + {diff.deletedLines != null && diff.deletedLines > 0 && ( + <StatusBadge variant="outline" size="sm" theme="danger"> + -{diff.deletedLines} + </StatusBadge> + )} + </Layout.Horizontal> + } + /> + ))} + </DropdownMenu.Content> + </DropdownMenu.Root> + ) +} diff --git a/packages/ui/src/views/repo/components/index.ts b/packages/ui/src/views/repo/components/index.ts index 83622dd50b..38f57dfe83 100644 --- a/packages/ui/src/views/repo/components/index.ts +++ b/packages/ui/src/views/repo/components/index.ts @@ -6,3 +6,4 @@ export * from './commit-selector' export * from './path-action-bar' export * from './repo-header' export * from './token-dialog/clone-credential-dialog' +export * from './changed-files-short-info/changed-files-short-info' diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index 098c3e2e48..3ccd87cf88 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -1,9 +1,14 @@ import { FC, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { DropdownMenu, IconV2, Layout, ListActions, StatusBadge, Text } from '@/components' -import { useTranslation } from '@/context' -import { formatNumber } from '@/utils' -import { DiffModeOptions, InViewDiffRenderer, jumpToFile, PrincipalPropsType, TypesDiffStats } from '@/views' +import { ListActions } from '@/components' +import { + ChangedFilesShortInfo, + DiffModeOptions, + InViewDiffRenderer, + jumpToFile, + PrincipalPropsType, + TypesDiffStats +} from '@/views' import { DiffModeEnum } from '@git-diff-view/react' import { chunk } from 'lodash-es' @@ -40,8 +45,6 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ onGetFullDiff, toRepoFileDetails }) => { - const { t } = useTranslation() - const [diffMode, setDiffMode] = useState<DiffModeEnum>(DiffModeEnum.Split) const handleDiffModeChange = (value: string) => { setDiffMode(value === 'Split' ? DiffModeEnum.Split : DiffModeEnum.Unified) @@ -84,65 +87,11 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ [setOpenItems] ) - const changedFilesCount = diffStats.files_changed || 0 - return ( <> <ListActions.Root className="sticky top-[100px] z-20 bg-cn-background-1 py-2"> <ListActions.Left> - <DropdownMenu.Root> - <Text as="p" variant="body-single-line-normal" className="text-2 leading-tight text-cn-foreground-2 pt-1.5"> - {t('views:commits.commitDetailsDiffShowing', 'Showing')}{' '} - <FilesChangedCount showAsDropdown={changedFilesCount !== 0}> - <Text - as="span" - variant="body-single-line-normal" - className="cursor-pointer text-cn-foreground-accent ease-in-out" - > - {formatNumber(changedFilesCount)} {t('views:commits.commitDetailsDiffChangedFiles', 'changed files')} - </Text> - </FilesChangedCount>{' '} - {t('views:commits.commitDetailsDiffWith', 'with')} {formatNumber(diffStats?.additions || 0)}{' '} - {t('views:commits.commitDetailsDiffAdditionsAnd', 'additions and')}{' '} - {formatNumber(diffStats?.deletions || 0)} {t('views:commits.commitDetailsDiffDeletions', 'deletions')} - </Text> - <DropdownMenu.Content className="max-h-[360px] max-w-[800px]" align="start"> - {diffData?.map(diff => ( - <DropdownMenu.Item - key={diff.filePath} - onClick={() => { - if (diff.filePath) { - setJumpToDiff(diff.filePath) - } - }} - title={ - <Layout.Flex direction="row" align="center" className=" min-w-0 gap-x-3"> - <Layout.Flex direction="row" align="center" justify="start" className=" min-w-0 flex-1 gap-x-1.5"> - <IconV2 name="page" className="shrink-0 text-icons-1" /> - <Text className="min-w-0 break-words">{diff.filePath}</Text> - </Layout.Flex> - <Layout.Flex direction="row" align="center" justify="center" className=" shrink-0 text-2"> - {diff.addedLines != null && diff.addedLines > 0 && ( - <StatusBadge variant="outline" size="sm" theme="success"> - +{diff.addedLines} - </StatusBadge> - )} - {diff.addedLines != null && - diff.addedLines > 0 && - diff.deletedLines != null && - diff.deletedLines > 0 && <span className="mx-1.5 h-3 w-px bg-cn-background-3" />} - {diff.deletedLines != null && diff.deletedLines > 0 && ( - <StatusBadge variant="outline" size="sm" theme="danger"> - -{diff.deletedLines} - </StatusBadge> - )} - </Layout.Flex> - </Layout.Flex> - } - /> - ))} - </DropdownMenu.Content> - </DropdownMenu.Root> + <ChangedFilesShortInfo diffData={diffData} diffStats={diffStats} goToDiff={setJumpToDiff} /> </ListActions.Left> <ListActions.Right> <ListActions.Dropdown @@ -198,14 +147,4 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ ) } -function FilesChangedCount({ - children, - showAsDropdown = false -}: { - children: React.ReactNode - showAsDropdown: boolean -}) { - return showAsDropdown ? <DropdownMenu.Trigger asChild>{children}</DropdownMenu.Trigger> : <>{children}</> -} - export default PullRequestCompareDiffList diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 101db18950..deecf032c3 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -1,10 +1,9 @@ import { useEffect, useMemo, useState } from 'react' -import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton, Text } from '@/components' +import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton } from '@/components' import { useTranslation } from '@/context' import { TypesUser } from '@/types' -import { formatNumber } from '@/utils' -import { TypesCommit } from '@/views' +import { ChangedFilesShortInfo, TypesCommit } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' import { @@ -239,54 +238,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = </DropdownMenu.Content> </DropdownMenu.Root> */} - <DropdownMenu.Root> - <Layout.Horizontal align="center"> - <p className="text-2 leading-tight text-cn-foreground-2"> - {t('views:commits.commitDetailsDiffShowing', 'Showing')}{' '} - <DropdownMenu.Trigger className="group"> - <span className="group-hover:decoration-foreground-accent text-cn-foreground-accent underline decoration-transparent underline-offset-4 transition-colors duration-200"> - {formatNumber(pullReqStats?.files_changed || 0)}{' '} - {t('views:commits.commitDetailsDiffChangedFiles', 'changed files')} - </span> - </DropdownMenu.Trigger>{' '} - {t('views:commits.commitDetailsDiffWith', 'with')} {formatNumber(pullReqStats?.additions || 0)}{' '} - {t('views:commits.commitDetailsDiffAdditionsAnd', 'additions and')}{' '} - {formatNumber(pullReqStats?.deletions || 0)} {t('views:commits.commitDetailsDiffDeletions', 'deletions')} - </p> - </Layout.Horizontal> - <DropdownMenu.Content className="max-h-[360px] max-w-[800px]" align="start"> - {diffData?.map(diff => ( - <DropdownMenu.Item - key={diff.filePath} - onClick={() => { - setJumpToDiff(diff.filePath) - }} - title={ - <Layout.Horizontal align="center" className="min-w-0 gap-x-3"> - <Layout.Horizontal align="center" justify="start" className="min-w-0 flex-1 gap-x-1.5"> - <IconV2 name="page" className="shrink-0 text-icons-1" /> - <Text className="min-w-0 break-words">{diff.filePath}</Text> - </Layout.Horizontal> - </Layout.Horizontal> - } - label={ - <Layout.Horizontal className="shrink-0 text-2" gap="none"> - {diff.addedLines != null && diff.addedLines > 0 && ( - <span className="text-cn-foreground-success">+{diff.addedLines}</span> - )} - {diff.addedLines != null && - diff.addedLines > 0 && - diff.deletedLines != null && - diff.deletedLines > 0 && <span className="mx-1.5 h-3 w-px bg-cn-background-3" />} - {diff.deletedLines != null && diff.deletedLines > 0 && ( - <span className="text-cn-foreground-danger">-{diff.deletedLines}</span> - )} - </Layout.Horizontal> - } - /> - ))} - </DropdownMenu.Content> - </DropdownMenu.Root> + <ChangedFilesShortInfo diffData={diffData} diffStats={pullReqStats} goToDiff={setJumpToDiff} /> </Layout.Horizontal> <Layout.Horizontal className="gap-x-7"> diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx index 4d28e9e5b7..3d83fa839a 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx @@ -22,36 +22,28 @@ interface HeaderProps { isBinary?: boolean } -interface LineTitleProps { - header: HeaderProps -} - interface DataProps { data: HeaderProps[] diffMode: DiffModeEnum } -const LineTitle: FC<LineTitleProps> = ({ header }) => { - const { t: _t } = useTranslation() - const { text, numAdditions, numDeletions } = header +const LineTitle: FC<HeaderProps> = ({ text, numAdditions, numDeletions }) => { return ( - <div className="flex items-center justify-between gap-3"> - <div className="inline-flex items-center gap-2 overflow-hidden"> - <Text className="flex-1" variant="body-strong" truncate> - {text} - </Text> - <CopyButton name={text} color="gray" /> - {!!numAdditions && ( - <StatusBadge variant="outline" size="sm" theme="success"> - +{numAdditions} - </StatusBadge> - )} - {!!numDeletions && ( - <StatusBadge variant="outline" size="sm" theme="danger"> - -{numDeletions} - </StatusBadge> - )} - </div> + <div className="flex w-full max-w-full items-center gap-2"> + <Text variant="body-strong" truncate color="foreground-1"> + {text} + </Text> + <CopyButton name={text} color="gray" buttonVariant="ghost" className="relative z-10" /> + {!!numAdditions && ( + <StatusBadge variant="outline" size="sm" theme="success"> + +{numAdditions} + </StatusBadge> + )} + {!!numDeletions && ( + <StatusBadge variant="outline" size="sm" theme="danger"> + -{numDeletions} + </StatusBadge> + )} </div> ) } @@ -83,7 +75,7 @@ const CommitsAccordion: FC<{ return ( <StackedList.Root> - <StackedList.Item disableHover isHeader className="cursor-default p-0 hover:bg-transparent"> + <StackedList.Item disableHover isHeader className="cursor-default p-0"> <Accordion.Root type="multiple" className="w-full" @@ -92,11 +84,12 @@ const CommitsAccordion: FC<{ indicatorPosition="left" > <Accordion.Item value={header?.text ?? ''} className="border-none"> - <Accordion.Trigger className="px-4 [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center"> - <StackedList.Field className="grid" title={<LineTitle header={header} />} /> - </Accordion.Trigger> + <div className="py-cn-xs px-cn-sm relative"> + <Accordion.Trigger className="py-cn-xs px-cn-sm rounded-t-3 absolute inset-0 z-0 hover:cursor-pointer [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" /> + <StackedList.Field className="grid pl-5" title={<LineTitle {...header} />} disableTruncate /> + </div> <Accordion.Content className="pb-0"> - <div className="border-t bg-transparent"> + <div className="rounded-b-3 overflow-hidden border-t bg-transparent"> {(fileDeleted || isDiffTooLarge || fileUnchanged || header?.isBinary) && !showHiddenDiff ? ( <Layout.Vertical align="center" className="w-full py-5"> <Button @@ -120,11 +113,11 @@ const CommitsAccordion: FC<{ </Layout.Vertical> ) : ( <> - {startingLine ? ( + {startingLine && ( <div className="bg-[--diff-hunk-lineNumber--]"> <div className="ml-16 w-full px-2 py-1">{startingLine}</div> </div> - ) : null} + )} <PullRequestDiffViewer /** * In commit changes we don't need principal props as we don't have any comments. @@ -170,7 +163,7 @@ export const CommitChanges: FC<DataProps> = ({ data, diffMode }) => { [setOpenItems] ) return ( - <div className="flex flex-col gap-4"> + <Layout.Grid gapY="md"> {data.map((item, index) => { return ( <CommitsAccordion @@ -182,7 +175,7 @@ export const CommitChanges: FC<DataProps> = ({ data, diffMode }) => { /> ) })} - </div> + </Layout.Grid> ) } diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx index a74933b4b5..ad04a4fcdd 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx @@ -1,6 +1,5 @@ -import { useTranslation } from '@/context' -import { formatNumber } from '@/utils' -import { ICommitDetailsStore } from '@/views' +import { ChangedFilesShortInfo, ICommitDetailsStore } from '@/views' +import { Layout } from '@components/layout' import { CommitChanges } from './commit-changes' @@ -9,21 +8,13 @@ export interface CommitDiffsViewProps { } export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsStore }) => { - const { t } = useTranslation() const { diffs, diffStats } = useCommitDetailsStore() return ( - <div className="min-h-[calc(100vh-var(--cn-page-nav-height))] pt-4"> - <p className="mb-3 text-2 leading-tight text-cn-foreground-2"> - {t('views:commits.commitDetailsDiffShowing', 'Showing')}{' '} - <span className="text-cn-foreground-accent"> - {formatNumber(diffStats?.files_changed ?? 0)}{' '} - {t('views:commits.commitDetailsDiffChangedFiles', 'changed files')} - </span>{' '} - {t('views:commits.commitDetailsDiffWith', 'with')} {formatNumber(diffStats?.additions ?? 0)}{' '} - {t('views:commits.commitDetailsDiffAdditionsAnd', 'additions and')} {formatNumber(diffStats?.deletions ?? 0)}{' '} - {t('views:commits.commitDetailsDiffDeletions', 'deletions')} - </p> + <Layout.Flex direction="column" className="w-full pb-cn-xl min-h-[calc(100vh-var(--cn-page-nav-height))]" gapY="sm"> + {/* TODO: add goToDiff handler */} + <ChangedFilesShortInfo diffData={diffs} diffStats={diffStats} goToDiff={() => {}} /> + <CommitChanges data={diffs.map(item => ({ text: item.filePath, @@ -41,6 +32,6 @@ export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsSto }))} diffMode={2} /> - </div> + </Layout.Flex> ) } diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx index 6a875735ab..2b6d38e13f 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx @@ -1,7 +1,6 @@ import { ReactNode } from 'react' -import { ScrollArea, SearchFiles, Spacer } from '@/components' -import { SandboxLayout } from '@/views' +import { Layout, ScrollArea, SearchFiles } from '@/components' interface CommitsSidebarProps { navigateToFile: (file: string) => void @@ -11,24 +10,14 @@ interface CommitsSidebarProps { export const CommitSidebar = ({ navigateToFile, filesList, children }: CommitsSidebarProps) => { return ( - <div className="nested-sidebar-height sticky top-[var(--cn-page-nav-height)]"> - <SandboxLayout.LeftSubPanel className="w-[248px]"> - <SandboxLayout.Content className="flex h-full overflow-hidden p-0"> - <div className="flex size-full flex-col gap-3 pt-5"> - <div className="px-5"> - <SearchFiles - navigateToFile={navigateToFile} - filesList={filesList} - contentClassName="width-popover-max-width" - /> - </div> - <ScrollArea className="pr-cn-sm grid-cols-[100%]"> - {children} - <Spacer size={7} /> - </ScrollArea> - </div> - </SandboxLayout.Content> - </SandboxLayout.LeftSubPanel> + <div className="nested-sidebar-height pt-cn-md -mt-cn-md sticky top-[var(--cn-page-nav-height)]"> + <Layout.Flex direction="column" className="max-h-full overflow-hidden" gapY="sm"> + <SearchFiles navigateToFile={navigateToFile} filesList={filesList} contentClassName="width-popover-max-width" /> + + <ScrollArea className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5" classNameContent="w-[248px]"> + {children} + </ScrollArea> + </Layout.Flex> </div> ) } diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index 026d907ae5..ae21bc5b35 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -19,7 +19,6 @@ export interface RepoCommitDetailsViewProps extends RoutingProps { export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ useCommitDetailsStore, - showSidebar = true, toCommitDetails, toPullRequest, toCode, @@ -29,62 +28,69 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ const { t } = useTranslation() const { commitData } = useCommitDetailsStore() - return ( - <SandboxLayout.Main className="overflow-visible" fullWidth> - {loadingCommitDetails ? ( + if (loadingCommitDetails) { + return ( + <SandboxLayout.Main fullWidth> <SandboxLayout.Content> <Skeleton.List /> </SandboxLayout.Content> - ) : ( - <SandboxLayout.Content> - <Layout.Horizontal gap="3xs"> - <Text variant="heading-section">{t('views:commits.commitDetailsTitle', 'Commit')}</Text> - <Text variant="heading-section" color="foreground-2"> - {commitData?.sha?.slice(0, 7)} - </Text> - </Layout.Horizontal> + </SandboxLayout.Main> + ) + } + + return ( + <SandboxLayout.Main fullWidth> + <SandboxLayout.Content className="gap-y-cn-md pb-0"> + <Text variant="heading-section" as="h2"> + {t('views:commits.commitDetailsTitle', 'Commit')}  + <Text variant="heading-section" color="foreground-3" as="span"> + {commitData?.sha?.substring(0, 7)} + </Text> + </Text> + + <div className="border-cn-borders-3 rounded-3 overflow-hidden border"> + <Layout.Grid + flow="column" + justify="between" + align="center" + className="border-cn-borders-3 bg-cn-background-2 px-cn-md py-cn-sm border-b" + gapX="md" + > + <CommitTitleWithPRLink + toPullRequest={toPullRequest} + commitMessage={commitData?.title} + title={commitData?.title} + textProps={{ variant: 'body-code' }} + /> + + <Button variant="outline" asChild> + <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> + <IconV2 name="folder" /> + {t('views:commits.browseFiles', 'Browse Files')} + </Link> + </Button> + </Layout.Grid> - <div className="mt-4 rounded-md border border-cn-borders-2"> - <div className="flex items-center justify-between rounded-t-md border-b border-cn-borders-2 bg-cn-background-2 px-4 py-3"> - <CommitTitleWithPRLink - toPullRequest={toPullRequest} - commitMessage={commitData?.title} - title={commitData?.title} - textProps={{ variant: 'body-code' }} - /> - <Button variant="outline" asChild> - <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> - <IconV2 name="folder" /> - {t('views:commits.browseFiles', 'Browse Files')} - </Link> - </Button> - </div> - <div className="flex items-center justify-between px-4 py-2.5"> - <div className="gap-cn-2xs flex items-center"> - {commitData?.author?.identity?.name && ( - <Avatar name={commitData?.author?.identity?.name} size="md" rounded /> - )} - <Text variant="body-single-line-strong">{commitData?.author?.identity?.name || ''}</Text> - <Text variant="body-single-line-normal" color="foreground-3"> - committed on{' '} - <TimeAgoCard - timestamp={commitData?.author?.when} - dateTimeFormatOptions={{ dateStyle: 'medium' }} - textProps={{ color: 'foreground-4' }} - /> + <Layout.Flex align="center" justify="between" className="px-cn-md py-cn-sm"> + {commitData?.author?.identity?.name && commitData?.author?.when && ( + <Layout.Flex align="center" gapX="2xs"> + <Avatar name={commitData.author.identity.name} rounded /> + <Text variant="body-single-line-strong" color="foreground-1"> + {commitData.author.identity.name} </Text> - </div> - <CommitCopyActions toCommitDetails={toCommitDetails} sha={commitData?.sha || ''} /> - </div> - </div> - {!showSidebar && <Outlet />} - </SandboxLayout.Content> - )} - {showSidebar && ( - <SandboxLayout.Content className="border-cn-borders-4 mt-5 grid grid-cols-[auto_1fr] border-t py-0 pl-0 pr-5"> - <Outlet /> - </SandboxLayout.Content> - )} + <Text variant="body-single-line-normal"> + {t('views:commits.commitDetailsAuthored', 'authored')}{' '} + <TimeAgoCard timestamp={new Date(commitData.author.when).getTime()} /> + </Text> + </Layout.Flex> + )} + + <CommitCopyActions toCommitDetails={toCommitDetails} sha={commitData?.sha || ''} /> + </Layout.Flex> + </div> + + <Outlet /> + </SandboxLayout.Content> </SandboxLayout.Main> ) } diff --git a/packages/ui/src/views/repo/repo-sidebar/index.tsx b/packages/ui/src/views/repo/repo-sidebar/index.tsx index 9af02a0a10..ad9aa7172c 100644 --- a/packages/ui/src/views/repo/repo-sidebar/index.tsx +++ b/packages/ui/src/views/repo/repo-sidebar/index.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react' -import { Button, IconV2, Layout, ScrollArea, SearchFiles, Spacer } from '@/components' +import { Button, IconV2, Layout, ScrollArea, SearchFiles } from '@/components' interface RepoSidebarProps { navigateToNewFile: () => void @@ -35,10 +35,7 @@ export const RepoSidebar = ({ contentClassName="w-[800px]" /> - <ScrollArea className="-mr-5 grid-cols-[100%] pr-5"> - {children} - <Spacer size={10} /> - </ScrollArea> + <ScrollArea className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5">{children}</ScrollArea> </Layout.Flex> </div> </> diff --git a/packages/ui/tailwind-utils-config/components/accordion.ts b/packages/ui/tailwind-utils-config/components/accordion.ts index 543de66647..60716c0164 100644 --- a/packages/ui/tailwind-utils-config/components/accordion.ts +++ b/packages/ui/tailwind-utils-config/components/accordion.ts @@ -36,6 +36,11 @@ export default { } }, + '&:where(:focus-visible:not([data-disabled]))': { + boxShadow: 'var(--cn-ring-focus)', + outline: 'none' + }, + '&:where([data-state="open"])': { '.cn-accordion-trigger-indicator': { '@apply rotate-180': '' diff --git a/packages/ui/tailwind-utils-config/components/badge.ts b/packages/ui/tailwind-utils-config/components/badge.ts index f9166f3ff7..f0f2ac11a2 100644 --- a/packages/ui/tailwind-utils-config/components/badge.ts +++ b/packages/ui/tailwind-utils-config/components/badge.ts @@ -59,6 +59,7 @@ export default { paddingBlock: 'var(--cn-badge-md-py)', paddingInline: 'var(--cn-badge-md-px)', gap: 'var(--cn-badge-md-gap)', + minWidth: 'fit-content', '@apply select-none font-body-single-line-normal truncate': '', /** Size */ diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index 6d0add0a3c..9dae879d70 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -104,6 +104,7 @@ export default { height: 'var(--cn-btn-size-md)', gap: 'var(--cn-btn-gap-md)', flexShrink: '0', + minWidth: 'fit-content', border: 'var(--cn-btn-border) solid var(--cn-set-gray-surface-border)', '@apply font-body-single-line-strong select-none overflow-hidden inline-flex items-center justify-center whitespace-nowrap': '', From 12f6e539870393e5ceb8d4d350df12c051939fb6 Mon Sep 17 00:00:00 2001 From: Pranesh TG <pranesh.g@harness.io> Date: Tue, 12 Aug 2025 14:53:57 +0000 Subject: [PATCH 066/180] Show pagination only if data is available and update clear filter/search to secondary button variant (#10184) * 771ca1 Show pagination only if data is available and update clear filter/search to secondary button variant --- .../ui/src/views/labels/components/labels-list-view.tsx | 2 +- .../views/pipelines/execution-list/execution-list.tsx | 2 +- .../src/views/pipelines/pipeline-list/pipeline-list.tsx | 2 +- .../project-members/components/project-member-list.tsx | 2 +- .../views/repo/repo-branch/components/branch-list.tsx | 7 ++++++- .../src/views/repo/repo-branch/repo-branch-list-view.tsx | 6 +++++- .../ui/src/views/repo/repo-commits/repo-commits-view.tsx | 2 +- .../components/repo-settings-general-rules.tsx | 2 +- .../views/repo/repo-tags/components/repo-tags-list.tsx | 7 ++++++- .../ui/src/views/repo/repo-tags/repo-tags-list-page.tsx | 6 +++++- .../webhook-list/components/repo-webhook-list.tsx | 9 +++++++-- .../components/no-search-results/no-search-results.tsx | 2 +- 12 files changed, 36 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/views/labels/components/labels-list-view.tsx b/packages/ui/src/views/labels/components/labels-list-view.tsx index 99302e5e71..a074e204fe 100644 --- a/packages/ui/src/views/labels/components/labels-list-view.tsx +++ b/packages/ui/src/views/labels/components/labels-list-view.tsx @@ -64,7 +64,7 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ t('views:noData.checkSpelling', 'Check your spelling and filter options,'), t('views:noData.changeSearch', 'or search for a different keyword.') ]} - primaryButton={{ + secondaryButton={{ label: ( <> <IconV2 name="trash" /> diff --git a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx index cb64babc5b..faf15dcef3 100644 --- a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx +++ b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx @@ -69,7 +69,7 @@ export const ExecutionList = ({ t('views:noData.checkSpelling', 'Check your spelling and filter options,'), t('views:noData.changeSearch', 'or search for a different keyword.') ]} - primaryButton={{ + secondaryButton={{ label: t('views:noData.clearSearch', 'Clear search'), onClick: handleResetQuery }} diff --git a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx index 94d8935cfa..325bd9a1dd 100644 --- a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx +++ b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx @@ -62,7 +62,7 @@ export const PipelineList = ({ t('views:noData.checkSpelling', 'Check your spelling and filter options,'), t('views:noData.changeSearch', 'or search for a different keyword.') ]} - primaryButton={{ + secondaryButton={{ label: t('views:noData.clearSearch', 'Clear search'), onClick: handleResetQuery }} diff --git a/packages/ui/src/views/project/project-members/components/project-member-list.tsx b/packages/ui/src/views/project/project-members/components/project-member-list.tsx index c630a673fa..fec0e97529 100644 --- a/packages/ui/src/views/project/project-members/components/project-member-list.tsx +++ b/packages/ui/src/views/project/project-members/components/project-member-list.tsx @@ -39,7 +39,7 @@ const ProjectMembersList: FC<ProjectMembersListProps> = ({ } ) ]} - primaryButton={{ + secondaryButton={{ label: t('views:noData.clearSearch', 'Clear search'), onClick: handleResetFiltersQueryAndPages }} diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 1756a9787e..b68721a824 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -66,7 +66,7 @@ export const BranchesList: FC<BranchListPageProps> = ({ t('views:noData.startBranchDescription', 'Start branching to see your work organized.') ] } - primaryButton={ + secondaryButton={ isDirtyList ? { label: ( @@ -77,6 +77,11 @@ export const BranchesList: FC<BranchListPageProps> = ({ ), onClick: handleResetFiltersAndPages } + : undefined + } + primaryButton={ + isDirtyList + ? undefined : { label: ( <> diff --git a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx index 49f430f133..70f7ae11cb 100644 --- a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx +++ b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx @@ -45,6 +45,10 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ return `?page=${xNextPage}` }, [xNextPage]) + const canShowPagination = useMemo(() => { + return !isLoading && !!branchList.length + }, [isLoading, branchList.length]) + return ( <SandboxLayout.Main> <SandboxLayout.Content className={cn({ 'h-full': !isLoading && !branchList.length && !searchQuery })}> @@ -92,7 +96,7 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ isDirtyList={isDirtyList} {...routingProps} /> - {!isLoading && ( + {canShowPagination && ( <Pagination indeterminate hasNext={xNextPage > 0} diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index a7216b952b..9fe6f574e0 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -88,7 +88,7 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ "Your commits will appear here once they're made. Start committing to see your changes reflected." ) ]} - primaryButton={ + secondaryButton={ isDirtyList ? { label: ( diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index 44c1fb8ad6..dd508211c4 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -260,7 +260,7 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ } ) ]} - primaryButton={{ + secondaryButton={{ label: t('views:noData.clearSearch', 'Clear search'), onClick: resetSearch }} diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index a78a016d99..4879ac7e70 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -79,7 +79,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ ] } textWrapperClassName={isDirtyList ? '' : 'max-w-[360px]'} - primaryButton={ + secondaryButton={ isDirtyList ? { label: ( @@ -90,6 +90,11 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ ), onClick: onResetFiltersAndPages } + : undefined + } + primaryButton={ + isDirtyList + ? undefined : { label: ( <> diff --git a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx index 3b90b9947f..41fd6192ac 100644 --- a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx +++ b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx @@ -45,6 +45,10 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ return `?page=${xNextPage}` }, [xNextPage]) + const canShowPagination = useMemo(() => { + return !isLoading && !!tagsList.length + }, [isLoading, tagsList.length]) + return ( <SandboxLayout.Main> <SandboxLayout.Content @@ -92,7 +96,7 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ <Spacer size={5} /> - {!isLoading && ( + {canShowPagination && ( <Pagination indeterminate hasNext={xNextPage > 0} diff --git a/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx b/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx index eb75324a34..45929c5082 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-list/components/repo-webhook-list.tsx @@ -88,17 +88,22 @@ export function RepoWebhookList({ 'Add or manage webhooks to automate tasks and connect external services to your project.' ) ]} - primaryButton={ + secondaryButton={ isDirtyList ? { label: ( <> <IconV2 name="trash" /> - {t('views:noData.clearFilters', 'Clear Filters')} + {t('views:noData.clearSearch', 'Clear Search')} </> ), onClick: handleReset } + : undefined + } + primaryButton={ + isDirtyList + ? undefined : { label: ( <> diff --git a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx index e4e2c2c7d5..fe5260febf 100644 --- a/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx +++ b/packages/ui/src/views/user-management/components/page-components/content/components/users-list/components/no-search-results/no-search-results.tsx @@ -18,7 +18,7 @@ export const NoSearchResults = () => { type: 'users' }) ]} - primaryButton={{ + secondaryButton={{ label: ( <> <IconV2 name="trash" /> From 27b1eac6d874de3fef265031169317159e1154e0 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Tue, 12 Aug 2025 15:50:01 +0000 Subject: [PATCH 067/180] fix: design for sticky file headers (#10181) * b539cf fix: design for sticky file headers --- packages/ui/src/views/layouts/PullRequestLayout.tsx | 2 +- packages/ui/src/views/layouts/subheader-wrapper.tsx | 11 +---------- .../pull-request/commits/pull-request-commits.tsx | 1 + .../components/pull-request-accordian.tsx | 8 ++++---- .../changes/pull-request-changes-filter.tsx | 2 +- .../details/pull-request-conversation-page.tsx | 4 ++-- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/views/layouts/PullRequestLayout.tsx b/packages/ui/src/views/layouts/PullRequestLayout.tsx index 9cbc8ef754..1aa127a152 100644 --- a/packages/ui/src/views/layouts/PullRequestLayout.tsx +++ b/packages/ui/src/views/layouts/PullRequestLayout.tsx @@ -50,7 +50,7 @@ export const PullRequestLayout: FC<PullRequestLayoutProps> = ({ )} <Tabs.NavRoot> - <Tabs.List className="-mx-6 mb-cn-xl px-6" variant="overlined"> + <Tabs.List className="-mx-6 mb-cn-sm px-6" variant="overlined"> <Tabs.Trigger value={PullRequestTabsKeys.CONVERSATION} icon="chat-bubble-empty" diff --git a/packages/ui/src/views/layouts/subheader-wrapper.tsx b/packages/ui/src/views/layouts/subheader-wrapper.tsx index 54a1213233..db4ff9940c 100644 --- a/packages/ui/src/views/layouts/subheader-wrapper.tsx +++ b/packages/ui/src/views/layouts/subheader-wrapper.tsx @@ -3,14 +3,5 @@ import { FC } from 'react' import { cn } from '@/utils' export const SubHeaderWrapper: FC<{ children?: React.ReactNode; className?: string }> = ({ children, className }) => { - return ( - <div - className={cn( - 'layer-high sticky top-[var(--cn-breadcrumbs-height)] bg-cn-background-1 rounded-[inherit]', - className - )} - > - {children} - </div> - ) + return <div className={cn('bg-cn-background-1 rounded-[inherit]', className)}>{children}</div> } diff --git a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx index c4064403be..908cdabe81 100644 --- a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx +++ b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx @@ -46,6 +46,7 @@ const PullRequestCommitsView: FC<RepoPullRequestCommitsViewProps> = ({ <CommitsList toCode={toCode} toCommitDetails={toCommitDetails} + className="mt-cn-sm" data={commitsList.map((item: TypesCommit) => ({ sha: item.sha, parent_shas: item.parent_shas, diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 8cf02fb285..cb479cfd6f 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -320,10 +320,10 @@ export const PullRequestAccordion: React.FC<{ onValueChange={onToggle} indicatorPosition="left" > - <Accordion.Item value={header?.text ?? ''} className="border-cn-borders-2 rounded-3 border"> + <Accordion.Item value={header?.text ?? ''} className="rounded-3 border-none"> <Accordion.Trigger - className="bg-cn-background-2 rounded-tl-3 rounded-tr-3 px-4 py-2 [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" - headerClassName="sticky top-[138px] z-10 border-cn-borders-2 border-b" + className="rounded-t-3 bg-cn-background-2 px-4 py-2 [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" + headerClassName="z-[18] sticky top-[107px] border-cn-borders-2 border rounded-t-3" > <LineTitle header={header} @@ -340,7 +340,7 @@ export const PullRequestAccordion: React.FC<{ currentRefForDiff={currentRefForDiff} /> </Accordion.Trigger> - <Accordion.Content className="pb-0" containerClassName="rounded-bl-3 rounded-br-3"> + <Accordion.Content className="pb-0" containerClassName="rounded-b-3 border-x border-b border-cn-borders-2"> <div className="bg-transparent"> {(fileDeleted || isDiffTooLarge || fileUnchanged || header?.isBinary) && !showHiddenDiff ? ( <Layout.Vertical align="center" className="py-5"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index deecf032c3..6234a3e756 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -191,7 +191,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = <Layout.Horizontal align="center" justify="between" - className="gap-x-5 sticky top-[100px] z-20 bg-cn-background-1 pb-2 pt-1" + className="layer-high sticky top-[55px] gap-x-5 bg-cn-background-1 py-2" > <Layout.Horizontal className="grow gap-x-5"> <DropdownMenu.Root> diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx index 6b6d02d47b..481601e37d 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx @@ -37,9 +37,9 @@ export const PullRequestConversationPage: FC<PullRequestConversationPageProps> = principalProps }) => { return ( - <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px"> + <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px" className="mt-cn-sm"> <SandboxLayout.Column> - <SandboxLayout.Content className="pr-cn-xl pl-0 pt-0"> + <SandboxLayout.Content className="pl-0 pr-cn-xl pt-0"> {/*TODO: update with design */} {!!rebaseErrorMessage && ( <Alert.Root theme="danger" className="mb-5" dismissible> From c8c151c74c1984a8cb5e8cd302af15bc9007211d Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Tue, 12 Aug 2025 16:18:29 +0000 Subject: [PATCH 068/180] fix: checks bug and normalize git ref for calculateDivergence api (#10186) * 76c655 fix: checks bug and normalize git ref for calculateDivergence api --- .../pages-v2/pull-request/hooks/usePRChecksDecision.ts | 2 +- apps/gitness/src/pages-v2/repo/repo-branch-list.tsx | 9 ++++++++- apps/gitness/src/pages-v2/repo/repo-code.tsx | 2 +- apps/gitness/src/pages-v2/repo/repo-summary.tsx | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/hooks/usePRChecksDecision.ts b/apps/gitness/src/pages-v2/pull-request/hooks/usePRChecksDecision.ts index 9a02e260f9..b976a4a650 100644 --- a/apps/gitness/src/pages-v2/pull-request/hooks/usePRChecksDecision.ts +++ b/apps/gitness/src/pages-v2/pull-request/hooks/usePRChecksDecision.ts @@ -134,7 +134,7 @@ export function usePRChecksDecision({ setMessage(`${_count.success}/${total} ${pluralize('check', _count.success)} succeeded.`) } - setComplete(!_count.pending && !_count.running) + setComplete(!_count.pending && !_count.running && !!pullReqMetadata?.merged) } else { setComplete(false) } diff --git a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx index 9665d92541..a40199705c 100644 --- a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx @@ -22,6 +22,7 @@ import { useRuleViolationCheck } from '../../framework/hooks/useRuleViolationChe import usePaginationQueryStateWithStore from '../../hooks/use-pagination-query-state-with-store' import { PathParams } from '../../RouteDefinitions' import { orderSortDate, PageResponseHeader } from '../../types' +import { normalizeGitRef } from '../../utils/git-utils' import { useRepoBranchesStore } from './stores/repo-branches-store' import { transformBranchList } from './transform-utils/branch-transform' @@ -167,7 +168,13 @@ export function RepoBranchesListPage() { } calculateBranchDivergence({ - body: { requests: branches?.map(branch => ({ from: branch.name, to: repoMetadata?.default_branch })) || [] } + body: { + requests: + branches?.map(branch => ({ + from: normalizeGitRef(branch.name), + to: normalizeGitRef(repoMetadata?.default_branch) + })) || [] + } }) }, [calculateBranchDivergence, branches, repoMetadata?.default_branch]) diff --git a/apps/gitness/src/pages-v2/repo/repo-code.tsx b/apps/gitness/src/pages-v2/repo/repo-code.tsx index 743ffe2a89..ee83eacd29 100644 --- a/apps/gitness/src/pages-v2/repo/repo-code.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-code.tsx @@ -119,7 +119,7 @@ export const RepoCode = () => { calculateDivergence({ repo_ref: repoRef, body: { - requests: [{ from: fullGitRefWoDefault, to: repoData?.default_branch }] + requests: [{ from: normalizeGitRef(fullGitRefWoDefault), to: normalizeGitRef(repoData?.default_branch) }] } }) } diff --git a/apps/gitness/src/pages-v2/repo/repo-summary.tsx b/apps/gitness/src/pages-v2/repo/repo-summary.tsx index 455e7c6a8d..3feb37c8e1 100644 --- a/apps/gitness/src/pages-v2/repo/repo-summary.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-summary.tsx @@ -150,7 +150,7 @@ export default function RepoSummaryPage() { calculateDivergence({ repo_ref: repoRef, body: { - requests: [{ from: fullGitRefWoDefault, to: repoData?.default_branch }] + requests: [{ from: normalizeGitRef(fullGitRefWoDefault), to: normalizeGitRef(repoData?.default_branch) }] } }) } From 381929d697006e26a4dcab05891124172085239d Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Tue, 12 Aug 2025 18:20:50 +0000 Subject: [PATCH 069/180] Files - scroll issue for Preview and History tabs (#10182) * f01461 Removed unwanted code * 7c4309 Files - scroll issue for Preview and History tabs --- .../src/components-v2/file-content-viewer.tsx | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index 6d350e3ad5..5fc50627b3 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -7,6 +7,7 @@ import { getIsMarkdown, MarkdownViewer, Pagination, + ScrollArea, Skeleton, Tabs, ViewTypeValue @@ -156,7 +157,7 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp isNew={false} /> <Tabs.Root - className="flex h-full flex-col" + className="flex flex-col repo-files-height overflow-hidden" value={view as string} onValueChange={val => onChangeView(val as ViewTypeValue)} > @@ -172,28 +173,34 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp refType={selectedRefType} /> - <Tabs.Content value="preview" className="grow"> + <Tabs.Content value="preview" className="grow overflow-hidden"> {fileError && ( <div className="flex h-full items-center justify-center"> <FileReviewError onButtonClick={() => {}} className="my-0 h-full rounded-t-none border-t-0" /> </div> )} - {!fileError && getIsMarkdown(language) && <MarkdownViewer source={fileContent} withBorder />} + {!fileError && getIsMarkdown(language) && ( + <ScrollArea className="h-full grid-cols-[100%]"> + <MarkdownViewer source={fileContent} withBorder /> + </ScrollArea> + )} {!fileError && !getIsMarkdown(language) && ( - <CodeEditor - className="overflow-hidden" - height="100%" - language={language} - codeRevision={{ code: fileContent }} - onCodeRevisionChange={() => undefined} - themeConfig={themeConfig} - options={{ - readOnly: true - }} - theme={monacoTheme} - /> + <ScrollArea className="h-full grid-cols-[100%]"> + <CodeEditor + className="overflow-hidden" + height="100%" + language={language} + codeRevision={{ code: fileContent }} + onCodeRevisionChange={() => undefined} + themeConfig={themeConfig} + options={{ + readOnly: true + }} + theme={monacoTheme} + /> + </ScrollArea> )} </Tabs.Content> @@ -224,11 +231,11 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp /> </Tabs.Content> - <Tabs.Content value="history"> + <Tabs.Content value="history" className="grow overflow-hidden"> {isFetchingCommits ? ( <Skeleton.List /> ) : ( - <> + <ScrollArea className="h-full grid-cols-[100%]"> <CommitsList className="mt-cn-md" toCommitDetails={({ sha }: { sha: string }) => @@ -251,7 +258,7 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp getPrevPageLink={getPrevPageLink} getNextPageLink={getNextPageLink} /> - </> + </ScrollArea> )} </Tabs.Content> </Tabs.Root> From 2b488d9df455fb664061c976b129a9282dfdea38 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Tue, 12 Aug 2025 21:55:46 +0000 Subject: [PATCH 070/180] chore: bump UI (#10191) * f20354 chore: bump UI --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 9a39284904..3336d4a7c6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@harnessio/ui", "description": "Harness Canary UI component library", - "version": "0.0.97", + "version": "0.0.98", "private": false, "type": "module", "main": "./dist/index.js", From e095cfe354a73f81d3c2c0754c33166bc65330ed Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Wed, 13 Aug 2025 01:07:59 +0000 Subject: [PATCH 071/180] fix: Show email in Author filter dropdown on PR List page (#10189) * 7f0cce add line clamp * f64d50 fix lint check * da65cb check equality for display name and email * 3400a1 adding text wrap * 5430fa review comments * 0ff1f7 Merge branch 'main' into show-email-in-author-dropdown * 5c9170 fix: reverted default size for dropdown in filters * 918b7c feat: added support to add custom dropdown content class for filter fields * cbafb3 increase dropdown width to accommodate width --- .../actions/variants/combo-box.tsx | 2 +- .../src/components/filters/filters-field.tsx | 8 ++++++- packages/ui/src/components/filters/types.ts | 2 +- .../views/repo/constants/filter-options.ts | 2 +- .../pull-request/pull-request-list-page.tsx | 23 +++++++++++++++++-- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/filters/filters-bar/actions/variants/combo-box.tsx b/packages/ui/src/components/filters/filters-bar/actions/variants/combo-box.tsx index 786375e63d..b4d50331f5 100644 --- a/packages/ui/src/components/filters/filters-bar/actions/variants/combo-box.tsx +++ b/packages/ui/src/components/filters/filters-bar/actions/variants/combo-box.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react' import { DropdownMenu, ScrollArea, SearchInput } from '@components/index' export interface ComboBoxOptions { - label: string + label: string | ReactNode value: string } diff --git a/packages/ui/src/components/filters/filters-field.tsx b/packages/ui/src/components/filters/filters-field.tsx index ae519c2802..f8b4b0bd9e 100644 --- a/packages/ui/src/components/filters/filters-field.tsx +++ b/packages/ui/src/components/filters/filters-field.tsx @@ -3,6 +3,7 @@ import { useMemo, useState } from 'react' import { Button } from '@components/button' import { Checkbox } from '@components/checkbox' import { Label } from '@components/form-primitives' +import { cn } from '@utils/cn' import FilterBoxWrapper from './filter-box-wrapper' import Calendar from './filters-bar/actions/variants/calendar-field' @@ -26,6 +27,7 @@ export interface FiltersFieldProps< filterOption: FilterOptionConfig<T, CustomValue> removeFilter: () => void valueLabel?: string + dropdownContentClassName?: string shouldOpenFilter: boolean onOpenChange?: (open: boolean) => void onChange: (selectedValues: V) => void @@ -122,6 +124,7 @@ const FiltersField = <T extends string, V extends FilterValueTypes, CustomValue filterOption, removeFilter, shouldOpenFilter, + dropdownContentClassName, onOpenChange, onChange, value @@ -149,7 +152,10 @@ const FiltersField = <T extends string, V extends FilterValueTypes, CustomValue return ( <FilterBoxWrapper - contentClassName={filterOption.type === FilterFieldTypes.Calendar ? 'w-[250px]' : ''} + contentClassName={cn( + filterOption.type === FilterFieldTypes.Calendar ? 'w-[250px]' : '', + dropdownContentClassName + )} handleRemoveFilter={() => removeFilter()} isOpen={isOpen} setIsOpen={setIsOpen} diff --git a/packages/ui/src/components/filters/types.ts b/packages/ui/src/components/filters/types.ts index 393da054a0..8d8ffd5bcb 100644 --- a/packages/ui/src/components/filters/types.ts +++ b/packages/ui/src/components/filters/types.ts @@ -35,7 +35,7 @@ interface FilterOptionConfigBase<Key extends string, V = undefined> { interface ComboBoxFilterOptionConfig<Key extends string = string> extends FilterOptionConfigBase<Key, ComboBoxOptions> { type: FilterFieldTypes.ComboBox filterFieldConfig: { - options: Array<{ label: string; value: string }> + options: Array<{ label: string | React.ReactNode; value: string }> onSearch?: (query: string) => void noResultsMessage?: string loadingMessage?: string diff --git a/packages/ui/src/views/repo/constants/filter-options.ts b/packages/ui/src/views/repo/constants/filter-options.ts index 55d9b8e54e..3a6f1bc065 100644 --- a/packages/ui/src/views/repo/constants/filter-options.ts +++ b/packages/ui/src/views/repo/constants/filter-options.ts @@ -37,7 +37,7 @@ interface PRListFilterOptions { onAuthorSearch: (name: string) => void isPrincipalsLoading?: boolean isProjectLevel?: boolean - principalData: { label: string; value: string }[] + principalData: { label: string | React.ReactNode; value: string }[] customFilterOptions?: PRListFilterOptionConfig } diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index 795f19fdce..74948f6770 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -4,6 +4,7 @@ import { Button, ButtonGroup, IconV2, + Layout, ListActions, NoData, Pagination, @@ -14,6 +15,7 @@ import { Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' +import { PrincipalType } from '@/types' import { ExtendedScope, SandboxLayout } from '@/views' import { renderFilterSelectLabel } from '@components/filters/filter-select' import { @@ -110,17 +112,32 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ return principalData || (defaultSelectedAuthor && !principalsSearchQuery ? [defaultSelectedAuthor] : []) }, [principalData, defaultSelectedAuthor, principalsSearchQuery]) + const generateAuthorLabel = ({ + display_name, + email + }: Pick<PrincipalType, 'display_name' | 'email'>): React.ReactNode => + display_name !== email ? ( + <Layout.Flex align="center" className="gap-x-1"> + <Text wrap="nowrap">{display_name}</Text> + <Text color="foreground-4" variant="body-single-line-normal" lineClamp={1}> + ({email}) + </Text> + </Layout.Flex> + ) : ( + <Text lineClamp={1}>{display_name}</Text> + ) + const userSelectOptions = useMemo(() => { const otherUserOptions = computedPrincipalData .filter(user => !currentUser?.id || String(user?.id) !== String(currentUser?.id)) .map(user => ({ - label: user?.display_name || '', + label: generateAuthorLabel(user), value: String(user?.id) })) if (currentUser?.id && !principalsSearchQuery) { const currentUserOption = { - label: currentUser.display_name || '', + label: generateAuthorLabel(currentUser), value: String(currentUser.id) } return [currentUserOption, ...otherUserOptions] @@ -488,6 +505,8 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ filterOption, onChange, removeFilter, + // Increase width for Author filter to accomodate name and email as label + dropdownContentClassName: filterOption.value === 'created_by' ? 'w-[445px]' : '', value: value, onOpenChange: isOpen => { handleFilterOpen(filterOption.value, isOpen) From 5496e7738cba8dfc3c4073e20a054439d0742df3 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Wed, 13 Aug 2025 01:58:13 +0000 Subject: [PATCH 072/180] Conditional logic for Timestamps (#10190) * 66b54f Fixed PR conversation spacing issue * af85d6 updated PR Number color * 4c97c6 Conditional on Prefix for Timestamps --- packages/ui/src/components/time-ago-card.tsx | 19 +++++++--- .../views/repo/components/commits-list.tsx | 5 ++- .../components/pull-request-diff-viewer.tsx | 7 ++-- .../components/pull-request-header.tsx | 2 +- .../pull-request-timeline-item.tsx | 35 +++++++++++++------ .../repo-commit-details-view.tsx | 7 +++- .../ui/src/views/repo/repo-list/repo-list.tsx | 8 ++++- 7 files changed, 61 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index 6f9e06fadc..5a93f2a8e8 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -68,7 +68,7 @@ export const useFormattedTime = ( const isValidTime = !isNaN(time.getTime()) const now = new Date() const diff = now.getTime() - time.getTime() - const isBeyondCutoff = diff > cutoffDays * 24 * 60 * 60 * 1000 + const isBeyondCutoff = isValidTime && diff > cutoffDays * 24 * 60 * 60 * 1000 const formattedShort = () => { if (!isValidTime) return 'Unknown time' @@ -100,7 +100,8 @@ export const useFormattedTime = ( .replace(/^about\s+/i, '') .replace(/less than\s+/i, ''), formattedFull, - time + time, + isBeyondCutoff } } @@ -134,6 +135,8 @@ interface TimeAgoCardProps { timestamp?: string | number | null dateTimeFormatOptions?: Intl.DateTimeFormatOptions cutoffDays?: number + beforeCutoffPrefix?: string + afterCutoffPrefix?: string textProps?: TextProps<'time' | 'span'> & { ref?: Ref<HTMLSpanElement | HTMLTimeElement> } @@ -141,9 +144,13 @@ interface TimeAgoCardProps { export const TimeAgoCard = memo( forwardRef<HTMLButtonElement, TimeAgoCardProps>( - ({ timestamp, cutoffDays = 8, dateTimeFormatOptions, textProps }, ref) => { + ({ timestamp, cutoffDays = 8, dateTimeFormatOptions, beforeCutoffPrefix, afterCutoffPrefix, textProps }, ref) => { const [isOpen, setIsOpen] = useState(false) - const { formattedShort, formattedFull } = useFormattedTime(timestamp, cutoffDays, dateTimeFormatOptions) + const { formattedShort, formattedFull, isBeyondCutoff } = useFormattedTime( + timestamp, + cutoffDays, + dateTimeFormatOptions + ) if (timestamp === null || timestamp === undefined) { return ( @@ -162,12 +169,14 @@ export const TimeAgoCard = memo( const handleClickContent = (event: MouseEvent) => { event.stopPropagation() } + const hasPrefix = beforeCutoffPrefix || afterCutoffPrefix + const prefix = hasPrefix ? (isBeyondCutoff ? afterCutoffPrefix : beforeCutoffPrefix) : undefined return ( <Popover.Root open={isOpen} onOpenChange={setIsOpen}> <Popover.Trigger className="cn-time-ago-card-trigger" onClick={handleClick} ref={ref}> <Text<'time'> as="time" {...textProps} ref={textProps?.ref as Ref<HTMLTimeElement>}> - {formattedShort} + {prefix ? `${prefix} ${formattedShort}` : formattedShort} </Text> </Popover.Trigger> <Popover.Content onClick={handleClickContent} side="top"> diff --git a/packages/ui/src/views/repo/components/commits-list.tsx b/packages/ui/src/views/repo/components/commits-list.tsx index 0a590389b3..fed8037022 100644 --- a/packages/ui/src/views/repo/components/commits-list.tsx +++ b/packages/ui/src/views/repo/components/commits-list.tsx @@ -78,9 +78,12 @@ export const CommitsList: FC<CommitProps> = ({ data, toCommitDetails, toPullRequ {authorName && <Avatar name={authorName} src={avatarUrl} size="md" rounded />} <Text variant="body-single-line-strong">{authorName || ''}</Text> <Text variant="body-single-line-normal" color="foreground-3"> - committed on{' '} + committed{' '} <TimeAgoCard timestamp={when} + cutoffDays={3} + beforeCutoffPrefix="" + afterCutoffPrefix="on" dateTimeFormatOptions={{ dateStyle: 'medium' }} textProps={{ color: 'foreground-4' }} /> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index 8535f887da..85ad058c13 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -418,7 +418,7 @@ const PullRequestDiffViewer = ({ return ( <div className="bg-cn-background-1"> - {threads.map(thread => { + {threads.map((thread, idx) => { const parent = thread.parent const componentId = `activity-code-${parent?.id}` const parentIdAttr = `comment-${parent?.id}` @@ -430,14 +430,15 @@ const PullRequestDiffViewer = ({ setPrincipalsMentionMap={setPrincipalsMentionMap} mentions={parent?.payload?.mentions} payload={parent?.payload} - wrapperClassName="pb-3" + threadIndex={idx} + totalThreads={threads.length} key={parent.id} id={parentIdAttr} principalProps={principalProps} parentCommentId={parent.id} handleSaveComment={handleSaveComment} isLast={true} - contentWrapperClassName="col-start-1 row-start-1 col-end-3 row-end-3 px-4 pt-4 pb-1" + contentWrapperClassName="col-start-1 row-start-1 col-end-3 row-end-3 px-4 pb-2" header={[]} currentUser={currentUser} isComment diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index 71b32f0755..21aa661d3d 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -67,7 +67,7 @@ export const PullRequestHeader: React.FC<PullRequestTitleProps> = ({ <Layout.Horizontal gap="sm" align="end"> <Text as="h1" variant="heading-section" className="[&>*]:ml-cn-xs"> {title} - <Text as="span" variant="heading-section" color="foreground-2"> + <Text as="span" variant="heading-section" color="foreground-3"> #{number} </Text> </Text> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx index e93ba8e2c3..5801bb1b62 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx @@ -24,6 +24,25 @@ import { isEmpty } from 'lodash-es' import { replaceEmailAsKey, replaceMentionEmailWithId } from './utils' +//Utility function to calculate thread spacing based on position +const getThreadSpacingClasses = (threadIndex?: number, totalThreads?: number, isLast?: boolean) => { + if (threadIndex === undefined || totalThreads === undefined) { + return { + 'pb-cn-sm': !isLast, + 'pb-cn-md': isLast + } + } + const isFirstThread = threadIndex === 0 + const isLastThread = threadIndex === totalThreads - 1 + const isSingleThread = totalThreads === 1 + return { + 'pt-4 pb-2': isSingleThread, // Single conversation: lines to conversation to lines + 'pt-4 pb-1': isFirstThread && !isSingleThread, // First: lines to conversation + 'pt-1 pb-1': !isFirstThread && !isLastThread, // Middle: conversation to conversation + 'pt-1 pb-2': isLastThread && !isSingleThread // Last: conversation to lines + } +} + interface ItemHeaderProps { avatar?: ReactNode name?: string @@ -180,6 +199,8 @@ export interface TimelineItemProps { mentions?: PrincipalsMentionMap isReply?: boolean payload?: TypesPullReqActivity + threadIndex?: number + totalThreads?: number } const PullRequestTimelineItem: FC<TimelineItemProps> = ({ @@ -221,7 +242,9 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ principalsMentionMap, setPrincipalsMentionMap, mentions, - payload + payload, + threadIndex, + totalThreads }) => { const [comment, setComment] = useState('') const [isExpanded, setIsExpanded] = useState(!isResolved) @@ -296,15 +319,7 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ return ( <> <div id={id}> - <NodeGroup.Root - className={cn( - { - 'pb-cn-lg': !isLast, - 'pb-cn-md': isLast - }, - wrapperClassName - )} - > + <NodeGroup.Root className={cn(getThreadSpacingClasses(threadIndex, totalThreads, isLast), wrapperClassName)}> {!!icon && <NodeGroup.Icon className={cn({ 'border-transparent': hideIconBorder })}>{icon}</NodeGroup.Icon>} <NodeGroup.Title className={titleClassName}> {/* Ensure that header has at least one item */} diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index ae21bc5b35..a2c9b826f7 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -80,7 +80,12 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ </Text> <Text variant="body-single-line-normal"> {t('views:commits.commitDetailsAuthored', 'authored')}{' '} - <TimeAgoCard timestamp={new Date(commitData.author.when).getTime()} /> + <TimeAgoCard + timestamp={new Date(commitData.author.when).getTime()} + cutoffDays={3} + beforeCutoffPrefix="" + afterCutoffPrefix="on" + /> </Text> </Layout.Flex> )} diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index cc42c51b5b..20dcd49cf2 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -197,7 +197,13 @@ export function RepoList({ title={ <> {t('views:repos.updated', 'Updated')}{' '} - <TimeAgoCard timestamp={repo.timestamp} dateTimeFormatOptions={{ dateStyle: 'medium' }} /> + <TimeAgoCard + timestamp={repo.timestamp} + cutoffDays={3} + beforeCutoffPrefix="" + afterCutoffPrefix="on" + dateTimeFormatOptions={{ dateStyle: 'medium' }} + /> </> } description={<Stats pulls={repo.pulls} />} From 50e3ac8679f7dd5cfb628d87caaa58e94ab31b47 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Wed, 13 Aug 2025 02:20:30 +0000 Subject: [PATCH 073/180] feat: Add RBAC support (#10192) * b8a0aa fix typecheck * 6c1972 review comments * 56a92c remove unused component * 790f39 code cleanup - ai pass through * 663c34 code cleanup to use better names * 0b2ec7 add ts doc * 6afada add RBAC Split button * ba099a rename button export * 46af26 refactor interface RbacButtonProps * f94c8e cleanup * 3f1362 cleanup * ba0967 move RBAC types to ui pkg * 581d12 change revert * 13f020 cleanup * ecbb81 integrate Rbac Button on repo settings page * f9da3b integrate api with RBAC Button * c82f71 Add component context --- .../src/framework/context/MFEContext.tsx | 5 +++ .../src/framework/rbac/rbac-button.tsx | 20 +++++++++++ .../src/framework/rbac/rbac-split-button.tsx | 20 +++++++++++ apps/gitness/src/routes.tsx | 9 +++-- packages/ui/src/components/index.ts | 1 + packages/ui/src/components/rbac/index.ts | 1 + packages/ui/src/components/rbac/types.ts | 36 +++++++++++++++++++ packages/ui/src/context/component-context.tsx | 28 +++++++++++++++ packages/ui/src/context/index.ts | 1 + .../views/repo/repo-list/repo-list-page.tsx | 15 +++++--- .../repo-settings-general-delete.tsx | 27 +++++++++++--- 11 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 apps/gitness/src/framework/rbac/rbac-button.tsx create mode 100644 apps/gitness/src/framework/rbac/rbac-split-button.tsx create mode 100644 packages/ui/src/components/rbac/index.ts create mode 100644 packages/ui/src/components/rbac/types.ts create mode 100644 packages/ui/src/context/component-context.tsx diff --git a/apps/gitness/src/framework/context/MFEContext.tsx b/apps/gitness/src/framework/context/MFEContext.tsx index 7e132b90a5..75be433218 100644 --- a/apps/gitness/src/framework/context/MFEContext.tsx +++ b/apps/gitness/src/framework/context/MFEContext.tsx @@ -2,6 +2,8 @@ import { Context, createContext } from 'react' import { noop } from 'lodash-es' +import { PermissionsRequest } from '@harnessio/ui/components' + /** * @todo import from '@harness/microfrontends' * Currently, unable to do so due to npm access issues. @@ -52,8 +54,11 @@ export interface UseLogoutReturn { export declare const useLogout: () => UseLogoutReturn +export declare const usePermission: (permissionsRequest?: PermissionsRequest, deps?: Array<any>) => Array<boolean> + export interface Hooks { useLogout?: typeof useLogout + usePermission?: typeof usePermission } /**************/ diff --git a/apps/gitness/src/framework/rbac/rbac-button.tsx b/apps/gitness/src/framework/rbac/rbac-button.tsx new file mode 100644 index 0000000000..57646853fa --- /dev/null +++ b/apps/gitness/src/framework/rbac/rbac-button.tsx @@ -0,0 +1,20 @@ +import { Button, RbacButtonProps, Resource } from '@harnessio/ui/components' + +import { useMFEContext } from '../hooks/useMFEContext' + +export const RbacButton = ({ rbac, ...rest }: RbacButtonProps) => { + const { hooks } = useMFEContext() + + /** + * Enable the button only if the user has at least one required permission; otherwise, disable it. + */ + const hasPermission = + hooks + .usePermission?.({ + resource: rbac?.resource ?? ({} as Resource), + permissions: rbac?.permissions ?? [] + }) + ?.some(Boolean) ?? true + + return <Button {...rest} disabled={!hasPermission} /> +} diff --git a/apps/gitness/src/framework/rbac/rbac-split-button.tsx b/apps/gitness/src/framework/rbac/rbac-split-button.tsx new file mode 100644 index 0000000000..3fe523b5d3 --- /dev/null +++ b/apps/gitness/src/framework/rbac/rbac-split-button.tsx @@ -0,0 +1,20 @@ +import { RbacSplitButtonProps, Resource, SplitButton } from '@harnessio/ui/components' + +import { useMFEContext } from '../hooks/useMFEContext' + +export const RbacSplitButton = <T extends string>({ rbac, variant, ...rest }: RbacSplitButtonProps<T>) => { + const { hooks } = useMFEContext() + + /** + * Enable the button only if the user has at least one required permission; otherwise, disable it. + */ + const hasPermission = + hooks + .usePermission?.({ + resource: rbac?.resource ?? ({} as Resource), + permissions: rbac?.permissions ?? [] + }) + ?.some(Boolean) ?? true + + return <SplitButton<T> {...rest} variant={variant === 'outline' ? 'outline' : undefined} disabled={!hasPermission} /> +} diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index ae3e58db87..dfdd9b3394 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -1,6 +1,7 @@ import { Navigate, redirect } from 'react-router-dom' import { Breadcrumb, Layout, Sidebar } from '@harnessio/ui/components' +import { ComponentProvider } from '@harnessio/ui/context' import { EmptyPage, ProfileSettingsLayout, @@ -15,6 +16,8 @@ import { AppShell } from './components-v2/standalone/app-shell' import { AppProvider } from './framework/context/AppContext' import { AppRouterProvider } from './framework/context/AppRouterProvider' import { ExplorerPathsProvider } from './framework/context/ExplorerPathsContext' +import { RbacButton } from './framework/rbac/rbac-button' +import { RbacSplitButton } from './framework/rbac/rbac-split-button' import { CustomRouteObject, RouteConstants } from './framework/routing/types' import { CreateProject } from './pages-v2/create-project' import { LandingPage } from './pages-v2/landing-page-container' @@ -1248,8 +1251,10 @@ export const mfeRoutes = (mfeProjectId = '', mfeRouteRenderer: JSX.Element | nul element: ( <AppRouterProvider> <AppProvider> - {mfeRouteRenderer} - <AppShellMFE /> + <ComponentProvider components={{ RbacButton, RbacSplitButton }}> + {mfeRouteRenderer} + <AppShellMFE /> + </ComponentProvider> </AppProvider> </AppRouterProvider> ), diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 73347814f5..dbde0abd94 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -100,3 +100,4 @@ export * from './inputs' export * from './form-input' export * from './favorite' export * from './scope' +export * from './rbac' diff --git a/packages/ui/src/components/rbac/index.ts b/packages/ui/src/components/rbac/index.ts new file mode 100644 index 0000000000..c9f6f047dc --- /dev/null +++ b/packages/ui/src/components/rbac/index.ts @@ -0,0 +1 @@ +export * from './types' diff --git a/packages/ui/src/components/rbac/types.ts b/packages/ui/src/components/rbac/types.ts new file mode 100644 index 0000000000..de629e4267 --- /dev/null +++ b/packages/ui/src/components/rbac/types.ts @@ -0,0 +1,36 @@ +import { ButtonProps } from '@components/button' +import { SplitButtonProps } from '@components/split-button' + +/** + * @todo Replace with types from @harness/microfrontends once its accessible in the monorepo + */ +export interface Resource { + resourceType: ResourceType + resourceIdentifier?: string +} + +export enum ResourceType { + CODE_REPOSITORY = 'CODE_REPOSITORY' +} + +export enum PermissionIdentifier { + CODE_REPO_CREATE = 'code_repo_create', + CODE_REPO_DELETE = 'code_repo_delete' +} + +export interface PermissionsRequest { + resource: Resource + permissions: PermissionIdentifier[] +} + +interface RBACProps { + rbac?: PermissionsRequest +} + +/** + * Types for RBAC-enabled components. + * These components will automatically handle RBAC checks based on the provided `rbac` prop. + */ +export interface RbacButtonProps extends Omit<ButtonProps, 'resource'>, RBACProps {} + +export type RbacSplitButtonProps<T extends string> = SplitButtonProps<T> & RBACProps diff --git a/packages/ui/src/context/component-context.tsx b/packages/ui/src/context/component-context.tsx new file mode 100644 index 0000000000..7ee4e36afc --- /dev/null +++ b/packages/ui/src/context/component-context.tsx @@ -0,0 +1,28 @@ +// ComponentContext.tsx + +import React, { createContext, ReactNode, useContext } from 'react' + +import { RbacButtonProps, RbacSplitButtonProps } from '@components/rbac' + +interface ComponentContextValue { + RbacButton: React.ComponentType<RbacButtonProps> + RbacSplitButton: <T extends string>(props: RbacSplitButtonProps<T>) => JSX.Element +} + +const ComponentContext = createContext<ComponentContextValue | undefined>(undefined) + +export const ComponentProvider = ({ + components, + children +}: { + components: ComponentContextValue + children: ReactNode +}) => { + return <ComponentContext.Provider value={components}>{children}</ComponentContext.Provider> +} + +export const useComponents = () => { + const ctx = useContext(ComponentContext) + if (!ctx) throw new Error('useComponents must be used within ComponentProvider') + return ctx +} diff --git a/packages/ui/src/context/index.ts b/packages/ui/src/context/index.ts index 7aec64e54c..1488298dcc 100644 --- a/packages/ui/src/context/index.ts +++ b/packages/ui/src/context/index.ts @@ -2,3 +2,4 @@ export * from './portal-context' export * from './router-context' export * from './theme' export * from './translation-context' +export * from './component-context' diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index 39836263c1..b47e5c0ef4 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -1,7 +1,7 @@ import { FC, useCallback, useMemo, useRef, useState } from 'react' -import { IconV2, NoData, Pagination, Spacer, SplitButton, Text } from '@/components' -import { useRouterContext, useTranslation } from '@/context' +import { IconV2, NoData, Pagination, PermissionIdentifier, ResourceType, Spacer, Text } from '@/components' +import { useComponents, useRouterContext, useTranslation } from '@/context' import { SandboxLayout } from '@/views' import { ComboBoxOptions } from '@components/filters/filters-bar/actions/variants/combo-box' import { FilterFieldTypes, FilterOptionConfig } from '@components/filters/types' @@ -31,6 +31,7 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ scope, ...routingProps }) => { + const { RbacSplitButton } = useComponents() const { t } = useTranslation() const { navigate } = useRouterContext() const [showScope, setShowScope] = useState(false) @@ -175,7 +176,7 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ ref={filterRef} handleInputChange={handleSearch} headerAction={ - <SplitButton<string> + <RbacSplitButton<string> dropdownContentClassName="mt-0 min-w-[208px]" handleButtonClick={() => navigate(toCreateRepo?.() || '')} handleOptionChange={option => { @@ -203,10 +204,16 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ label: t('views:repos.importRepositories', 'Import Repositories') } ]} + rbac={{ + resource: { + resourceType: ResourceType.CODE_REPOSITORY + }, + permissions: [PermissionIdentifier.CODE_REPO_CREATE] + }} > <IconV2 name="plus" /> {t('views:repos.createRepository', 'Create Repository')} - </SplitButton> + </RbacSplitButton> } filterOptions={filterOptions} /> diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx index 9a15866dcc..14d72ca76e 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx @@ -1,7 +1,16 @@ import { FC } from 'react' -import { Button, ButtonLayout, FormSeparator, Layout, Spacer, Text } from '@/components' -import { useTranslation } from '@/context' +import { + Button, + ButtonLayout, + FormSeparator, + Layout, + PermissionIdentifier, + ResourceType, + Spacer, + Text +} from '@/components' +import { useComponents, useTranslation } from '@/context' import { ErrorTypes } from '@/views' export const RepoSettingsGeneralDelete: FC<{ @@ -12,6 +21,7 @@ export const RepoSettingsGeneralDelete: FC<{ openRepoArchiveDialog: () => void isUpdatingArchive?: boolean }> = ({ openRepoAlertDeleteDialog, apiError, openRepoArchiveDialog, archived, isUpdatingArchive }) => { + const { RbacButton } = useComponents() const { t } = useTranslation() return ( <Layout.Flex direction="column" gap="xl"> @@ -68,9 +78,18 @@ export const RepoSettingsGeneralDelete: FC<{ </Text> </Layout.Vertical> <ButtonLayout horizontalAlign="start"> - <Button type="button" variant="primary" theme="danger" onClick={openRepoAlertDeleteDialog}> + <RbacButton + type="button" + variant="primary" + theme="danger" + onClick={openRepoAlertDeleteDialog} + rbac={{ + resource: { resourceType: ResourceType.CODE_REPOSITORY }, + permissions: [PermissionIdentifier.CODE_REPO_DELETE] + }} + > {t('views:repos.deleteRepoButton', 'Delete Repository')} - </Button> + </RbacButton> </ButtonLayout> {apiError && apiError.type === ErrorTypes.DELETE_REPO && ( <> From 252fba1bcc5253acec2b191929fc7593a7d15cca Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Wed, 13 Aug 2025 04:04:37 +0000 Subject: [PATCH 074/180] fix: remove pagination on search (#10193) * 6dc567 chore: cleanup * eb95ee fix: remove pagination on search --- packages/ui/src/views/search/search-page.tsx | 22 ++------------------ 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/views/search/search-page.tsx b/packages/ui/src/views/search/search-page.tsx index cbfb43c507..1b7acbd241 100644 --- a/packages/ui/src/views/search/search-page.tsx +++ b/packages/ui/src/views/search/search-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, Layout, Pagination, SearchInput, Select, Spacer, Text, Toggle } from '@/components' +import { Button, Layout, SearchInput, Select, Spacer, Text, Toggle } from '@/components' import { useTranslation } from '@/context' import { RepositoryType, SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -86,7 +86,7 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ onClearFilters }) => { const { t } = useTranslation() - const { results, semanticResults, page, xNextPage, xPrevPage, setPage } = useSearchResultsStore() + const { results, semanticResults, page, setPage } = useSearchResultsStore() const handleSearchChange = useCallback( (value: string) => { @@ -100,14 +100,6 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ return page !== 1 || !!searchQuery }, [page, searchQuery]) - const getPrevPageLink = useCallback(() => { - return `?page=${xPrevPage}` - }, [xPrevPage]) - - const getNextPageLink = useCallback(() => { - return `?page=${xNextPage}` - }, [xNextPage]) - return ( <SandboxLayout.Main> <SandboxLayout.Content @@ -224,16 +216,6 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ )} <Spacer size={6} /> - - {!isLoading && (results?.length || semanticResults?.length) ? ( - <Pagination - indeterminate={true} - hasNext={xNextPage > 0} - hasPrevious={xPrevPage > 0} - getNextPageLink={getNextPageLink} - getPrevPageLink={getPrevPageLink} - /> - ) : null} </SandboxLayout.Content> </SandboxLayout.Main> ) From 7dc7dbf7df093c863c50d327fae2c92f55bcd8d7 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Wed, 13 Aug 2025 07:45:19 +0000 Subject: [PATCH 075/180] fix: merge strategies p2 fix (#10194) * 1f4869 fix: remove log * f8baba dix: merge strategies p2 fix --- apps/gitness/src/routes.tsx | 4 +++- .../repo-branch-rules/components/repo-branch-rules-fields.tsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index dfdd9b3394..43d8dd2425 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -784,7 +784,9 @@ export const routes: CustomRouteObject[] = [ <AppRouterProvider> <AppProvider> <Sidebar.Provider className="min-h-svh"> - <AppShell /> + <ComponentProvider components={{ RbacButton, RbacSplitButton }}> + <AppShell /> + </ComponentProvider> </Sidebar.Provider> </AppProvider> </AppRouterProvider> diff --git a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx index 35d2a1ae1b..92c7f1b623 100644 --- a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx +++ b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx @@ -332,8 +332,8 @@ export const BranchSettingsRuleListField: FC<{ <Fieldset className="gap-y-4 pl-[26px]"> {rule.submenuOptions.map(subOption => ( <Checkbox - key={subOption.id} - id={subOption.id} + key={`${rule.id}-${subOption.id}`} + id={`${rule.id}-${subOption.id}`} checked={rules[index].submenu?.includes(subOption.id as MergeStrategy)} onCheckedChange={checked => handleSubmenuChange(rule.id, subOption.id, checked === true)} label={subOption.label} From bdd65528446aecc27108f199c2775fd442622596 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Wed, 13 Aug 2025 08:00:56 +0000 Subject: [PATCH 076/180] light mode colors update (#10188) * 4cb016 Update switch.ts * 51e32c Update radio.ts * 6ed5f6 Update checkbox.ts * 52bd62 state upd * bed5fc selected brand state updated * 3f57e6 Update switch.ts * ef8ed8 Update checkbox.ts * e2ee14 Update label.ts * dc1e11 fix * 810365 fix * ce51bf Update button.ts * c679d9 Update avatar.ts * e83245 Update tooltip.ts * bae180 icon specific color for modules added * 0bbaef bug fix * 9986d8 bug fix * 30e7a5 dev config upd * 1cdf57 fix * 46732a upd * 0f79cd upd * 431c2c lg icon upd * a131c8 2xl icon-size removed * 473577 icons update --- .../design-tokens/$themes.json | 874 +++++++++++++++--- .../design-tokens/core/colors_hex.json | 102 +- .../design-tokens/core/colors_lch.json | 104 +-- .../design-tokens/core/dimensions.json | 10 +- .../mode/dark/default-deuteranopia.json | 22 + .../mode/dark/default-protanopia.json | 22 + .../mode/dark/default-tritanopia.json | 22 + .../design-tokens/mode/dark/default.json | 24 +- .../mode/dark/dimmer-deuteranopia.json | 22 + .../mode/dark/dimmer-protanopia.json | 22 + .../mode/dark/dimmer-tritanopia.json | 22 + .../design-tokens/mode/dark/dimmer.json | 22 + .../mode/dark/figma-icon-stroke-width.json | 2 +- .../mode/dark/high-contrast-deuteranopia.json | 22 + .../mode/dark/high-contrast-protanopia.json | 22 + .../mode/dark/high-contrast-tritanopia.json | 22 + .../mode/dark/high-contrast.json | 22 + .../mode/light/default-deuteranopia.json | 22 + .../mode/light/default-protanopia.json | 22 + .../mode/light/default-tritanopia.json | 22 + .../design-tokens/mode/light/default.json | 118 ++- .../mode/light/dimmer-deuteranopia.json | 22 + .../mode/light/dimmer-protanopia.json | 22 + .../mode/light/dimmer-tritanopia.json | 22 + .../design-tokens/mode/light/dimmer.json | 22 + .../mode/light/figma-icon-stroke-width.json | 2 +- .../light/high-contrast-deuteranopia.json | 22 + .../mode/light/high-contrast-protanopia.json | 22 + .../mode/light/high-contrast-tritanopia.json | 22 + .../mode/light/high-contrast.json | 22 + .../mode/light/icon-stroke-width.json | 2 +- .../components/avatar.ts | 4 +- .../components/button.ts | 2 +- .../components/checkbox.ts | 4 +- .../tailwind-utils-config/components/label.ts | 2 +- .../tailwind-utils-config/components/radio.ts | 2 +- .../components/switch.ts | 4 +- .../components/tooltip.ts | 9 +- 38 files changed, 1455 insertions(+), 294 deletions(-) diff --git a/packages/core-design-system/design-tokens/$themes.json b/packages/core-design-system/design-tokens/$themes.json index 680af808ed..1373f500fb 100644 --- a/packages/core-design-system/design-tokens/$themes.json +++ b/packages/core-design-system/design-tokens/$themes.json @@ -434,6 +434,233 @@ "pink.1000": "S:b44becf897db84ca9893e7bc6ee876d8047170b6," }, "$figmaVariableReferences": { + "pure.white": "52c419632539f17ba925c9feb62dd0997fb92fdd", + "pure.black": "03d57544487860db5ec0cbbd99a53328c00ec7ad", + "chrome.25": "ae6075847edad28383965a55fe01310f358fed19", + "chrome.50": "d5c9b8b55f8c02ce6a07a14015e8b0b7e8103009", + "chrome.100": "35fd77f7d38bd8f0bfb0fc0d4f7f3a62883f6902", + "chrome.150": "18e50f49838e2d1bca0d5098fdf2cfc0d353ab43", + "chrome.200": "ec6ebd01f3e71b5b64c2997a7f3966e96bb40368", + "chrome.300": "caea18adb5e2e77045ec70e30b16e2d1374bd424", + "chrome.400": "fc64ac62920f35141ae56bfb9b4e81435af83437", + "chrome.500": "3b19aeb3cc769350bf4d8da48ac08c11a4fb228b", + "chrome.600": "54f13a9fea5dcaf3292063ca0bd27f5f4c42755a", + "chrome.700": "1927e92c65fa54443f8aec6dfac17a4451ece999", + "chrome.800": "540d1b73bf1e2238ddcefe5ea1b4a64742dc1fb7", + "chrome.850": "dc5318382387623637702462cf86f13e87e54dc0", + "chrome.900": "1a87807bf31cb48a8fea896cb7766137620337d2", + "chrome.950": "085f3c57219e29a1ef1bb252ce87b9760cda4761", + "chrome.1000": "22e930b4e96b1fffc17e5ecff5ba135653c4ba60", + "tuna.25": "6a82de04c92085d5949b469d7b6540b97d403388", + "tuna.50": "1139e72428551ec9fb58fa048203f3ab0158aa4c", + "tuna.100": "18b570d191dccd950a51b94280ebd5a0cfcbea06", + "tuna.150": "a6c45b08225005b71fcf42fbeed5e929771c5eb2", + "tuna.200": "74b3a5036521b6a4f581a9cdd6fbbbeda243727e", + "tuna.300": "4262b86575bcb4b705617ab19c4a105dc6c241f6", + "tuna.400": "b9668eb88ae86e1bbc37f3096e9e167f5cc14af9", + "tuna.500": "b7e99072fbc2757a368b3b076446774656e7789d", + "tuna.600": "cab7e23cfbeb3799bb6d6d8b03e649c08c312ce8", + "tuna.700": "88e86ad8f6e636323c91ceb0381061bbf0e4ff7a", + "tuna.800": "ab6a7f877a89243eb6a3a961790d50b8db8ff409", + "tuna.850": "a6e2ed9ce51e598027a181a0c0547baf4cef3eb5", + "tuna.900": "289f6483bc124922a485a0713ab6f6b4ce12a975", + "tuna.950": "3115fb19229118ed67a6bc76f06bc76cff8b3e6c", + "tuna.1000": "984daddf01ded58576f307343b3e2e0d113c9f43", + "red.25": "17c655b51f23e51e69564d2b6661f87e089e9818", + "red.50": "ee865b781af07bf8af70c0d946c74a2fd77efd88", + "red.100": "8d5b8a7c2fa0a4ff5f355444fab407e5a0179c27", + "red.150": "11fef18b50abf2c07d0c9d464231ee1b5cbd2761", + "red.200": "91c2a849a54c0cb275dd7d855ea1b83d7459ba02", + "red.300": "18e3cda9bd86088ed7b36e5db8642300485da5db", + "red.400": "3daef62441eaa8589cbfbcfa9a4ab8d22c031837", + "red.500": "b9b564018ff89df47cd3e82e2e88353372578616", + "red.600": "8c6b1b1be04a82c3e34b76e74bbd0f06c5795388", + "red.700": "fdd4ac7069ab48f53a908395325b72cd73e54042", + "red.800": "3d1a9fa3142b23fb4e4910177767c2f24cc201ea", + "red.850": "bc2cb2368446ba6be5815998f75e5dfe98a22f23", + "red.900": "29707ae5fa9b5b5cc9f9bbd4748f34d4ead427bc", + "red.950": "50f3f7ab39a0ed99b704e6150d1224d604d5dca0", + "red.1000": "264873e81ee4404869340551cc58dbafbd8558aa", + "orange.25": "d74497ccb428fe8318a33089128af3d17314608e", + "orange.50": "9c7c6517d1149508022cf8e28ecc6596a89851f0", + "orange.100": "e357859b43127b3c501a2efdb6d78e89f2a5db40", + "orange.150": "e9432a330b9ac803f10a872efd86c18259ea5a91", + "orange.200": "bed4af9d2082e10c565a68768bfe118b778c1247", + "orange.300": "6db41aa7ca3db159c47fb098bed9696cb54d5d02", + "orange.400": "b1b5736e48f38698762bcc87c9d6ce7914f0227e", + "orange.500": "f1eaa5dce2008cf63ac67ed7f9e31dbbefb30858", + "orange.600": "8b0df950915030160fcb6d3c6a839657fef7984f", + "orange.700": "cb5d9b4dc173b9808189489e3367fdd37b891cfb", + "orange.800": "44e31ee98bbf00ea77a6ed03f3e0535d3ac6871b", + "orange.850": "98b0baa975b0e404b25e9c65244fc2835a320cfa", + "orange.900": "f0dd14c74d4fafcf96edb92a504e58d09440dc1e", + "orange.950": "fa7a2bc3309a27c7d67569ebd797faa0f1f3cc6e", + "orange.1000": "4020415b5c2548f65855cdd25ad7402ab5d06ca7", + "brown.25": "2f5421e4628a010bd1af219d1eb3f45cf7170f8a", + "brown.50": "a7438c1abdd3aa259800d7c72a8e5174069ec121", + "brown.100": "87e214b895a19d4908ca0531f4caad0ca611346d", + "brown.150": "ccd3c3c50e7f9220a5b6373215cc38253cb05318", + "brown.200": "4afecb32c9ea6d147d0afa04b329dc42392d23bb", + "brown.300": "11a9498015fe04a20a7fff44421d6bfe2725a2c5", + "brown.400": "c7d1a583e3ec749862bf63e3162c9c6525977be9", + "brown.500": "ec2c3b1f04279fad57a2300ba407b3fabe929a93", + "brown.600": "a756bdad7afcddc2b16c6e745953ad5b3483bdfd", + "brown.700": "1ee63f3d0a3cf73c6c6ee9adf2b5099cc7052a69", + "brown.800": "4c7fd08f3cf67434b0a59331b85a0277ddeff221", + "brown.850": "8bc8ba921283b61724734b63acf08adafde0e83f", + "brown.900": "85997ac2ef10e6bcfd0a28962b36fab626ade98f", + "brown.950": "f5225be85bdc6cfb1881e0d02733efb531865c3a", + "brown.1000": "ebe4198d367273095adc2b51f7b55791567fbe39", + "yellow.25": "615cc0fd884869bb0c4908bea5dfa0ea044442fd", + "yellow.50": "ce5a03b7528299c9644ac6e4a0af13ae1346034c", + "yellow.100": "c2781d47620e8d9495d8538522412298cf243dba", + "yellow.150": "05e75c4f99a6b2aad1d17bbd0cb1374266a44fdb", + "yellow.200": "94f7015cf02355ed76ea058c77499152584a8ee6", + "yellow.300": "d4382d055c2e852f8ae0da3f9d74c911ec6eab1c", + "yellow.400": "f2e254f2a6105258427de2e5069daf6c0ccf3b3d", + "yellow.500": "495773adfceb425fd8ba7a912b031b6efa04ca84", + "yellow.600": "87d2fb0a8e2233cb6140722c058ef28794fd4ced", + "yellow.700": "8043126913f3e0d37854f5ed2481740368e2da0f", + "yellow.800": "600acbdf53893938243d76b8a9ce2090c094f679", + "yellow.850": "96f599d0f4c767421daa05e71a971b0f5cd7b4f9", + "yellow.900": "73c7a35ba8ef4277b255fab8e9f07ae6dc1cce4d", + "yellow.950": "a4b61e2e0fbe2861783ef0d16416aca97fbbe132", + "yellow.1000": "b9f8494231ac1d9e91064e6d52ce87bf0723bb44", + "lime.25": "27a61fe5896c46b1985743f1866d77bfabb95f3b", + "lime.50": "584bde3b77c10df6796fe385b8e357371d4157ee", + "lime.100": "8c40e0ca2d0c8be80a4f5cc2137c1e08228cf72f", + "lime.150": "21a3fd5ddf458f90a5779c51d52ee033654bdb4f", + "lime.200": "911743949e8734bbc078108dcbd389de23bc4f18", + "lime.300": "cc124a400e33c863e8a576ec87f2ab84791dae40", + "lime.400": "4dc27066f3e9cbdd92cfecd5295d305c583875ac", + "lime.500": "addef49d2848e49a8c0cf8548d46acda77727cde", + "lime.600": "da4a480b93c186e7c746deeb93101a880eb6f447", + "lime.700": "7d555b4d2093ea48defe28a9000c7074bd05c826", + "lime.800": "288dbc58bd3d47fd0df277283ae2464d721fc4e0", + "lime.850": "ac695e942871a7d612a71334a0d8112da37936f9", + "lime.900": "794d87abada0ab221f2a891866c96c7920e68d10", + "lime.950": "297faded2a9ceef113e65b20673ee42bb42546c5", + "lime.1000": "dd60d1e7f143b0f184fe9bd2daa974a56b5121b3", + "green.25": "4f5369e88dcd18499a57f3e303c6df4822930e96", + "green.50": "49b322942e36d7ba52e57317f3327ce138a1c39c", + "green.100": "287c9d42d059eb3183ab06cc6ae86910b0a4696a", + "green.150": "f7eb50c3206deff113b611d1bed49310e120f314", + "green.200": "72f009263fdfbac45d18bfc23d2d2213e60449cd", + "green.300": "389857ed2b1063b6e9bbeecbe94de5cc2bb80c69", + "green.400": "b6e88cadfdfc0589eeb903be96e85a574967be0a", + "green.500": "cbd21f4e50e6424ab059526e24ae26afa0507873", + "green.600": "7d1d5003c21d0aa3b830249bfa298c629eb2b946", + "green.700": "a5226a1a03509c7ec8b9e35dba560635d323f234", + "green.800": "0a440f3617bf6a220225cdb5eefe8f7773d9e58b", + "green.850": "e85bfbcd546bbc08117fe89168946d2c585358a4", + "green.900": "e9e6bde7281bc9ea3cba48381c737ed758642a01", + "green.950": "7dbfc135f577e06404e010e398c1bae432d27ba9", + "green.1000": "e54fe7dfee1adead2c9c57de4aa3bcbfd368bbef", + "mint.25": "8bc6e19bbc96027797f74e51e8883d10a367fc93", + "mint.50": "29b96a5cea546c325f2c606935b94c748a6f418e", + "mint.100": "f1cbb72faaa2657d0739e2adf9072895a035fa38", + "mint.150": "2fd9d90231b1c59330096a815d61ff5a2293597f", + "mint.200": "99816b33171677e356a5718472377aae0c8eedc3", + "mint.300": "fbf3c59c4bfc0eec140057acfb9d01c479360bd9", + "mint.400": "d2bad453c669644d6317cbd787fb0c73838fdc02", + "mint.500": "7b607cb783a3b7de4244967b75e1c5852892232b", + "mint.600": "3d96cea7e8afb5e71cccbf8da155e538f57fa723", + "mint.700": "064cfd76590efe94233025e7b987d4b7acd353c6", + "mint.800": "2970b04365ab3da79c6abea3c54fc3d93a19cd74", + "mint.850": "c62bc5c3ea557c6aff3fd459e8b7c206f69953c2", + "mint.900": "93791a60a4690583ee078c1000ec178a149f8166", + "mint.950": "8d5368a28e6cd570585a1b18d3b29e4cce5c4637", + "mint.1000": "052855abdb01a20fd2201d1e91e04961a807cc16", + "cyan.25": "80c66267eee8a1cf333777cd4e42d9f2f6c02c0d", + "cyan.50": "2b2026ebaf7f15ed22f94ff8ad3ef1ef1f96c45c", + "cyan.100": "2adce54539ab28bb59ffe29c590f5d916213f558", + "cyan.150": "13c216fd468b3ba99c4b9c16f2c8acbb3883b1cc", + "cyan.200": "b5ca8c60e9e1129ff5077021ba8fa984a1a554e2", + "cyan.300": "be90471edbd4b6b74c9055e943f87bb53edc1a4f", + "cyan.400": "72fa9b2705a897568137bdb6e6a0f8e989e435d1", + "cyan.500": "4ea0b57db820a98091ecf6b11eb9e786029c80fe", + "cyan.600": "193ad7acd91b9a395612c9347f427c69f5c6a8fa", + "cyan.700": "4870a7aaba985d3052a60ddc42e42967c24c3e9e", + "cyan.800": "303defae08dc5bfbe9ec7dd691d7d63279a5187a", + "cyan.850": "5f338a5cf7030e423a9953e0a16faf2186b68f62", + "cyan.900": "0ec252ce704a02d1ec23d67661ad5e79bffc78db", + "cyan.950": "4c92bbeba1399f32cf1462094d7c77c880433c85", + "cyan.1000": "b811bb7e67229e269ee2b008b4e74c0acda7f541", + "blue.25": "5bebd07be28abcd21ce5f709920462be046e73d2", + "blue.50": "175691dbe40e01ccca8fdc32f9de1a42fe5d42fb", + "blue.100": "ecff2aa2eb0e6f3bc415c22cd9fbbcc8d5bf3d6f", + "blue.150": "2bc1b619fb278635382d039c4265aed5495d5c28", + "blue.200": "cb39037aa29172548515c777d14c40e3cd227484", + "blue.300": "f275014cc2011b1b9d77add7e714ac7738e6437f", + "blue.400": "83ad2125d830e88a209ca53b4423a8b26f644347", + "blue.500": "29a6c808a0f2a8dc33a17bec98f1bc075d58dac7", + "blue.600": "22ab20a552ed0bcd389c6705691a2bbe5f5aac67", + "blue.700": "0de869eca72b7fd1f73e0ee4c16e52a0fd9f39c2", + "blue.800": "4a172a17dee033863e840383d457653373b33fb6", + "blue.850": "c60af7ec9079c2a77239206898258fee0cd3ee7a", + "blue.900": "4676fdd02afd3ba152791afa3091b62af5d637b2", + "blue.950": "bc7463470e6ca5aa8d19f5f0832bb4087b8baea1", + "blue.1000": "39ca1df083667500db20f8dc6230de94a7173544", + "indigo.25": "1e051bd0d732caaa50fbe7b75055700c580965f7", + "indigo.50": "fb791a853aea1cf642282a20ea4a5118d1b037ee", + "indigo.100": "9f2643f6b2d70043ec08e6200f38ed7ed0ac8d3a", + "indigo.150": "aa3b35c4c28e0b234283764a94cc2083633fae76", + "indigo.200": "e77538637ad0870ff33bb7b5b754785e68ae06b1", + "indigo.300": "0cae97737cff14e000f3af46f23cc82e51312c79", + "indigo.400": "50223f1cb0f4e063c39907e33ea876f13a90a21d", + "indigo.500": "bf2e7cbced6172b78240ee9bc19dca13cef6e024", + "indigo.600": "9d0546f9c3d853e4bb9e53ad8fd99df9c5b9a45e", + "indigo.700": "0b3aae4e6d3a68c186acf0b16c4d321df02e8e3b", + "indigo.800": "c7aa16a29ca72984fa45999023717dda9e3d7529", + "indigo.850": "e1475f7aece07066c52140fb5e9ef4e47bfbf0a5", + "indigo.900": "26171e1ce82ac4d9a52c9ce8368a7d3daf9377b3", + "indigo.950": "f936b4a324ae56d07eb1dbc5c1741cc1b7424816", + "indigo.1000": "97a6cf884f01108046c3a10d3df521735e63c19e", + "violet.25": "5ab6cadd99e8ad463b349fb3f44386190d189cbe", + "violet.50": "e8bd5e618a46288cba10d3203293282e51a14158", + "violet.100": "60be982137a2bc497b65c8165d22ae9fa5043aae", + "violet.150": "5fdbcdf5a514e36723e1ef4233dded1eb6224523", + "violet.200": "1c57509f90900a359e0d12715caadea557490a6a", + "violet.300": "366751b76bcf8225ddaac2b746e8a163cf73e107", + "violet.400": "fa48b4b4d08e6f3883d357f2fc1e8e935399cf5d", + "violet.500": "fbd10981f0011e1cb0e5381b303eb4f74cd05e38", + "violet.600": "856451fe5b78f2c91aa68d3877aa2363c96213b6", + "violet.700": "647aa7a0410681b5947b939811151986b366a569", + "violet.800": "83245630b05e00340f2b79b8ae657e84b4656e73", + "violet.850": "8eb811f9cb1343531188f13950b5441d87158a0b", + "violet.900": "c01e954cc5c4e02c0f393bf204d4b878339c96c5", + "violet.950": "563aaa6e790164e390d3efcc15683700b993caf4", + "violet.1000": "6e31be4e73c4a42d180ee3fa4400f8d3e9d84368", + "purple.25": "3c01fe86e00f7fdf4bae7b1e16fcb5fc577e0a6e", + "purple.50": "e5482729116aa33b7dc90c26affc41eda96d6254", + "purple.100": "b8e1251c10c59df03a0acbd86903d65e1aacea2e", + "purple.150": "d8543758d19b266f44ed1bda954d52bd11ecd7eb", + "purple.200": "f8c593dc66783834ca84fe5032a836a7b1629ac4", + "purple.300": "adcb86070805e30319da1dfc68dca5517df70698", + "purple.400": "2922c142b1b6b4daa996c960dcbc24c5491c6e31", + "purple.500": "a020b1555bfc43dd729add3dd0f5caa45543707b", + "purple.600": "b7a8e2168f0b3409bfa976cdc85ac8d6792ca8a0", + "purple.700": "8bb3dcca42187beefb63712f07a704f4674198cf", + "purple.800": "c91502a1eb6c71020a0eb7a34b02aed24dd3d394", + "purple.850": "e7d9c6583abd9dc75e57eb51e3eaba3e193fab08", + "purple.900": "1ad941b48b9094ed09c6be2e69c593684025a5a6", + "purple.950": "9369acb40c83d4736cb0b242a608c72f61a47cdf", + "purple.1000": "2a96f0b360020f9f2a5ac3850a4b4b70e573763b", + "pink.25": "dddb9184ceb0bad0802db6eea54ebbedc5766f97", + "pink.50": "30c1a44af586a37fb76487302c201faa14165fa3", + "pink.100": "967ab963ee121f583127d7883cb37d9786ca7513", + "pink.150": "fee1bd84251922a49cc56fee2fcb1a22cf2489dc", + "pink.200": "d089f0d3f1268c46a860a9aa94fd7cf5964db462", + "pink.300": "e0bf8fb2d47ac0b5fd9428d8838de930d96cf453", + "pink.400": "986ccdd3f8b6a1c49f6a814d1cb117f08b94740a", + "pink.500": "8e8985665e6cff8801c4cf5a92fc05eea9269e6c", + "pink.600": "bb1acef38ec709d904bde91b2199e93e9c5ee2b7", + "pink.700": "803753fce87431ee9b1253134f9b3846dc55aa2e", + "pink.800": "abc92f7976b2e203998bab8482b1546c2a52523b", + "pink.850": "cfa2549e67a4e08db21c5f636f55062c20b6a30c", + "pink.900": "9751ce3d9877a74e0415e9f00274f845cc07bc57", + "pink.950": "24ddde3769d7b6e2355ac7c78ac21d64c54e38e4", + "pink.1000": "b8754e7ebf99dacdff9db965273e9aa8342714b1", "tracking.tighter": "a612b42a1649449f70026a485256dbbf92da6d92", "tracking.tight": "7a7825cb0165113837002059584dbce83e75a03f", "tracking.normal": "167b6fa66855f1546669aff796686de35514edd0", @@ -578,7 +805,123 @@ "lineHeight.multiplier.snug": "93bed03a85ad34fdee0d5bb86de4126a9ed08d20", "lineHeight.multiplier.normal": "a502673b02d83b87750c4e71c3091c317607e645", "lineHeight.multiplier.relaxed": "d8300d3cdd380169018f2db9feb5c1461dc098b3", - "lineHeight.multiplier.loose": "9216601ab3d5ad551db5e2da00a7eb5a11b0c7b1" + "lineHeight.multiplier.loose": "9216601ab3d5ad551db5e2da00a7eb5a11b0c7b1", + "spacing.0": "475f43f27d46745efb1151d34181ca93e485c323", + "spacing.1": "a88d7f7fa50cfde706a203262c7981abf2275b7d", + "spacing.2": "15d295c3581f15a4b4cc29bc3b2c9c4f02e88009", + "spacing.3": "a6fc69939f4e101df0846ffd8b244aa7f02b5b17", + "spacing.4": "d3e5a4611b45e280bf565885815d34b4f35b341f", + "spacing.5": "647b25f3b73bba5e0e7cad5197964902c3172271", + "spacing.6": "77538eb6f928a531f96653e917be0b3f0b04f1b2", + "spacing.7": "eabc95bcaec14d0cf6221b477c47891a1304c2d6", + "spacing.8": "559997e9c6f60fdd365bf970c84053807f2913ad", + "spacing.9": "4ca8e5e378ea054ccd38f617af4ec9db95487a17", + "spacing.10": "ca471ca65f9c794ddad2490cfb1e0e7e7467571d", + "spacing.11": "efe2a2a5b2a60d05c078a1c7a7950588a3a030c6", + "spacing.12": "ef37fcfa576beb8e91c51010ec3329ddbe985742", + "spacing.14": "1ae0ed4cd0c32b858ff791415edad283ef1c2af5", + "spacing.15": "7d0c7fdfa257a4833bb391e8197069da34dfc08b", + "spacing.16": "0d515ad8f73ae192356bd3fce04347ff8914bfa5", + "spacing.17": "ee98a1e0f71b564a1ccc117d34065908173dba1e", + "spacing.20": "8b4d19c935a89483725be041ce3e19182d81638b", + "spacing.24": "7b33d80680ee7f444bc05e404bf74b2fa7caac3b", + "spacing.28": "9e4816803f1f3739c0321eb5d351764da7ba9465", + "spacing.32": "f4344da90bf121d27d8fa80ddceeaa8d7c3e0f8e", + "spacing.36": "0127ed64e28cefa0e9b851a33c4eadc7ab340b30", + "spacing.40": "51e40236139d4fb0f3ef7d0e977093002253bc61", + "spacing.44": "648106e96c726104a5e737698f73f8f4dc85c150", + "spacing.48": "95524ca9ea66ebf7d20704e37d6672cbf39853b0", + "spacing.52": "dbf3f8ffb198ed478221f41f596efece5b17c59d", + "spacing.56": "3a73be8db57fa234fe23fb6c3debdc70e638ea55", + "spacing.60": "152dc4ff6aa1872e7691165d5769dbf8a4a45834", + "spacing.64": "b0478a9dae383d519e21253c882dfc85f702beb1", + "spacing.72": "c88ec41927fa21f2e77b259b95fdb95c420f60fe", + "spacing.80": "ec7856d13178d1e67c332ca681f3f8b407efecef", + "spacing.96": "bb4095e6118ca10fbb01085092263e8969247eba", + "spacing.px": "16fe4e3bc0b07f7f3d861c3fdee494d9d6d763e3", + "spacing.half": "ecfb730316867f575ec291afc3da55c32d57e10f", + "spacing.1-half": "ce6ffd93850926f8eb936f880fd2e006cf31c390", + "spacing.2-half": "0c948ab41410d710ba691e61e04d7523ebe2598e", + "spacing.3-half": "4e6064233484460281223f420b5015d31aeb5efc", + "spacing.4-half": "aca636954fe21b24a18ac1e2641cab71b2fce9a8", + "rounded.1": "4c8e9b29af661054f065ecfb517ffbf725d128ae", + "rounded.2": "5c9a222659e74e39ecb528719f03049f267ba7b6", + "rounded.3": "546448f107049e61fca518fd32468633bba50a51", + "rounded.4": "8355a7d82d6f8643701db782be4fb5cc61e0fe2a", + "rounded.5": "babea9851a66e652c1b014b1b4e5ae3703efb06a", + "rounded.6": "00e5a4d652385f2496ba39014f6c1daca0e26736", + "rounded.7": "f36f7c646daf87ab3f745e9f73c885a957b414e8", + "rounded.px": "3c023b948516d2071836e07348eb83cbba78a0e9", + "rounded.none": "dee9b0b76ac1ec8e902c7f542c80e776ce9fee0f", + "rounded.full": "4691cfbd097fadbf1bcc96d2f48a40f9852cc7bf", + "size.0": "995df3a4b1b9b4f63001434f1d6725f9b299eac0", + "size.1": "c84e4d0afc5158ef9686053a2bd5d6de68929ed9", + "size.2": "2d22b5c7aa2af1b3c275a61921eedea63b897388", + "size.3": "9f9e971446627d49af5f282dde4d32278b01e4ea", + "size.4": "7ec1913fa9d0ba99a9c9ab479c214d2134048880", + "size.5": "8735292eb38dbebd85dc8ee43f2d23ec7b42d283", + "size.6": "52822e56506b18300e0fbbdf2bb7e365e7bf7ff2", + "size.7": "6e063a94e51cebd7c9a219d176c7d787ca821d47", + "size.8": "a49fe5980cf079c97127f5438130dd0c5849570d", + "size.9": "9538fa61069ecb323a822444b1a50c5c31cc9a76", + "size.10": "58d1040e7172436e7ee0e20affe14526f17cdb1d", + "size.11": "a09585144f13eeed9212adba9567769b0f50fe0f", + "size.12": "e6dd420d85cc30a8bac1788e21d7b6572f069cde", + "size.14": "827d8aab20a4c2491204ab2667997e5290363d01", + "size.15": "62e1e9eb74ab5b4f461acfe135981095568e6e63", + "size.16": "5b7b04b1ade9b423f1d5b9720898fd4776956a36", + "size.17": "9d66ea8b44421a613827b5efb3a3dbbac30bdaef", + "size.20": "51754ed1d031bdf73b871a0194e054b6bcdb99d2", + "size.24": "c9ff66a843bc50465f8632bd973a3805f551c773", + "size.25": "7ed72372fd133e223724d79ceb68252f04f444c4", + "size.28": "3d67fc88f56362f7cf66ac51f2f88cd9bb61385c", + "size.32": "3f3df7e30c35ec68258af2b3cf03322b3c12fccd", + "size.36": "0a36770ba6fc4a8dfad5e6ef9cea63dd3922547b", + "size.40": "4c98adca6ab6f590dcc5dcb8a25d8b32a8b9cffd", + "size.44": "19cc767ac465f9c37655b12bcd8a66ce8be013b0", + "size.48": "442e1202060ff89b13c597f3ec9a29ddf3eaf839", + "size.52": "662fff88f39d26ffe3870ec41daeb3759e47163d", + "size.56": "e2bc8e5ee61c219b9e4ba8b4183d693867bbe248", + "size.60": "6f80ba41ca6c5d047a9d4ce27d1bfe05846ff2f6", + "size.64": "1c15ed3b9ec4bf2bdc3ff99093f7ec93633b944e", + "size.72": "c49f20e7589d9e10bb30c4b7ad45440a9a789d01", + "size.80": "f0e6046ee68bb26d971366e664771f36ffd3964f", + "size.90": "71e16ac59f8643ff6056f04a8628df44751a621b", + "size.96": "481530a9303c4b3abe4882fc4b13c01e82d6d32f", + "size.5-half": "02cdcd540dc35d36541a458b41b6570be8c77863", + "size.6-half": "8f662f722e060dd67f978dcbb70aca99c4e96042", + "size.11-half": "9a980f6bceb28276332e8cfc9e9f979914fd5419", + "size.px": "facc62d09b9290c8ae5a39e684e076f0078cce60", + "size.half": "a18c6726902f0dd03d09fda796c7793f1e5a3be9", + "size.1-half": "84a71e411cbd12832d0ab8d7d4581fcf24fa8f45", + "size.2-half": "a1e7743b917f7797d104b2a158c78db905590160", + "size.3-half": "ce599118fc3730b354fa05d449bc8f9b99f91720", + "size.4-half": "59175fbc6ac90d7b1bb49c2dd7adc7008266738e", + "borderWidth.0": "950d5f6e9b3fed7b192e5a150f74927b462bca29", + "borderWidth.1": "554d33ab7c952fbe9558f3c9562483abda105891", + "borderWidth.2": "c259dae921de08038e1efb5f295d8ab4be540990", + "borderWidth.4": "4f4280b0a24f816e2dfb5ba5a39199c24d7c4b61", + "borderWidth.8": "c1632660a255d86cde0647e810c28fb8bb6e8f65", + "iconSize.2xs": "7094639967ebfc8bd85f38a38d26f021655fec1a", + "iconSize.xs": "60a419ef3a3707069d4332226fd5de5026262723", + "iconSize.sm": "f857bc614dc0f9dafc613bf5f04eb305d194fe36", + "iconSize.md": "30dfaa7e6c403c1b81191920634c612f67662aea", + "iconSize.lg": "451a4646eb5faf5fc5ef43f979672417d5f019fd", + "iconSize.xl": "1da69111eca3d6355427d1523736d7ef13fb460c", + "opacity.5": "fba99779f0643e77980a4d90e7a48e8c8f093392", + "opacity.10": "c8297f2efa59795f049fc78e7e8393c4c3d42db5", + "opacity.20": "325ce676a14c53488a2fa61c8c44e04e269f9737", + "opacity.30": "a638aefcf451b8d97245893c7aa59869e7a82243", + "opacity.40": "370944b9142b1bacefa0312e20861cf9abefc775", + "opacity.50": "9ec5475265d5784de6162cb26dc9846f49f84bac", + "opacity.60": "24a25f920c1de8559e48a5828b4c982bcccf75c0", + "opacity.70": "24e70506912d5e69d78083757e230896de95107a", + "opacity.80": "33302799180ddad5fc64a467c05b838de900cbe4", + "opacity.90": "2e5e5354d12c683fe7425b9893eee79eaa1f8f42", + "opacity.100": "5481fb438b0a55df0a75453db8621b3b883468c2", + "logoSize.sm": "dc9f868330a7dca4cb948c8e0f5539426331ac14", + "logoSize.md": "c32145ffe89c63f259d9a9b73f7b945eb5c04969", + "logoSize.lg": "5abef04efe31ce7124dc51738ee7f70dc5f35ba9" }, "$figmaCollectionId": "VariableCollectionId:426:491161", "$figmaModeId": "426:0", @@ -1500,8 +1843,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -1515,6 +1858,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -1682,6 +2026,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -1702,6 +2047,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -1768,6 +2123,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -1847,9 +2204,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "$figmaCollectionId": "VariableCollectionId:1340:156918", "$figmaModeId": "1340:3", @@ -2462,8 +2817,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -2477,6 +2832,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -2644,6 +3000,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -2664,6 +3021,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -2730,6 +3097,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -2809,9 +3178,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -3423,8 +3790,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -3438,6 +3805,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -3605,6 +3973,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -3625,6 +3994,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -3691,6 +4070,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -3770,9 +4151,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -4383,8 +4762,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -4398,6 +4777,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -4565,6 +4945,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -4585,6 +4966,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -4651,6 +5042,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -4730,9 +5123,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -5344,8 +5735,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -5359,6 +5750,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -5526,6 +5918,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -5546,6 +5939,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -5612,6 +6015,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -5691,9 +6096,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -6305,8 +6708,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -6320,6 +6723,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -6487,6 +6891,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -6507,6 +6912,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -6573,6 +6988,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -6652,9 +7069,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -7266,8 +7681,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -7281,6 +7696,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -7448,6 +7864,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -7468,6 +7885,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -7534,6 +7961,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -7613,9 +8042,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -8227,8 +8654,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -8242,6 +8669,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -8409,6 +8837,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -8429,6 +8858,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -8495,6 +8934,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -8574,9 +9015,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -9188,8 +9627,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -9203,6 +9642,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -9370,6 +9810,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -9390,6 +9831,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -9456,6 +9907,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -9535,9 +9988,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -10149,8 +10600,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -10164,6 +10615,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -10331,6 +10783,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -10351,6 +10804,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -10417,6 +10880,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -10496,9 +10961,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -11110,8 +11573,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -11125,6 +11588,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -11292,6 +11756,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -11312,6 +11777,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -11378,6 +11853,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -11457,9 +11934,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -12071,8 +12546,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -12086,6 +12561,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -12253,6 +12729,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -12273,6 +12750,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -12339,6 +12826,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -12418,9 +12907,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -13032,8 +13519,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -13047,6 +13534,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -13214,6 +13702,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -13234,6 +13723,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -13300,6 +13799,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -13379,9 +13880,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -13992,8 +14491,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -14007,6 +14506,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -14174,6 +14674,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -14194,6 +14695,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -14260,6 +14771,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -14339,9 +14852,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -14952,8 +15463,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -14967,6 +15478,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -15134,6 +15646,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -15154,6 +15667,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -15220,6 +15743,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -15299,9 +15824,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -15912,8 +16435,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -15927,6 +16450,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -16094,6 +16618,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -16114,6 +16639,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -16180,6 +16715,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -16259,9 +16796,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -16872,8 +17407,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -16887,6 +17422,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -17054,6 +17590,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -17074,6 +17611,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -17140,6 +17687,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -17219,9 +17768,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -17832,8 +18379,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -17847,6 +18394,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -18014,6 +18562,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -18034,6 +18583,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -18100,6 +18659,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -18179,9 +18740,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -18792,8 +19351,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -18807,6 +19366,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -18974,6 +19534,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -18994,6 +19555,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -19060,6 +19631,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -19139,9 +19712,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -19776,8 +20347,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -19791,6 +20362,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -19958,6 +20530,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -19978,6 +20551,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -20044,6 +20627,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -20123,9 +20708,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -20736,8 +21319,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -20751,6 +21334,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -20918,6 +21502,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -20938,6 +21523,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -21004,6 +21599,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -21083,9 +21680,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -21696,8 +22291,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -21711,6 +22306,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -21878,6 +22474,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -21898,6 +22495,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -21964,6 +22571,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -22043,9 +22652,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -22656,8 +23263,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -22671,6 +23278,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -22838,6 +23446,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -22858,6 +23467,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -22924,6 +23543,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -23003,9 +23624,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -23616,8 +24235,8 @@ "iconStrokeWidth.xs": "e445b22e1f18af07002a1dc76f900dada5477383", "iconStrokeWidth.sm": "4377ec04d21e3851d7f71914a0051ed09daad5fb", "iconStrokeWidth.md": "580043ec5bcae9c2097bff41e3e24e49883451c9", - "iconStrokeWidth.lg": "e72029e275f9e020f7d772662521389ea67a279a", - "iconStrokeWidth.xl": "1e5cea2de7cba95cc1db412bd516dc72776136c0", + "iconStrokeWidth.lg": "f29dd93a5c1e0ef5552fe49e12b89a3e5dbde9ea", + "iconStrokeWidth.xl": "e72029e275f9e020f7d772662521389ea67a279a", "bg.0": "33d211d40bf9cb2b44b39504224ce89c82ce1a04", "bg.1": "633e5f77aa1bd1bd133852d8372b9c9f1231a9e4", "bg.2": "14e8d4b56cc273b508af8a2c58cb00e99e08d1d3", @@ -23631,6 +24250,7 @@ "border.1": "5ad2231a611605964ba70f80e7aabb48f04bc704", "border.2": "9084bef359e38136e0dddac25a464896456a9b1a", "border.3": "5ff5d5233f7cb0233ce7634bc480026668c818f6", + "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", "border.success": "b13758973865eab0b4d79028a48e457eae8bec47", "border.danger": "33d53c1486021bb3c9edce4123b484a2aabc8de0", "border.warning": "027e57e08a1edcaaa7fbe033902bb3f65497abb4", @@ -23798,6 +24418,7 @@ "set.violet.surface.bg": "5c87a91673aceb3fe3aeb32786b71743e9ab72a0", "set.violet.surface.border": "2948d30bd8e42511bbb7c6a9f3c703c4eb9237d3", "set.violet.surface.bg-hover": "0947d26c3e42ab66f47a78a6fa4c27110a47d005", + "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa", "comp.chat.ai-icon": "eb739da4d92fa16ad32a953f2d487fd33de4ae09", "comp.dialog.backdrop": "893e60ed671f891241a3084707057185398446ed", "comp.dialog.backdrop-nested": "4d6dd58029a86c9031f029656bab6dbc55f4db66", @@ -23818,6 +24439,16 @@ "comp.diff.hunk-lineNumber": "bccfad80b84e35edf4b47af2ca30057eba2b2ebf", "comp.diff.hunk-content-color": "68c5d6a95d475f7275062e3eb58dba464df3d059", "comp.diff.expand-content": "1f0c136f21d9abc2ce4a585f7750043932bfc30f", + "comp.diff.hljs": "a5aa8887c6d36068ead6b622ca75806a9e21d48a", + "comp.diff.hljs-comment": "ba54ebdd4f060185050bc73c1f28c69975f6ff2d", + "comp.diff.hljs-keyword": "2ad396c7b8ef99b8154ac874f2e2043f7dff07aa", + "comp.diff.hljs-title-class": "fda84cacdafc8b1157c1fbaad621abd0af2d645f", + "comp.diff.hljs-string": "e992ac0c70cf1c867272bd82fff804c6f2b06d40", + "comp.diff.hljs-title-function": "1d9ffc9e11db82a5d9b99fe181c4e6bcdd3e014d", + "comp.diff.hljs-name": "b2aae58649e3aed5a0d76b9d89ad48e31bc871c0", + "comp.diff.hljs-literal": "bcbc594088038f7fb703cfca2201b1db438e7d0d", + "comp.diff.hljs-attr": "c36b0c9b3d68bde0a73429136aef6e61fec35791", + "comp.input.bg": "baa07e89fb941a827a073f593cc94e73c4c86c73", "comp.link.text": "e387bb4185d01d8a3a7d52b15f3868ba40b31ec9", "comp.link.text-hover": "73352dab8131667fad74ea687f74d6673f7d04dc", "comp.pipeline.arrow.border": "e2beaac31e79580c5cb1d64e29cb8bffec601ad6", @@ -23884,6 +24515,8 @@ "gradient.skeleton.gradient-stop-1": "5427016b990b75f0d1ce60eef8cb9b96d75fbde2", "gradient.skeleton.gradient-stop-2": "01442c311e6da751c30c3518fc32970306767274", "gradient.skeleton.gradient-stop-3": "7fda4c834a3daf0c56bb59777c525670ae2dee3e", + "gradient.skeleton.mask.from": "a488b25a1290fe3c971b7cf2f3ff37dc26d01492", + "gradient.skeleton.mask.to": "d470f119c7ab311dc64d72594c91b38f681cd2c4", "gradient.pipeline.running.gradient-stop-1": "0c187d1ebb015ec66bac356009795316f84807ae", "gradient.pipeline.running.gradient-stop-2": "d1251f86c91bd0f29d8ac8cfa01056bc307156b4", "gradient.pipeline.running.gradient-stop-3": "b09bdab234cdf7c204cb153ec94ea0a9b2bb4976", @@ -23963,9 +24596,7 @@ "flow.onboard.ellipse-t-2.default": "dcf39668c229dd78326b87e66f3187c3575f2376", "flow.onboard.ellipse-t-2.danger": "5c480adf9a3351f8f66fe42722729fca8a4ec0da", "flow.onboard.ellipse-b.default": "d527a05727ae29b8d86af64efc7f7a26b01f22ea", - "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01", - "border.brand": "8db219e896acc4d828aaaf4add646d96540a84ea", - "comp.avatar.shadow": "2eb8cc124f0bea395b5584e42732709a8967bfaa" + "flow.onboard.ellipse-b.danger": "18261f1dee39801e11bab7001f53775aa48f8e01" }, "group": "mode" }, @@ -24144,7 +24775,6 @@ "btn.py.md": "dbadfa238e54880a13fdd4fc66f14dc44afab1b3", "link.gap.sm": "e63b120b0015804d7581ad9bdcde168886bf350c", "link.gap.md": "dd3aa27e1885fd22d4ab02fd4055d911d46d075c", - "btn.chevron.pr.md": "c0f76918843361ce010b1484e44d81b4ae507bc5", "badge.size.sm": "2aaf247c1dcb25b9744e4a10e2c0315c096468fe", "badge.size.md": "2b7170084780c9924660ee2aa997511f778b83ee", "badge.md.gap": "0c5418b2e9318e1ce2963748e72056c7c6736142", diff --git a/packages/core-design-system/design-tokens/core/colors_hex.json b/packages/core-design-system/design-tokens/core/colors_hex.json index e80e24d3d1..0d37f3ee82 100644 --- a/packages/core-design-system/design-tokens/core/colors_hex.json +++ b/packages/core-design-system/design-tokens/core/colors_hex.json @@ -10,13 +10,13 @@ }, "chrome": { "25": { - "$value": "#f9f9f9" + "$value": "#fbfbfb" }, "50": { - "$value": "#efeff1" + "$value": "#f6f6f7" }, "100": { - "$value": "#e7e7e9" + "$value": "#e9e9eb" }, "150": { "$value": "#dadadc" @@ -31,28 +31,28 @@ "$value": "#94949c" }, "500": { - "$value": "#87878f" + "$value": "#8a8a91" }, "600": { - "$value": "#74747d" + "$value": "#71717a" }, "700": { "$value": "#63636b" }, "800": { - "$value": "#47474d" + "$value": "#46464c" }, "850": { - "$value": "#313135" + "$value": "#333339" }, "900": { - "$value": "#232327" + "$value": "#27272c" }, "950": { - "$value": "#18181b" + "$value": "#222226" }, "1000": { - "$value": "#141416" + "$value": "#18181b" }, "$type": "color" }, @@ -73,7 +73,7 @@ "$value": "#cacbde" }, "300": { - "$value": "#b3b5cd" + "$value": "#b7b9d0" }, "400": { "$value": "#9193ab" @@ -106,97 +106,97 @@ }, "blue": { "25": { - "$value": "#f6fbfe" + "$value": "#f9faff" }, "50": { - "$value": "#dcebfe" + "$value": "#e5e9ff" }, "100": { - "$value": "#b4d4f9" + "$value": "#c7cffa" }, "150": { - "$value": "#9dc8fb" + "$value": "#b6c2fb" }, "200": { - "$value": "#7cb6f8" + "$value": "#9daff8" }, "300": { - "$value": "#54a1ff" + "$value": "#7a9afe" }, "400": { - "$value": "#2c93fa" + "$value": "#5d8cfe" }, "500": { - "$value": "#0085eb" + "$value": "#427ef6" }, "600": { - "$value": "#1276d4" + "$value": "#2b71e6" }, "700": { - "$value": "#0e65b5" + "$value": "#2460c5" }, "800": { - "$value": "#06559a" + "$value": "#2751a2" }, "850": { - "$value": "#134276" + "$value": "#2a3f75" }, "900": { - "$value": "#203556" + "$value": "#293356" }, "950": { - "$value": "#162740" + "$value": "#1d2540" }, "1000": { - "$value": "#111a29" + "$value": "#151929" }, "$type": "color" }, "cyan": { "25": { - "$value": "#f2fcfd" + "$value": "#f3fcff" }, "50": { - "$value": "#d0eff7" + "$value": "#daecff" }, "100": { - "$value": "#aaddf3" + "$value": "#b7d9fa" }, "150": { - "$value": "#7ed4f5" + "$value": "#90d0ff" }, "200": { - "$value": "#42c5f2" + "$value": "#6abfff" }, "300": { - "$value": "#1caad6" + "$value": "#32a6ee" }, "400": { - "$value": "#049bcd" + "$value": "#2d98da" }, "500": { - "$value": "#068eb9" + "$value": "#238bc9" }, "600": { - "$value": "#017ba4" + "$value": "#0e79b2" }, "700": { - "$value": "#01698e" + "$value": "#01679a" }, "800": { - "$value": "#00597a" + "$value": "#18577f" }, "850": { - "$value": "#17465e" + "$value": "#1a4563" }, "900": { "$value": "#153851" }, "950": { - "$value": "#152839" + "$value": "#142839" }, "1000": { - "$value": "#0c1a30" + "$value": "#0d1a25" }, "$type": "color" }, @@ -208,7 +208,7 @@ "$value": "#fee3e3" }, "100": { - "$value": "#fecaca" + "$value": "#ffd5d5" }, "150": { "$value": "#fbb4b4" @@ -301,7 +301,7 @@ "$value": "#fdfbee" }, "50": { - "$value": "#f9ecb1" + "$value": "#fff2b6" }, "100": { "$value": "#f4d55e" @@ -319,16 +319,16 @@ "$value": "#d57919" }, "500": { - "$value": "#bd6a1d" + "$value": "#bf6914" }, "600": { - "$value": "#a25918" + "$value": "#aa5814" }, "700": { - "$value": "#8d511c" + "$value": "#964b0a" }, "800": { - "$value": "#73461f" + "$value": "#7b4218" }, "850": { "$value": "#5f391f" @@ -448,7 +448,7 @@ "$value": "#d0f2d5" }, "100": { - "$value": "#a9e4b5" + "$value": "#bbebc4" }, "150": { "$value": "#55e284" @@ -466,7 +466,7 @@ "$value": "#0f944b" }, "600": { - "$value": "#0B7C42" + "$value": "#0b7c42" }, "700": { "$value": "#196e40" @@ -493,10 +493,10 @@ "$value": "#f2fdfa" }, "50": { - "$value": "#cef1e7" + "$value": "#dcf9f0" }, "100": { - "$value": "#a1e3d3" + "$value": "#c6f0e5" }, "150": { "$value": "#61ddc2" @@ -514,7 +514,7 @@ "$value": "#00937d" }, "600": { - "$value": "#00806a" + "$value": "#027d68" }, "700": { "$value": "#016e5c" diff --git a/packages/core-design-system/design-tokens/core/colors_lch.json b/packages/core-design-system/design-tokens/core/colors_lch.json index 854a5d60ba..6f1528cab4 100644 --- a/packages/core-design-system/design-tokens/core/colors_lch.json +++ b/packages/core-design-system/design-tokens/core/colors_lch.json @@ -11,13 +11,13 @@ }, "chrome": { "25": { - "$value": "lch(97.93% 0 134.54)" + "$value": "lch(98.5% 0.1 249.94)" }, "50": { - "$value": "lch(94.5% 1 285.31)" + "$value": "lch(97% 0.5 265)" }, "100": { - "$value": "lch(91.69% 1.01 285.32)" + "$value": "lch(92.5% 1.01 285.32)" }, "150": { "$value": "lch(87.09% 1.02 285.32)" @@ -32,28 +32,28 @@ "$value": "lch(61.5% 4.34 285.64)" }, "500": { - "$value": "lch(56.51% 4.47 285.69)" + "$value": "lch(57.5% 4 285.69)" }, "600": { - "$value": "lch(49% 5.19 285.84)" + "$value": "lch(48% 5.19 285.84)" }, "700": { "$value": "lch(42% 4.8 285.89)" }, "800": { - "$value": "lch(30.32% 3.75 285.83)" + "$value": "lch(30% 3.75 285.83)" }, "850": { - "$value": "lch(20.5% 2.62 285.74)" + "$value": "lch(21.5% 3.75 285.83)" }, "900": { - "$value": "lch(14% 2.73 285.86)" + "$value": "lch(16% 3 285.74)" }, "950": { - "$value": "lch(8.35% 2.2 285.9)" + "$value": "lch(13.5% 2.73 285.86)" }, "1000": { - "$value": "lch(6.38% 1.34 285.25)" + "$value": "lch(8.35% 2.2 285.9)" }, "$type": "color" }, @@ -62,7 +62,7 @@ "$value": "lch(98.4% 0.4 284)" }, "50": { - "$value": "lch(92.5% 2.2 284)" + "$value": "lch(92.5% 2.5 285.5)" }, "100": { "$value": "lch(89% 4.8 284)" @@ -74,7 +74,7 @@ "$value": "lch(82% 10.12 284)" }, "300": { - "$value": "lch(74% 13.1 283)" + "$value": "lch(75.5% 12.5 283)" }, "400": { "$value": "lch(61.5% 13 283)" @@ -113,7 +113,7 @@ "$value": "lch(92.5% 10.21 20.43)" }, "100": { - "$value": "lch(85.99% 20.39 21.3)" + "$value": "lch(89% 16 21.3)" }, "150": { "$value": "lch(80.15% 28.7 22.17)" @@ -254,7 +254,7 @@ "$value": "lch(98.51% 6.57 98.39)" }, "50": { - "$value": "lch(93.5% 30.5 93.5)" + "$value": "lch(95.4% 30.5 93.5)" }, "100": { "$value": "lch(86.14% 60.99 88.5)" @@ -272,16 +272,16 @@ "$value": "lch(60.31% 70.07 62.5)" }, "500": { - "$value": "lch(53.72% 61.73 61)" + "$value": "lch(53.72% 65.5 61)" }, "600": { - "$value": "lch(46% 55 60)" + "$value": "lch(47% 59.5 58.5)" }, "700": { - "$value": "lch(40.93% 46.26 60.5)" + "$value": "lch(40.93% 56 58.5)" }, "800": { - "$value": "lch(34.64% 35.72 61.5)" + "$value": "lch(34.64% 42 57)" }, "850": { "$value": "lch(28.18% 27.83 56)" @@ -353,7 +353,7 @@ "$value": "lch(92.5% 18.45 146.75)" }, "100": { - "$value": "lch(85.63% 31.28 147.73)" + "$value": "lch(89% 26 147.73)" }, "150": { "$value": "lch(80.71% 64.76 148.08)" @@ -398,10 +398,10 @@ "$value": "lch(98.42% 4.14 178.34)" }, "50": { - "$value": "lch(92.5% 13.19 176.43)" + "$value": "lch(95.5% 11 176.43)" }, "100": { - "$value": "lch(85.48% 24.15 178.07)" + "$value": "lch(91.5% 15.5 178.07)" }, "150": { "$value": "lch(80.61% 41.17 176.79)" @@ -419,7 +419,7 @@ "$value": "lch(54.2% 38.75 177.33)" }, "600": { - "$value": "lch(47.43% 35.71 175.08)" + "$value": "lch(46.5% 35 175.08)" }, "700": { "$value": "lch(40.95% 31.64 176.23)" @@ -443,97 +443,97 @@ }, "cyan": { "25": { - "$value": "lch(98.23% 3.67 207.25)" + "$value": "lch(98.23% 3.67 227.2)" }, "50": { - "$value": "lch(92.5% 11.87 218.88)" + "$value": "lch(92.5% 11.87 252.5)" }, "100": { - "$value": "lch(85.1% 21.1 231.13)" + "$value": "lch(85.1% 21.1 253)" }, "150": { - "$value": "lch(80.39% 32.22 230.4)" + "$value": "lch(80.39% 32.22 248.6)" }, "200": { - "$value": "lch(74% 42 232)" + "$value": "lch(74% 42 252.2)" }, "300": { - "$value": "lch(64.28% 41.18 233.22)" + "$value": "lch(64.28% 48 253)" }, "400": { - "$value": "lch(59.18% 41.74 239.34)" + "$value": "lch(59.18% 45 253)" }, "500": { - "$value": "lch(54.42% 38.19 237.03)" + "$value": "lch(54.42% 43 253)" }, "600": { - "$value": "lch(47.57% 35.48 239.65)" + "$value": "lch(47.57% 40.5 253)" }, "700": { - "$value": "lch(40.85% 32.12 241.01)" + "$value": "lch(40.85% 37 253)" }, "800": { - "$value": "lch(34.68% 28.97 241.99)" + "$value": "lch(34.68% 30 253)" }, "850": { - "$value": "lch(27.46% 21.44 243.5)" + "$value": "lch(27.46% 23.5 253)" }, "900": { - "$value": "lch(21.95% 20.15 253.18)" + "$value": "lch(21.95% 20.15 253)" }, "950": { - "$value": "lch(15.15% 13.97 256.02)" + "$value": "lch(15.15% 13.97 253)" }, "1000": { - "$value": "lch(9% 16.8 272)" + "$value": "lch(8.65% 9.95 253)" }, "$type": "color" }, "blue": { "25": { - "$value": "lch(98.3% 2.43 237.48)" + "$value": "lch(98.3% 2.43 280)" }, "50": { - "$value": "lch(92.5% 11.26 257.83)" + "$value": "lch(92.5% 11.26 280)" }, "100": { - "$value": "lch(83.49% 22.44 257.6)" + "$value": "lch(83.49% 22.44 280)" }, "150": { - "$value": "lch(78.9% 30.56 259.89)" + "$value": "lch(78.9% 30.56 280)" }, "200": { - "$value": "lch(72.03% 39.64 261.54)" + "$value": "lch(72.03% 39.64 280)" }, "300": { - "$value": "lch(64.63% 54.74 268.18)" + "$value": "lch(64.63% 54.74 280)" }, "400": { - "$value": "lch(59.19% 60.71 269)" + "$value": "lch(59.19% 64 280)" }, "500": { - "$value": "lch(53.78% 61 269.4)" + "$value": "lch(53.78% 68 280)" }, "600": { - "$value": "lch(48.5% 56.5 270.6)" + "$value": "lch(48.5% 68 280)" }, "700": { - "$value": "lch(41.5% 50 270.3)" + "$value": "lch(41.5% 60.5 280)" }, "800": { - "$value": "lch(35% 44.5 270)" + "$value": "lch(35% 50 280)" }, "850": { - "$value": "lch(27.23% 34.85 270)" + "$value": "lch(27.23% 34.85 280)" }, "900": { - "$value": "lch(21.78% 22.8 270)" + "$value": "lch(21.78% 22.8 280)" }, "950": { - "$value": "lch(15.04% 18.61 270)" + "$value": "lch(15.04% 18.61 280)" }, "1000": { - "$value": "lch(8.79% 12.01 270)" + "$value": "lch(8.79% 12.01 280)" }, "$type": "color" }, diff --git a/packages/core-design-system/design-tokens/core/dimensions.json b/packages/core-design-system/design-tokens/core/dimensions.json index 31eb0e0f49..52fd6dd4d8 100644 --- a/packages/core-design-system/design-tokens/core/dimensions.json +++ b/packages/core-design-system/design-tokens/core/dimensions.json @@ -339,6 +339,10 @@ "$type": "sizing", "$value": "384px" }, + "5-half": { + "$type": "sizing", + "$value": "22px" + }, "6-half": { "$type": "sizing", "$value": "26px" @@ -419,15 +423,15 @@ }, "md": { "$type": "sizing", - "$value": "{size.5}" + "$value": "{size.4-half}" }, "lg": { "$type": "sizing", - "$value": "{size.8}" + "$value": "{size.5-half}" }, "xl": { "$type": "sizing", - "$value": "{size.10}" + "$value": "{size.8}" } }, "opacity": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json index 803720782f..d1278488a0 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json index 6797a63e87..c64a0f44f1 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json index 414027cd45..14657df423 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index 1de91d31cf..226ca7993d 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -186,7 +186,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{blue.700}", "$description": "Selected state background color for active brand items." } }, @@ -2786,5 +2786,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json index e732ce2d52..68aa0f594e 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json index 35eab46470..f693464f2c 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json index 1d79ea42e2..fcaf15623a 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer.json b/packages/core-design-system/design-tokens/mode/dark/dimmer.json index e0b1be69b9..2fb01508a3 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer.json @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/figma-icon-stroke-width.json b/packages/core-design-system/design-tokens/mode/dark/figma-icon-stroke-width.json index b5fbaf9ae3..e794e046bd 100644 --- a/packages/core-design-system/design-tokens/mode/dark/figma-icon-stroke-width.json +++ b/packages/core-design-system/design-tokens/mode/dark/figma-icon-stroke-width.json @@ -18,7 +18,7 @@ }, "lg": { "$type": "borderWidth", - "$value": "2" + "$value": "1.2" }, "xl": { "$type": "borderWidth", diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json index c6ee19bef2..0e0e49d15e 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json index f34f3fa087..c5aa556917 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json index 601866b827..b104ab4402 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json index f034d19905..770ca380ae 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.200}" + }, + "testing": { + "$type": "color", + "$value": "{orange.200}" + }, + "security": { + "$type": "color", + "$value": "{blue.400}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.200}" + }, + "platform": { + "$type": "color", + "$value": "{purple.300}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json index 27c572ed8a..43ad0bc37f 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json index 476040c9a0..d08744feb1 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json index cc4ac2f356..81e601b205 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index f054bc8719..5d301bc19d 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -24,17 +24,17 @@ "text": { "1": { "$type": "color", - "$value": "{chrome.1000}", + "$value": "{pure.black}", "$description": "Highest contrast color for text and icons in important content. Creates maximum readability and emphasis for key information.\n\nCommon uses: Headings, labels, emphasized text." }, "2": { "$type": "color", - "$value": "{chrome.800}", + "$value": "{tuna.800}", "$description": "Primary color for text and icons in general content. Provides optimal readability for extended reading while reducing visual strain.\n\nCommon uses: Body text, primary content, default icons, form inputs." }, "3": { "$type": "color", - "$value": "{chrome.700}", + "$value": "{tuna.700}", "$description": "Reduced emphasis color for text and icons in secondary information. Creates visual hierarchy through lower contrast.\n\nCommon uses: Supporting text, metadata, secondary information, placeholders." }, "success": { @@ -102,7 +102,7 @@ } }, "$type": "color", - "$value": "{chrome.300}", + "$value": "{tuna.300}", "$description": "Light overlay for gentle interactive effects. Creates subtle visual feedback during user interaction.\n\nCommon uses: Button hover states, link hover states, soft interactions, menu items." }, "selected": { @@ -110,13 +110,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.22", + "value": "0.14", "space": "lch" } } }, "$type": "color", - "$value": "{chrome.300}", + "$value": "{tuna.300}", "$description": "Strong overlay for emphasized selection states. Creates distinct visual indication of active or selected elements.\n\nCommon uses: Selected list items, active tabs, chosen options, current navigation item." }, "disabled": { @@ -186,7 +186,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{blue.700}", "$description": "Selected state background color for active brand items." } }, @@ -259,12 +259,12 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.50}", + "$value": "{orange.25}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.50}", + "$value": "{pink.50}", "$description": "Selected state background color for active AI items." } } @@ -278,51 +278,51 @@ }, "bg": { "$type": "color", - "$value": "{chrome.300}", + "$value": "{tuna.200}", "$description": "Background color for prominent gray surfaces." }, "bg-hover": { "$type": "color", - "$value": "{chrome.400}", + "$value": "{tuna.300}", "$description": "Hover state background color for prominent gray surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.400}", + "$value": "{tuna.300}", "$description": "Selected state background color for prominent gray surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{tuna.850}", "$description": "Text color for subtle gray surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{tuna.50}", "$description": "Background color for subdued gray surfaces." }, "bg-hover": { "$type": "color", - "$value": "{chrome.150}", + "$value": "{tuna.100}", "$description": "Hover state background color for interactive soft gray surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.150}", + "$value": "{tuna.150}", "$description": "Selected state background color for active soft gray items." } }, "surface": { "text": { "$type": "color", - "$value": "{chrome.800}", + "$value": "{tuna.850}", "$description": "Standard text color for gray surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{chrome.25}", + "$value": "{tuna.25}", "$description": "Base background color for gray surfaces in default states." }, "border": { @@ -332,12 +332,12 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.50}", + "$value": "{tuna.50}", "$description": "Hover state background color for interactive gray surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{tuna.100}", "$description": "Selected state background color for active gray items." } } @@ -351,56 +351,56 @@ }, "bg": { "$type": "color", - "$value": "{green.600}", + "$value": "{mint.600}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{green.600}", + "$value": "{mint.600}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{green.50}", + "$value": "{mint.50}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{green.100}", + "$value": "{mint.100}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.100}", + "$value": "{mint.100}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.800}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{green.25}", + "$value": "{mint.25}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{green.100}", + "$value": "{mint.100}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{green.50}", + "$value": "{mint.50}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.100}", + "$value": "{mint.100}", "$description": "Selected state background color for active green items." } } @@ -506,7 +506,7 @@ "surface": { "text": { "$type": "color", - "$value": "{yellow.800}", + "$value": "{yellow.700}", "$description": "Standard text color for yellow surfaces with balanced readability." }, "bg": { @@ -1123,13 +1123,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.55", + "value": "0.2", "space": "lch" } } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Overlay color behind dialog components. Creates focus on the dialog by dimming content underneath." }, "backdrop-nested": { @@ -1137,13 +1137,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.15", + "value": "0.1", "space": "lch" } } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Overlay color behind dialog components. Creates focus on the dialog by dimming content underneath." }, "fade-start": { @@ -1831,7 +1831,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Transparent shadow color. Used when no shadow effect is desired." }, "1": { @@ -1845,7 +1845,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Light shadow color. Creates subtle depth for small UI elements." }, "2": { @@ -1859,7 +1859,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Medium shadow color with 60% opacity. Provides balanced depth for common UI components." }, "3": { @@ -1873,7 +1873,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Medium shadow color. Creates moderate depth for elevated components." }, "4": { @@ -1887,7 +1887,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Deep shadow color. Provides significant depth for floating elements." }, "5": { @@ -1895,13 +1895,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.1", + "value": "0.07", "space": "lch" } } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Very deep shadow color. Creates dramatic elevation for modal content." }, "6": { @@ -1915,7 +1915,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." }, "inner": { @@ -1929,7 +1929,7 @@ } }, "$type": "color", - "$value": "{chrome.900}", + "$value": "{tuna.800}", "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, @@ -1941,16 +1941,16 @@ "x": "0", "y": "0", "blur": "0", - "spread": "0.8", - "color": "{bg.1}", + "spread": "0.7", + "color": "{border.brand}", "type": "dropShadow" }, { "x": "0", "y": "0", "blur": "0", - "spread": "2", - "color": "{border.brand}", + "spread": "4", + "color": "{set.brand.surface.bg}", "type": "dropShadow" } ], @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json index 461a120437..00b078fa90 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json @@ -2822,5 +2822,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json index 8f305c1eae..c8bdc3d8fe 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json @@ -2822,5 +2822,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json index 06aa9acd5f..3105fbdf46 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json @@ -2822,5 +2822,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer.json b/packages/core-design-system/design-tokens/mode/light/dimmer.json index 59fa9b6f0a..eb1c075198 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer.json @@ -2804,5 +2804,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/figma-icon-stroke-width.json b/packages/core-design-system/design-tokens/mode/light/figma-icon-stroke-width.json index 63f49c3398..0cd2bb9a38 100644 --- a/packages/core-design-system/design-tokens/mode/light/figma-icon-stroke-width.json +++ b/packages/core-design-system/design-tokens/mode/light/figma-icon-stroke-width.json @@ -18,7 +18,7 @@ }, "lg": { "$type": "borderWidth", - "$value": "2" + "$value": "1.2" }, "xl": { "$type": "borderWidth", diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json index 3298c6b06c..f41087d36e 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json index 6a4c7b7a12..3d16da37a2 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json index fe3f2c8c59..5c0a74ad1d 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast.json b/packages/core-design-system/design-tokens/mode/light/high-contrast.json index d574a2f211..1a3cea16ca 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast.json @@ -2813,5 +2813,27 @@ } } } + }, + "icon": { + "devops": { + "$type": "color", + "$value": "{lime.500}" + }, + "testing": { + "$type": "color", + "$value": "{orange.400}" + }, + "security": { + "$type": "color", + "$value": "{blue.500}" + }, + "efficiency": { + "$type": "color", + "$value": "{cyan.400}" + }, + "platform": { + "$type": "color", + "$value": "{purple.500}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json b/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json index bfd5f87e3d..fe467e1a16 100644 --- a/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json +++ b/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json @@ -18,7 +18,7 @@ }, "lg": { "$type": "borderWidth", - "$value": "1" + "$value": "1.1" }, "xl": { "$type": "borderWidth", diff --git a/packages/ui/tailwind-utils-config/components/avatar.ts b/packages/ui/tailwind-utils-config/components/avatar.ts index 0248c1800c..150911ede7 100644 --- a/packages/ui/tailwind-utils-config/components/avatar.ts +++ b/packages/ui/tailwind-utils-config/components/avatar.ts @@ -38,8 +38,8 @@ export default { }, '.cn-avatar-fallback': { - backgroundColor: `var(--cn-set-brand-soft-bg)`, - color: `var(--cn-set-brand-soft-text)`, + backgroundColor: `var(--cn-set-gray-solid-bg)`, + color: `var(--cn-set-gray-solid-text)`, fontSize: 'inherit', borderRadius: 'inherit', boxShadow: 'inherit', diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index 9dae879d70..0b359597a2 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -106,7 +106,7 @@ export default { flexShrink: '0', minWidth: 'fit-content', border: 'var(--cn-btn-border) solid var(--cn-set-gray-surface-border)', - '@apply font-body-single-line-strong select-none overflow-hidden inline-flex items-center justify-center whitespace-nowrap': + '@apply font-body-single-line-normal select-none overflow-hidden inline-flex items-center justify-center whitespace-nowrap': '', '&:where(.cn-button-split-dropdown)': { diff --git a/packages/ui/tailwind-utils-config/components/checkbox.ts b/packages/ui/tailwind-utils-config/components/checkbox.ts index 458f2b9621..2a2c5c75ee 100644 --- a/packages/ui/tailwind-utils-config/components/checkbox.ts +++ b/packages/ui/tailwind-utils-config/components/checkbox.ts @@ -17,7 +17,7 @@ export default { '.cn-checkbox-label-wrapper': { display: 'flex', flexDirection: 'column', - gap: 'var(--cn-spacing-1)' + gap: 'var(--cn-layout-4xs)' }, '.cn-checkbox-root': { @@ -118,7 +118,7 @@ export default { }, '.cn-checkbox-label': { - font: 'var(--cn-body-strong) !important', + font: 'var(--cn-body-normal) !important', color: 'var(--cn-text-1) !important', '&:where(.disabled)': { color: 'var(--cn-state-disabled-text) !important' diff --git a/packages/ui/tailwind-utils-config/components/label.ts b/packages/ui/tailwind-utils-config/components/label.ts index a7944a6a72..e694d21ec4 100644 --- a/packages/ui/tailwind-utils-config/components/label.ts +++ b/packages/ui/tailwind-utils-config/components/label.ts @@ -1,6 +1,6 @@ export default { '.cn-label': { - '@apply font-body-strong': '', + '@apply font-body-normal': '', display: 'grid', gridTemplateColumns: 'auto auto', justifyContent: 'start', diff --git a/packages/ui/tailwind-utils-config/components/radio.ts b/packages/ui/tailwind-utils-config/components/radio.ts index 43a01cba6d..a1a6283835 100644 --- a/packages/ui/tailwind-utils-config/components/radio.ts +++ b/packages/ui/tailwind-utils-config/components/radio.ts @@ -17,7 +17,7 @@ export default { '.cn-radio-item-label-wrapper': { display: 'flex', flexDirection: 'column', - gap: 'var(--cn-spacing-1)' + gap: 'var(--cn-layout-4xs)' }, '.cn-radio-item': { diff --git a/packages/ui/tailwind-utils-config/components/switch.ts b/packages/ui/tailwind-utils-config/components/switch.ts index e702da2405..fd9b019953 100644 --- a/packages/ui/tailwind-utils-config/components/switch.ts +++ b/packages/ui/tailwind-utils-config/components/switch.ts @@ -8,7 +8,7 @@ export default { '.cn-switch-label-wrapper': { display: 'flex', flexDirection: 'column', - gap: 'var(--cn-spacing-1)' + gap: 'var(--cn-layout-4xs)' }, '.cn-switch-root': { @@ -85,7 +85,7 @@ export default { }, '.cn-switch-label': { - font: 'var(--cn-body-strong) !important', + font: 'var(--cn-body-normal) !important', color: 'var(--cn-text-1) !important', '&:where([disabled])': { color: 'var(--cn-state-disabled-text) !important' diff --git a/packages/ui/tailwind-utils-config/components/tooltip.ts b/packages/ui/tailwind-utils-config/components/tooltip.ts index eeb3962e63..e2deb4a7d9 100644 --- a/packages/ui/tailwind-utils-config/components/tooltip.ts +++ b/packages/ui/tailwind-utils-config/components/tooltip.ts @@ -3,11 +3,10 @@ export default { minWidth: 'var(--cn-tooltip-min)', maxWidth: 'var(--cn-tooltip-max)', borderRadius: 'var(--cn-tooltip-radius)', - border: 'var(--cn-tooltip-border) solid var(--cn-set-gray-soft-bg)', - background: 'var(--cn-set-gray-soft-bg)', + border: 'var(--cn-tooltip-border) solid var(--cn-set-gray-solid-bg)', + background: 'var(--cn-set-gray-solid-bg)', padding: 'var(--cn-tooltip-py) var(--cn-tooltip-px)', - color: 'var(--cn-set-gray-soft-text)', - boxShadow: 'var(--cn-shadow-2)', + color: 'var(--cn-set-gray-solid-text)', '@apply flex flex-col z-50 font-body-normal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95': '', @@ -16,7 +15,7 @@ export default { }, '&-arrow': { - color: 'var(--cn-set-gray-soft-bg)', + color: 'var(--cn-set-gray-solid-bg)', '@apply w-5 h-2': '' } } From 62085749021670226b4ba5eba13868175ec3c95c Mon Sep 17 00:00:00 2001 From: Pranesh TG <pranesh.g@harness.io> Date: Wed, 13 Aug 2025 08:12:46 +0000 Subject: [PATCH 077/180] Update offset for PR comment options dropdown menu to avoid click overlap (#10187) * bbe1c8 update correct offset value to more options dropdown * 2e1737 Update offset for PR comment options dropdown menu to avoid click overlap --- packages/ui/src/components/more-actions-tooltip.tsx | 1 + .../components/conversation/pull-request-timeline-item.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/more-actions-tooltip.tsx b/packages/ui/src/components/more-actions-tooltip.tsx index 3fe30b2227..1ae80ad139 100644 --- a/packages/ui/src/components/more-actions-tooltip.tsx +++ b/packages/ui/src/components/more-actions-tooltip.tsx @@ -96,3 +96,4 @@ export const MoreActionsTooltip: FC<MoreActionsTooltipProps> = ({ </DropdownMenu.Root> ) } +MoreActionsTooltip.displayName = 'MoreActionsTooltip' diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx index 5801bb1b62..540d8024f5 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx @@ -144,7 +144,7 @@ const ItemHeader: FC<ItemHeaderProps> = memo( </Text> </Layout.Horizontal> {isComment && !isDeleted && !isResolved && ( - <MoreActionsTooltip iconName="more-horizontal" sideOffset={-8} alignOffset={2} actions={actions} /> + <MoreActionsTooltip iconName="more-horizontal" sideOffset={4} alignOffset={0} actions={actions} /> )} </Layout.Horizontal> ) From b2eea15f2e608b878dc1f0ad2886b8caf4a6721d Mon Sep 17 00:00:00 2001 From: Pavel <pavel@pixelpoint.io> Date: Wed, 13 Aug 2025 13:04:46 +0400 Subject: [PATCH 078/180] Update repo-tags-list-page (#2060) * feat: update repo-tags-list-page * feat: enhance TimeAgoCard and RepoTagsList components with additional className props and improve layout responsiveness * small fixes * fix pretty * feat: rename className props to triggerClassName and contentClassName in TimeAgoCard component; update usage in RepoTagsListView --- .../content/docs/components/time-ago-card.mdx | 12 ++ packages/ui/locales/en/views.json | 6 +- packages/ui/src/components/alert-dialog.tsx | 2 +- .../images/no-data-tags-light.svg | 2 +- .../illustration/images/no-data-tags.svg | 73 +------------ .../src/components/more-actions-tooltip.tsx | 63 +++++++---- packages/ui/src/components/time-ago-card.tsx | 21 +++- .../components/create-branch-dialog.tsx | 1 + .../create-tag/create-tag-dialog.tsx | 19 ++-- .../repo-tags/components/repo-tags-list.tsx | 27 +++-- .../repo/repo-tags/repo-tags-list-page.tsx | 103 +++++++++--------- 11 files changed, 159 insertions(+), 170 deletions(-) diff --git a/apps/portal/src/content/docs/components/time-ago-card.mdx b/apps/portal/src/content/docs/components/time-ago-card.mdx index 59b1863982..de80cf1b55 100644 --- a/apps/portal/src/content/docs/components/time-ago-card.mdx +++ b/apps/portal/src/content/docs/components/time-ago-card.mdx @@ -90,5 +90,17 @@ return ( required: false, value: "'TextProps'", }, + { + name: "triggerClassName", + description: "Class names for the trigger element.", + required: false, + value: "'string'", + }, + { + name: "contentClassName", + description: "Class names for the content element.", + required: false, + value: "'string'", + }, ]} /> diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 381444d82b..673967203f 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -134,7 +134,7 @@ "createRuleButton": "Create Rule", "updatingRuleButton": "Updating Rule...", "creatingRuleButton": "Creating Rule...", - "createBranch": "Create Branch", + "createBranch": "Create branch", "branch": "Branch", "update": "Updated", "checkStatus": "Check status", @@ -279,8 +279,8 @@ "createTagRule": "Create tag rule", "createTagTitle": "Create a tag", "repoTagDescriptionPlaceholder": "Enter tag description here", - "creatingTagButton": "Creating Tag...", - "createTagButton": "Create Tag", + "creatingTagButton": "Creating tag...", + "createTagButton": "Create tag", "createTagDialog": { "validation": { "name": "Tag name is required", diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx index 09ed6babde..073124060c 100644 --- a/packages/ui/src/components/alert-dialog.tsx +++ b/packages/ui/src/components/alert-dialog.tsx @@ -77,7 +77,7 @@ const Content = ({ title, children }: ContentProps) => { }) return ( - <Dialog.Content> + <Dialog.Content onOpenAutoFocus={event => event.preventDefault()}> <Dialog.Header icon={ context.theme === 'danger' ? 'xmark-circle' : context.theme === 'warning' ? 'warning-triangle' : undefined diff --git a/packages/ui/src/components/illustration/images/no-data-tags-light.svg b/packages/ui/src/components/illustration/images/no-data-tags-light.svg index be0d7f1ff3..31f6731898 100644 --- a/packages/ui/src/components/illustration/images/no-data-tags-light.svg +++ b/packages/ui/src/components/illustration/images/no-data-tags-light.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="112" height="112" fill="none"><circle cx="56" cy="56" r="37.5" stroke="url(#a)"/><circle cx="56" cy="56" r="43.5" stroke="url(#b)" opacity=".8"/><circle cx="56" cy="56" r="49.5" stroke="url(#c)" opacity=".6"/><circle cx="56" cy="56" r="55.5" stroke="url(#d)" opacity=".4"/><mask id="e" width="64" height="64" x="24" y="24" maskUnits="userSpaceOnUse" style="mask-type:alpha"><circle cx="56" cy="56" r="32" fill="#131316" style="fill:#131316;fill:color(display-p3 .0748 .0748 .086);fill-opacity:1"/></mask><g mask="url(#e)"><circle cx="56" cy="56" r="32" fill="#fff" style="fill:#fff;fill-opacity:1"/><g filter="url(#f)" opacity=".3"><circle cx="56" cy="81" r="21" fill="#D7D7E9" style="fill:#d7d7e9;fill:color(display-p3 .8447 .8447 .9153);fill-opacity:1"/></g><g filter="url(#g)" opacity=".4"><ellipse cx="56" cy="88" fill="#D7D7E9" rx="10" ry="13" style="fill:#d7d7e9;fill:color(display-p3 .8447 .8447 .9153);fill-opacity:1"/></g><circle cx="56" cy="56" r="31.5" stroke="url(#h)"/></g><g filter="url(#i)"><path fill="url(#j)" d="M69.828 55.414 55.414 41H41v14.414l14.414 14.414A3.98 3.98 0 0 0 58.242 71a3.978 3.978 0 0 0 2.829-1.172l8.757-8.757A3.971 3.971 0 0 0 71 58.243a3.975 3.975 0 0 0-1.172-2.829ZM50.5 53a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5Z"/></g><defs><linearGradient id="a" x1="56" x2="56" y1="18" y2="94" gradientUnits="userSpaceOnUse"><stop stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/><stop offset=".5" stop-color="#EFEFF6" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:1"/><stop offset="1" stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/></linearGradient><linearGradient id="b" x1="56" x2="56" y1="12" y2="100" gradientUnits="userSpaceOnUse"><stop stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/><stop offset=".5" stop-color="#EFEFF6" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:1"/><stop offset="1" stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/></linearGradient><linearGradient id="c" x1="56" x2="56" y1="6" y2="106" gradientUnits="userSpaceOnUse"><stop stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/><stop offset=".5" stop-color="#EFEFF6" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:1"/><stop offset="1" stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/></linearGradient><linearGradient id="d" x1="56" x2="56" y1="0" y2="112" gradientUnits="userSpaceOnUse"><stop stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/><stop offset=".5" stop-color="#EFEFF6" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:1"/><stop offset="1" stop-color="#EFEFF6" stop-opacity=".5" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:.5"/></linearGradient><linearGradient id="h" x1="56" x2="56" y1="24" y2="88" gradientUnits="userSpaceOnUse"><stop stop-color="#D7D7E9" style="stop-color:#d7d7e9;stop-color:color(display-p3 .8447 .8447 .9153);stop-opacity:1"/><stop offset="1" stop-color="#EFEFF6" style="stop-color:#efeff6;stop-color:color(display-p3 .9373 .9373 .9647);stop-opacity:1"/></linearGradient><linearGradient id="j" x1="70.937" x2="53.809" y1="41.195" y2="69.683" gradientUnits="userSpaceOnUse"><stop stop-color="#C6C6D3" style="stop-color:#c6c6d3;stop-color:color(display-p3 .7765 .7765 .8275);stop-opacity:1"/><stop offset="1" stop-color="#686775" style="stop-color:#686775;stop-color:color(display-p3 .4078 .4039 .4588);stop-opacity:1"/></linearGradient><filter id="f" width="90" height="90" x="11" y="36" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_24994_169431" stdDeviation="12"/></filter><filter id="g" width="40" height="46" x="36" y="65" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_24994_169431" stdDeviation="5"/></filter><filter id="i" width="30" height="30.8" x="41" y="41" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy=".8"/><feGaussianBlur stdDeviation=".8"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/><feBlend in2="shape" result="effect1_innerShadow_24994_169431"/></filter></defs></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="114" height="114" fill="none"><path stroke="url(#a)" d="M95 57c0 20.987-17.013 38-38 38S19 77.987 19 57s17.013-38 38-38 38 17.013 38 38Z"/><path stroke="url(#b)" d="M101 57c0 24.3-19.7 44-44 44S13 81.3 13 57s19.7-44 44-44 44 19.7 44 44Z"/><path stroke="url(#c)" d="M107 57c0 27.614-22.386 50-50 50S7 84.614 7 57 29.386 7 57 7s50 22.386 50 50Z"/><path stroke="url(#d)" d="M113 57c0 30.928-25.072 56-56 56S1 87.928 1 57 26.072 1 57 1s56 25.072 56 56Z"/><path fill="url(#e)" d="M57 25.5c17.397 0 31.5 14.103 31.5 31.5S74.397 88.5 57 88.5 25.5 74.397 25.5 57 39.603 25.5 57 25.5Z"/><path stroke="url(#f)" d="M57 25.5c17.397 0 31.5 14.103 31.5 31.5S74.397 88.5 57 88.5 25.5 74.397 25.5 57 39.603 25.5 57 25.5Z"/><mask id="g" width="32" height="32" x="41" y="41" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M50.125 49.188a.937.937 0 1 1-1.875 0 .937.937 0 0 1 1.875 0Z"/><path fill="#000" d="M42 42v-1a1 1 0 0 0-1 1h1Zm13.636 0 .707-.707a1 1 0 0 0-.707-.293v1ZM70.8 57.166l-.707.707.707-.707Zm0 5.784-.706-.708.706.708Zm-13.635 7.851.707-.706-.707.706ZM42 55.636h-1a1 1 0 0 0 .293.707l.707-.707ZM42 42v1h13.636v-2H42v1Zm13.636 0-.707.707 15.165 15.166.707-.707.707-.707-15.165-15.166-.707.707ZM70.8 57.166l-.707.707a3.089 3.089 0 0 1 0 4.37l.707.707.707.708a5.089 5.089 0 0 0 0-7.2l-.707.708Zm0 5.784-.707-.707-7.851 7.851.707.707.707.707 7.852-7.85-.708-.708Zm-7.851 7.851-.707-.707a3.089 3.089 0 0 1-4.37 0l-.707.707-.708.707a5.089 5.089 0 0 0 7.2 0l-.708-.707Zm-5.784 0 .707-.707L42.707 54.93l-.707.707-.707.707 15.166 15.165.707-.707ZM42 55.636h1V42h-2v13.636h1Zm8.125-6.449h-1c0-.034.028-.062.063-.062v2c1.07 0 1.937-.867 1.937-1.938h-1Zm-.938.938v-1c.035 0 .063.028.063.063h-2c0 1.07.867 1.937 1.938 1.937v-1Zm-.937-.938h1a.063.063 0 0 1-.063.063v-2c-1.07 0-1.937.867-1.937 1.938h1Zm.938-.937v1a.063.063 0 0 1-.063-.063h2c0-1.07-.867-1.937-1.938-1.937v1Z"/></mask><g mask="url(#g)"><path fill="url(#h)" d="M0 0h40v40H0z" transform="translate(37 37)"/></g><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#CACACE"/><stop offset="1" stop-color="#fff"/></radialGradient><radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#CACACE"/><stop offset="1" stop-color="#fff"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#CACACE"/><stop offset="1" stop-color="#fff"/></radialGradient><radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#CACACE"/><stop offset="1" stop-color="#fff"/></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(0 -53 34.617 0 57 90)" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset=".274" stop-color="#fff"/><stop offset=".457" stop-color="#F9F9F9"/><stop offset=".631" stop-color="#F9F9F9"/><stop offset=".837" stop-color="#EFEFF1"/><stop offset="1" stop-color="#E7E7E9"/></radialGradient><radialGradient id="h" cx="0" cy="0" r="1" gradientTransform="matrix(0 -21 12.5457 0 20 25.5)" gradientUnits="userSpaceOnUse"><stop stop-color="#CACACE"/><stop offset=".38" stop-color="#B5B5BB"/><stop offset="1" stop-color="#94949C"/></radialGradient><linearGradient id="f" x1="57" x2="57" y1="25" y2="89" gradientUnits="userSpaceOnUse"><stop stop-color="#0F0F11" stop-opacity=".1"/><stop offset="1" stop-color="#0F0F11" stop-opacity=".02"/></linearGradient></defs></svg> \ No newline at end of file diff --git a/packages/ui/src/components/illustration/images/no-data-tags.svg b/packages/ui/src/components/illustration/images/no-data-tags.svg index 708aff314e..a70f7d6ff2 100644 --- a/packages/ui/src/components/illustration/images/no-data-tags.svg +++ b/packages/ui/src/components/illustration/images/no-data-tags.svg @@ -1,72 +1 @@ -<svg width="112" height="112" viewBox="0 0 112 112" fill="none" xmlns="http://www.w3.org/2000/svg"> -<circle cx="56" cy="56" r="37.5" stroke="url(#paint0_linear_16731_78016)" style=""/> -<circle opacity="0.8" cx="56" cy="56" r="43.5" stroke="url(#paint1_linear_16731_78016)" style=""/> -<circle opacity="0.6" cx="56" cy="56" r="49.5" stroke="url(#paint2_linear_16731_78016)" style=""/> -<circle opacity="0.4" cx="56" cy="56" r="55.5" stroke="url(#paint3_linear_16731_78016)" style=""/> -<mask id="mask0_16731_78016" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="24" y="24" width="64" height="64"> -<circle cx="56" cy="56" r="32" fill="#131316" style="fill:#131316;fill:color(display-p3 0.0748 0.0748 0.0860);fill-opacity:1;"/> -</mask> -<g mask="url(#mask0_16731_78016)"> -<circle cx="56" cy="56" r="32" fill="#131316" style="fill:#131316;fill:color(display-p3 0.0745 0.0745 0.0863);fill-opacity:1;"/> -<g opacity="0.3" filter="url(#filter0_f_16731_78016)"> -<circle cx="56" cy="81" r="21" fill="#93939F" style="fill:#93939F;fill:color(display-p3 0.5760 0.5760 0.6240);fill-opacity:1;"/> -</g> -<g opacity="0.4" filter="url(#filter1_f_16731_78016)"> -<ellipse cx="56" cy="88" rx="10" ry="13" fill="#93939F" style="fill:#93939F;fill:color(display-p3 0.5760 0.5760 0.6240);fill-opacity:1;"/> -</g> -<circle cx="56" cy="56" r="31.5" stroke="url(#paint4_linear_16731_78016)" style=""/> -</g> -<g filter="url(#filter2_i_16731_78016)"> -<path d="M69.828 55.414L55.414 41H41V55.414L55.414 69.828C56.169 70.583 57.174 70.999 58.242 71C59.311 71 60.315 70.583 61.071 69.828L69.828 61.071C70.584 60.316 71 59.311 71 58.243C71 57.175 70.584 56.17 69.828 55.414ZM50.5 53C49.119 53 48 51.881 48 50.5C48 49.119 49.119 48 50.5 48C51.881 48 53 49.119 53 50.5C53 51.881 51.881 53 50.5 53Z" fill="url(#paint5_linear_16731_78016)" style=""/> -</g> -<defs> -<filter id="filter0_f_16731_78016" x="11" y="36" width="90" height="90" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> -<feFlood flood-opacity="0" result="BackgroundImageFix"/> -<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> -<feGaussianBlur stdDeviation="12" result="effect1_foregroundBlur_16731_78016"/> -</filter> -<filter id="filter1_f_16731_78016" x="36" y="65" width="40" height="46" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> -<feFlood flood-opacity="0" result="BackgroundImageFix"/> -<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> -<feGaussianBlur stdDeviation="5" result="effect1_foregroundBlur_16731_78016"/> -</filter> -<filter id="filter2_i_16731_78016" x="41" y="41" width="30" height="30.8" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> -<feFlood flood-opacity="0" result="BackgroundImageFix"/> -<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> -<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> -<feOffset dy="0.80003"/> -<feGaussianBlur stdDeviation="0.80003"/> -<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> -<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/> -<feBlend mode="normal" in2="shape" result="effect1_innerShadow_16731_78016"/> -</filter> -<linearGradient id="paint0_linear_16731_78016" x1="56" y1="18" x2="56" y2="94" gradientUnits="userSpaceOnUse"> -<stop stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -<stop offset="0.5" stop-color="#18181B" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:1;"/> -<stop offset="1" stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -</linearGradient> -<linearGradient id="paint1_linear_16731_78016" x1="56" y1="12" x2="56" y2="100" gradientUnits="userSpaceOnUse"> -<stop stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -<stop offset="0.5" stop-color="#18181B" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:1;"/> -<stop offset="1" stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -</linearGradient> -<linearGradient id="paint2_linear_16731_78016" x1="56" y1="6" x2="56" y2="106" gradientUnits="userSpaceOnUse"> -<stop stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -<stop offset="0.5" stop-color="#18181B" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:1;"/> -<stop offset="1" stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -</linearGradient> -<linearGradient id="paint3_linear_16731_78016" x1="56" y1="0" x2="56" y2="112" gradientUnits="userSpaceOnUse"> -<stop stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -<stop offset="0.5" stop-color="#18181B" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:1;"/> -<stop offset="1" stop-color="#18181B" stop-opacity="0.3" style="stop-color:#18181B;stop-color:color(display-p3 0.0941 0.0941 0.1059);stop-opacity:0.3;"/> -</linearGradient> -<linearGradient id="paint4_linear_16731_78016" x1="56" y1="24" x2="56" y2="88" gradientUnits="userSpaceOnUse"> -<stop stop-color="white" stop-opacity="0.1" style="stop-color:white;stop-opacity:0.1;"/> -<stop offset="1" stop-color="white" stop-opacity="0" style="stop-color:none;stop-opacity:0;"/> -</linearGradient> -<linearGradient id="paint5_linear_16731_78016" x1="70.9366" y1="41.1948" x2="53.8095" y2="69.6831" gradientUnits="userSpaceOnUse"> -<stop stop-color="#AEAEB7" style="stop-color:#AEAEB7;stop-color:color(display-p3 0.6820 0.6820 0.7180);stop-opacity:1;"/> -<stop offset="1" stop-color="#60606C" style="stop-color:#60606C;stop-color:color(display-p3 0.3760 0.3760 0.4240);stop-opacity:1;"/> -</linearGradient> -</defs> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="114" height="114" fill="none"><path stroke="url(#a)" d="M95 57c0 20.987-17.013 38-38 38S19 77.987 19 57s17.013-38 38-38 38 17.013 38 38Z"/><path stroke="url(#b)" d="M101 57c0 24.3-19.7 44-44 44S13 81.3 13 57s19.7-44 44-44 44 19.7 44 44Z"/><path stroke="url(#c)" d="M107 57c0 27.614-22.386 50-50 50S7 84.614 7 57 29.386 7 57 7s50 22.386 50 50Z"/><path stroke="url(#d)" d="M113 57c0 30.928-25.072 56-56 56S1 87.928 1 57 26.072 1 57 1s56 25.072 56 56Z"/><path fill="url(#e)" d="M57 25.5c17.397 0 31.5 14.103 31.5 31.5S74.397 88.5 57 88.5 25.5 74.397 25.5 57 39.603 25.5 57 25.5Z"/><path stroke="url(#f)" d="M57 25.5c17.397 0 31.5 14.103 31.5 31.5S74.397 88.5 57 88.5 25.5 74.397 25.5 57 39.603 25.5 57 25.5Z"/><mask id="g" width="32" height="32" x="41" y="41" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M50.125 49.188a.937.937 0 1 1-1.875 0 .937.937 0 0 1 1.875 0Z" style="fill:#000;fill-opacity:1"/><path fill="#000" d="M42 42v-1a1 1 0 0 0-1 1h1Zm13.636 0 .707-.707a1 1 0 0 0-.707-.293v1ZM70.8 57.166l-.707.707.707-.707Zm0 5.784-.706-.708.706.708Zm-13.635 7.851.707-.706-.707.706ZM42 55.636h-1a1 1 0 0 0 .293.707l.707-.707ZM42 42v1h13.636v-2H42v1Zm13.636 0-.707.707 15.165 15.166.707-.707.707-.707-15.165-15.166-.707.707ZM70.8 57.166l-.707.707a3.089 3.089 0 0 1 0 4.37l.707.707.707.708a5.089 5.089 0 0 0 0-7.2l-.707.708Zm0 5.784-.707-.707-7.851 7.851.707.707.707.707 7.852-7.85-.708-.708Zm-7.851 7.851-.707-.707a3.089 3.089 0 0 1-4.37 0l-.707.707-.708.707a5.089 5.089 0 0 0 7.2 0l-.708-.707Zm-5.784 0 .707-.707L42.707 54.93l-.707.707-.707.707 15.166 15.165.707-.707ZM42 55.636h1V42h-2v13.636h1Zm8.125-6.449h-1c0-.034.028-.062.063-.062v2c1.07 0 1.937-.867 1.937-1.938h-1Zm-.938.938v-1c.035 0 .063.028.063.063h-2c0 1.07.867 1.937 1.938 1.937v-1Zm-.937-.938h1a.063.063 0 0 1-.063.063v-2c-1.07 0-1.937.867-1.937 1.938h1Zm.938-.937v1a.063.063 0 0 1-.063-.063h2c0-1.07-.867-1.937-1.938-1.937v1Z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#g)"><path fill="url(#h)" d="M0 0h40v40H0z" transform="translate(37 37)"/></g><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#232327" style="stop-color:#232327;stop-color:color(display-p3 .1373 .1373 .1529);stop-opacity:1"/><stop offset="1" stop-color="#0F0F11" style="stop-color:#0f0f11;stop-color:color(display-p3 .0588 .0588 .0667);stop-opacity:1"/></radialGradient><radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#232327" style="stop-color:#232327;stop-color:color(display-p3 .1373 .1373 .1529);stop-opacity:1"/><stop offset="1" stop-color="#0F0F11" style="stop-color:#0f0f11;stop-color:color(display-p3 .0588 .0588 .0667);stop-opacity:1"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#232327" style="stop-color:#232327;stop-color:color(display-p3 .1373 .1373 .1529);stop-opacity:1"/><stop offset="1" stop-color="#0F0F11" style="stop-color:#0f0f11;stop-color:color(display-p3 .0588 .0588 .0667);stop-opacity:1"/></radialGradient><radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(0 56 -68.1429 0 57 57)" gradientUnits="userSpaceOnUse"><stop stop-color="#232327" style="stop-color:#232327;stop-color:color(display-p3 .1373 .1373 .1529);stop-opacity:1"/><stop offset="1" stop-color="#0F0F11" style="stop-color:#0f0f11;stop-color:color(display-p3 .0588 .0588 .0667);stop-opacity:1"/></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(0 -53 34.617 0 57 90)" gradientUnits="userSpaceOnUse"><stop stop-color="#74747D" style="stop-color:#74747d;stop-color:color(display-p3 .4549 .4549 .4902);stop-opacity:1"/><stop offset=".274" stop-color="#47474D" style="stop-color:#47474d;stop-color:color(display-p3 .2784 .2784 .302);stop-opacity:1"/><stop offset=".457" stop-color="#313135" style="stop-color:#313135;stop-color:color(display-p3 .1922 .1922 .2078);stop-opacity:1"/><stop offset=".631" stop-color="#232327" style="stop-color:#232327;stop-color:color(display-p3 .1373 .1373 .1529);stop-opacity:1"/><stop offset=".837" stop-color="#18181B" style="stop-color:#18181b;stop-color:color(display-p3 .0941 .0941 .1059);stop-opacity:1"/><stop offset="1" stop-color="#141416" style="stop-color:#141416;stop-color:color(display-p3 .0784 .0784 .0863);stop-opacity:1"/></radialGradient><radialGradient id="h" cx="0" cy="0" r="1" gradientTransform="matrix(0 -21 12.5457 0 20 25.5)" gradientUnits="userSpaceOnUse"><stop stop-color="#E7E7E9" style="stop-color:#e7e7e9;stop-color:color(display-p3 .9059 .9059 .9137);stop-opacity:1"/><stop offset=".38" stop-color="#CACACE" style="stop-color:#cacace;stop-color:color(display-p3 .7922 .7922 .8078);stop-opacity:1"/><stop offset="1" stop-color="#94949C" style="stop-color:#94949c;stop-color:color(display-p3 .5804 .5804 .6118);stop-opacity:1"/></radialGradient><linearGradient id="f" x1="57" x2="57" y1="25" y2="89" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity=".1" style="stop-color:white;stop-opacity:.1"/><stop offset="1" stop-color="#fff" stop-opacity=".01" style="stop-color:white;stop-opacity:.01"/></linearGradient></defs></svg> \ No newline at end of file diff --git a/packages/ui/src/components/more-actions-tooltip.tsx b/packages/ui/src/components/more-actions-tooltip.tsx index 1ae80ad139..bdac4ccbeb 100644 --- a/packages/ui/src/components/more-actions-tooltip.tsx +++ b/packages/ui/src/components/more-actions-tooltip.tsx @@ -6,7 +6,6 @@ import { DropdownMenu } from '@components/dropdown-menu' import { IconV2, type IconPropsV2 } from '@components/icon-v2' import { cn } from '@utils/cn' -import { Layout } from './layout' import { Text } from './text' export interface ActionData { @@ -32,8 +31,8 @@ export interface MoreActionsTooltipProps { export const MoreActionsTooltip: FC<MoreActionsTooltipProps> = ({ actions, iconName = 'more-vert', - sideOffset = -6, - alignOffset = 10, + sideOffset = 7, + alignOffset = 0, className }) => { const { Link } = useRouterContext() @@ -46,12 +45,17 @@ export const MoreActionsTooltip: FC<MoreActionsTooltipProps> = ({ className={cn('text-icons-1 hover:text-icons-2 data-[state=open]:text-icons-2')} variant="ghost" iconOnly - size="sm" + size="md" > <IconV2 name={iconName} /> </Button> </DropdownMenu.Trigger> - <DropdownMenu.Content className={className} align="end" sideOffset={sideOffset} alignOffset={alignOffset}> + <DropdownMenu.Content + className={cn('min-w-[208px]', className)} + align="end" + sideOffset={sideOffset} + alignOffset={alignOffset} + > {actions.map((action, idx) => action.to ? ( <Link @@ -61,28 +65,47 @@ export const MoreActionsTooltip: FC<MoreActionsTooltipProps> = ({ e.stopPropagation() }} > - <DropdownMenu.Item - title={ - <Layout.Horizontal gap="xs" className="items-center"> - {action.iconName ? <IconV2 name={action.iconName} /> : null} + {action.iconName ? ( + <DropdownMenu.IconItem + icon={action.iconName} + iconClassName={cn({ 'text-cn-foreground-danger': action.isDanger })} + title={ <Text color={action.isDanger ? 'danger' : 'foreground-2'} truncate> {action.title} </Text> - </Layout.Horizontal> - } - /> + } + /> + ) : ( + <DropdownMenu.Item + title={ + <Text color={action.isDanger ? 'danger' : 'foreground-2'} truncate> + {action.title} + </Text> + } + /> + )} </Link> + ) : action.iconName ? ( + <DropdownMenu.IconItem + icon={action.iconName} + title={ + <Text color={action.isDanger ? 'danger' : 'foreground-2'} truncate> + {action.title} + </Text> + } + iconClassName={cn({ 'text-cn-foreground-danger': action.isDanger })} + key={`${action.title}-${idx}`} + onClick={e => { + e.stopPropagation() + action?.onClick?.() + }} + /> ) : ( <DropdownMenu.Item title={ - <Layout.Horizontal gap="xs" className="items-center"> - {action.iconName ? ( - <IconV2 className={cn({ 'text-cn-foreground-danger': action.isDanger })} name={action.iconName} /> - ) : null} - <Text color={action.isDanger ? 'danger' : 'foreground-2'} truncate> - {action.title} - </Text> - </Layout.Horizontal> + <Text color={action.isDanger ? 'danger' : 'foreground-2'} truncate> + {action.title} + </Text> } key={`${action.title}-${idx}`} onClick={e => { diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index 5a93f2a8e8..cbe139eb01 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -1,6 +1,7 @@ import { FC, forwardRef, Fragment, memo, MouseEvent, Ref, useState } from 'react' import { Popover, StatusBadge, Text, TextProps } from '@/components' +import { cn } from '@utils/cn' import { LOCALE } from '@utils/TimeUtils' import { formatDistanceToNow } from 'date-fns' @@ -140,11 +141,25 @@ interface TimeAgoCardProps { textProps?: TextProps<'time' | 'span'> & { ref?: Ref<HTMLSpanElement | HTMLTimeElement> } + triggerClassName?: string + contentClassName?: string } export const TimeAgoCard = memo( forwardRef<HTMLButtonElement, TimeAgoCardProps>( - ({ timestamp, cutoffDays = 8, dateTimeFormatOptions, beforeCutoffPrefix, afterCutoffPrefix, textProps }, ref) => { + ( + { + timestamp, + cutoffDays = 8, + dateTimeFormatOptions, + beforeCutoffPrefix, + afterCutoffPrefix, + textProps, + triggerClassName, + contentClassName + }, + ref + ) => { const [isOpen, setIsOpen] = useState(false) const { formattedShort, formattedFull, isBeyondCutoff } = useFormattedTime( timestamp, @@ -174,12 +189,12 @@ export const TimeAgoCard = memo( return ( <Popover.Root open={isOpen} onOpenChange={setIsOpen}> - <Popover.Trigger className="cn-time-ago-card-trigger" onClick={handleClick} ref={ref}> + <Popover.Trigger className={cn('cn-time-ago-card-trigger', triggerClassName)} onClick={handleClick} ref={ref}> <Text<'time'> as="time" {...textProps} ref={textProps?.ref as Ref<HTMLTimeElement>}> {prefix ? `${prefix} ${formattedShort}` : formattedShort} </Text> </Popover.Trigger> - <Popover.Content onClick={handleClickContent} side="top"> + <Popover.Content onClick={handleClickContent} side="top" className={contentClassName}> <TimeAgoContent formattedFullArray={formattedFull} /> </Popover.Content> </Popover.Root> diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index 615b89662e..fb7a9b8589 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -113,6 +113,7 @@ export function CreateBranchDialog({ {...register('name')} maxLength={250} placeholder={t('views:forms.enterBranchName', 'Enter branch name')} + autoFocus /> <ControlGroup> diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 81a483f873..ededc76ddc 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -48,15 +48,17 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ }, [clearErrors, reset]) useEffect(() => { - if (open) { - resetForm() - - if (selectedBranchOrTag) { - setValue('target', selectedBranchOrTag.name, { shouldValidate: true }) - } + if (open && selectedBranchOrTag) { + setValue('target', selectedBranchOrTag.name, { shouldValidate: true }) } }, [open, resetForm, selectedBranchOrTag, setValue]) + useEffect(() => { + if (!open) { + resetForm() + } + }, [open, resetForm]) + const handleClose = () => { resetForm() onClose() @@ -78,6 +80,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ maxLength={250} placeholder={t('views:forms.enterTagName', 'Enter a tag name here')} disabled={isLoading} + autoFocus /> <ControlGroup> @@ -109,8 +112,8 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ </Dialog.Close> <Button type="submit" disabled={isLoading} loading={isLoading}> {isLoading - ? t('views:repos.creatingTagButton', 'Creating Tag...') - : t('views:repos.createTagButton', 'Create Tag')} + ? t('views:repos.creatingTagButton', 'Creating tag...') + : t('views:repos.createTagButton', 'Create tag')} </Button> </ButtonLayout> </Dialog.Footer> diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index 4879ac7e70..ce813d89d0 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -6,6 +6,7 @@ import { CommitCopyActions, CopyTag, IconV2, + Layout, MoreActionsTooltip, NoData, Skeleton, @@ -43,7 +44,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ const getTableActions = (tag: CommitTagType): ActionData[] => [ { iconName: 'git-branch', - title: t('views:repos.createBranch', 'Create Branch'), + title: t('views:repos.createBranch', 'Create branch'), onClick: () => onOpenCreateBranchDialog(tag) }, { @@ -79,6 +80,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ ] } textWrapperClassName={isDirtyList ? '' : 'max-w-[360px]'} + className="flex-1" secondaryButton={ isDirtyList ? { @@ -114,15 +116,15 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ } return ( - <Table.Root tableClassName="table-fixed"> + <Table.Root tableClassName="table-fixed" size="compact"> <Table.Header> <Table.Row> <Table.Head className="w-[15%]">{t('views:repos.tag', 'Tag')}</Table.Head> - <Table.Head className="w-[32%]">{t('views:repos.description', 'Description')}</Table.Head> - <Table.Head className="w-[10%]">{t('views:repos.commit', 'Commit')}</Table.Head> - <Table.Head className="w-1/5">{t('views:repos.tagger', 'Tagger')}</Table.Head> - <Table.Head className="w-[16%]">{t('views:repos.creationDate', 'Creation date')}</Table.Head> - <Table.Head className="w-[46px]" /> + <Table.Head className="w-[33%]">{t('views:repos.description', 'Description')}</Table.Head> + <Table.Head className="w-[15%]">{t('views:repos.commit', 'Commit')}</Table.Head> + <Table.Head className="w-[15%]">{t('views:repos.tagger', 'Tagger')}</Table.Head> + <Table.Head className="w-[15%]">{t('views:repos.creationDate', 'Creation date')}</Table.Head> + <Table.Head className="w-[7%]" /> </Table.Row> </Table.Header> @@ -130,7 +132,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ {tagsList.map(tag => ( <Table.Row key={tag.sha} to={`../summary/refs/tags/${tag.name}`}> <Table.Cell> - <CopyTag value={tag.name} theme="violet" size="md" variant="secondary" /> + <CopyTag value={tag.name} theme="violet" size="md" variant="secondary" className="max-w-full" /> </Table.Cell> <Table.Cell> <Text variant="body-normal" className="line-clamp-3"> @@ -140,8 +142,8 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ <Table.Cell disableLink> <CommitCopyActions sha={tag.commit?.sha ?? ''} toCommitDetails={toCommitDetails} /> </Table.Cell> - <Table.Cell> - <div className="flex items-center gap-2"> + <Table.Cell disableLink> + <Layout.Horizontal gap="xs"> {tag.tagger?.identity.name ? ( <> <Avatar name={tag.tagger?.identity.name} size="sm" rounded /> @@ -150,7 +152,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ </Text> </> ) : null} - </div> + </Layout.Horizontal> </Table.Cell> <Table.Cell> {tag.tagger?.when ? ( @@ -158,10 +160,11 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ timestamp={new Date(tag.tagger?.when).getTime()} dateTimeFormatOptions={{ dateStyle: 'medium' }} textProps={{ variant: 'body-normal' }} + triggerClassName="text-left" /> ) : null} </Table.Cell> - <Table.Cell className="w-[46px] text-right" disableLink> + <Table.Cell className="text-right" disableLink> <MoreActionsTooltip actions={getTableActions(tag).map(action => ({ ...action, diff --git a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx index 41fd6192ac..32a56d2020 100644 --- a/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx +++ b/packages/ui/src/views/repo/repo-tags/repo-tags-list-page.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, IconV2, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' +import { Button, IconV2, Layout, ListActions, Pagination, SearchInput, Text } from '@/components' import { useTranslation } from '@/context' import { RepoTagsListViewProps, SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -30,7 +30,7 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ const handleResetFiltersAndPages = useCallback(() => { setPage(1) - setSearchQuery(null) + setSearchQuery('') }, [setPage, setSearchQuery]) const isDirtyList = useMemo(() => { @@ -57,54 +57,57 @@ export const RepoTagsListView: FC<RepoTagsListViewProps> = ({ 'h-full': !isLoading && !tagsList.length && !searchQuery })} > - <Text as="h1" variant="heading-section"> - {t('views:repos.tags', 'Tags')} - </Text> - - <Spacer size={6} /> - - <ListActions.Root> - <ListActions.Left> - <SearchInput - inputContainerClassName="max-w-80" - defaultValue={searchQuery || ''} - onChange={handleSearchChange} - placeholder={t('views:repos.search', 'Search')} - autoFocus - /> - </ListActions.Left> - <ListActions.Right> - <Button onClick={openCreateTagDialog}> - <IconV2 name="plus" /> - <span>{t('views:repos.createTag', 'Create Tag')}</span> - </Button> - </ListActions.Right> - </ListActions.Root> - - <Spacer size={5} /> - - <RepoTagsList - onDeleteTag={onDeleteTag} - useRepoTagsStore={useRepoTagsStore} - toCommitDetails={toCommitDetails} - onOpenCreateBranchDialog={openCreateBranchDialog} - isLoading={isLoading} - isDirtyList={isDirtyList} - onResetFiltersAndPages={handleResetFiltersAndPages} - onOpenCreateTagDialog={openCreateTagDialog} - /> - - <Spacer size={5} /> - - {canShowPagination && ( - <Pagination - indeterminate - hasNext={xNextPage > 0} - hasPrevious={xPrevPage > 0} - getNextPageLink={getNextPageLink} - getPrevPageLink={getPrevPageLink} - /> - )} + <Layout.Vertical gap="xl" className="flex-1"> + {(!!tagsList?.length || !!searchQuery) && ( + <Text as="h1" variant="heading-section"> + {t('views:repos.tags', 'Tags')} + </Text> + )} + <Layout.Vertical gap="md" className="flex-1"> + {(!!tagsList?.length || !!searchQuery) && ( + <ListActions.Root> + <ListActions.Left> + <SearchInput + inputContainerClassName="max-w-80" + defaultValue={searchQuery || ''} + onChange={handleSearchChange} + placeholder={t('views:repos.search', 'Search')} + autoFocus + /> + </ListActions.Left> + <ListActions.Right> + <Button onClick={openCreateTagDialog}> + <IconV2 name="plus" /> + <span>{t('views:repos.createTag', 'Create Tag')}</span> + </Button> + </ListActions.Right> + </ListActions.Root> + )} + + <Layout.Vertical gap="none" className="flex-1"> + <RepoTagsList + onDeleteTag={onDeleteTag} + useRepoTagsStore={useRepoTagsStore} + toCommitDetails={toCommitDetails} + onOpenCreateBranchDialog={openCreateBranchDialog} + isLoading={isLoading} + isDirtyList={isDirtyList} + onResetFiltersAndPages={handleResetFiltersAndPages} + onOpenCreateTagDialog={openCreateTagDialog} + /> + + {canShowPagination && ( + <Pagination + indeterminate + hasNext={xNextPage > 0} + hasPrevious={xPrevPage > 0} + getNextPageLink={getNextPageLink} + getPrevPageLink={getPrevPageLink} + /> + )} + </Layout.Vertical> + </Layout.Vertical> + </Layout.Vertical> </SandboxLayout.Content> </SandboxLayout.Main> ) From a301bf45f9dd6d13b9e1459006d8273eb6796018 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Wed, 13 Aug 2025 10:43:03 +0000 Subject: [PATCH 079/180] gray set light mode fix (#10195) * mint 100 color fix * green and red surface set in light mode fix * gray set light mode fix --- .../core-design-system/design-tokens/core/colors_hex.json | 2 +- .../core-design-system/design-tokens/core/colors_lch.json | 2 +- .../design-tokens/mode/light/default.json | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core-design-system/design-tokens/core/colors_hex.json b/packages/core-design-system/design-tokens/core/colors_hex.json index 0d37f3ee82..f5a68791f5 100644 --- a/packages/core-design-system/design-tokens/core/colors_hex.json +++ b/packages/core-design-system/design-tokens/core/colors_hex.json @@ -496,7 +496,7 @@ "$value": "#dcf9f0" }, "100": { - "$value": "#c6f0e5" + "$value": "#c7eae1" }, "150": { "$value": "#61ddc2" diff --git a/packages/core-design-system/design-tokens/core/colors_lch.json b/packages/core-design-system/design-tokens/core/colors_lch.json index 6f1528cab4..edd3cb53ee 100644 --- a/packages/core-design-system/design-tokens/core/colors_lch.json +++ b/packages/core-design-system/design-tokens/core/colors_lch.json @@ -401,7 +401,7 @@ "$value": "lch(95.5% 11 176.43)" }, "100": { - "$value": "lch(91.5% 15.5 178.07)" + "$value": "lch(90% 13 178)" }, "150": { "$value": "lch(80.61% 41.17 176.79)" diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index 5d301bc19d..c1023c177e 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -310,7 +310,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{tuna.150}", + "$value": "{tuna.100}", "$description": "Selected state background color for active soft gray items." } }, @@ -337,7 +337,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{tuna.100}", + "$value": "{tuna.50}", "$description": "Selected state background color for active gray items." } } @@ -400,7 +400,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{mint.100}", + "$value": "{mint.50}", "$description": "Selected state background color for active green items." } } @@ -463,7 +463,7 @@ }, "bg-selected": { "$type": "color", - "$value": "{red.100}", + "$value": "{red.50}", "$description": "Selected state background color for active red items." } } From 07f3c8d814fdb14349819efdecdef01b4cf17c1b Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Wed, 13 Aug 2025 16:34:20 +0300 Subject: [PATCH 080/180] Fix Dialog scrolling in Shadow DOM and Forms usage inside Dialog (#2066) --- .../src/content/docs/components/form.mdx | 71 +++++++++ .../commit-suggestions-dialog.tsx | 42 +++--- packages/ui/src/components/dialog.tsx | 36 +++-- .../form-primitives/form-wrapper.tsx | 3 +- .../git-commit-dialog/git-commit-dialog.tsx | 6 +- .../connector-test-connection-dialog.tsx | 138 +++++++++--------- .../create-pipeline-dialog.tsx | 41 +++--- .../profile-settings-keys-create-dialog.tsx | 22 +-- .../profile-settings-token-create-dialog.tsx | 40 ++--- .../components/invite-member-dialog.tsx | 4 +- .../pull-request-header-edit-dialog.tsx | 39 +++-- .../components/create-branch-dialog.tsx | 50 +++---- .../create-tag/create-tag-dialog.tsx | 34 ++--- .../components/edit-user/edit-user.tsx | 2 +- .../components/dialog.ts | 28 +++- 15 files changed, 324 insertions(+), 232 deletions(-) diff --git a/apps/portal/src/content/docs/components/form.mdx b/apps/portal/src/content/docs/components/form.mdx index 3440c14ed7..f26e2eb39b 100644 --- a/apps/portal/src/content/docs/components/form.mdx +++ b/apps/portal/src/content/docs/components/form.mdx @@ -176,3 +176,74 @@ function FormWithZodAndReactHookForm() { ); } ``` + +### Form in Dialog + +The `FormWrapper` should be placed inside the `Dialog.Body` and contain an `id`. +The submit button should be in the `Dialog.Footer` with `type="submit"` and a `form` attribute set to the form’s `id`. + +```typescript jsx +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { Button, FormInput, FormWrapper, Dialog } from "@harnessio/ui/components"; + +// ... + +/** + * 1️⃣ Define the validation schema with zod + */ +const formSchema = z.object({ + /** 💡 As a best practice, use trim() to remove whitespace from the input */ + name: z.string().trim().min(3, "Input must be at least 3 characters"), +}); + +// ... + +/** 2️⃣ Infer the type from the schema */ +const FormValuesType = z.infer<typeof formSchema>; + +function FormWithZodAndReactHookForm() { + /** 3️⃣ Initialize react-hook-form with zod resolver */ + const formMethods = useForm<FormValuesType>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "John doe" + }, + }); + + /** 4️⃣ Extract register and handleSubmit from formMethods */ + const { register, handleSubmit } = formMethods; + + /** 6️⃣ Handle form submission */ + const onSubmit = (data: FormValuesType) => { + // ... + }; + + /** 5️⃣ Use FormWrapper with formMethods and onSubmit */ + return ( + <Dialog.Root> + <Dialog.Trigger asChild> + <Button>Open Dialog with Form</Button> + </Dialog.Trigger> + + <Dialog.Content> + <Dialog.Header> + <Dialog.Title>Form</Dialog.Title> + </Dialog.Header> + + <Dialog.Body> + <FormWrapper id="id-form" {...formMethods} onSubmit={handleSubmit(onSubmit)}> + <FormInput.Text {...register("name")} label="Name" /> + </FormWrapper> + </Dialog.Body> + + <Dialog.Footer> + <Dialog.Close>Cancel</Dialog.Close> + <Button type="submit" form="id-form" variant="primary">Submit</Button> + </Dialog.Footer> + </Dialog.Content> + </Dialog.Root> + ); +} +``` diff --git a/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx b/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx index a01fa7f989..7af25b4388 100644 --- a/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx +++ b/packages/ui/src/components/commit-suggestions-dialog/commit-suggestions-dialog.tsx @@ -61,8 +61,8 @@ export const CommitSuggestionsDialog: FC<CommitSuggestionsDialogProps> = ({ <Dialog.Title>Commit Changes</Dialog.Title> </Dialog.Header> - <FormWrapper {...formMethods} onSubmit={handleSubmit(onFormSubmit)} className="block"> - <Dialog.Body> + <Dialog.Body> + <FormWrapper {...formMethods} onSubmit={handleSubmit(onFormSubmit)} id="commit-suggestions-form"> <FormInput.Text id="title" label="Commit Message" @@ -77,27 +77,27 @@ export const CommitSuggestionsDialog: FC<CommitSuggestionsDialogProps> = ({ label="Extended description" className="h-44" /> - </Dialog.Body> - {error && ( - <Alert.Root theme="danger" className="mt-4"> - <Alert.Title> - {error.message || 'An error occurred while applying suggestions. Please try again.'} - </Alert.Title> - </Alert.Root> - )} + {error && ( + <Alert.Root theme="danger" className="mt-4"> + <Alert.Title> + {error.message || 'An error occurred while applying suggestions. Please try again.'} + </Alert.Title> + </Alert.Root> + )} + </FormWrapper> + </Dialog.Body> - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={onClose} disabled={isSubmitting}> - Cancel - </Dialog.Close> - <Button type="submit" disabled={isSubmitting}> - {isSubmitting ? 'Committing...' : 'Commit changes'} - </Button> - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={onClose} disabled={isSubmitting}> + Cancel + </Dialog.Close> + <Button type="submit" form="commit-suggestions-form" disabled={isSubmitting}> + {isSubmitting ? 'Committing...' : 'Commit changes'} + </Button> + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index 0b32921d06..20e26ff055 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -55,22 +55,26 @@ const Content = forwardRef<HTMLDivElement, ContentProps>( return ( <DialogPrimitive.Portal container={portalContainer}> - <DialogPrimitive.Overlay className="cn-modal-dialog-overlay" /> - <DialogPrimitive.Content - ref={ref} - className={cn(contentVariants({ size }), className)} - onOpenAutoFocus={event => event.preventDefault()} - {...props} - > - {!hideClose && ( - <DialogPrimitive.Close asChild> - <Button variant="transparent" className="cn-modal-dialog-close" iconOnly> - <IconV2 name="xmark" /> - </Button> - </DialogPrimitive.Close> - )} - {children} - </DialogPrimitive.Content> + {/* !!! */} + {/* For the scroll to work when using the dialog in Shadow DOM, the Overlay needs to wrap the Content */} + {/* Here’s the issue for the scroll bug in Shadow DOM - https://github.com/radix-ui/primitives/issues/3353 */} + <DialogPrimitive.Overlay className="cn-modal-dialog-overlay"> + <DialogPrimitive.Content + ref={ref} + className={cn(contentVariants({ size }), className)} + onOpenAutoFocus={event => event.preventDefault()} + {...props} + > + {!hideClose && ( + <DialogPrimitive.Close asChild> + <Button variant="transparent" className="cn-modal-dialog-close" iconOnly> + <IconV2 name="xmark" /> + </Button> + </DialogPrimitive.Close> + )} + {children} + </DialogPrimitive.Content> + </DialogPrimitive.Overlay> </DialogPrimitive.Portal> ) } diff --git a/packages/ui/src/components/form-primitives/form-wrapper.tsx b/packages/ui/src/components/form-primitives/form-wrapper.tsx index 754712b9f6..441fbadc4e 100644 --- a/packages/ui/src/components/form-primitives/form-wrapper.tsx +++ b/packages/ui/src/components/form-primitives/form-wrapper.tsx @@ -37,12 +37,13 @@ export function FormWrapper<T extends FieldValues>({ control, onSubmit, orientation = 'vertical', + id, ...props }: FormWrapperProps<T>) { return ( <FormProvider {...props} formState={formState} control={control}> <FormWrapperContext.Provider value={{ orientation }}> - <form className={cn('cn-form', className)} ref={formRef} onSubmit={onSubmit} noValidate> + <form ref={formRef} className={cn('cn-form', className)} onSubmit={onSubmit} noValidate id={id}> {children} </form> </FormWrapperContext.Provider> diff --git a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx index 7efc4f186b..001e949b83 100644 --- a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx +++ b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx @@ -132,7 +132,7 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ </Dialog.Header> <Dialog.Body> - <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> + <FormWrapper id="commit-form" {...formMethods} onSubmit={handleSubmit(onSubmit)}> {isFileNameRequired && ( <FormInput.Text id="fileName" @@ -249,13 +249,13 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ {t('component:cancel', 'Cancel')} </Dialog.Close> {!bypassable ? ( - <Button type="button" onClick={handleSubmit(onSubmit)} disabled={isDisabledSubmission}> + <Button type="submit" form="commit-form" disabled={isDisabledSubmission}> {isSubmitting ? t('component:commitDialog.form.submit.loading', 'Committing...') : t('component:commitDialog.form.submit.default', 'Commit changes')} </Button> ) : ( - <Button onClick={handleSubmit(onSubmit)} variant="outline" theme="danger" type="submit"> + <Button type="submit" form="commit-form" variant="outline" theme="danger"> {commitToGitRefValue === CommitToGitRefOption.NEW_BRANCH ? t('component:commitDialog.form.submit.bypassable.new', 'Bypass rules and commit via new branch') : t('component:commitDialog.form.submit.bypassable.directly', 'Bypass rules and commit directly')} diff --git a/packages/ui/src/views/connectors/components/connector-test-connection-dialog.tsx b/packages/ui/src/views/connectors/components/connector-test-connection-dialog.tsx index a422576490..46fd4c2588 100644 --- a/packages/ui/src/views/connectors/components/connector-test-connection-dialog.tsx +++ b/packages/ui/src/views/connectors/components/connector-test-connection-dialog.tsx @@ -73,84 +73,84 @@ export const ConnectorTestConnectionDialog = ({ <Dialog.Content> <Dialog.Header> <Dialog.Title>{title}</Dialog.Title> - <Dialog.Body> - <div className="text-cn-foreground-4 text-sm font-normal"> - <Layout.Horizontal gap="xs" align="center"> - <span className="items-center">{t('views:connectors.connector', 'Connector') + ':'}</span> - <span className="text-cn-foreground-1">{apiUrl}</span> - - {status === 'error' && ( - <Button type="button" variant="outline" className="ml-auto"> - {t('views:connectors.viewConnectorDetails', 'View Connector Details')} - </Button> - )} - </Layout.Horizontal> - <Layout.Horizontal gap="xs"> - <span>{t('views:connectors.status', 'Status') + ':'}</span> + </Dialog.Header> + <Dialog.Body> + <div className="text-cn-foreground-4 text-sm font-normal"> + <Layout.Horizontal gap="xs" align="center"> + <span className="items-center">{t('views:connectors.connector', 'Connector') + ':'}</span> + <span className="text-cn-foreground-1">{apiUrl}</span> - <div className="text-cn-foreground-1"> - <ConnectivityStatus status={status as ExecutionState} /> - </div> - </Layout.Horizontal> - </div> + {status === 'error' && ( + <Button type="button" variant="outline" className="ml-auto"> + {t('views:connectors.viewConnectorDetails', 'View Connector Details')} + </Button> + )} + </Layout.Horizontal> + <Layout.Horizontal gap="xs"> + <span>{t('views:connectors.status', 'Status') + ':'}</span> - <div> - <Layout.Horizontal gap="xs" align="center" className="text-center"> - {(status === 'success' || status === 'error') && ( - <IconV2 - className={status === 'success' ? 'text-cn-foreground-success' : 'text-cn-foreground-danger'} - name={status === 'success' ? 'check-circle-solid' : 'warning-triangle-solid'} - size="xs" - /> - )} - <div className="letter-spacing-1 text-cn-foreground-1 text-base font-medium">{description}</div> - </Layout.Horizontal> + <div className="text-cn-foreground-1"> + <ConnectivityStatus status={status as ExecutionState} /> + </div> + </Layout.Horizontal> + </div> - {status === 'running' && ( - <div className="mb-1 mt-4"> - <Progress value={percentageFilled / 100} state="processing" size="sm" hideIcon hidePercentage /> - </div> + <div> + <Layout.Horizontal gap="xs" align="center" className="text-center"> + {(status === 'success' || status === 'error') && ( + <IconV2 + className={status === 'success' ? 'text-cn-foreground-success' : 'text-cn-foreground-danger'} + name={status === 'success' ? 'check-circle-solid' : 'warning-triangle-solid'} + size="xs" + /> )} - {status === 'error' && ( - <> - {errorMessage && ( - <div className="mb-1 mt-2"> - <div className="gap-x-0 space-x-2"> - <span className="text-cn-foreground-4 text-sm font-normal"> - {errorMessage} - {viewDocClick && ( - <span className="text-cn-foreground-accent ml-1"> - <Button - variant="link" - onClick={viewDocClick} - className={cn('h-auto', 'p-0', 'font-inherit', 'text-cn-foreground-accent')} - > - {t('views:connectors.viewDocumentation', 'View Documentation')} - <IconV2 name="open-new-window" className="text-cn-foreground-accent ml-1" size="2xs" /> - </Button> - </span> - )} - </span> - </div> + <div className="letter-spacing-1 text-cn-foreground-1 text-base font-medium">{description}</div> + </Layout.Horizontal> + + {status === 'running' && ( + <div className="mb-1 mt-4"> + <Progress value={percentageFilled / 100} state="processing" size="sm" hideIcon hidePercentage /> + </div> + )} + {status === 'error' && ( + <> + {errorMessage && ( + <div className="mb-1 mt-2"> + <div className="gap-x-0 space-x-2"> + <span className="text-cn-foreground-4 text-sm font-normal"> + {errorMessage} + {viewDocClick && ( + <span className="text-cn-foreground-accent ml-1"> + <Button + variant="link" + onClick={viewDocClick} + className={cn('h-auto', 'p-0', 'font-inherit', 'text-cn-foreground-accent')} + > + {t('views:connectors.viewDocumentation', 'View Documentation')} + <IconV2 name="open-new-window" className="text-cn-foreground-accent ml-1" size="2xs" /> + </Button> + </span> + )} + </span> </div> - )} - {errorData && ( - <div className="mb-1 mt-2"> - <MarkdownViewer - source={` + </div> + )} + {errorData && ( + <div className="mb-1 mt-2"> + <MarkdownViewer + source={` \`\`\`text ${JSON.stringify(errorData, null, 2)} \`\`\` `} - showLineNumbers - /> - </div> - )} - </> - )} - </div> - </Dialog.Body> - </Dialog.Header> + showLineNumbers + /> + </div> + )} + </> + )} + </div> + </Dialog.Body> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx index 9d6f9e15a9..1d1ed2589e 100644 --- a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx +++ b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx @@ -97,8 +97,9 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { <Dialog.Header> <Dialog.Title>Create Pipeline</Dialog.Title> </Dialog.Header> - <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)} className="block"> - <Dialog.Body> + + <Dialog.Body> + <FormWrapper id="create-pipline-form" {...formMethods} onSubmit={handleSubmit(onSubmit)}> <Fieldset> <Input id="name" @@ -133,24 +134,24 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { <Alert.Title>{errorMessage}</Alert.Title> </Alert.Root> )} - </Dialog.Body> - - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close - onClick={() => { - onCancel() - reset() - }} - > - Cancel - </Dialog.Close> - <Button type="submit" disabled={isLoadingBranchNames}> - Create Pipeline - </Button> - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close + onClick={() => { + onCancel() + reset() + }} + > + Cancel + </Dialog.Close> + <Button type="submit" form="create-pipline-form" disabled={isLoadingBranchNames}> + Create Pipeline + </Button> + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-keys-create-dialog.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-keys-create-dialog.tsx index 69c7e3aacb..31e83ecabc 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-keys-create-dialog.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-keys-create-dialog.tsx @@ -56,8 +56,8 @@ export const ProfileSettingsKeysCreateDialog: FC<ProfileSettingsKeysCreateDialog <Dialog.Header> <Dialog.Title>{t('views:profileSettings.newSshKey', 'New SSH key')}</Dialog.Title> </Dialog.Header> - <FormWrapper {...formMethods} onSubmit={handleSubmit(handleFormSubmit)} className="block"> - <Dialog.Body> + <Dialog.Body> + <FormWrapper id="ssh-key-form" {...formMethods} onSubmit={handleSubmit(handleFormSubmit)}> <FormInput.Text id="identifier" value={identifier} @@ -77,14 +77,16 @@ export const ProfileSettingsKeysCreateDialog: FC<ProfileSettingsKeysCreateDialog <Alert.Title>{error.message}</Alert.Title> </Alert.Root> )} - </Dialog.Body> - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={onClose}>{t('views:profileSettings.cancel', 'Cancel')}</Dialog.Close> - <Button type="submit">{t('views:profileSettings.save', 'Save')}</Button> - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={onClose}>{t('views:profileSettings.cancel', 'Cancel')}</Dialog.Close> + <Button type="submit" form="ssh-key-form"> + {t('views:profileSettings.save', 'Save')} + </Button> + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx index 588ea2c4b7..fd10ae9cc4 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx @@ -101,8 +101,8 @@ export const ProfileSettingsTokenCreateDialog: FC<ProfileSettingsTokenCreateDial <Dialog.Header> <Dialog.Title>{t('views:profileSettings.createToken', 'Create a token')}</Dialog.Title> </Dialog.Header> - <FormWrapper {...formMethods} onSubmit={handleSubmit(handleFormSubmit)} className="block"> - <Dialog.Body> + <Dialog.Body> + <FormWrapper id="create-token-form" {...formMethods} onSubmit={handleSubmit(handleFormSubmit)}> <FormInput.Text id="identifier" value={identifier} @@ -167,24 +167,24 @@ export const ProfileSettingsTokenCreateDialog: FC<ProfileSettingsTokenCreateDial <Alert.Title>{error.message}</Alert.Title> </Alert.Root> )} - </Dialog.Body> - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={onClose}> - {createdTokenData - ? t('views:profileSettings.gotItButton', 'Got It') - : t('views:profileSettings.cancel', 'Cancel')} - </Dialog.Close> - {!createdTokenData && ( - <Button type="submit" disabled={isLoading}> - {!isLoading - ? t('views:profileSettings.generateTokenButton', 'Generate Token') - : t('views:profileSettings.generatingTokenButton', 'Generating Token...')} - </Button> - )} - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={onClose}> + {createdTokenData + ? t('views:profileSettings.gotItButton', 'Got It') + : t('views:profileSettings.cancel', 'Cancel')} + </Dialog.Close> + {!createdTokenData && ( + <Button type="submit" form="create-token-form" disabled={isLoading}> + {!isLoading + ? t('views:profileSettings.generateTokenButton', 'Generate Token') + : t('views:profileSettings.generatingTokenButton', 'Generating Token...')} + </Button> + )} + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx b/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx index 30bdc43a8b..997c80651e 100644 --- a/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx +++ b/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx @@ -79,7 +79,7 @@ export const InviteMemberDialog: FC<InviteMemberDialogProps> = ({ </Dialog.Header> <Dialog.Body> - <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> + <FormWrapper id="new-member-form" {...formMethods} onSubmit={handleSubmit(onSubmit)}> <Select options={memberOptions} value={invitedMember} @@ -130,7 +130,7 @@ export const InviteMemberDialog: FC<InviteMemberDialogProps> = ({ <Dialog.Close onClick={onClose} loading={isInvitingMember}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> - <Button type="button" onClick={handleSubmit(onSubmit)} disabled={isInvitingMember || !isValid}> + <Button type="submit" form="new-member-form" disabled={isInvitingMember || !isValid}> {t('views:projectSettings.addMember', 'Add member to this project')} </Button> </ButtonLayout> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx index 40b25ee854..bf8ce232c9 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx @@ -113,17 +113,12 @@ export const PullRequestHeaderEditDialog: FC<PullRequestHeaderEditDialogProps> = return ( <Dialog.Root open={open} onOpenChange={handleDialogClose}> <Dialog.Content aria-describedby={undefined}> - <FormWrapper - {...formMethods} - onSubmit={handleSubmit(handleFormSubmit)} - id="edit-pr-title-form" - className="block" - > - <Dialog.Header> - <Dialog.Title>Edit PR title</Dialog.Title> - </Dialog.Header> - - <Dialog.Body> + <Dialog.Header> + <Dialog.Title>Edit PR title</Dialog.Title> + </Dialog.Header> + + <Dialog.Body> + <FormWrapper {...formMethods} onSubmit={handleSubmit(handleFormSubmit)} id="edit-pr-title-form"> <FormInput.Text id={FIELD_TITLE} {...register(FIELD_TITLE)} @@ -152,17 +147,17 @@ export const PullRequestHeaderEditDialog: FC<PullRequestHeaderEditDialogProps> = )} {error && <p className="text-cn-foreground-danger">{error}</p>} - </Dialog.Body> - - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={handleDialogClose}>Cancel</Dialog.Close> - <Button type="submit" disabled={isDisabled}> - {isLoading ? 'Saving...' : 'Save'} - </Button> - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={handleDialogClose}>Cancel</Dialog.Close> + <Button type="submit" form="edit-pr-title-form" disabled={isDisabled}> + {isLoading ? 'Saving...' : 'Save'} + </Button> + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index fb7a9b8589..d38edc2902 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -105,8 +105,8 @@ export function CreateBranchDialog({ <Dialog.Header> <Dialog.Title>{t('views:repos.createBranchTitle', 'Create a branch')}</Dialog.Title> </Dialog.Header> - <FormWrapper {...formMethods} onSubmit={handleSubmit(handleFormSubmit)} className="block"> - <Dialog.Body> + <Dialog.Body> + <FormWrapper id="create-branch-form" {...formMethods} onSubmit={handleSubmit(handleFormSubmit)}> <FormInput.Text id="name" label="Branch name" @@ -142,29 +142,29 @@ export function CreateBranchDialog({ </Alert.Title> </Alert.Root> )} - </Dialog.Body> - - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={handleClose} loading={isCreatingBranch} disabled={isCreatingBranch}> - {t('views:repos.cancel', 'Cancel')} - </Dialog.Close> - {!bypassable ? ( - <Button type="button" onClick={handleSubmit(onSubmit)} disabled={isCreatingBranch}> - {isCreatingBranch - ? t('component:branchDialog.loading', 'Creating branch...') - : t('component:branchDialog.default', 'Create branch')} - </Button> - ) : ( - <Button onClick={handleSubmit(onSubmit)} variant="outline" theme="danger" type="submit"> - {isCreatingBranch - ? t('component:branchDialog.loading', 'Creating branch...') - : t('component:branchDialog.bypassButton', 'Bypass rules and create branch')} - </Button> - )} - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={handleClose} loading={isCreatingBranch} disabled={isCreatingBranch}> + {t('views:repos.cancel', 'Cancel')} + </Dialog.Close> + {!bypassable ? ( + <Button type="submit" form="create-branch-form" disabled={isCreatingBranch}> + {isCreatingBranch + ? t('component:branchDialog.loading', 'Creating branch...') + : t('component:branchDialog.default', 'Create branch')} + </Button> + ) : ( + <Button type="submit" form="create-branch-form" variant="outline" theme="danger"> + {isCreatingBranch + ? t('component:branchDialog.loading', 'Creating branch...') + : t('component:branchDialog.bypassButton', 'Bypass rules and create branch')} + </Button> + )} + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index ededc76ddc..403b832b92 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -71,8 +71,8 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ <Dialog.Title className="font-medium">{t('views:repos.createTagTitle', 'Create a tag')}</Dialog.Title> </Dialog.Header> - <FormWrapper<CreateTagFormFields> {...formMethods} onSubmit={handleSubmit(onSubmit)} className="block"> - <Dialog.Body> + <Dialog.Body> + <FormWrapper<CreateTagFormFields> id="create-tag-form" {...formMethods} onSubmit={handleSubmit(onSubmit)}> <FormInput.Text id="name" label={t('views:forms.tagName', 'Name')} @@ -103,21 +103,21 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ </Alert.Title> </Alert.Root> )} - </Dialog.Body> - - <Dialog.Footer> - <ButtonLayout> - <Dialog.Close onClick={handleClose} loading={isLoading} disabled={isLoading}> - {t('views:repos.cancel', 'Cancel')} - </Dialog.Close> - <Button type="submit" disabled={isLoading} loading={isLoading}> - {isLoading - ? t('views:repos.creatingTagButton', 'Creating tag...') - : t('views:repos.createTagButton', 'Create tag')} - </Button> - </ButtonLayout> - </Dialog.Footer> - </FormWrapper> + </FormWrapper> + </Dialog.Body> + + <Dialog.Footer> + <ButtonLayout> + <Dialog.Close onClick={handleClose} loading={isLoading} disabled={isLoading}> + {t('views:repos.cancel', 'Cancel')} + </Dialog.Close> + <Button type="submit" form="create-tag-form" disabled={isLoading} loading={isLoading}> + {isLoading + ? t('views:repos.creatingTagButton', 'Creating tag...') + : t('views:repos.createTagButton', 'Create tag')} + </Button> + </ButtonLayout> + </Dialog.Footer> </Dialog.Content> </Dialog.Root> ) diff --git a/packages/ui/src/views/user-management/components/dialogs/components/edit-user/edit-user.tsx b/packages/ui/src/views/user-management/components/dialogs/components/edit-user/edit-user.tsx index 41fdc3b8e9..3c8c0e9f2d 100644 --- a/packages/ui/src/views/user-management/components/dialogs/components/edit-user/edit-user.tsx +++ b/packages/ui/src/views/user-management/components/dialogs/components/edit-user/edit-user.tsx @@ -94,7 +94,7 @@ export function EditUserDialog({ handleUpdateUser, open, onClose }: EditUserDial <Dialog.Close onClick={onClose} disabled={isUpdatingUser}> {t('views:userManagement.cancel', 'Cancel')} </Dialog.Close> - <Button type="submit" disabled={isUpdatingUser} form="edit-user-form"> + <Button type="submit" form="edit-user-form" disabled={isUpdatingUser}> {isUpdatingUser ? t('views:userManagement.editUser.pending', 'Saving...') : t('views:userManagement.editUser.save', 'Save')} diff --git a/packages/ui/tailwind-utils-config/components/dialog.ts b/packages/ui/tailwind-utils-config/components/dialog.ts index c07ee1715c..d3ecab8187 100644 --- a/packages/ui/tailwind-utils-config/components/dialog.ts +++ b/packages/ui/tailwind-utils-config/components/dialog.ts @@ -2,7 +2,16 @@ export default { '.cn-modal-dialog-overlay': { backgroundColor: 'var(--cn-comp-dialog-backdrop)', padding: 'var(--cn-dialog-safezone)', - '@apply fixed inset-0 z-50': '' + '@apply fixed inset-0 z-50': '', + + '&[data-state="open"]': { + animation: 'cn-overlay-fadeIn 0.2s ease-out forwards', + }, + + '&[data-state="closed"]': { + animation: 'cn-overlay-fadeOut 0.2s ease forwards', + 'animation-delay': '0.2s' + } }, '.cn-modal-dialog-content': { @@ -22,10 +31,10 @@ export default { '@apply duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95': '', '&[data-state="open"]': { - animation: 'cnDialogSlideIn 0.2s ease-out forwards' + animation: 'cn-dialog-slideIn 0.2s ease-out forwards' }, '&[data-state="closed"]': { - animation: 'cnDialogSlideOut 0.2s ease-in forwards' + animation: 'cn-dialog-slideOut 0.2s ease-in forwards' }, '&.cn-modal-dialog-sm': { @@ -136,8 +145,17 @@ export default { } }, + '@keyframes cn-overlay-fadeIn': { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + '@keyframes cn-overlay-fadeOut': { + '0%': { opacity: '1' }, + '100%': { opacity: '0' } + }, + // Slide in and slide out animations - '@keyframes cnDialogSlideIn': { + '@keyframes cn-dialog-slideIn': { '0%': { transform: 'translate(-50%, -48%)', opacity: '0' @@ -147,7 +165,7 @@ export default { opacity: '1' } }, - '@keyframes cnDialogSlideOut': { + '@keyframes cn-dialog-slideOut': { '0%': { transform: 'translate(-50%, -50%)', opacity: '1' From 6a64fafc5cc3dd2edabeee6ad5aab5530c851572 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Wed, 13 Aug 2025 14:17:30 +0000 Subject: [PATCH 081/180] fix: batch for 20 files else throttle with 500ms delay (#10197) * fix: batch for 20 files else throttle with 500ms delay * fix: batch for 20 files else throttle with 500ms delay * fix: batch for 20 files else throttle with 500ms delay --- .../src/hooks/useRepoFileContentDetails.ts | 68 ++++++++++++++++++- apps/gitness/src/pages-v2/repo/repo-code.tsx | 4 +- .../src/pages-v2/repo/repo-summary.tsx | 4 +- .../views/repo/components/summary/summary.tsx | 40 +++++------ .../views/repo/repo-files/repo-files-view.tsx | 6 +- .../views/repo/repo-summary/repo-summary.tsx | 6 +- 6 files changed, 98 insertions(+), 30 deletions(-) diff --git a/apps/gitness/src/hooks/useRepoFileContentDetails.ts b/apps/gitness/src/hooks/useRepoFileContentDetails.ts index cc5f0bbf0f..4d2adb54c6 100644 --- a/apps/gitness/src/hooks/useRepoFileContentDetails.ts +++ b/apps/gitness/src/hooks/useRepoFileContentDetails.ts @@ -22,8 +22,12 @@ interface UseRepoContentDetailsResult { files: RepoFile[] loading: boolean loadMetadataForPaths: (paths: string[]) => Promise<void> + scheduleFileMetaFetch: (paths: string[]) => void } +export const BATCH_OVERFLOW_TIMEOUT_DELAY = 200 +export const METADETA_FETCH_DELAY = 500 + /** * show files immediately and load metadata lazily */ @@ -42,6 +46,10 @@ export const useRepoFileContentDetails = ({ // files that already have metadata loaded const metadataLoadedRef = useRef(new Set<string>()) + // Batching and throttling refs + const pendingPathsRef = useRef(new Set<string>()) + const throttleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null) + // convert content type to summary item type const getSummaryItemType = useCallback((type: OpenapiGetContentOutput['type']): SummaryItemType => { if (type === 'dir') { @@ -144,6 +152,56 @@ export const useRepoFileContentDetails = ({ [repoRef, fullGitRef, createFileObjectWithMetadata] ) + // Batch and throttle metadata loading + const scheduleFileMetaFetch = useCallback( + (newPaths: string[]) => { + if (!loadMetadataForPaths || newPaths.length === 0) return + + // Add new paths to pending set + newPaths.forEach(path => pendingPathsRef.current.add(path)) + + // Clear existing timeout + if (throttleTimeoutRef.current) { + clearTimeout(throttleTimeoutRef.current) + } + + const currentPendingCount = pendingPathsRef.current.size + const batchSize = 20 + + // If full batch (20 files), process immediately otherwise, throttle with 500ms delay + const throttleDelay = currentPendingCount >= batchSize ? 0 : METADETA_FETCH_DELAY + + // Schedule batched load after throttle delay + throttleTimeoutRef.current = setTimeout(() => { + const pathsToLoad = Array.from(pendingPathsRef.current) + pendingPathsRef.current.clear() + + if (pathsToLoad.length === 0) return + + // Process in batches of 20 (API limit) + const processBatch = async (startIndex: number) => { + if (startIndex >= pathsToLoad.length) return + + const batch = pathsToLoad.slice(startIndex, startIndex + batchSize) + + try { + await loadMetadataForPaths(batch) + } catch (error) { + console.error('Failed to load metadata batch:', error) + } + + // Process next batch with a small delay to avoid overwhelming the API + if (startIndex + batchSize < pathsToLoad.length) { + setTimeout(() => processBatch(startIndex + batchSize), BATCH_OVERFLOW_TIMEOUT_DELAY) + } + } + + processBatch(0) + }, throttleDelay) + }, + [loadMetadataForPaths] + ) + // Initialize files immediately when pathToTypeMap changes useEffect(() => { const shouldUpdateFiles = pathToTypeMap.size > 0 @@ -166,11 +224,19 @@ export const useRepoFileContentDetails = ({ setLoading(false) metadataLoadedRef.current.clear() } + + return () => { + if (throttleTimeoutRef.current) { + clearTimeout(throttleTimeoutRef.current) + } + pendingPathsRef.current.clear() + } }, [pathToTypeMap, allPaths, createBasicFileObject, repoRef, fullGitRef, fullResourcePath]) return { files, loading, - loadMetadataForPaths + loadMetadataForPaths, + scheduleFileMetaFetch } } diff --git a/apps/gitness/src/pages-v2/repo/repo-code.tsx b/apps/gitness/src/pages-v2/repo/repo-code.tsx index ee83eacd29..4d55caf474 100644 --- a/apps/gitness/src/pages-v2/repo/repo-code.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-code.tsx @@ -79,7 +79,7 @@ export const RepoCode = () => { return new Map(nonEmptyPathEntries.map((entry: OpenapiContentInfo) => [entry?.path ? entry.path : '', entry.type])) }, [repoDetails]) - const { files, loading, loadMetadataForPaths } = useRepoFileContentDetails({ + const { files, loading, scheduleFileMetaFetch } = useRepoFileContentDetails({ repoRef, fullGitRef, fullResourcePath, @@ -175,7 +175,7 @@ export const RepoCode = () => { toRepoFileDetails={({ path }: { path: string }) => `../${path}`} showContributeBtn={showContributeBtn} repoDetailsError={repoDetailsError} - loadMetadataForPaths={loadMetadataForPaths} + scheduleFileMetaFetch={scheduleFileMetaFetch} > {renderCodeView} </RepoFiles> diff --git a/apps/gitness/src/pages-v2/repo/repo-summary.tsx b/apps/gitness/src/pages-v2/repo/repo-summary.tsx index 3feb37c8e1..460f542e97 100644 --- a/apps/gitness/src/pages-v2/repo/repo-summary.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-summary.tsx @@ -236,7 +236,7 @@ export default function RepoSummaryPage() { return new Map(nonEmtpyPathEntries.map((entry: OpenapiContentInfo) => [entry.path, entry.type])) }, [repoDetails?.content?.entries]) - const { files, loading, loadMetadataForPaths } = useRepoFileContentDetails({ + const { files, loading, scheduleFileMetaFetch } = useRepoFileContentDetails({ repoRef, fullGitRef, fullResourcePath: '', @@ -325,7 +325,7 @@ export default function RepoSummaryPage() { toRepoTags={() => routes.toRepoTags({ spaceId, repoId })} toRepoPullRequests={() => routes.toRepoPullRequests({ spaceId, repoId })} showContributeBtn={showContributeBtn} - loadMetadataForPaths={loadMetadataForPaths} + scheduleFileMetaFetch={scheduleFileMetaFetch} /> {showTokenDialog && createdTokenData && ( <CloneCredentialDialog diff --git a/packages/ui/src/views/repo/components/summary/summary.tsx b/packages/ui/src/views/repo/components/summary/summary.tsx index 71b8c698f7..b774de51ec 100644 --- a/packages/ui/src/views/repo/components/summary/summary.tsx +++ b/packages/ui/src/views/repo/components/summary/summary.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useCallback, useEffect, useRef } from 'react' import { IconV2, Skeleton, Table, Text, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' @@ -15,7 +15,7 @@ interface SummaryProps extends RoutingProps { hideHeader?: boolean toCommitDetails?: ({ sha }: { sha: string }) => string toRepoFileDetails?: ({ path }: { path: string }) => string - loadMetadataForPaths?: (paths: string[]) => Promise<void> + scheduleFileMetaFetch?: (paths: string[]) => void } export const Summary = ({ @@ -24,7 +24,7 @@ export const Summary = ({ hideHeader = false, toCommitDetails, toRepoFileDetails, - loadMetadataForPaths + scheduleFileMetaFetch }: SummaryProps) => { const { t } = useTranslation() @@ -35,7 +35,7 @@ export const Summary = ({ // Initialize intersection observer for lazy metadata loading useEffect(() => { - if (!loadMetadataForPaths) return + if (!scheduleFileMetaFetch) return // Disconnect existing observer if (intersectionObserverRef.current) { @@ -57,11 +57,9 @@ export const Summary = ({ } }) - // Load metadata for newly visible files + // Report visible files to hook for batched processing if (visiblePaths.length > 0) { - loadMetadataForPaths(visiblePaths).catch(error => { - console.error('Failed to load metadata for visible files:', error) - }) + scheduleFileMetaFetch(visiblePaths) } }, { @@ -83,7 +81,7 @@ export const Summary = ({ intersectionObserverRef.current.disconnect() } } - }, [loadMetadataForPaths, files]) + }, [scheduleFileMetaFetch, files]) // Reset observed files and table row refs when files list changes useEffect(() => { @@ -91,6 +89,19 @@ export const Summary = ({ tableRowsRef.current.clear() }, [files]) + // Set table row ref for intersection observer + const setTableRowRef = useCallback((element: HTMLTableRowElement | null, fileId: string) => { + if (element) { + tableRowsRef.current.set(fileId, element) + // Observe new elements immediately + if (intersectionObserverRef.current) { + intersectionObserverRef.current.observe(element) + } + } else { + tableRowsRef.current.delete(fileId) + } + }, []) + return ( <> {!hideHeader && <FileLastChangeBar toCommitDetails={toCommitDetails} {...latestFile} />} @@ -118,16 +129,7 @@ export const Summary = ({ key={file.id} to={toRepoFileDetails?.({ path: file.path }) ?? ''} data-file-id={file.id} - ref={el => { - // Store table row ref - if (el) { - tableRowsRef.current.set(file.id, el) - } - // Observe each table row for intersection - if (el && intersectionObserverRef.current) { - intersectionObserverRef.current.observe(el) - } - }} + ref={element => setTableRowRef(element, file.id)} > <Table.Cell className="relative"> <div diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 8985529dcb..81cd2bcab2 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -43,7 +43,7 @@ interface RepoFilesProps { fullResourcePath?: string showContributeBtn?: boolean repoDetailsError?: UsererrorError | null - loadMetadataForPaths?: (paths: string[]) => Promise<void> + scheduleFileMetaFetch?: (paths: string[]) => void } export const RepoFiles: FC<RepoFilesProps> = ({ @@ -71,7 +71,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ fullResourcePath, showContributeBtn, repoDetailsError, - loadMetadataForPaths + scheduleFileMetaFetch }) => { const { t } = useTranslation() @@ -113,7 +113,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ latestFile={latestFile} files={files} toRepoFileDetails={toRepoFileDetails} - loadMetadataForPaths={loadMetadataForPaths} + scheduleFileMetaFetch={scheduleFileMetaFetch} /> </> ) diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 4f47d696e3..28ba92f27e 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -87,7 +87,7 @@ export interface RepoSummaryViewProps extends Partial<RoutingProps> { refType?: BranchSelectorTab prCandidateBranches?: TypesBranchTable[] showContributeBtn?: boolean - loadMetadataForPaths?: (paths: string[]) => Promise<void> + scheduleFileMetaFetch?: (paths: string[]) => void } export function RepoSummaryView({ @@ -120,7 +120,7 @@ export function RepoSummaryView({ tokenGenerationError, refType = BranchSelectorTab.BRANCHES, showContributeBtn, - loadMetadataForPaths, + scheduleFileMetaFetch, ...props }: RepoSummaryViewProps) { const { Link } = useRouterContext() @@ -232,7 +232,7 @@ export function RepoSummaryView({ files={files} toRepoFileDetails={toRepoFileDetails} hideHeader - loadMetadataForPaths={loadMetadataForPaths} + scheduleFileMetaFetch={scheduleFileMetaFetch} /> <Spacer size={5} /> <StackedList.Root onlyTopRounded borderBackground> From 66b3cd12596960cb99bac96ad27a3171d9c7ebf7 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Wed, 13 Aug 2025 15:19:30 +0000 Subject: [PATCH 082/180] fix: codeowners required panel state in case review is required from codeowners (#10196) * fix: typecheck * fix: codeowners table for outdated approvals * refactor: self review * fix: codeowners required panel state in case review is required from codeowners --- .../__tests__/pull-request-utils.test.ts | 2 +- .../pull-request-conversation.tsx | 6 ++++- .../pull-request/pull-request-utils.ts | 26 ++++++++++++++----- .../conversation/pull-request-panel.tsx | 1 + .../components/code-owners-section.tsx | 9 ++++--- .../sections/pull-request-changes-section.tsx | 4 ++- .../details/pull-request-details-types.ts | 4 ++- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/__tests__/pull-request-utils.test.ts b/apps/gitness/src/pages-v2/pull-request/__tests__/pull-request-utils.test.ts index 4eec734ac2..9d06a1f078 100644 --- a/apps/gitness/src/pages-v2/pull-request/__tests__/pull-request-utils.test.ts +++ b/apps/gitness/src/pages-v2/pull-request/__tests__/pull-request-utils.test.ts @@ -180,7 +180,7 @@ describe('findChangeReqDecisions', () => { describe('findWaitingDecisions', () => { it('should find waiting decisions from entries', () => { - const result = findWaitingDecisions(mockEntriesForWaitingDecisions) + const result = findWaitingDecisions(mockEntriesForWaitingDecisions, '', true) expect(result).toEqual([ { pattern: '**', diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 06c6e8c810..6019eff9e0 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -501,7 +501,11 @@ export default function PullRequestConversationPage() { return { codeOwners: codeOwners, codeOwnerChangeReqEntries: findChangeReqDecisions(data, CodeOwnerReqDecision.CHANGEREQ), - codeOwnerPendingEntries: findWaitingDecisions(data), + codeOwnerPendingEntries: findWaitingDecisions( + data, + pullReqMetadata?.source_sha ?? '', + prPanelData?.reqCodeOwnerLatestApproval + ), codeOwnerApprovalEntries, latestCodeOwnerApprovalArr, reqCodeOwnerApproval: prPanelData?.reqCodeOwnerApproval, diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-utils.ts b/apps/gitness/src/pages-v2/pull-request/pull-request-utils.ts index 1fbf47b8ad..58c2c03d37 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-utils.ts +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-utils.ts @@ -6,6 +6,7 @@ import { EnumPullReqReviewDecision, ListPullReqQueryQueryParams, TypesCodeOwnerEvaluationEntry, + TypesOwnerEvaluation, TypesPrincipalInfo, TypesPullReqReviewer, TypesRuleViolations, @@ -260,18 +261,29 @@ export const findChangeReqDecisions = ( } } -export const findWaitingDecisions = (entries: TypesCodeOwnerEvaluationEntry[] | null | undefined) => { - if (entries === null || entries === undefined) { +export const findWaitingDecisions = ( + entries: TypesCodeOwnerEvaluationEntry[] | null | undefined, + source_sha: string, + reqCodeOwnerLatestApproval?: boolean +) => { + if (isEmpty(entries) || entries === null || entries === undefined) { + //adding extra check to bypass typescript error return [] } else { return entries.filter((entry: TypesCodeOwnerEvaluationEntry) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hasEmptyDecision = entry?.owner_evaluations?.some((evaluation: any) => evaluation?.review_decision === '') - const hasNoApprovedDecision = !entry?.owner_evaluations?.some( - evaluation => evaluation.review_decision === 'approved' + const hasNoReview = entry?.owner_evaluations?.every( + (evaluation: TypesOwnerEvaluation | { review_decision: string }) => evaluation.review_decision === '' ) - return hasEmptyDecision && hasNoApprovedDecision + // add entry if no review found from codeowners + if (hasNoReview) return true + // add entry to waiting decision array if approved changes are outdated or no approvals are found for the given entry + const hasNoApprovedDecision = !entry?.owner_evaluations?.some( + evaluation => + evaluation.review_decision === PullReqReviewDecision.approved && + (reqCodeOwnerLatestApproval ? evaluation.review_sha === source_sha : true) + ) + return hasNoApprovedDecision }) } } diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 880b9aaeac..079f316af4 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -730,6 +730,7 @@ const PullRequestPanel = ({ changeReqReviewer={changeReqReviewer} codeOwnersData={codeOwnersData} defaultReviewersData={defaultReviewersData} + pullReqMetadata={pullReqMetadata} /> )} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/components/code-owners-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/components/code-owners-section.tsx index 47fdab0242..39ae541a6a 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/components/code-owners-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/components/code-owners-section.tsx @@ -1,7 +1,7 @@ import { FC, useMemo } from 'react' import { IconV2, StatusBadge, Table } from '@/components' -import { CodeOwnersSectionProps, TypesOwnerEvaluation } from '@/views' +import { CodeOwnersSectionProps, PullReqReviewDecision, TypesOwnerEvaluation } from '@/views' import { cn } from '@utils/cn' import { isEmpty } from 'lodash-es' @@ -17,6 +17,7 @@ const mapEvaluationsToUsers = (evaluations: TypesOwnerEvaluation[]): AvatarUser[ export const CodeOwnersSection: FC<CodeOwnersSectionProps> = ({ codeOwners, + pullReqMetadata, reqCodeOwnerApproval, reqCodeOwnerLatestApproval, codeOwnerChangeReqEntries, @@ -140,10 +141,12 @@ export const CodeOwnersSection: FC<CodeOwnersSectionProps> = ({ <Table.Body> {codeOwners?.evaluation_entries?.map(entry => { const changeReqEvaluations = entry?.owner_evaluations?.filter( - evaluation => evaluation.review_decision === 'changereq' + evaluation => evaluation.review_decision === PullReqReviewDecision.changeReq ) const approvedEvaluations = entry?.owner_evaluations?.filter( - evaluation => evaluation.review_decision === 'approved' + evaluation => + evaluation.review_decision === PullReqReviewDecision.approved && + (reqCodeOwnerLatestApproval ? evaluation.review_sha === pullReqMetadata?.source_sha : true) ) return ( <Table.Row key={entry.pattern} className="cursor-pointer"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx index d4c0dd8b8d..35f0d2f1ca 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx @@ -36,7 +36,8 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ changeReqReviewer, codeOwnersData, accordionValues, - defaultReviewersData + defaultReviewersData, + pullReqMetadata }) => { const { codeOwners, @@ -150,6 +151,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ <CodeOwnersSection codeOwners={codeOwners} + pullReqMetadata={pullReqMetadata} reqCodeOwnerApproval={reqCodeOwnerApproval} reqCodeOwnerLatestApproval={reqCodeOwnerLatestApproval} codeOwnerChangeReqEntries={codeOwnerChangeReqEntries} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts b/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts index 42f9eb42b2..a4e8d938c8 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts @@ -566,6 +566,7 @@ export interface PullRequestChangesSectionProps { changeReqReviewer?: string accordionValues: string[] defaultReviewersData?: DefaultReviewersDataProps + pullReqMetadata?: TypesPullReq } export interface CodeOwnersData { @@ -599,7 +600,8 @@ export interface CodeOwnersData { )[] } -export type CodeOwnersSectionProps = Pick<PullRequestChangesSectionProps, 'minReqLatestApproval'> & CodeOwnersData +export type CodeOwnersSectionProps = Pick<PullRequestChangesSectionProps, 'minReqLatestApproval' | 'pullReqMetadata'> & + CodeOwnersData export const PullRequestFilterOption = { ...PullRequestState, From 563a77c14c4f998cf282e8c38e93efd041a7e7a6 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Wed, 13 Aug 2025 17:32:14 +0000 Subject: [PATCH 083/180] feat: file explorer functionality for PR diffs in changes and compare page - changes tab (#10185) * feat: file explorer functionality for PR diffs in changes and compare page - changes tab * feat: file explorer functionality for PR diffs in changes and compare page - changes tab * feat: file explorer functionality for PR diffs in changes and compare page - changes tab * feat: file explorer functionality for PR diffs in changes and compare page - changes tab * feat: file explorer functionality for PR diffs in changes and compare page - changes tab --- .../src/pages-v2/repo/repo-sidebar.tsx | 78 +---- packages/ui/src/components/file-explorer.tsx | 8 +- .../components/draggable-sidebar-divider.tsx | 92 +++++ .../ui/src/views/repo/components/index.ts | 1 + .../pull-request-compare-diff-list.tsx | 150 +++++---- .../compare/pull-request-compare-page.tsx | 1 + .../components/pull-request-diff-sidebar.tsx | 49 +++ .../changes/pull-request-changes-explorer.tsx | 315 ++++++++++++++++++ .../changes/pull-request-changes-filter.tsx | 24 +- .../details/pull-request-changes-page.tsx | 88 +++-- 10 files changed, 639 insertions(+), 167 deletions(-) create mode 100644 packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx create mode 100644 packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx create mode 100644 packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index d184dbafce..c1e85200f9 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Outlet, useNavigate, useParams } from 'react-router-dom' import { @@ -9,8 +9,15 @@ import { useListPathsQuery, useListTagsQuery } from '@harnessio/code-service-client' -import { SkeletonFileExplorer } from '@harnessio/ui/components' -import { BranchSelectorListItem, BranchSelectorTab, RepoSidebar as RepoSidebarView } from '@harnessio/ui/views' +import { Layout, SkeletonFileExplorer } from '@harnessio/ui/components' +import { + BranchSelectorListItem, + BranchSelectorTab, + DraggableSidebarDivider, + RepoSidebar as RepoSidebarView, + SIDEBAR_MAX_WIDTH, + SIDEBAR_MIN_WIDTH +} from '@harnessio/ui/views' import { BranchSelectorContainer } from '../../components-v2/branch-selector-container' import { CreateBranchDialog } from '../../components-v2/create-branch-dialog' @@ -22,9 +29,6 @@ import { PathParams } from '../../RouteDefinitions' import { FILE_SEPERATOR, normalizeGitRef, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../../utils/git-utils' import { transformBranchList } from './transform-utils/branch-transform' -const SIDEBAR_MIN_WIDTH = 264 -const SIDEBAR_MAX_WIDTH = 700 - /** * TODO: This code was migrated from V2 and needs to be refactored. */ @@ -37,6 +41,7 @@ export const RepoSidebar = () => { const [isCreateBranchDialogOpen, setCreateBranchDialogOpen] = useState(false) const [branchQueryForNewBranch, setBranchQueryForNewBranch] = useState<string>('') const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const containerRef = useRef<HTMLDivElement>(null) const { fullGitRef, @@ -185,56 +190,12 @@ export const RepoSidebar = () => { [fullGitRef, navigate, repoId] ) - const handleMouseDown = useCallback( - (e: React.MouseEvent<HTMLDivElement>) => { - const startX = e.clientX - const startWidth = sidebarWidth - - const handleMouseMove = (e: MouseEvent) => { - const newWidth = startWidth + (e.clientX - startX) - setSidebarWidth(Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, newWidth))) - } - - const handleMouseUp = () => { - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) - } - - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) - }, - [sidebarWidth] - ) - - const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => { - const step = e.shiftKey ? 50 : 10 // Larger steps with Shift key - - switch (e.key) { - case 'ArrowLeft': - e.preventDefault() - setSidebarWidth(prev => Math.max(SIDEBAR_MIN_WIDTH, prev - step)) - break - case 'ArrowRight': - e.preventDefault() - setSidebarWidth(prev => Math.min(SIDEBAR_MAX_WIDTH, prev + step)) - break - case 'Home': - e.preventDefault() - setSidebarWidth(SIDEBAR_MIN_WIDTH) // Minimum width - break - case 'End': - e.preventDefault() - setSidebarWidth(SIDEBAR_MAX_WIDTH) // Maximum width - break - } - }, []) - // TODO: repoId and spaceId must be defined if (!repoId) return <></> return ( <> - <div className="flex flex-1"> + <Layout.Flex className="flex-1" ref={containerRef}> <div className={`shrink-0 overflow-hidden min-w-[${SIDEBAR_MIN_WIDTH}px] max-w-[${SIDEBAR_MAX_WIDTH}px]`} style={{ @@ -263,21 +224,10 @@ export const RepoSidebar = () => { </RepoSidebarView> </div> - {/* Resizable Divider */} - <div - onMouseDown={handleMouseDown} - onKeyDown={handleKeyDown} - className="border-cn-borders-2 focus-within:border-cn-borders-1 focus-visible:border-cn-borders-1 w-1 shrink-0 cursor-col-resize select-none border-r transition-colors" - role="slider" - aria-valuenow={sidebarWidth} - aria-valuemax={SIDEBAR_MAX_WIDTH} - aria-valuemin={SIDEBAR_MIN_WIDTH} - tabIndex={0} - aria-label="Resize panel by dragging or using arrow keys (with shift for larger steps)" - /> + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> <Outlet /> - </div> + </Layout.Flex> <CreateBranchDialog open={isCreateBranchDialogOpen} onClose={() => setCreateBranchDialogOpen(false)} diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index 787cfccab5..48f70f73ce 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -35,7 +35,7 @@ interface FolderItemProps { value?: string isActive?: boolean content?: ReactNode - link: string + link?: string } function FolderItem({ children, value = '', isActive, content, link, level }: FolderItemProps) { @@ -47,7 +47,7 @@ function FolderItem({ children, value = '', isActive, content, link, level }: Fo className="pl-cn-2xs mb-cn-4xs p-0 [&>.cn-accordion-trigger-indicator]:mt-0 [&>.cn-accordion-trigger-indicator]:-rotate-90 [&>.cn-accordion-trigger-indicator]:self-center [&>.cn-accordion-trigger-indicator]:data-[state=open]:-rotate-0" indicatorProps={{ size: '2xs' }} > - <Link to={link}> + <Link to={link || ''}> <Item icon="folder" isActive={isActive} @@ -78,9 +78,10 @@ interface FileItemProps { children: ReactNode isActive?: boolean link?: string + onClick?: () => void } -function FileItem({ children, isActive, level, link }: FileItemProps) { +function FileItem({ children, isActive, level, link, onClick }: FileItemProps) { const { Link } = useRouterContext() const comp = ( <Item @@ -91,6 +92,7 @@ function FileItem({ children, isActive, level, link }: FileItemProps) { marginLeft: `calc(-16px * ${level})`, paddingLeft: `calc(16px * ${level + 1} + 8px)` }} + onClick={onClick} > {children} </Item> diff --git a/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx new file mode 100644 index 0000000000..7361679054 --- /dev/null +++ b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx @@ -0,0 +1,92 @@ +import { RefObject, useCallback } from 'react' + +import { cn } from '@utils/cn' + +interface DraggableSidebarDividerProps { + width: number + setWidth: (width: number | ((prev: number) => number)) => void + containerRef?: RefObject<HTMLElement> + minWidth?: number + maxWidth?: number + ariaLabel?: string + className?: string +} + +export const SIDEBAR_MIN_WIDTH = 264 +export const SIDEBAR_MAX_WIDTH = 700 + +export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ({ + width, + setWidth, + containerRef, + minWidth = SIDEBAR_MIN_WIDTH, + maxWidth = SIDEBAR_MAX_WIDTH, + ariaLabel = 'Resize panel by dragging or using arrow keys (with shift for larger steps)', + className +}) => { + const handleMouseDown = useCallback( + (e: React.MouseEvent<HTMLDivElement>) => { + const startX = e.clientX + const startWidth = width + const container = containerRef?.current || document + + const handleMouseMove = (e: Event) => { + const mouseEvent = e as MouseEvent + const newWidth = startWidth + (mouseEvent.clientX - startX) + setWidth(Math.max(minWidth, Math.min(maxWidth, newWidth))) + } + + const handleMouseUp = () => { + container.removeEventListener('mousemove', handleMouseMove) + container.removeEventListener('mouseup', handleMouseUp) + } + + container.addEventListener('mousemove', handleMouseMove) + container.addEventListener('mouseup', handleMouseUp) + }, + [width, setWidth, minWidth, maxWidth, containerRef] + ) + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent<HTMLDivElement>) => { + const step = e.shiftKey ? 50 : 10 // Larger steps with Shift key + + switch (e.key) { + case 'ArrowLeft': + e.preventDefault() + setWidth(prev => Math.max(minWidth, prev - step)) + break + case 'ArrowRight': + e.preventDefault() + setWidth(prev => Math.min(maxWidth, prev + step)) + break + case 'Home': + e.preventDefault() + setWidth(minWidth) // Minimum width + break + case 'End': + e.preventDefault() + setWidth(maxWidth) // Maximum width + break + } + }, + [setWidth, minWidth, maxWidth] + ) + + return ( + <div + onMouseDown={handleMouseDown} + onKeyDown={handleKeyDown} + className={cn( + 'border-cn-borders-3 focus-within:border-cn-borders-1 focus-visible:border-cn-borders-1 w-1 shrink-0 cursor-col-resize select-none border-r transition-colors', + className + )} + role="slider" + aria-valuenow={width} + aria-valuemax={maxWidth} + aria-valuemin={minWidth} + tabIndex={0} + aria-label={ariaLabel} + /> + ) +} diff --git a/packages/ui/src/views/repo/components/index.ts b/packages/ui/src/views/repo/components/index.ts index 38f57dfe83..54061595a0 100644 --- a/packages/ui/src/views/repo/components/index.ts +++ b/packages/ui/src/views/repo/components/index.ts @@ -7,3 +7,4 @@ export * from './path-action-bar' export * from './repo-header' export * from './token-dialog/clone-credential-dialog' export * from './changed-files-short-info/changed-files-short-info' +export * from './draggable-sidebar-divider' diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index 3ccd87cf88..61c70ffe4b 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -1,18 +1,21 @@ import { FC, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { ListActions } from '@/components' +import { Button, IconV2, Layout, ListActions } from '@/components' import { - ChangedFilesShortInfo, DiffModeOptions, + DraggableSidebarDivider, InViewDiffRenderer, jumpToFile, PrincipalPropsType, + SIDEBAR_MIN_WIDTH, TypesDiffStats } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' +import { cn } from '@utils/cn' import { chunk } from 'lodash-es' import { HeaderProps, PullRequestAccordion } from '../../components/pull-request-accordian' +import { PullRequestDiffSidebar } from '../../components/pull-request-diff-sidebar' import { calculateDetectionMargin, IN_VIEWPORT_DETECTION_MARGIN, @@ -23,7 +26,7 @@ import { } from '../../utils' interface PullRequestCompareDiffListProps { - diffStats: TypesDiffStats + diffStats?: TypesDiffStats diffData: HeaderProps[] currentUser?: string sourceBranch?: string @@ -35,7 +38,6 @@ interface PullRequestCompareDiffListProps { } const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ - diffStats, diffData, currentUser, jumpToDiff, @@ -50,8 +52,11 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ setDiffMode(value === 'Split' ? DiffModeEnum.Split : DiffModeEnum.Unified) } const [openItems, setOpenItems] = useState<string[]>([]) + const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) const diffBlocks = useMemo(() => chunk(diffData, PULL_REQUEST_DIFF_RENDERING_BLOCK_SIZE), [diffData]) const diffsContainerRef = useRef<HTMLDivElement | null>(null) + const containerRef = useRef<HTMLDivElement>(null) + const [showExplorer, setShowExplorer] = useState(true) useEffect(() => { if (diffData.length > 0) { @@ -88,62 +93,91 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ ) return ( - <> - <ListActions.Root className="sticky top-[100px] z-20 bg-cn-background-1 py-2"> - <ListActions.Left> - <ChangedFilesShortInfo diffData={diffData} diffStats={diffStats} goToDiff={setJumpToDiff} /> - </ListActions.Left> - <ListActions.Right> - <ListActions.Dropdown - selectedValue={diffMode === DiffModeEnum.Split ? 'Split' : 'Unified'} - onChange={handleDiffModeChange} - title={diffMode === DiffModeEnum.Split ? 'Split' : 'Unified'} - items={DiffModeOptions} + <Layout.Flex className="flex-1" ref={containerRef}> + {showExplorer && ( + <> + <PullRequestDiffSidebar + sidebarWidth={sidebarWidth} + filePaths={diffData?.map(diff => diff.filePath) || []} + setJumpToDiff={setJumpToDiff} + diffsData={ + diffData?.map(item => ({ + addedLines: item.addedLines || 0, + deletedLines: item.deletedLines || 0, + lang: item.filePath.split('.')[1], + filePath: item.filePath, + isDeleted: !!item.isDeleted, + unchangedPercentage: item.unchangedPercentage || 0 + })) || [] + } /> - </ListActions.Right> - </ListActions.Root> - <div className="flex flex-col" ref={diffsContainerRef}> - {diffBlocks?.map((diffsBlock, blockIndex) => { - return ( - <InViewDiffRenderer - key={blockIndex} - blockName={outterBlockName(blockIndex)} - root={document as unknown as RefObject<Element>} - shouldRetainChildren={shouldRetainDiffChildren} - detectionMargin={calculateDetectionMargin(diffData?.length)} + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> + </> + )} + <Layout.Flex className={cn('p-0', showExplorer ? 'pl-cn-xl' : '')} direction="column"> + <ListActions.Root className="layer-high bg-cn-background-1 sticky top-[55px] py-2"> + <ListActions.Left> + <Button + size="md" + title={showExplorer ? 'Collapse Sidebar' : 'Expand Sidebar'} + variant="transparent" + onClick={() => setShowExplorer(!showExplorer)} > - {diffsBlock?.map((item, index) => ( - <div className="pt-4" key={item.filePath}> - <InViewDiffRenderer - key={item.filePath} - blockName={innerBlockName(item?.filePath ?? (blockIndex + index).toString())} - root={diffsContainerRef} - shouldRetainChildren={shouldRetainDiffChildren} - detectionMargin={IN_VIEWPORT_DETECTION_MARGIN} - > - <PullRequestAccordion - principalProps={principalProps} - key={`item?.title ? ${item?.title}-${index} : ${index}`} - header={item} - currentUser={currentUser} - diffMode={diffMode} - openItems={openItems} - onToggle={() => toggleOpen(item.text)} - setCollapsed={val => setCollapsed(item.text, val)} - onGetFullDiff={onGetFullDiff} - toRepoFileDetails={toRepoFileDetails} - sourceBranch={sourceBranch} - hideViewedCheckbox - addWidget={false} - /> - </InViewDiffRenderer> - </div> - ))} - </InViewDiffRenderer> - ) - })} - </div> - </> + <IconV2 name={showExplorer ? 'collapse-sidebar' : 'expand-sidebar'} size="md" /> + </Button> + </ListActions.Left> + <ListActions.Right> + <ListActions.Dropdown + selectedValue={diffMode === DiffModeEnum.Split ? 'Split' : 'Unified'} + onChange={handleDiffModeChange} + title={diffMode === DiffModeEnum.Split ? 'Split' : 'Unified'} + items={DiffModeOptions} + /> + </ListActions.Right> + </ListActions.Root> + <div className="flex flex-col" ref={diffsContainerRef}> + {diffBlocks?.map((diffsBlock, blockIndex) => { + return ( + <InViewDiffRenderer + key={blockIndex} + blockName={outterBlockName(blockIndex)} + root={document as unknown as RefObject<Element>} + shouldRetainChildren={shouldRetainDiffChildren} + detectionMargin={calculateDetectionMargin(diffData?.length)} + > + {diffsBlock?.map((item, index) => ( + <div className="pt-4" key={item.filePath}> + <InViewDiffRenderer + key={item.filePath} + blockName={innerBlockName(item?.filePath ?? (blockIndex + index).toString())} + root={diffsContainerRef} + shouldRetainChildren={shouldRetainDiffChildren} + detectionMargin={IN_VIEWPORT_DETECTION_MARGIN} + > + <PullRequestAccordion + principalProps={principalProps} + key={`item?.title ? ${item?.title}-${index} : ${index}`} + header={item} + currentUser={currentUser} + diffMode={diffMode} + openItems={openItems} + onToggle={() => toggleOpen(item.text)} + setCollapsed={val => setCollapsed(item.text, val)} + onGetFullDiff={onGetFullDiff} + toRepoFileDetails={toRepoFileDetails} + sourceBranch={sourceBranch} + hideViewedCheckbox + addWidget={false} + /> + </InViewDiffRenderer> + </div> + ))} + </InViewDiffRenderer> + ) + })} + </div> + </Layout.Flex> + </Layout.Flex> ) } diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index f45236f0d8..c538a44f27 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -222,6 +222,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ // Return a default value return review_decision } + return ( <SandboxLayout.Main fullWidth> <SandboxLayout.Content> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx new file mode 100644 index 0000000000..c54cedc83a --- /dev/null +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx @@ -0,0 +1,49 @@ +import { ScrollArea, SearchFiles } from '@/components' + +import { SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_WIDTH } from '../../components/draggable-sidebar-divider' +import { + ExplorerDiffData, + PullRequestChangesExplorer +} from '../details/components/changes/pull-request-changes-explorer' + +interface PullRequestDiffSidebarProps { + sidebarWidth: number + filePaths: string[] + setJumpToDiff: (fileName: string) => void + activePath?: string + diffsData?: ExplorerDiffData[] +} + +export const PullRequestDiffSidebar: React.FC<PullRequestDiffSidebarProps> = ({ + sidebarWidth, + filePaths, + setJumpToDiff, + activePath, + diffsData +}) => { + return ( + <div + className={`h-screen sticky top-[55px] shrink-0 min-w-[${SIDEBAR_MIN_WIDTH}px] max-w-[${SIDEBAR_MAX_WIDTH}px] pr-cn-xs overflow-hidden`} + style={{ + width: `${sidebarWidth}px` + }} + > + <div className="flex h-full flex-col gap-3 pt-1.5"> + <SearchFiles + navigateToFile={file => { + setJumpToDiff(file) + }} + filesList={filePaths} + /> + <ScrollArea className="pb-cn-xl -mr-cn-xs pr-cn-xs flex-1"> + <PullRequestChangesExplorer + paths={filePaths} + setJumpToDiff={setJumpToDiff} + activePath={activePath} + diffsData={diffsData} + /> + </ScrollArea> + </div> + </div> + ) +} diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx new file mode 100644 index 0000000000..082bc467f0 --- /dev/null +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx @@ -0,0 +1,315 @@ +import { memo, useMemo, useRef, useState } from 'react' + +import { FileExplorer, StatusBadge, Text, Tooltip } from '@/components' + +type TreeNode = FolderNode | FileNode + +interface FileNode { + type: 'file' + name: string + path: string +} + +interface FolderNode { + type: 'folder' + name: string + path: string + children: TreeNode[] +} + +export interface ExplorerDiffData { + addedLines: number + deletedLines: number + lang: string + filePath: string + isDeleted: boolean + unchangedPercentage: number +} + +/** + * Builds a hierarchical file tree from flat file paths for UI rendering. + * STEPS: + * 1. Tree Construction: Iteratively builds nested object structure from path segments + * 2. Type Inference: determines file vs folder based on path position + * 3. Flattening: Collapses single-child folder chains for better UX + * @param rawPaths - Array of file paths (e.g., ["src/utils.ts", "README.md"]) + * @returns Hierarchical tree structure optimized for React rendering + */ +export function buildFileTree(rawPaths: string[]): FolderNode[] { + type FileTree = { + name: string + path: string + type: 'file' | 'folder' + children: Record<string, FileTree> + } + const root: Record<string, FileTree> = {} + + rawPaths.forEach(raw => { + const parts = raw.split('/').filter(Boolean) + let level = root + let accum = '' + + parts.forEach((seg, i) => { + accum = accum ? `${accum}/${seg}` : seg + const leaf = i === parts.length - 1 + + if (!level[seg]) { + level[seg] = { + name: seg, + path: accum, + type: leaf ? 'file' : 'folder', + children: {} + } + } else if (!leaf && level[seg].type === 'file') { + // upgrade file→folder if a deeper path exists + level[seg].type = 'folder' + } + + level = level[seg].children + }) + }) + + // convert fileTree → TreeNode[] + function fileTreeToNodes(fileTree: Record<string, FileTree>): TreeNode[] { + return Object.values(fileTree).map(node => + node.type === 'folder' + ? { + type: 'folder' as const, + name: node.name, + path: node.path, + children: fileTreeToNodes(node.children) + } + : { + type: 'file' as const, + name: node.name, + path: node.path + } + ) + } + + /* flatten any pure folder chains + * Prevents deeply nested single folders (e.g., "src" → "components" → "ui") -> "src/components/ui" as single node + */ + function flatten(nodes: TreeNode[]): TreeNode[] { + return nodes.map(node => { + if (node.type === 'folder') { + // first, flatten children recursively + let kids = flatten(node.children) + + // then collapse chains of single‐child folders + let mergedName = node.name + let mergedPath = node.path + + while (kids.length === 1 && kids[0].type === 'folder') { + const onlyChild = kids[0] as FolderNode + mergedName = `${mergedName}/${onlyChild.name}` + mergedPath = onlyChild.path + kids = onlyChild.children + } + + return { + type: 'folder' as const, + name: mergedName, + path: mergedPath, + children: kids + } + } else { + return node + } + }) + } + + const rawTree = fileTreeToNodes(root) + return flatten(rawTree) as FolderNode[] +} + +/** + * Flattens file tree into linear array. + * @param rawPaths - Array of file paths to process + * @returns File paths + */ +export function getFilePathsInTreeOrder(rawPaths: string[]): string[] { + type FileTree = { + name: string + path: string + type: 'file' | 'folder' + children: Record<string, FileTree> + } + const root: Record<string, FileTree> = {} + + // Build the same tree structure as buildFileTree + rawPaths.forEach(raw => { + const parts = raw.split('/').filter(Boolean) + let level = root + let accum = '' + + parts.forEach((seg, i) => { + accum = accum ? `${accum}/${seg}` : seg + const leaf = i === parts.length - 1 + + if (!level[seg]) { + level[seg] = { + name: seg, + path: accum, + type: leaf ? 'file' : 'folder', + children: {} + } + } else if (!leaf && level[seg].type === 'file') { + level[seg].type = 'folder' + } + + level = level[seg].children + }) + }) + + function flattenTreePaths(fileTree: Record<string, FileTree>): string[] { + return Object.values(fileTree).flatMap(node => { + if (node.type === 'folder') { + return flattenTreePaths(node.children) + } else { + return [node.path] + } + }) + } + + return flattenTreePaths(root) +} + +interface PullRequestChangesExplorerProps { + paths: string[] + activePath?: string + onFolderValueChange?: (values: string[]) => void + setJumpToDiff: (fileName: string) => void + diffsData?: ExplorerDiffData[] +} + +export const PullRequestChangesExplorer: React.FC<PullRequestChangesExplorerProps> = memo( + ({ paths, activePath, onFolderValueChange, setJumpToDiff, diffsData }) => { + // build once per paths change + const tree = useMemo(() => buildFileTree(paths), [paths]) + + // Track the last paths we initialized folders for + const lastInitializedPaths = useRef<string>('') + + // Extract all folder paths from tree for default expanded state + const allFolderPaths = useMemo(() => { + function extractFolderPaths(nodes: TreeNode[]): string[] { + const folderPaths: string[] = [] + + nodes.forEach(node => { + if (node.type === 'folder') { + folderPaths.push(node.path) + // Recursively collect folder paths from children + folderPaths.push(...extractFolderPaths(node.children)) + } + }) + + return folderPaths + } + + return extractFolderPaths(tree) + }, [tree]) + + // control which folders are open - initialize with all folders expanded + const [openFolders, setOpenFolders] = useState<string[]>(() => allFolderPaths) + + // Only reset folders when paths actually change + const pathsKey = paths.join(',') + if (pathsKey !== lastInitializedPaths.current) { + lastInitializedPaths.current = pathsKey + // Only call setOpenFolders if not in initial render + if (lastInitializedPaths.current !== '') { + setOpenFolders(allFolderPaths) + } + } + + const handleValueChange = (val: string | string[]) => { + const arr = Array.isArray(val) ? val : [val] + setOpenFolders(arr) + onFolderValueChange?.(arr) + } + + return ( + <FileExplorer.Root value={openFolders} onValueChange={handleValueChange}> + {renderTree(tree, setJumpToDiff, activePath, diffsData)} + </FileExplorer.Root> + ) + } +) + +PullRequestChangesExplorer.displayName = 'PullRequestChangesExplorer' + +/** + * Recursively renders tree structure into React file explorer components. + * @param nodes - Tree nodes to render at current level + * @param setJumpToDiff - Navigation handler for file selection + * @param activePath - Currently active file path for highlighting + * @returns Array of React elements representing the tree structure + */ +function renderTree( + nodes: TreeNode[], + setJumpToDiff: (fileName: string) => void, + activePath?: string, + diffsData?: ExplorerDiffData[] +): React.ReactNode[] { + return nodes.map(node => { + // Calculate level based on path depth + const level = (node.path ?? '').split('/').length - 1 + + if (node.type === 'folder') { + const isActive = activePath?.startsWith(node.path) + return ( + <FileExplorer.FolderItem + key={node.path} + value={node.path} + isActive={isActive} + level={level} + content={<>{renderTree(node.children, setJumpToDiff, activePath, diffsData)}</>} + > + {node.name} + </FileExplorer.FolderItem> + ) + } else { + const isActive = activePath === node.path + const addedLines = getDiffFileAddedLines(diffsData || [], node.path) + const deletedLines = getDiffFileDeletedLines(diffsData || [], node.path) + return ( + <FileExplorer.FileItem + key={node.path} + isActive={isActive} + level={level} + onClick={() => setJumpToDiff(node.path)} + > + <Tooltip + content={ + <> + {addedLines > 0 && ( + <StatusBadge variant="outline" size="sm" theme="success"> + +{addedLines} + </StatusBadge> + )} + {deletedLines > 0 && ( + <StatusBadge variant="outline" size="sm" theme="danger"> + -{deletedLines} + </StatusBadge> + )} + </> + } + > + <Text title="">{node.name}</Text> + </Tooltip> + </FileExplorer.FileItem> + ) + } + }) +} + +function getDiffFileAddedLines(diffsData: ExplorerDiffData[], filePath: string): number { + const diff = diffsData?.find(diff => diff.filePath === filePath) + return diff?.addedLines || 0 +} + +function getDiffFileDeletedLines(diffsData: ExplorerDiffData[], filePath: string): number { + const diff = diffsData?.find(diff => diff.filePath === filePath) + return diff?.deletedLines || 0 +} diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 6234a3e756..042897fe2c 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react' import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton } from '@/components' import { useTranslation } from '@/context' import { TypesUser } from '@/types' -import { ChangedFilesShortInfo, TypesCommit } from '@/views' +import { TypesCommit } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' import { @@ -45,13 +45,9 @@ export interface PullRequestChangesFilterProps { viewedFiles: number commitSuggestionsBatchCount: number onCommitSuggestionsBatch: () => void - diffData?: { - filePath: string - addedLines: number - deletedLines: number - }[] pullReqStats?: TypesPullReqStats - setJumpToDiff: (fileName: string) => void + showExplorer: boolean + setShowExplorer: (value: boolean) => void } export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = ({ @@ -70,9 +66,9 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = viewedFiles, commitSuggestionsBatchCount, onCommitSuggestionsBatch, - diffData, pullReqStats, - setJumpToDiff + showExplorer, + setShowExplorer }) => { const { t } = useTranslation() const [commitFilterOptions, setCommitFilterOptions] = useState([defaultCommitFilter]) @@ -194,6 +190,14 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = className="layer-high sticky top-[55px] gap-x-5 bg-cn-background-1 py-2" > <Layout.Horizontal className="grow gap-x-5"> + <Button + size="md" + title={showExplorer ? 'Collapse Sidebar' : 'Expand Sidebar'} + variant="transparent" + onClick={() => setShowExplorer(!showExplorer)} + > + <IconV2 name={showExplorer ? 'collapse-sidebar' : 'expand-sidebar'} size="md" /> + </Button> <DropdownMenu.Root> <DropdownMenu.Trigger className="group flex items-center gap-x-1.5"> <Button size="sm" variant="transparent"> @@ -237,8 +241,6 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = ))} </DropdownMenu.Content> </DropdownMenu.Root> */} - - <ChangedFilesShortInfo diffData={diffData} diffStats={pullReqStats} goToDiff={setJumpToDiff} /> </Layout.Horizontal> <Layout.Horizontal className="gap-x-7"> diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index 38387afc58..288e79a3ee 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -1,11 +1,14 @@ -import { FC, useMemo } from 'react' +import { FC, useMemo, useRef, useState } from 'react' -import { Skeleton } from '@/components' +import { Layout, Skeleton } from '@/components' import { TypesUser } from '@/types' import { DiffModeEnum } from '@git-diff-view/react' -import { activityToCommentItem, HandleUploadType, TypesCommit } from '@views/index' +import { cn } from '@utils/cn' +import { activityToCommentItem, HandleUploadType, SandboxLayout, TypesCommit } from '@views/index' import { orderBy } from 'lodash-es' +import { DraggableSidebarDivider, SIDEBAR_MIN_WIDTH } from '../../components/draggable-sidebar-divider' +import { PullRequestDiffSidebar } from '../components/pull-request-diff-sidebar' import { CommitSuggestion, PullReqReviewDecision, TypesPullReq } from '../pull-request.types' import { PullRequestChanges } from './components/changes/pull-request-changes' import { CommitFilterItemProps, PullRequestChangesFilter } from './components/changes/pull-request-changes-filter' @@ -105,6 +108,9 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ currentRefForDiff }) => { const { diffs, pullReqStats } = usePullRequestProviderStore() + const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const containerRef = useRef<HTMLDivElement>(null) + const [showExplorer, setShowExplorer] = useState(true) // Convert activities to comment threads const activityBlocks = useMemo(() => { @@ -181,34 +187,54 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ } return ( - <> - <PullRequestChangesFilter - active={''} - isApproving={isApproving} - currentUser={currentUser ?? {}} - pullRequestMetadata={pullReqMetadata ? pullReqMetadata : undefined} - reviewers={reviewers} - submitReview={submitReview} - refetchReviewers={refetchReviewers} - diffMode={diffMode} - setDiffMode={setDiffMode} - pullReqCommits={pullReqCommits} - defaultCommitFilter={defaultCommitFilter} - selectedCommits={selectedCommits} - setSelectedCommits={setSelectedCommits} - viewedFiles={diffs?.[0]?.fileViews?.size || 0} - pullReqStats={pullReqStats} - onCommitSuggestionsBatch={onCommitSuggestionsBatch} - commitSuggestionsBatchCount={commitSuggestionsBatchCount} - diffData={diffs?.map(diff => ({ - filePath: diff.filePath, - addedLines: diff.addedLines, - deletedLines: diff.deletedLines - }))} - setJumpToDiff={setJumpToDiff} - /> - {renderContent()} - </> + <Layout.Flex className="flex-1" ref={containerRef}> + {showExplorer && ( + <> + <PullRequestDiffSidebar + sidebarWidth={sidebarWidth} + filePaths={diffs?.map(diff => diff.filePath) || []} + setJumpToDiff={setJumpToDiff} + diffsData={ + diffs?.map(item => ({ + addedLines: item.addedLines, + deletedLines: item.deletedLines, + lang: item.filePath.split('.')[1], + filePath: item.filePath, + isDeleted: !!item.isDeleted, + unchangedPercentage: item.unchangedPercentage || 0 + })) || [] + } + /> + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> + </> + )} + <SandboxLayout.Main> + <SandboxLayout.Content className={cn('flex flex-col p-0', showExplorer ? 'pl-cn-xl' : '')}> + <PullRequestChangesFilter + active={''} + isApproving={isApproving} + currentUser={currentUser ?? {}} + pullRequestMetadata={pullReqMetadata ? pullReqMetadata : undefined} + reviewers={reviewers} + submitReview={submitReview} + refetchReviewers={refetchReviewers} + diffMode={diffMode} + setDiffMode={setDiffMode} + pullReqCommits={pullReqCommits} + defaultCommitFilter={defaultCommitFilter} + selectedCommits={selectedCommits} + setSelectedCommits={setSelectedCommits} + viewedFiles={diffs?.[0]?.fileViews?.size || 0} + pullReqStats={pullReqStats} + onCommitSuggestionsBatch={onCommitSuggestionsBatch} + commitSuggestionsBatchCount={commitSuggestionsBatchCount} + showExplorer={showExplorer} + setShowExplorer={setShowExplorer} + /> + {renderContent()} + </SandboxLayout.Content> + </SandboxLayout.Main> + </Layout.Flex> ) } From 92ed7f6b6ec7773ab8a4460f5c6b1d292c2fea18 Mon Sep 17 00:00:00 2001 From: Pranesh TG <pranesh.g@harness.io> Date: Wed, 13 Aug 2025 18:58:12 +0000 Subject: [PATCH 084/180] Update offset values for dropdown menu in PR panel (#10199) * 3bf829 Update offset values for dropdown menu in PR panel --- .../details/components/conversation/pull-request-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 079f316af4..8161f154b1 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -652,8 +652,8 @@ const PullRequestPanel = ({ <MoreActionsTooltip className="!ml-2" iconName="more-horizontal" - sideOffset={-8} - alignOffset={2} + sideOffset={4} + alignOffset={0} actions={[ { title: 'Mark as draft', From 769a31b77433bc227ac4feb115b0a1522986806b Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Wed, 13 Aug 2025 19:38:29 +0000 Subject: [PATCH 085/180] add plus icon to missed Create File button (#10200) * bfd91a title case * 829988 add plus icon to missed Create File button --- packages/ui/locales/en/views.json | 2 +- packages/ui/locales/fr/views.json | 2 +- .../ui/src/views/repo/repo-summary/repo-empty-view.tsx | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 673967203f..3d03c94259 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -243,7 +243,7 @@ "0": "We recommend every repository include a", "1": "README, LICENSE, and .gitignrore." }, - "createFile": "Create file" + "createFile": "Create File" }, "cloneInstructions": { "title": "Please generate git credentials if it’s your first time cloning the repository", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 1431fe9d2d..bc162fa1ae 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -1,6 +1,6 @@ { "repos": { - "createFile": "Create file", + "createFile": "Create File", "uploadFiles": "Télécharger des fichiers", "summary": "Résumé", "files": "Fichiers", diff --git a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx index 9ca6ec4f38..64e1fbcc9a 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, Layout, MarkdownViewer, NoData, Text } from '@/components' +import { Alert, Button, IconV2, Layout, MarkdownViewer, NoData, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -83,7 +83,12 @@ ${sshUrl} t('views:repos.emptyRepoPage.noData.description.1', 'README, LICENSE, and .gitignrore') ]} primaryButton={{ - label: t('views:repos.emptyRepoPage.noData.createFile', 'Create file'), + label: ( + <> + <IconV2 name="plus" /> + {t('views:repos.emptyRepoPage.noData.createFile', 'Create File')} + </> + ), to: `${projName ? `/${projName}` : ''}/repos/${repoName}/files/new/${gitRef}/~/` }} className="py-cn-3xl" From 65079454a537bb66b02293c65089d079b091c8ca Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Wed, 13 Aug 2025 19:43:43 +0000 Subject: [PATCH 086/180] Branch rule - Hide placeholder after select a value for multi select (#10201) * bca628 Branch rule - Hide placeholder after select a value --- packages/ui/src/components/multi-select.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/multi-select.tsx b/packages/ui/src/components/multi-select.tsx index ad817662d6..37d5023281 100644 --- a/packages/ui/src/components/multi-select.tsx +++ b/packages/ui/src/components/multi-select.tsx @@ -363,7 +363,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>( setOpen(true) inputProps?.onFocus?.(event) }} - placeholder={disabled ? '' : placeholder} + placeholder={disabled || getSelectedOptions().length > 0 ? '' : placeholder} className={cn('cn-multi-select-input', inputProps?.className)} asChild > From 139a339a7b191134ee5b4b9e4a8edf4ea2f6845e Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Wed, 13 Aug 2025 23:57:27 +0000 Subject: [PATCH 087/180] fix: design for approve button and branch info bar (#10204) * 6fb064 fix: design for approve button and branch info bar --- packages/ui/src/components/split-button.tsx | 2 +- .../src/views/repo/components/branch-info-bar.tsx | 8 ++++---- .../changes/pull-request-changes-filter.tsx | 3 ++- .../pull-request/details/pull-request-utils.ts | 15 +++++++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/split-button.tsx b/packages/ui/src/components/split-button.tsx index 464ec271e8..704e98ee4a 100644 --- a/packages/ui/src/components/split-button.tsx +++ b/packages/ui/src/components/split-button.tsx @@ -33,7 +33,7 @@ interface SplitButtonBaseProps<T extends string> { // For solid variant with primary theme interface SplitButtonSolidProps<T extends string> extends SplitButtonBaseProps<T> { variant?: 'primary' - theme?: 'default' + theme?: 'success' | 'danger' | 'default' } // For surface variant with success or danger theme diff --git a/packages/ui/src/views/repo/components/branch-info-bar.tsx b/packages/ui/src/views/repo/components/branch-info-bar.tsx index 3bed528833..523702cdab 100644 --- a/packages/ui/src/views/repo/components/branch-info-bar.tsx +++ b/packages/ui/src/views/repo/components/branch-info-bar.tsx @@ -34,7 +34,7 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ return ( <Layout.Flex - className="border-cn-borders-2 bg-cn-background-2 min-h-[3.25rem] rounded-md border px-4 py-2" + className="min-h-[3.25rem] rounded-md border border-cn-borders-2 bg-cn-background-2 py-2 pl-4 pr-2" align="center" justify="between" gapX="xs" @@ -65,7 +65,7 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ <DropdownMenu.Root> <DropdownMenu.Trigger asChild> <Button - className="group/contribute data-[state=open]:border-cn-borders-9 data-[state=open]:text-cn-foreground-1 py-2" + className="group/contribute data-[state=open]:border-cn-borders-9 py-2 data-[state=open]:text-cn-foreground-1" variant="outline" > <IconV2 name="git-pull-request" size="xs" /> @@ -77,11 +77,11 @@ export const BranchInfoBar: FC<BranchInfoBarProps> = ({ /> </Button> </DropdownMenu.Trigger> - <DropdownMenu.Content align="end" className="w-60"> + <DropdownMenu.Content align="end" className="w-80"> <DropdownMenu.Slot> <Layout.Grid gapY="xs" className="p-2"> <Layout.Grid flow="column" gapX="xs"> - <div className="border-cn-borders-4 rounded-2 flex size-8 shrink-0 items-center justify-center border"> + <div className="border-cn-borders-4 flex size-8 shrink-0 items-center justify-center rounded-2 border"> <IconV2 name="git-pull-request" size="md" /> </div> <Layout.Grid gapY="xs"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 042897fe2c..01e1fee715 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -18,6 +18,7 @@ import { determineOverallDecision, getApprovalItems, getApprovalStateTheme, + getApprovalStateVariant, processReviewDecision } from '../../pull-request-utils' import * as FileViewGauge from './file-viewed-gauge' @@ -269,7 +270,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = theme={getApprovalStateTheme(approveState)} disabled={isActiveUserPROwner} loading={isApproving} - variant="outline" + variant={getApprovalStateVariant(approveState)} handleOptionChange={selectedMethod => { submitReview?.(selectedMethod as PullReqReviewDecision) }} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts index b9d34061f6..6fda0af748 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts @@ -58,16 +58,31 @@ export function getApprovalItems(approveState: PullReqReviewDecision, approvalIt } return [] } + export const getApprovalStateTheme = (state: PullReqReviewDecision) => { switch (state) { case PullReqReviewDecision.approved: case PullReqReviewDecision.approve: return 'success' case PullReqReviewDecision.changeReq: + return 'danger' default: return 'default' } } + +export const getApprovalStateVariant = (state: PullReqReviewDecision) => { + switch (state) { + case PullReqReviewDecision.approved: + case PullReqReviewDecision.changeReq: + return 'primary' + case PullReqReviewDecision.approve: + return 'outline' + default: + return 'outline' + } +} + export const approvalItems = [ { id: 0, From 327513c88b8e1dfc51f8eea784d045f76e1054f9 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Thu, 14 Aug 2025 00:53:23 +0000 Subject: [PATCH 088/180] feat: Add RBAC tooltips to Button and Splitbutton (#10205) * 08ad6e add forward ref to split button to make tooltips work * d40a02 cleanup * ded867 wrap rbac buttons in tooltip * 4ef8e9 add Component provider in design system app --- apps/design-system/src/AppRouterProvider.tsx | 7 +- .../src/framework/rbac/rbac-button.tsx | 14 +- .../src/framework/rbac/rbac-split-button.tsx | 16 +- packages/ui/src/components/rbac/types.ts | 8 +- packages/ui/src/components/split-button.tsx | 168 ++++++++++-------- 5 files changed, 124 insertions(+), 89 deletions(-) diff --git a/apps/design-system/src/AppRouterProvider.tsx b/apps/design-system/src/AppRouterProvider.tsx index 378d850bc6..6dfd15f883 100644 --- a/apps/design-system/src/AppRouterProvider.tsx +++ b/apps/design-system/src/AppRouterProvider.tsx @@ -10,7 +10,8 @@ import { useSearchParams } from 'react-router-dom' -import { RouterContextProvider } from '@harnessio/ui/context' +import { Button, SplitButton } from '@harnessio/ui/components' +import { ComponentProvider, RouterContextProvider } from '@harnessio/ui/context' const AppRouterProvider: FC = () => { const navigate = useNavigate() @@ -27,7 +28,9 @@ const AppRouterProvider: FC = () => { useMatches={useMatches} useParams={useParams} > - <Outlet /> + <ComponentProvider components={{ RbacButton: Button, RbacSplitButton: SplitButton }}> + <Outlet /> + </ComponentProvider> </RouterContextProvider> ) } diff --git a/apps/gitness/src/framework/rbac/rbac-button.tsx b/apps/gitness/src/framework/rbac/rbac-button.tsx index 57646853fa..bd2271ab9d 100644 --- a/apps/gitness/src/framework/rbac/rbac-button.tsx +++ b/apps/gitness/src/framework/rbac/rbac-button.tsx @@ -1,8 +1,8 @@ -import { Button, RbacButtonProps, Resource } from '@harnessio/ui/components' +import { Button, RbacButtonProps, Resource, Tooltip } from '@harnessio/ui/components' import { useMFEContext } from '../hooks/useMFEContext' -export const RbacButton = ({ rbac, ...rest }: RbacButtonProps) => { +export const RbacButton = ({ rbac, tooltip, ...rest }: RbacButtonProps) => { const { hooks } = useMFEContext() /** @@ -16,5 +16,13 @@ export const RbacButton = ({ rbac, ...rest }: RbacButtonProps) => { }) ?.some(Boolean) ?? true - return <Button {...rest} disabled={!hasPermission} /> + const button = <Button {...rest} disabled={!hasPermission} /> + + return !hasPermission ? ( + <Tooltip title={tooltip?.title ?? 'You are missing the permission for this action.'} content={tooltip?.content}> + {button} + </Tooltip> + ) : ( + button + ) } diff --git a/apps/gitness/src/framework/rbac/rbac-split-button.tsx b/apps/gitness/src/framework/rbac/rbac-split-button.tsx index 3fe523b5d3..aaf15dc952 100644 --- a/apps/gitness/src/framework/rbac/rbac-split-button.tsx +++ b/apps/gitness/src/framework/rbac/rbac-split-button.tsx @@ -1,8 +1,8 @@ -import { RbacSplitButtonProps, Resource, SplitButton } from '@harnessio/ui/components' +import { RbacSplitButtonProps, Resource, SplitButton, Tooltip } from '@harnessio/ui/components' import { useMFEContext } from '../hooks/useMFEContext' -export const RbacSplitButton = <T extends string>({ rbac, variant, ...rest }: RbacSplitButtonProps<T>) => { +export const RbacSplitButton = <T extends string>({ rbac, variant, tooltip, ...rest }: RbacSplitButtonProps<T>) => { const { hooks } = useMFEContext() /** @@ -16,5 +16,15 @@ export const RbacSplitButton = <T extends string>({ rbac, variant, ...rest }: Rb }) ?.some(Boolean) ?? true - return <SplitButton<T> {...rest} variant={variant === 'outline' ? 'outline' : undefined} disabled={!hasPermission} /> + const button = ( + <SplitButton<T> {...rest} variant={variant === 'outline' ? 'outline' : undefined} disabled={!hasPermission} /> + ) + + return !hasPermission ? ( + <Tooltip title={tooltip?.title ?? 'You are missing the permission for this action.'} content={tooltip?.content}> + {button} + </Tooltip> + ) : ( + button + ) } diff --git a/packages/ui/src/components/rbac/types.ts b/packages/ui/src/components/rbac/types.ts index de629e4267..d259469657 100644 --- a/packages/ui/src/components/rbac/types.ts +++ b/packages/ui/src/components/rbac/types.ts @@ -1,5 +1,6 @@ import { ButtonProps } from '@components/button' import { SplitButtonProps } from '@components/split-button' +import { TooltipProps } from '@components/tooltip' /** * @todo Replace with types from @harness/microfrontends once its accessible in the monorepo @@ -31,6 +32,9 @@ interface RBACProps { * Types for RBAC-enabled components. * These components will automatically handle RBAC checks based on the provided `rbac` prop. */ -export interface RbacButtonProps extends Omit<ButtonProps, 'resource'>, RBACProps {} +export interface RbacButtonProps extends Omit<ButtonProps, 'resource'>, RBACProps { + tooltip?: Pick<TooltipProps, 'title' | 'content'> +} -export type RbacSplitButtonProps<T extends string> = SplitButtonProps<T> & RBACProps +export type RbacSplitButtonProps<T extends string> = SplitButtonProps<T> & + RBACProps & { tooltip?: Pick<TooltipProps, 'title' | 'content'> } diff --git a/packages/ui/src/components/split-button.tsx b/packages/ui/src/components/split-button.tsx index 704e98ee4a..8fcb498bbb 100644 --- a/packages/ui/src/components/split-button.tsx +++ b/packages/ui/src/components/split-button.tsx @@ -1,4 +1,4 @@ -import { MouseEvent, ReactNode } from 'react' +import React, { forwardRef, MouseEvent, ReactNode } from 'react' import { Button, buttonVariants } from '@/components/button' import { DropdownMenu } from '@components/dropdown-menu' @@ -54,83 +54,93 @@ export type SplitButtonProps<T extends string> = SplitButtonSolidProps<T> | Spli * - variant=solid with theme=primary (default) * - variant=surface with theme=success|danger|muted */ -export const SplitButton = <T extends string>({ - handleButtonClick, - loading = false, - selectedValue, - options, - handleOptionChange, - className, - buttonClassName, - theme = 'default', - variant = 'primary', - disabled = false, - disableDropdown = false, - disableButton = false, - children, - dropdownContentClassName, - size = 'md' -}: SplitButtonProps<T>) => { - return ( - <div className={cn('flex', className)}> - <Button - className={cn('rounded-r-none border-r-0', buttonClassName)} - theme={theme} - variant={variant} - size={size} - onClick={handleButtonClick} - type="button" - disabled={disabled || disableButton} - loading={loading} - > - {children} - </Button> - <DropdownMenu.Root> - <DropdownMenu.Trigger - className={cn(buttonVariants({ theme, variant, size }), 'cn-button-split-dropdown')} - disabled={disabled || loading || disableDropdown} +const SplitButtonInner = forwardRef( + <T extends string>( + { + handleButtonClick, + loading = false, + selectedValue, + options, + handleOptionChange, + className, + buttonClassName, + theme = 'default', + variant = 'primary', + disabled = false, + disableDropdown = false, + disableButton = false, + children, + dropdownContentClassName, + size = 'md' + }: SplitButtonProps<T>, + ref: React.ForwardedRef<HTMLButtonElement> + ) => { + return ( + <div className={cn('flex', className)}> + <Button + ref={ref} + className={cn('rounded-r-none border-r-0', buttonClassName)} + theme={theme} + variant={variant} + size={size} + onClick={handleButtonClick} + type="button" + disabled={disabled || disableButton} + loading={loading} > - <IconV2 name="nav-arrow-down" /> - </DropdownMenu.Trigger> - <DropdownMenu.Content className={cn('mt-1 max-w-80', dropdownContentClassName)} align="end"> - {selectedValue ? ( - <DropdownMenu.RadioGroup value={String(selectedValue)}> - {options.map(option => ( - <DropdownMenu.RadioItem - title={option.label} - description={option?.description} - value={String(option.value)} - key={String(option.value)} - onClick={() => { - if (!loading && !option.disabled) { - handleOptionChange(option.value) - } - }} - disabled={loading || option.disabled} - /> - ))} - </DropdownMenu.RadioGroup> - ) : ( - <DropdownMenu.Group> - {options.map(option => ( - <DropdownMenu.Item - title={option.label} - description={option.description} - key={String(option.value)} - onClick={() => { - if (!loading && !option.disabled) { - handleOptionChange(option.value) - } - }} - disabled={loading || option.disabled} - /> - ))} - </DropdownMenu.Group> - )} - </DropdownMenu.Content> - </DropdownMenu.Root> - </div> - ) -} + {children} + </Button> + <DropdownMenu.Root> + <DropdownMenu.Trigger + className={cn(buttonVariants({ theme, variant, size }), 'cn-button-split-dropdown')} + disabled={disabled || loading || disableDropdown} + > + <IconV2 name="nav-arrow-down" /> + </DropdownMenu.Trigger> + <DropdownMenu.Content className={cn('mt-1 max-w-80', dropdownContentClassName)} align="end"> + {selectedValue ? ( + <DropdownMenu.RadioGroup value={String(selectedValue)}> + {options.map(option => ( + <DropdownMenu.RadioItem + title={option.label} + description={option?.description} + value={String(option.value)} + key={String(option.value)} + onClick={() => { + if (!loading && !option.disabled) { + handleOptionChange(option.value) + } + }} + disabled={loading || option.disabled} + /> + ))} + </DropdownMenu.RadioGroup> + ) : ( + <DropdownMenu.Group> + {options.map(option => ( + <DropdownMenu.Item + title={option.label} + description={option.description} + key={String(option.value)} + onClick={() => { + if (!loading && !option.disabled) { + handleOptionChange(option.value) + } + }} + disabled={loading || option.disabled} + /> + ))} + </DropdownMenu.Group> + )} + </DropdownMenu.Content> + </DropdownMenu.Root> + </div> + ) + } +) + +SplitButtonInner.displayName = 'SplitButton' -SplitButton.displayName = 'SplitButton' +export const SplitButton = SplitButtonInner as <T extends string>( + props: SplitButtonProps<T> & { ref?: React.ForwardedRef<HTMLButtonElement> } +) => JSX.Element From 6659c4c5485bac78bea566c35d7946542c79c119 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Thu, 14 Aug 2025 01:03:18 +0000 Subject: [PATCH 089/180] for new files only show Preview tab when its a markdown file (#10202) * 1293b3 initialize state based on input values instead of defaulting to true * 4c55f8 for new files only show Preview tab when its a markdown file --- apps/gitness/src/components-v2/file-editor.tsx | 9 ++++++++- .../file-control-bars/file-editor-control-bar.tsx | 10 ++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/gitness/src/components-v2/file-editor.tsx b/apps/gitness/src/components-v2/file-editor.tsx index bfac1ce421..cdffef015d 100644 --- a/apps/gitness/src/components-v2/file-editor.tsx +++ b/apps/gitness/src/components-v2/file-editor.tsx @@ -59,6 +59,8 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) () => [(parentPath || '').trim(), (fileName || '').trim()].filter(p => !!p.trim()).join(FILE_SEPERATOR), [parentPath, fileName] ) + const isShowPreview = () => !isNew || getIsMarkdown(language) + const [showPreview, setShowPreview] = useState(isShowPreview()) const pathToSplit = useMemo(() => { if (isNew) { @@ -75,6 +77,11 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) } }, [isNew, fullResourcePath, parentPath]) + useEffect(() => { + setLanguage(filenameToLanguage(fileName) || '') + setShowPreview(isShowPreview()) + }, [fileName, isNew, language]) + const pathParts = useMemo( () => [ { @@ -193,7 +200,7 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) value={view as string} onValueChange={val => onChangeView(val as EditViewTypeValue)} > - <FileEditorControlBar /> + <FileEditorControlBar showPreview={showPreview} /> <Tabs.Content value="edit" className="grow"> <CodeEditor diff --git a/packages/ui/src/components/file-control-bars/file-editor-control-bar.tsx b/packages/ui/src/components/file-control-bars/file-editor-control-bar.tsx index 7056dba8c5..ebe27b8a2d 100644 --- a/packages/ui/src/components/file-control-bars/file-editor-control-bar.tsx +++ b/packages/ui/src/components/file-control-bars/file-editor-control-bar.tsx @@ -1,12 +1,18 @@ +import { FC } from 'react' + import { StackedList, Tabs } from '@/components' -export const FileEditorControlBar = () => { +export interface FileEditorControlBarProps { + showPreview?: boolean +} + +export const FileEditorControlBar: FC<FileEditorControlBarProps> = ({ showPreview = true }) => { return ( <StackedList.Root className="bg-cn-background-2" onlyTopRounded> <StackedList.Item disableHover isHeader className="px-cn-md py-cn-2xs"> <Tabs.List variant="ghost"> <Tabs.Trigger value="edit">Edit</Tabs.Trigger> - <Tabs.Trigger value="preview">Preview</Tabs.Trigger> + {showPreview && <Tabs.Trigger value="preview">Preview</Tabs.Trigger>} </Tabs.List> </StackedList.Item> </StackedList.Root> From 0bceb9c6431ee711df75c80daaf04b3beb01430e Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:39:31 +0400 Subject: [PATCH 090/180] add repo files design review (#2064) * add repo files design review * fix resizer * fixes --- .../repo-files/components/repo-file-edit.tsx | 2 +- .../components/repo-files-wrapper.tsx | 1 - apps/gitness/src/components-v2/GitBlame.tsx | 40 +++--- .../src/components-v2/file-content-viewer.tsx | 119 ++++++++++-------- .../gitness/src/components-v2/file-editor.tsx | 79 +++++++----- apps/gitness/src/pages-v2/repo/repo-code.tsx | 13 +- .../src/pages-v2/repo/repo-sidebar.tsx | 10 +- apps/gitness/src/utils/git-utils.ts | 2 +- packages/ui/src/components/button-group.tsx | 3 +- .../file-viewer-control-bar.tsx | 9 +- packages/ui/src/components/file-explorer.tsx | 6 +- .../ui/src/components/path-breadcrumbs.tsx | 28 ++--- packages/ui/src/components/search-files.tsx | 75 +++++------ packages/ui/src/styles/styles.css | 2 + .../ui/src/views/components/contributors.tsx | 61 +++++---- .../components/draggable-sidebar-divider.tsx | 9 +- .../views/repo/components/path-action-bar.tsx | 12 +- .../views/repo/repo-files/repo-files-view.tsx | 9 +- .../yaml-editor/src/components/CodeEditor.tsx | 4 +- 19 files changed, 259 insertions(+), 225 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-files/components/repo-file-edit.tsx b/apps/design-system/src/subjects/views/repo-files/components/repo-file-edit.tsx index cbfea8c25d..b9d0623b84 100644 --- a/apps/design-system/src/subjects/views/repo-files/components/repo-file-edit.tsx +++ b/apps/design-system/src/subjects/views/repo-files/components/repo-file-edit.tsx @@ -73,7 +73,7 @@ export const RepoFileEdit = () => { /> <Tabs.Root - className="flex flex-col h-full" + className="flex h-full flex-col" value={view as string} onValueChange={val => onChangeView(val as EditViewTypeValue)} > diff --git a/apps/design-system/src/subjects/views/repo-files/components/repo-files-wrapper.tsx b/apps/design-system/src/subjects/views/repo-files/components/repo-files-wrapper.tsx index e3b720fe84..d6d2af88b5 100644 --- a/apps/design-system/src/subjects/views/repo-files/components/repo-files-wrapper.tsx +++ b/apps/design-system/src/subjects/views/repo-files/components/repo-files-wrapper.tsx @@ -38,7 +38,6 @@ export const RepoFilesWrapper: FC<RepoFilesWrapperProps> = ({ codeMode, isDir, i <RepoFiles isRepoEmpty={repoFilesStore.repository.is_empty} pathParts={repoFilesStore.pathParts} - loading={false} files={repoFilesStore.files} isDir={isDir} isShowSummary={true} diff --git a/apps/gitness/src/components-v2/GitBlame.tsx b/apps/gitness/src/components-v2/GitBlame.tsx index eb2fd5141e..f3f6553671 100644 --- a/apps/gitness/src/components-v2/GitBlame.tsx +++ b/apps/gitness/src/components-v2/GitBlame.tsx @@ -67,23 +67,25 @@ export default function GitBlame({ themeConfig, codeContent, language, height, t commitInfo: commitInfo, infoContent: ( /* IMPORTANT: itemContent accepts only atomic component that are not depends on external state (e.g. context provider) */ - <div className="flex items-center gap-4 pl-4"> + <Layout.Flex align="center" gapX="lg" className="pl-4"> <Text style={{ width: '125px' }} truncate> {formatDistanceToNow(commitInfo.author?.when)} </Text> - <Avatar name={name} rounded title={name + '\n' + email} /> - <Text - style={{ width: '250px' }} - className="cursor-pointer text-2 leading-snug hover:underline" - truncate - title={commitInfo.title} - onClick={() => { - navigate(toCommitDetails({ sha: commitInfo.sha })) - }} - > - {commitInfo.title} - </Text> - </div> + <Layout.Flex align="center" gapX="xs"> + <Avatar name={name} rounded title={name + '\n' + email} /> + <Text + style={{ width: '250px' }} + className="cursor-pointer hover:underline" + truncate + title={commitInfo.title} + onClick={() => { + navigate(toCommitDetails({ sha: commitInfo.sha })) + }} + > + {commitInfo.title} + </Text> + </Layout.Flex> + </Layout.Flex> ) }) @@ -102,11 +104,13 @@ export default function GitBlame({ themeConfig, codeContent, language, height, t const { theme } = useThemeStore() const monacoTheme = (theme ?? '').startsWith('dark') ? 'dark' : 'light' - return !isFetching && blameBlocks.length ? ( + if (isFetching || !blameBlocks.length) return null + + return ( <Layout.Vertical className="h-full" gap="none"> - <div className="flex items-center border-x border-b px-cn-md py-cn-sm"> + <Layout.Flex align="center" className="px-cn-md py-cn-sm border-x border-b"> <Contributors contributors={contributors} /> - </div> + </Layout.Flex> <BlameEditorV2 code={codeContent} language={language} @@ -118,7 +122,5 @@ export default function GitBlame({ themeConfig, codeContent, language, height, t className="flex h-full grow" /> </Layout.Vertical> - ) : ( - <></> ) } diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index 5fc50627b3..b5e9029de8 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -5,6 +5,8 @@ import { OpenapiGetContentOutput, TypesCommit, useListCommitsQuery } from '@harn import { FileViewerControlBar, getIsMarkdown, + IconV2, + Layout, MarkdownViewer, Pagination, ScrollArea, @@ -12,6 +14,7 @@ import { Tabs, ViewTypeValue } from '@harnessio/ui/components' +import { cn } from '@harnessio/ui/utils' import { CommitsList, FileReviewError, monacoThemes } from '@harnessio/ui/views' import { CodeEditor } from '@harnessio/yaml-editor' @@ -36,12 +39,13 @@ const getDefaultView = (language?: string): ViewTypeValue => { interface FileContentViewerProps { repoContent?: OpenapiGetContentOutput + loading?: boolean } /** * TODO: This code was migrated from V2 and needs to be refactored. */ -export default function FileContentViewer({ repoContent }: FileContentViewerProps) { +export default function FileContentViewer({ repoContent, loading }: FileContentViewerProps) { const routes = useRoutes() const { spaceId, repoId } = useParams<PathParams>() const fileName = repoContent?.name || '' @@ -131,6 +135,12 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp navigate(`${routes.toRepoFiles({ spaceId, repoId })}/edit/${fullGitRef}/~/${fullResourcePath}`) } + const Loader = () => ( + <Layout.Flex align="center" justify="center" className="rounded-b-3 flex h-full rounded-t-none border border-t-0"> + <IconV2 className="animate-spin" name="loader" size="lg" /> + </Layout.Flex> + ) + return ( <> <GitCommitDialog @@ -157,7 +167,7 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp isNew={false} /> <Tabs.Root - className="flex flex-col repo-files-height overflow-hidden" + className="repo-files-height flex flex-col overflow-hidden" value={view as string} onValueChange={val => onChangeView(val as ViewTypeValue)} > @@ -173,62 +183,73 @@ export default function FileContentViewer({ repoContent }: FileContentViewerProp refType={selectedRefType} /> - <Tabs.Content value="preview" className="grow overflow-hidden"> - {fileError && ( - <div className="flex h-full items-center justify-center"> - <FileReviewError onButtonClick={() => {}} className="my-0 h-full rounded-t-none border-t-0" /> - </div> - )} + <Tabs.Content + value="preview" + className={cn('grow overflow-hidden', { 'border border-t-0 rounded-b-3': getIsMarkdown(language) })} + > + {loading && <Loader />} - {!fileError && getIsMarkdown(language) && ( - <ScrollArea className="h-full grid-cols-[100%]"> - <MarkdownViewer source={fileContent} withBorder /> - </ScrollArea> - )} + {!loading && ( + <> + {fileError && ( + <div className="flex h-full items-center justify-center"> + <FileReviewError onButtonClick={() => {}} className="my-0 h-full rounded-t-none border-t-0" /> + </div> + )} - {!fileError && !getIsMarkdown(language) && ( - <ScrollArea className="h-full grid-cols-[100%]"> - <CodeEditor - className="overflow-hidden" - height="100%" - language={language} - codeRevision={{ code: fileContent }} - onCodeRevisionChange={() => undefined} - themeConfig={themeConfig} - options={{ - readOnly: true - }} - theme={monacoTheme} - /> - </ScrollArea> + {!fileError && getIsMarkdown(language) && ( + <ScrollArea className="h-full grid-cols-[100%]"> + <MarkdownViewer source={fileContent} withBorder className="border-x-0 border-b-0" /> + </ScrollArea> + )} + + {!fileError && !getIsMarkdown(language) && ( + <ScrollArea className="h-full grid-cols-[100%]"> + <CodeEditor + className="overflow-hidden" + height="100%" + language={language} + codeRevision={{ code: fileContent }} + themeConfig={themeConfig} + options={{ readOnly: true }} + theme={monacoTheme} + /> + </ScrollArea> + )} + </> )} </Tabs.Content> <Tabs.Content value="code" className="grow"> - <CodeEditor - className="overflow-hidden" - height="100%" - language={language} - codeRevision={{ code: fileContent }} - onCodeRevisionChange={() => undefined} - themeConfig={themeConfig} - options={{ - readOnly: true - }} - theme={monacoTheme} - /> + {loading && <Loader />} + + {!loading && ( + <CodeEditor + className="overflow-hidden" + height="100%" + language={language} + codeRevision={{ code: fileContent }} + themeConfig={themeConfig} + options={{ readOnly: true }} + theme={monacoTheme} + /> + )} </Tabs.Content> <Tabs.Content value="blame" className="grow"> - <GitBlame - height="100%" - themeConfig={themeConfig} - codeContent={fileContent} - language={language} - toCommitDetails={({ sha }: { sha: string }) => { - return routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha }) - }} - /> + {loading && <Loader />} + + {!loading && ( + <GitBlame + height="100%" + themeConfig={themeConfig} + codeContent={fileContent} + language={language} + toCommitDetails={({ sha }: { sha: string }) => { + return routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha }) + }} + /> + )} </Tabs.Content> <Tabs.Content value="history" className="grow overflow-hidden"> diff --git a/apps/gitness/src/components-v2/file-editor.tsx b/apps/gitness/src/components-v2/file-editor.tsx index cdffef015d..e2073b1a5f 100644 --- a/apps/gitness/src/components-v2/file-editor.tsx +++ b/apps/gitness/src/components-v2/file-editor.tsx @@ -2,7 +2,16 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { OpenapiGetContentOutput } from '@harnessio/code-service-client' -import { EditViewTypeValue, FileEditorControlBar, getIsMarkdown, MarkdownViewer, Tabs } from '@harnessio/ui/components' +import { + EditViewTypeValue, + FileEditorControlBar, + getIsMarkdown, + IconV2, + Layout, + MarkdownViewer, + Tabs +} from '@harnessio/ui/components' +import { cn } from '@harnessio/ui/utils' import { monacoThemes, PathActionBar } from '@harnessio/ui/views' import { CodeDiffEditor, CodeEditor, CodeEditorProps } from '@harnessio/yaml-editor' @@ -13,15 +22,16 @@ import { useExitPrompt } from '../framework/hooks/useExitPrompt' import useCodePathDetails from '../hooks/useCodePathDetails' import { useRepoBranchesStore } from '../pages-v2/repo/stores/repo-branches-store' import { PathParams } from '../RouteDefinitions' -import { decodeGitContent, FILE_SEPERATOR, filenameToLanguage, GitCommitAction, PLAIN_TEXT } from '../utils/git-utils' +import { decodeGitContent, FILE_SEPARATOR, filenameToLanguage, GitCommitAction, PLAIN_TEXT } from '../utils/git-utils' import { splitPathWithParents } from '../utils/path-utils' export interface FileEditorProps { repoDetails?: OpenapiGetContentOutput defaultBranch: string + loading?: boolean } -export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) => { +export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch, loading }) => { const routes = useRoutes() const navigate = useNavigate() const { codeMode, fullGitRef, gitRefName, fullResourcePath } = useCodePathDetails() @@ -53,10 +63,10 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) const isNew = useMemo(() => !repoDetails || repoDetails?.type === 'dir', [repoDetails]) const [parentPath, setParentPath] = useState( - isNew ? fullResourcePath : fullResourcePath?.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR) + isNew ? fullResourcePath : fullResourcePath?.split(FILE_SEPARATOR).slice(0, -1).join(FILE_SEPARATOR) ) const fileResourcePath = useMemo( - () => [(parentPath || '').trim(), (fileName || '').trim()].filter(p => !!p.trim()).join(FILE_SEPERATOR), + () => [(parentPath || '').trim(), (fileName || '').trim()].filter(p => !!p.trim()).join(FILE_SEPARATOR), [parentPath, fileName] ) const isShowPreview = () => !isNew || getIsMarkdown(language) @@ -66,7 +76,7 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) if (isNew) { return fullResourcePath || parentPath } else if (parentPath?.length && fileName.length) { - return [parentPath, fileName].join(FILE_SEPERATOR) + return [parentPath, fileName].join(FILE_SEPARATOR) } return parentPath?.length ? parentPath : fileName }, [isNew, parentPath, fileName, fullResourcePath]) @@ -119,13 +129,13 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) } const rebuildPaths = useCallback(() => { - const _tokens = fileName?.split(FILE_SEPERATOR).filter(part => !!part.trim()) || [] + const _tokens = fileName?.split(FILE_SEPARATOR).filter(part => !!part.trim()) || [] const _fileName = (_tokens.pop() || '').trim() - const _parentPath = (parentPath?.split(FILE_SEPERATOR) || []) + const _parentPath = (parentPath?.split(FILE_SEPARATOR) || []) .concat(_tokens) .map(p => p.trim()) .filter(part => !!part.trim()) - .join(FILE_SEPERATOR) + .join(FILE_SEPARATOR) if (_fileName) { const normalizedFilename = _fileName.trim() @@ -159,6 +169,12 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) setView(value) } + const Loader = () => ( + <Layout.Flex align="center" justify="center" className="rounded-b-3 flex h-full rounded-t-none border border-t-0"> + <IconV2 className="animate-spin" name="loader" size="lg" /> + </Layout.Flex> + ) + return ( <> <GitCommitDialog @@ -196,30 +212,39 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) /> <Tabs.Root - className="flex h-full flex-col" + className="flex h-full flex-col overflow-auto" value={view as string} onValueChange={val => onChangeView(val as EditViewTypeValue)} > <FileEditorControlBar showPreview={showPreview} /> <Tabs.Content value="edit" className="grow"> - <CodeEditor - height="100%" - language={language} - codeRevision={contentRevision} - onCodeRevisionChange={valueRevision => setContentRevision(valueRevision ?? { code: '' })} - themeConfig={themeConfig} - theme={monacoTheme} - options={{ - readOnly: false - }} - /> + {loading && <Loader />} + + {!loading && ( + <CodeEditor + height="100%" + language={language} + codeRevision={contentRevision} + onCodeRevisionChange={valueRevision => setContentRevision(valueRevision ?? { code: '' })} + themeConfig={themeConfig} + theme={monacoTheme} + options={{ readOnly: false }} + /> + )} </Tabs.Content> - <Tabs.Content value="preview" className="grow"> - {getIsMarkdown(language) ? ( - <MarkdownViewer className="max-h-screen overflow-auto" source={contentRevision.code} withBorder /> - ) : ( + <Tabs.Content + value="preview" + className={cn('grow', { 'overflow-auto border border-t-0 rounded-b-3': getIsMarkdown(language) })} + > + {loading && <Loader />} + + {!loading && getIsMarkdown(language) && ( + <MarkdownViewer source={contentRevision.code} withBorder className="border-x-0 border-b-0" /> + )} + + {!loading && !getIsMarkdown(language) && ( <CodeDiffEditor height="100%" language={language} @@ -227,9 +252,7 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch }) modified={contentRevision.code} themeConfig={themeConfig} theme={monacoTheme} - options={{ - readOnly: true - }} + options={{ readOnly: true }} /> )} </Tabs.Content> diff --git a/apps/gitness/src/pages-v2/repo/repo-code.tsx b/apps/gitness/src/pages-v2/repo/repo-code.tsx index 4d55caf474..8dab5a141f 100644 --- a/apps/gitness/src/pages-v2/repo/repo-code.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-code.tsx @@ -16,7 +16,7 @@ import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useGitRef } from '../../hooks/useGitRef' import { useRepoFileContentDetails } from '../../hooks/useRepoFileContentDetails' import { PathParams } from '../../RouteDefinitions' -import { FILE_SEPERATOR, isRefACommitSHA, isRefATag, normalizeGitRef, REFS_TAGS_PREFIX } from '../../utils/git-utils' +import { FILE_SEPARATOR, isRefACommitSHA, isRefATag, normalizeGitRef, REFS_TAGS_PREFIX } from '../../utils/git-utils' import { splitPathWithParents } from '../../utils/path-utils' /** @@ -107,7 +107,7 @@ export const RepoCode = () => { return `new/${fullGitRef}/~/${fullResourcePath}` } - const parentDirPath = fullResourcePath?.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR) + const parentDirPath = fullResourcePath?.split(FILE_SEPARATOR).slice(0, -1).join(FILE_SEPARATOR) return `new/${fullGitRef}/~/${parentDirPath}` } @@ -137,16 +137,18 @@ export const RepoCode = () => { * Render File content view or Edit file view */ const renderCodeView = useMemo(() => { + const isLoading = [isLoadingRepoDetails, loading].some(Boolean) + if (codeMode === CodeModes.VIEW && !!repoDetails?.type && repoDetails.type !== 'dir') { - return <FileContentViewer repoContent={repoDetails} /> + return <FileContentViewer repoContent={repoDetails} loading={isLoading} /> } if (codeMode !== CodeModes.VIEW) { - return <FileEditor repoDetails={repoDetails} defaultBranch={repoData?.default_branch || ''} /> + return <FileEditor repoDetails={repoDetails} defaultBranch={repoData?.default_branch || ''} loading={isLoading} /> } return <></> - }, [codeMode, repoDetails, repoData?.default_branch]) + }, [codeMode, repoDetails, repoData?.default_branch, loading, isLoadingRepoDetails]) if (!repoId) return <></> @@ -154,7 +156,6 @@ export const RepoCode = () => { <RepoFiles toCommitDetails={({ sha }: { sha: string }) => routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha })} pathParts={pathParts} - loading={loading} files={files} fullResourcePath={fullResourcePath} isRepoEmpty={repoData?.is_empty} diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index c1e85200f9..c16619e76d 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -26,7 +26,7 @@ import { useRoutes } from '../../framework/context/NavigationContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useGitRef } from '../../hooks/useGitRef' import { PathParams } from '../../RouteDefinitions' -import { FILE_SEPERATOR, normalizeGitRef, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../../utils/git-utils' +import { FILE_SEPARATOR, normalizeGitRef, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../../utils/git-utils' import { transformBranchList } from './transform-utils/branch-transform' /** @@ -174,7 +174,7 @@ export const RepoSidebar = () => { if (response.body.type === 'dir') { navigate(routes.toRepoFiles({ spaceId, repoId, '*': `new/${fullGitRef}/~/${fullResourcePath}` })) } else { - const parentDirPath = fullResourcePath?.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR) + const parentDirPath = fullResourcePath?.split(FILE_SEPARATOR).slice(0, -1).join(FILE_SEPARATOR) navigate(routes.toRepoFiles({ spaceId, repoId, '*': `new/${fullGitRef}/~/${parentDirPath}` })) } }) @@ -197,9 +197,11 @@ export const RepoSidebar = () => { <> <Layout.Flex className="flex-1" ref={containerRef}> <div - className={`shrink-0 overflow-hidden min-w-[${SIDEBAR_MIN_WIDTH}px] max-w-[${SIDEBAR_MAX_WIDTH}px]`} + className="shrink-0 overflow-hidden" style={{ - width: `${sidebarWidth}px` + width: `${sidebarWidth}px`, + minWidth: `${SIDEBAR_MIN_WIDTH}px`, + maxWidth: `${SIDEBAR_MAX_WIDTH}px` }} > <RepoSidebarView diff --git a/apps/gitness/src/utils/git-utils.ts b/apps/gitness/src/utils/git-utils.ts index 132d322911..ff6ebaf60c 100644 --- a/apps/gitness/src/utils/git-utils.ts +++ b/apps/gitness/src/utils/git-utils.ts @@ -94,7 +94,7 @@ const MONACO_SUPPORTED_LANGUAGES = [ 'yaml' ] -export const FILE_SEPERATOR = '/' +export const FILE_SEPARATOR = '/' export const PLAIN_TEXT = 'plaintext' diff --git a/packages/ui/src/components/button-group.tsx b/packages/ui/src/components/button-group.tsx index 0c9608799f..206472299e 100644 --- a/packages/ui/src/components/button-group.tsx +++ b/packages/ui/src/components/button-group.tsx @@ -2,6 +2,7 @@ import { ComponentProps, FC, ReactNode } from 'react' import { Button, ButtonProps, DropdownMenu, Tooltip, TooltipProps } from '@/components' import { cn } from '@utils/cn' +import omit from 'lodash-es/omit' type ButtonGroupTooltipProps = Pick<TooltipProps, 'title' | 'content' | 'side' | 'align'> @@ -91,7 +92,7 @@ export const ButtonGroup: FC<ButtonGroupProps> = ({ variant="outline" size={size} iconOnly={iconOnly} - {...restButtonProps} + {...omit(restButtonProps, ['tooltipProps', 'dropdownProps'])} /> </Wrapper> ) diff --git a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx index 56c596d52a..ce5be9b3d4 100644 --- a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx +++ b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx @@ -43,7 +43,7 @@ export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ const RightDetails = () => { return ( <Layout.Horizontal gap="xl" align="center"> - <Layout.Horizontal gap="xs" align="center"> + <Layout.Horizontal gap="2xs" align="center"> <Text color="foreground-3">{`${fileContent?.split('\n').length || 0} lines`}</Text> <Separator orientation="vertical" className="h-3" /> <Text color="foreground-3">{fileBytesSize}</Text> @@ -61,10 +61,7 @@ export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ content: ( <> <DropdownMenu.Item onSelect={handleViewRaw} title="View Raw" /> - <DropdownMenu.Item - onSelect={handleOpenDeleteDialog} - title={<span className="truncate text-sm text-cn-foreground-danger">Delete</span>} - /> + <DropdownMenu.Item onSelect={handleOpenDeleteDialog} title={<Text color="danger">Delete</Text>} /> </> ), contentProps: { @@ -80,7 +77,7 @@ export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ return ( <StackedList.Root className="bg-cn-background-2" onlyTopRounded={view !== 'history'}> - <StackedList.Item disableHover isHeader className="px-cn-md py-cn-2xs"> + <StackedList.Item disableHover isHeader className="px-cn-md py-cn-2xs gap-cn-sm flex-nowrap"> <Tabs.List variant="ghost"> {isMarkdown && <Tabs.Trigger value="preview">Preview</Tabs.Trigger>} <Tabs.Trigger value="code">Code</Tabs.Trigger> diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index 48f70f73ce..8dcf09b185 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -24,7 +24,9 @@ const Item = ({ className, children, icon, isActive, ...props }: ItemProps) => { {...props} > <IconV2 className="text-inherit" name={icon} size="md" /> - <Text className="line-clamp-1 overflow-hidden text-inherit">{children}</Text> + <Text className="text-inherit" truncate> + {children} + </Text> </Layout.Grid> ) } @@ -90,7 +92,7 @@ function FileItem({ children, isActive, level, link, onClick }: FileItemProps) { className="mb-cn-4xs" style={{ marginLeft: `calc(-16px * ${level})`, - paddingLeft: `calc(16px * ${level + 1} + 8px)` + paddingLeft: level ? `calc(16px * ${level} + 8px)` : '40px' }} onClick={onClick} > diff --git a/packages/ui/src/components/path-breadcrumbs.tsx b/packages/ui/src/components/path-breadcrumbs.tsx index a134b406b5..2f2f7132df 100644 --- a/packages/ui/src/components/path-breadcrumbs.tsx +++ b/packages/ui/src/components/path-breadcrumbs.tsx @@ -87,21 +87,20 @@ export const PathBreadcrumbs = ({ items, isEdit, isNew, ...props }: PathBreadcru } return ( - <> - <Breadcrumb.Separator /> - <InputPathBreadcrumbItem - path={fileName} - changeFileName={changeFileName} - gitRefName={gitRefName} - handleOnBlur={handleOnBlur} - isNew={isNew} - parentPath={parentPath} - setParentPath={setParentPath} - /> - </> + <InputPathBreadcrumbItem + path={fileName} + changeFileName={changeFileName} + gitRefName={gitRefName} + handleOnBlur={handleOnBlur} + isNew={isNew} + parentPath={parentPath} + setParentPath={setParentPath} + /> ) } + const isRenderInput = isNew || isEdit + return ( <Layout.Flex gap="2xs" align="center" wrap="wrap"> <Breadcrumb.Root> @@ -116,12 +115,13 @@ export const PathBreadcrumbs = ({ items, isEdit, isNew, ...props }: PathBreadcru {idx < items.length - 1 && <Breadcrumb.Separator />} </Fragment> ))} + {isRenderInput && <Breadcrumb.Separator />} </Breadcrumb.List> </Breadcrumb.Root> - {(isNew || isEdit) && renderInput()} + {isRenderInput && renderInput()} - {items.length > 0 && !(isNew || isEdit) && ( + {items.length > 0 && !isRenderInput && ( <CopyButton name={items.map(item => item.path).join('/')} className="ml-cn-2xs" /> )} </Layout.Flex> diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index 6afb4baf3b..b5bf1b6097 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -1,6 +1,6 @@ import { ReactNode, useCallback, useEffect, useState } from 'react' -import { Command, Popover, SearchInput, SearchInputProps, Text } from '@/components' +import { DropdownMenu, SearchInput, SearchInputProps, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@utils/cn' @@ -67,26 +67,26 @@ export const SearchFiles = ({ } const lowerCaseQuery = currentQuery.toLowerCase() - const filteredFiles: FilteredFile[] = [] + const _filteredFiles: FilteredFile[] = [] for (const file of filesList) { const lowerCaseFile = file.toLowerCase() const matchIndex = lowerCaseFile.indexOf(lowerCaseQuery) if (matchIndex > -1) { - filteredFiles.push({ + _filteredFiles.push({ file, element: getMarkedFileElement(file, lowerCaseQuery, matchIndex) }) } // Limiting the result to 50, refactor this once backend supports pagination - if (filteredFiles.length === MAX_FILES) { + if (_filteredFiles.length === MAX_FILES) { break } } - setFilteredFiles(filteredFiles) + setFilteredFiles(_filteredFiles) }, [filesList, currentQuery]) const handleInputChange = useCallback((searchQuery: string) => { @@ -95,46 +95,29 @@ export const SearchFiles = ({ }, []) return ( - <Popover.Root open={isOpen} onOpenChange={setIsOpen}> - <Popover.Anchor asChild> - <div className={inputContainerClassName}> - <SearchInput size={searchInputSize} onChange={handleInputChange} /> - </div> - </Popover.Anchor> - <Popover.Content - align="start" - hideArrow - onOpenAutoFocus={event => { - event.preventDefault() - }} - className={cn('!p-1', 'width-popover-max-width', contentClassName)} - > - <Command.Root className="bg-transparent"> - <Command.List - scrollAreaProps={{ className: 'max-h-96', classNameContent: 'overflow-hidden [&>[cmdk-group]]:!p-0' }} - > - {filteredFiles.length ? ( - <Command.Group> - {filteredFiles?.map(({ file, element }) => ( - <Command.Item - key={file} - className="!cn-dropdown-menu-item" - value={file} - onSelect={() => { - navigateToFile(file) - setIsOpen(false) - }} - > - {element} - </Command.Item> - ))} - </Command.Group> - ) : ( - <Command.Empty>{t('component:searchFile.noFile', 'No file found.')}</Command.Empty> - )} - </Command.List> - </Command.Root> - </Popover.Content> - </Popover.Root> + <DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen} modal={false}> + <div className={cn('relative', inputContainerClassName)}> + <DropdownMenu.Trigger className="pointer-events-none absolute inset-0 -z-0" tabIndex={-1} /> + <SearchInput size={searchInputSize} onChange={handleInputChange} /> + </div> + + <DropdownMenu.Content align="start" className={cn('max-h-96', 'width-popover-max-width', contentClassName)}> + {filteredFiles.length ? ( + filteredFiles?.map(({ file, element }) => ( + <DropdownMenu.IconItem + key={file} + onSelect={() => { + navigateToFile(file) + setIsOpen(false) + }} + title={element} + icon="page" + /> + )) + ) : ( + <DropdownMenu.NoOptions>{t('component:searchFile.noFile', 'No file found.')}</DropdownMenu.NoOptions> + )} + </DropdownMenu.Content> + </DropdownMenu.Root> ) } diff --git a/packages/ui/src/styles/styles.css b/packages/ui/src/styles/styles.css index eafa4cdf1e..a6b695a856 100644 --- a/packages/ui/src/styles/styles.css +++ b/packages/ui/src/styles/styles.css @@ -553,6 +553,8 @@ mark { .monaco-editor.monaco-editor { --vscode-editorGutter-background: transparent; --vscode-editor-background: transparent; + --vscode-editorLineNumber-foreground: var(--cn-text-3); + --vscode-editorLineNumber-activeForeground: var(--cn-text-1); outline-color: transparent !important; } diff --git a/packages/ui/src/views/components/contributors.tsx b/packages/ui/src/views/components/contributors.tsx index e52fdb1aa0..10066e3812 100644 --- a/packages/ui/src/views/components/contributors.tsx +++ b/packages/ui/src/views/components/contributors.tsx @@ -1,4 +1,14 @@ -import { Avatar, AvatarTooltipProps, AvatarWithTooltip, Layout, Tag, Text } from '@components/index' +import { Avatar, AvatarTooltipProps, AvatarWithTooltip, Layout, StatusBadge, Text } from '@components/index' + +const TooltipContent = ({ contributor }: { contributor: ContributorsProps['contributors'][number] }) => ( + <Layout.Horizontal align="center" justify="between" className="m-1"> + <Avatar name={contributor.name || ''} size="lg" rounded /> + <Layout.Vertical gap="2xs"> + <Text>{contributor.name}</Text> + <Text>{contributor.email}</Text> + </Layout.Vertical> + </Layout.Horizontal> +) export interface ContributorsProps { contributors: { name: string; email: string }[] @@ -13,33 +23,30 @@ export const Contributors = (props: ContributorsProps) => { const contributorsToShow = contributors.length > maxNames ? contributors.slice(0, 10) : contributors return ( - <Layout.Horizontal gapX="2xs" align={'center'}> - {contributorsToShow.map((contributor, idx) => { - const tooltipProps: AvatarTooltipProps = { - side: 'top', - content: ( - <Layout.Horizontal align="center" justify="between" className="m-1"> - <Avatar name={contributor.name || ''} size="lg" rounded /> - <Layout.Vertical gap="2xs"> - <Text>{contributor.name}</Text> - <Text>{contributor.email}</Text> - </Layout.Vertical> - </Layout.Horizontal> + <Layout.Horizontal gapX="2xs" align="center"> + <Layout.Flex align="center"> + {contributorsToShow.map(contributor => { + const tooltipProps: AvatarTooltipProps = { + side: 'top', + content: <TooltipContent contributor={contributor} /> + } + return ( + <AvatarWithTooltip + key={contributor.email} + name={contributor.name} + size="sm" + rounded + tooltipProps={tooltipProps} + className="[&:not(:first-child)]:-ml-1" + /> ) - } - return ( - <AvatarWithTooltip - key={contributor.email} - name={contributor.name} - size="md" - rounded - tooltipProps={tooltipProps} - className={idx !== 0 ? '-ml-3' : undefined} - /> - ) - })} - <Text>Contributors</Text> - <Tag value={`${contributors.length}`} theme={'blue'} className="px-1" /> + })} + </Layout.Flex> + + <Text variant="body-single-line-normal">Contributors</Text> + <StatusBadge variant="outline" theme="info" size="sm"> + {contributors.length} + </StatusBadge> </Layout.Horizontal> ) } diff --git a/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx index 7361679054..1a4e189239 100644 --- a/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx +++ b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx @@ -25,7 +25,7 @@ export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ( className }) => { const handleMouseDown = useCallback( - (e: React.MouseEvent<HTMLDivElement>) => { + (e: React.MouseEvent<HTMLButtonElement>) => { const startX = e.clientX const startWidth = width const container = containerRef?.current || document @@ -48,7 +48,7 @@ export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ( ) const handleKeyDown = useCallback( - (e: React.KeyboardEvent<HTMLDivElement>) => { + (e: React.KeyboardEvent<HTMLButtonElement>) => { const step = e.shiftKey ? 50 : 10 // Larger steps with Shift key switch (e.key) { @@ -74,18 +74,17 @@ export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ( ) return ( - <div + <button onMouseDown={handleMouseDown} onKeyDown={handleKeyDown} className={cn( - 'border-cn-borders-3 focus-within:border-cn-borders-1 focus-visible:border-cn-borders-1 w-1 shrink-0 cursor-col-resize select-none border-r transition-colors', + 'after:bg-cn-borders-3 focus:after:bg-cn-borders-accent relative w-px shrink-0 cursor-col-resize select-none after:transition-[width,background,left] before:absolute before:left-[-150%] before:top-0 before:h-full before:w-1 after:absolute after:top-0 after:left-0 after:h-full after:w-px hover:after:left-[-150%] hover:after:w-1 focus:after:left-[-150%] focus:after:w-1', className )} role="slider" aria-valuenow={width} aria-valuemax={maxWidth} aria-valuemin={minWidth} - tabIndex={0} aria-label={ariaLabel} /> ) diff --git a/packages/ui/src/views/repo/components/path-action-bar.tsx b/packages/ui/src/views/repo/components/path-action-bar.tsx index 53c2413748..b111856fa3 100644 --- a/packages/ui/src/views/repo/components/path-action-bar.tsx +++ b/packages/ui/src/views/repo/components/path-action-bar.tsx @@ -41,7 +41,7 @@ export const PathActionBar: FC<PathActionBarProps> = ({ const { t } = useTranslation() return ( - <Layout.Horizontal className="items-center justify-between"> + <Layout.Horizontal align="center" justify="between"> <PathBreadcrumbs isEdit={codeMode === CodeModes.EDIT} isNew={codeMode === CodeModes.NEW} @@ -59,14 +59,14 @@ export const PathActionBar: FC<PathActionBarProps> = ({ pathUploadFiles && selectedRefType === BranchSelectorTab.BRANCHES && ( <Button asChild> - <Link className="relative grid grid-cols-[auto_1fr] items-center gap-1.5" to={pathNewFile}> + <Link to={pathNewFile}> <IconV2 name="plus" /> - <span className="truncate">{t('views:repos.createFile', 'Create File')}</span> + {t('views:repos.createFile', 'Create File')} </Link> </Button> )} {codeMode !== CodeModes.VIEW && ( - <div className="flex gap-2.5"> + <Layout.Flex gapX="sm"> {!!handleCancelFileEdit && ( <Button variant="outline" onClick={handleCancelFileEdit}> Cancel Changes @@ -74,11 +74,11 @@ export const PathActionBar: FC<PathActionBarProps> = ({ )} {!!handleOpenCommitDialog && ( <Button onClick={handleOpenCommitDialog}> - <IconV2 name="git-commit" /> + <IconV2 name="upload" /> Commit Changes </Button> )} - </div> + </Layout.Flex> )} </Layout.Horizontal> ) diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 81cd2bcab2..4242493a8a 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode, useMemo } from 'react' -import { IconV2, NoData, PathParts, Skeleton } from '@/components' +import { IconV2, NoData, PathParts } from '@/components' import { useTranslation } from '@/context' import { UsererrorError } from '@/types' import { @@ -20,7 +20,6 @@ import { interface RepoFilesProps { pathParts: PathParts[] - loading: boolean files: RepoFile[] isDir: boolean isRepoEmpty?: boolean @@ -48,7 +47,6 @@ interface RepoFilesProps { export const RepoFiles: FC<RepoFilesProps> = ({ pathParts, - loading, files, isDir, isShowSummary, @@ -78,8 +76,6 @@ export const RepoFiles: FC<RepoFilesProps> = ({ const isView = useMemo(() => codeMode === CodeModes.VIEW, [codeMode]) const content = useMemo(() => { - if (loading || isLoadingRepoDetails) return <Skeleton.Table countColumns={3} countRows={8} /> - if (!isView) return children if (!isRepoEmpty && !isDir && !repoDetailsError) { @@ -144,7 +140,6 @@ export const RepoFiles: FC<RepoFilesProps> = ({ children, isDir, latestFile, - loading, isShowSummary, files, selectedBranchTag?.name, @@ -159,7 +154,7 @@ export const RepoFiles: FC<RepoFilesProps> = ({ return ( <SandboxLayout.Main className="repo-files-height bg-transparent"> - <SandboxLayout.Content className="flex h-full flex-col pl-cn-xl gap-y-cn-md"> + <SandboxLayout.Content className="pl-cn-xl gap-y-cn-md flex h-full flex-col"> {isView && !isRepoEmpty && ( <PathActionBar codeMode={codeMode} diff --git a/packages/yaml-editor/src/components/CodeEditor.tsx b/packages/yaml-editor/src/components/CodeEditor.tsx index dc840d2478..6f92c8ba2f 100644 --- a/packages/yaml-editor/src/components/CodeEditor.tsx +++ b/packages/yaml-editor/src/components/CodeEditor.tsx @@ -20,7 +20,7 @@ const defaultOptions: monaco.editor.IStandaloneEditorConstructionOptions = { export interface CodeEditorProps<_> { codeRevision: CodeRevision - onCodeRevisionChange: (codeRevision: CodeRevision | undefined, ev: monaco.editor.IModelContentChangedEvent) => void + onCodeRevisionChange?: (codeRevision: CodeRevision | undefined, ev: monaco.editor.IModelContentChangedEvent) => void language: string themeConfig?: { rootElementSelector?: string; defaultTheme?: string; themes?: ThemeDefinition[] } theme?: string @@ -105,7 +105,7 @@ export function CodeEditor<T>({ height={height} onChange={(value, data) => { currentRevisionRef.current = { code: value ?? '', revisionId: data.versionId } - onCodeRevisionChange({ ...currentRevisionRef.current }, data) + onCodeRevisionChange?.({ ...currentRevisionRef.current }, data) }} language={language} theme={theme} From f4eacbfd5ba366131e688eebfb3f3f4321cb3426 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Thu, 14 Aug 2025 13:53:26 +0300 Subject: [PATCH 091/180] Fix text-input autofocus at Dialog (#2068) --- .../ui/src/components/inputs/text-input.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/inputs/text-input.tsx b/packages/ui/src/components/inputs/text-input.tsx index 3ba1ca81c3..7eb193731b 100644 --- a/packages/ui/src/components/inputs/text-input.tsx +++ b/packages/ui/src/components/inputs/text-input.tsx @@ -1,6 +1,7 @@ -import { forwardRef, useMemo } from 'react' +import { forwardRef, useEffect, useMemo, useRef } from 'react' import { CommonInputsProp, ControlGroup, FormCaption, Label } from '@/components' +import { useMergeRefs } from '@/utils' import { BaseInput, InputProps } from './base-input' @@ -21,9 +22,10 @@ const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => { orientation, informerProps, informerContent, + autoFocus, ...restProps } = props - + const inputRef = useRef<HTMLInputElement | null>(null) const isHorizontal = orientation === 'horizontal' // override theme based on error and warning @@ -32,6 +34,23 @@ const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => { // Generate a unique ID if one isn't provided const inputId = useMemo(() => props.id || `input-${Math.random().toString(36).substring(2, 9)}`, [props.id]) + const mergedRef = useMergeRefs<HTMLInputElement>([ + node => { + if (!node) return + + inputRef.current = node + }, + ref + ]) + + useEffect(() => { + if (autoFocus && inputRef.current) { + const t = setTimeout(() => inputRef.current?.focus(), 0) + + return () => clearTimeout(t) + } + }, [autoFocus]) + return ( <ControlGroup.Root className={wrapperClassName} orientation={orientation}> {(!!label || (isHorizontal && !!caption)) && ( @@ -53,7 +72,7 @@ const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props, ref) => { )} <ControlGroup.InputWrapper> - <BaseInput {...restProps} ref={ref} theme={theme} id={inputId} disabled={disabled} /> + <BaseInput {...restProps} ref={mergedRef} theme={theme} id={inputId} disabled={disabled} /> {error ? ( <FormCaption disabled={disabled} theme="danger"> From 8f4157d852bd36c57f13079c8780d2480fbde276 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Thu, 14 Aug 2025 11:37:21 +0000 Subject: [PATCH 092/180] fix: order of unsetHidden -> transform -> removeTempFields (#10209) * fb9647 fix: order of unsetHidden -> transform -> removeTempFields * 398fcf fix: order of unsetHidden -> transform -> removeTempFields --- packages/ui/package.json | 2 +- .../src/views/connectors/connector-entity-form.tsx | 14 ++++++-------- .../src/views/connectors/connectors-list/utils.ts | 7 ++++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 3336d4a7c6..cb772b7e0f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@harnessio/ui", "description": "Harness Canary UI component library", - "version": "0.0.98", + "version": "0.0.99", "private": false, "type": "module", "main": "./dist/index.js", diff --git a/packages/ui/src/views/connectors/connector-entity-form.tsx b/packages/ui/src/views/connectors/connector-entity-form.tsx index 40983c063d..d1a9287500 100644 --- a/packages/ui/src/views/connectors/connector-entity-form.tsx +++ b/packages/ui/src/views/connectors/connector-entity-form.tsx @@ -144,15 +144,13 @@ export const ConnectorEntityForm: FC<ConnectorEntityFormProps> = ({ mode="onSubmit" onSubmit={values => { const definition = getConnectorDefinition(connector.type) + const unsetValues = definition?.formDefinition + ? unsetHiddenInputsValues(definition.formDefinition, values) + : values const transformers = definition?.formDefinition ? getTransformers(definition.formDefinition) : [] - const formattedValues = removeTemporaryFieldsValue( - definition?.formDefinition ? unsetHiddenInputsValues(definition.formDefinition, values) : values - ) - const transformedValues = transformers.length - ? outputTransformValues(formattedValues, transformers) - : formattedValues - - onSubmit({ values: transformedValues, connector, intent }) + const transformedValues = transformers.length ? outputTransformValues(unsetValues, transformers) : unsetValues + const formattedValues = removeTemporaryFieldsValue(transformedValues) + onSubmit({ values: formattedValues, connector, intent }) }} validateAfterFirstSubmit={true} > diff --git a/packages/ui/src/views/connectors/connectors-list/utils.ts b/packages/ui/src/views/connectors/connectors-list/utils.ts index 3dd424f0cd..e04c52ef64 100644 --- a/packages/ui/src/views/connectors/connectors-list/utils.ts +++ b/packages/ui/src/views/connectors/connectors-list/utils.ts @@ -34,5 +34,10 @@ export const ConnectorTypeToLogoNameMap: Map<ConnectorConfigType, keyof typeof L ['Azure', 'azure'], ['Artifactory', 'artifactory'], ['OciHelmRepo', 'helm'], - ['Tas', 'tanzu'] + ['Tas', 'tanzu'], + ['AzureArtifacts', 'azure'], + ['Jenkins', 'jenkins'], + ['Bamboo', 'bamboo'], + ['PagerDuty', 'pagerduty'], + ['JDBC', 'java'] ]) From 84233be1fb583fa73fb988ec9248de11b4c4bc1a Mon Sep 17 00:00:00 2001 From: Pranesh TG <pranesh.g@harness.io> Date: Thu, 14 Aug 2025 12:11:19 +0000 Subject: [PATCH 093/180] Add shrink to stepper button in number input (#10210) * 8b5ab4 Add shrink to stepper button in number input --- packages/ui/src/components/inputs/number-input.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/components/inputs/number-input.tsx b/packages/ui/src/components/inputs/number-input.tsx index 4798ee213f..9ad19708a9 100644 --- a/packages/ui/src/components/inputs/number-input.tsx +++ b/packages/ui/src/components/inputs/number-input.tsx @@ -140,6 +140,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>( <div className="flex flex-col"> <Button tabIndex={-1} + className="shrink" aria-label="Increment value" variant="ghost" iconOnly @@ -152,6 +153,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>( <hr /> <Button tabIndex={-1} + className="shrink" aria-label="Decrement value" variant="ghost" iconOnly From 6c8e565316a01979ef57dcea1191d0732abd21f0 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Thu, 14 Aug 2025 13:00:19 +0000 Subject: [PATCH 094/180] Add line selection and state in the fragment (#10198) * 2c9e8d update color * 8d7061 integrate line selection * ef8d3c code editor line selection --- .../src/components-v2/file-content-viewer.tsx | 19 ++- .../src/hooks/useCodeEditorSelectionState.ts | 30 +++++ .../demo-shadowdom-codeeditor.tsx | 105 +++++++++++++++++ .../yaml-editor/playground/src/playground.tsx | 14 ++- .../yaml-editor/src/components/CodeEditor.css | 19 +++ .../yaml-editor/src/components/CodeEditor.tsx | 37 +++++- packages/yaml-editor/src/global.d.ts | 5 + .../src/hooks/useLinesSelection.tsx | 108 ++++++++++++++++++ 8 files changed, 328 insertions(+), 9 deletions(-) create mode 100644 apps/gitness/src/hooks/useCodeEditorSelectionState.ts create mode 100644 packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx create mode 100644 packages/yaml-editor/src/components/CodeEditor.css create mode 100644 packages/yaml-editor/src/hooks/useLinesSelection.tsx diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index b5e9029de8..c8746c0cfa 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -25,6 +25,7 @@ import { useDownloadRawFile } from '../framework/hooks/useDownloadRawFile' import { useGetRepoRef } from '../framework/hooks/useGetRepoPath' import { parseAsInteger, useQueryState } from '../framework/hooks/useQueryState' import { useAPIPath } from '../hooks/useAPIPath' +import { useCodeEditorSelectionState } from '../hooks/useCodeEditorSelectionState' import useCodePathDetails from '../hooks/useCodePathDetails' import { useGitRef } from '../hooks/useGitRef' import { useRepoBranchesStore } from '../pages-v2/repo/stores/repo-branches-store' @@ -62,6 +63,7 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV const { selectedBranchTag, selectedRefType } = useRepoBranchesStore() const [page, _setPage] = useQueryState('page', parseAsInteger.withDefault(1)) const { theme } = useThemeStore() + const { selectedLine, setSelectedLine } = useCodeEditorSelectionState() const { gitRefName } = useGitRef() @@ -110,6 +112,13 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV [monacoTheme] ) + const codeRevision = useMemo( + () => ({ + code: fileContent + }), + [fileContent] + ) + const handleDownloadFile = () => { downloadFile({ repoRef, @@ -169,7 +178,10 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV <Tabs.Root className="repo-files-height flex flex-col overflow-hidden" value={view as string} - onValueChange={val => onChangeView(val as ViewTypeValue)} + onValueChange={val => { + setSelectedLine(undefined) + onChangeView(val as ViewTypeValue) + }} > <FileViewerControlBar view={view} @@ -228,10 +240,13 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV className="overflow-hidden" height="100%" language={language} - codeRevision={{ code: fileContent }} + codeRevision={codeRevision} themeConfig={themeConfig} options={{ readOnly: true }} theme={monacoTheme} + enableLinesSelection={true} + onSelectedLineChange={setSelectedLine} + selectedLine={selectedLine} /> )} </Tabs.Content> diff --git a/apps/gitness/src/hooks/useCodeEditorSelectionState.ts b/apps/gitness/src/hooks/useCodeEditorSelectionState.ts new file mode 100644 index 0000000000..a5b0293661 --- /dev/null +++ b/apps/gitness/src/hooks/useCodeEditorSelectionState.ts @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' + +export function useCodeEditorSelectionState() { + const { hash } = useLocation() + const navigate = useNavigate() + const [selectedLine, setSelectedLineLocal] = useState<number | undefined>(undefined) + + useEffect(() => { + if (!hash) { + setSelectedLineLocal(undefined) + } else if (hash.startsWith('#L')) { + const lineStr = hash.replace('#L', '') + const lineInt = parseInt(lineStr) + setSelectedLineLocal(isNaN(lineInt) ? undefined : lineInt) + } + }, [hash, setSelectedLineLocal]) + + const setSelectedLine = useCallback( + (line: number | undefined) => { + if (selectedLine !== line) { + navigate({ hash: line ? `L${line}` : undefined }, { replace: true }) + setSelectedLineLocal(line) + } + }, + [navigate, selectedLine] + ) + + return { selectedLine, setSelectedLine } +} diff --git a/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx b/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx new file mode 100644 index 0000000000..ae0d7635b9 --- /dev/null +++ b/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx @@ -0,0 +1,105 @@ +import { useMemo, useState } from 'react' + +import styleCss from 'monaco-editor/min/vs/editor/editor.main.css?inline' + +import { CodeEditor, ThemeDefinition } from '../../../src' +import { CodeRevision } from '../../../src/components/CodeEditor' +import codeEditorCss from '../../../src/components/CodeEditor.css?inline' +import ShadowDomWrapper from '../common/components/shadow-dom-wrapper' +import { reactFileContent } from '../common/content/react' +import { harnessDarkTheme, harnessLightTheme } from '../common/theme/theme' + +const themes: ThemeDefinition[] = [ + { themeName: 'dark', themeData: harnessDarkTheme }, + { themeName: 'light', themeData: harnessLightTheme } +] + +const themeConfig = { + defaultTheme: 'dark', + themes +} + +export const DemoCodeEditorShadowDom: React.FC<React.PropsWithChildren<React.HTMLAttributes<HTMLElement>>> = () => { + const [codeRevision, setCodeRevision] = useState<CodeRevision>({ code: reactFileContent }) + const [showEditor, setShowEditor] = useState(true) + + const [fragment, setFragment] = useState('') + + const [bounds, setBounds] = useState<DOMRect>() + + const selectedLine = useMemo(() => { + if (fragment.startsWith('L')) { + const lineStr = fragment.replace('L', '') + const line = parseInt(lineStr) + return isNaN(line) ? undefined : line + } + }, [fragment]) + + return ( + <div className="demo-holder"> + <div className="buttons-holder"> + <button + onClick={() => { + setCodeRevision({ code: reactFileContent }) + }} + > + Reset editor content + </button> + <button + onClick={() => { + setShowEditor(!showEditor) + }} + > + Toggle mount editor + </button> + </div> + <div className="editor-holder"> + <ShadowDomWrapper> + {showEditor && ( + <div style={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}> + <style>{styleCss}</style> + <style>{codeEditorCss}</style> + <CodeEditor + onCodeRevisionChange={value => { + setCodeRevision(value ?? { code: '', revisionId: 0 }) + }} + codeRevision={codeRevision} + themeConfig={themeConfig} + language={'typescript'} + options={{ readOnly: true }} + enableLinesSelection={true} + selectedLine={selectedLine} + onSelectedLineChange={line => { + setFragment(line ? `L${line}` : '') + }} + onSelectedLineButtonClick={btnEL => { + if (btnEL) { + const bounds = btnEL.getBoundingClientRect() + setBounds(bounds) + } else { + setBounds(undefined) + } + }} + /> + </div> + )} + </ShadowDomWrapper> + </div> + {bounds ? ( + <div + style={{ + position: 'absolute', + left: bounds?.left, + top: bounds?.top + 30, + background: 'lightgray', + padding: '10px', + border: '1px solid gray' + }} + > + <div>Copy line</div> + <div>Copy permalink</div> + </div> + ) : null} + </div> + ) +} diff --git a/packages/yaml-editor/playground/src/playground.tsx b/packages/yaml-editor/playground/src/playground.tsx index e217194be0..c2f0835373 100644 --- a/packages/yaml-editor/playground/src/playground.tsx +++ b/packages/yaml-editor/playground/src/playground.tsx @@ -3,12 +3,21 @@ import { useState } from 'react' import './playground.css' import { DemoShadowDomBlame } from './demo-shadowdom-blame/demo-shadowdom-blame' +import { DemoCodeEditorShadowDom } from './demo-shadowdom-codeeditor/demo-shadowdom-codeeditor' import { DemoShadowDom } from './demo-shadowdom/demo-shadowdom' import { Demo1 } from './demo1/demo1' import { Demo2 } from './demo2/demo2' import { Demo3 } from './demo3/demo3' const demoArr = [ + { + name: 'CodeEditor ShadowDom', + component: DemoCodeEditorShadowDom + }, + { + name: 'CodeEditor', + component: Demo3 + }, { name: 'YamlEditor ShadowDom', component: DemoShadowDom @@ -21,10 +30,7 @@ const demoArr = [ name: 'YamlEditor - Animation', component: Demo2 }, - { - name: 'CodeEditor', - component: Demo3 - }, + { name: 'BlameEditor ShadowDom', component: DemoShadowDomBlame diff --git a/packages/yaml-editor/src/components/CodeEditor.css b/packages/yaml-editor/src/components/CodeEditor.css new file mode 100644 index 0000000000..56c50fd445 --- /dev/null +++ b/packages/yaml-editor/src/components/CodeEditor.css @@ -0,0 +1,19 @@ +.CodeEditor_HighlightedLine { + opacity: 0.1; + background-color: var(--cn-yellow-300) !important; +} + +.CodeEditor_HighlightedGlyphMargin { + background: rgb(183, 183, 183); + border: 1px solid gray; + border-radius: 4px; + margin-left: 5px; + height: 20px; + position: relative; +} + +.CodeEditor_HighlightedGlyphMargin::after { + content: '...'; + position: absolute; + padding: 7px 3px; +} diff --git a/packages/yaml-editor/src/components/CodeEditor.tsx b/packages/yaml-editor/src/components/CodeEditor.tsx index 6f92c8ba2f..4f6d956af8 100644 --- a/packages/yaml-editor/src/components/CodeEditor.tsx +++ b/packages/yaml-editor/src/components/CodeEditor.tsx @@ -4,8 +4,11 @@ import Editor, { EditorProps, loader, Monaco, useMonaco } from '@monaco-editor/r import * as monaco from 'monaco-editor' import { MonacoCommonDefaultOptions } from '../constants/monaco-common-default-options' +import { useLinesSelection } from '../hooks/useLinesSelection' import { useTheme } from '../hooks/useTheme' import { ThemeDefinition } from '../types/themes' +import { createRandomString } from '../utils/utils' +import codeEditorCss from './CodeEditor.css?raw' loader.config({ monaco }) @@ -27,6 +30,10 @@ export interface CodeEditorProps<_> { options?: monaco.editor.IStandaloneEditorConstructionOptions height?: EditorProps['height'] className?: string + enableLinesSelection?: boolean + selectedLine?: number + onSelectedLineChange?: (line: number | undefined) => void + onSelectedLineButtonClick?: (ev: HTMLDivElement | undefined) => void } export function CodeEditor<T>({ @@ -36,9 +43,15 @@ export function CodeEditor<T>({ themeConfig, options, theme: themeFromProps, + enableLinesSelection = false, + selectedLine, + onSelectedLineChange, + onSelectedLineButtonClick, height = '75vh', className }: CodeEditorProps<T>): JSX.Element { + const instanceId = useRef(createRandomString(5)) + const monaco = useMonaco() const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor | undefined>() const monacoRef = useRef<typeof monaco>() @@ -90,18 +103,36 @@ export function CodeEditor<T>({ } } } - }, [codeRevision, editorRef.current]) + }, [codeRevision, options?.readOnly, editorRef]) const { theme } = useTheme({ monacoRef, themeConfig, editor, theme: themeFromProps }) + useLinesSelection({ + enable: enableLinesSelection, + editor, + selectedLine, + onSelectedLineChange, + onSelectedLineButtonClick + }) + const mergedOptions = useMemo(() => { - return { ...defaultOptions, ...(options ? options : {}) } + return { + ...defaultOptions, + ...(options ? options : {}) + // TODO: this will be used in the future + // ...(enableLinesSelection ? { glyphMargin: true } : {}) + } }, [options]) + const styleCss = useMemo(() => { + return `.monaco-editor-${instanceId.current} .margin-view-overlays .line-numbers { cursor: pointer !important; } ${codeEditorCss}` + }, []) + return ( <> + {enableLinesSelection && <style dangerouslySetInnerHTML={{ __html: styleCss }}></style>} <Editor - className={className} + className={`monaco-editor-${instanceId.current} ${className}`} height={height} onChange={(value, data) => { currentRevisionRef.current = { code: value ?? '', revisionId: data.versionId } diff --git a/packages/yaml-editor/src/global.d.ts b/packages/yaml-editor/src/global.d.ts index e609ee4487..edc44a15b1 100644 --- a/packages/yaml-editor/src/global.d.ts +++ b/packages/yaml-editor/src/global.d.ts @@ -17,3 +17,8 @@ declare module 'monaco-editor/esm/vs/editor/standalone/browser/standaloneService get: (id: unknown) => { documentSymbolProvider: unknown } } } + +declare module '*.css?raw' { + const content: string + export default content +} diff --git a/packages/yaml-editor/src/hooks/useLinesSelection.tsx b/packages/yaml-editor/src/hooks/useLinesSelection.tsx new file mode 100644 index 0000000000..6e3b575e52 --- /dev/null +++ b/packages/yaml-editor/src/hooks/useLinesSelection.tsx @@ -0,0 +1,108 @@ +import { useEffect, useRef } from 'react' + +import * as monaco from 'monaco-editor' + +export interface UseLinesSelectionProps { + enable: boolean + editor?: monaco.editor.IStandaloneCodeEditor + selectedLine?: number + onSelectedLineChange?: (line: number | undefined) => void + onSelectedLineButtonClick?: (ev: HTMLDivElement | undefined) => void +} +export function useLinesSelection(props: UseLinesSelectionProps) { + const { enable, editor, onSelectedLineChange, selectedLine, onSelectedLineButtonClick } = props + + // user selection + const currentlySelectedLineRef = useRef<number | null>(null) + + // is user selection is in progress + const isSelectingRef = useRef(false) + + const decorationsRef = useRef<monaco.editor.IEditorDecorationsCollection | null>(null) + decorationsRef.current = decorationsRef.current ?? editor?.createDecorationsCollection([]) ?? null + + useEffect(() => { + let mouseDownListener: monaco.IDisposable + + const timeoutHandle = setTimeout(() => { + if (!enable || !editor) return + + const handleMouseDown = (e: monaco.editor.IEditorMouseEvent) => { + if (e.target?.element?.classList.contains('CodeEditor_HighlightedGlyphMargin')) { + onSelectedLineButtonClick?.(e.target?.element as HTMLDivElement) + + return + } + + const lineNumber = getLineNumber(e.target?.element) + + if (!lineNumber) { + onSelectedLineChange?.(undefined) + onSelectedLineButtonClick?.(undefined) + updateSelection(editor, decorationsRef.current, undefined) + + return + } + + isSelectingRef.current = true + currentlySelectedLineRef.current = lineNumber + + onSelectedLineChange?.(lineNumber) + updateSelection(editor, decorationsRef.current, lineNumber, false) + onSelectedLineButtonClick?.(undefined) + } + + mouseDownListener = editor.onMouseDown(handleMouseDown) + }, 100) + + return () => { + clearTimeout(timeoutHandle) + mouseDownListener?.dispose() + } + }, [isSelectingRef, selectedLine, onSelectedLineChange, enable, editor, onSelectedLineButtonClick, decorationsRef]) + + useEffect(() => { + if (!editor) return + if (currentlySelectedLineRef.current === selectedLine) return + + updateSelection(editor, decorationsRef.current, selectedLine, true) + }, [editor, selectedLine, decorationsRef]) +} + +function updateSelection( + editor: monaco.editor.IStandaloneCodeEditor | null, + decorations: monaco.editor.IEditorDecorationsCollection | null, + line: number | undefined, + revealInCenter?: boolean +) { + if (!line) { + decorations?.set([]) + return + } + + decorations?.set([ + { + range: new monaco.Range(line, 1, line, 1), + options: { + isWholeLine: true, + className: 'CodeEditor_HighlightedLine' + // TODO: gonna be used if in the future + // glyphMarginClassName: 'CodeEditor_HighlightedGlyphMargin' + } + } + ]) + + editor?.setSelection(new monaco.Range(line, 0, line, 0)) + + if (revealInCenter) editor?.revealLineInCenter(line) +} + +function getLineNumber(el: Element | null): number | undefined { + const lineNumberEl = el?.classList.contains('line-numbers') ? el : undefined + if (!lineNumberEl) return + + const lineNumber = parseInt(lineNumberEl.textContent ?? '') + if (isNaN(lineNumber)) return + + return lineNumber +} From d5f01e5f7ed8c58081e96794771529422fed00eb Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Thu, 14 Aug 2025 16:06:10 +0000 Subject: [PATCH 095/180] pull request header - User avatar missing (#10203) * fa3d6a Resolve merge conflict in split-button.tsx - combine forwardRef refactoring with dropdown closing fix * f7dca1 Merge remote-tracking branch 'origin/main' into rk-branch-rule-bug * 304d38 Fixed conflicts * 4b4cdf email as a fallback * 71f5fa Fixed Dropdown stayed open after selecting * 683000 PR Header - Added user Avatar * 8f327e Merge remote-tracking branch 'origin/main' into rk-branch-rule-bug * bca628 Branch rule - Hide placeholder after select a value --- packages/ui/src/components/split-button.tsx | 55 ++++++++++--------- .../components/pull-request-header.tsx | 3 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/ui/src/components/split-button.tsx b/packages/ui/src/components/split-button.tsx index 8fcb498bbb..d5d55dbc47 100644 --- a/packages/ui/src/components/split-button.tsx +++ b/packages/ui/src/components/split-button.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, MouseEvent, ReactNode } from 'react' +import React, { forwardRef, MouseEvent, ReactNode, useCallback, useState } from 'react' import { Button, buttonVariants } from '@/components/button' import { DropdownMenu } from '@components/dropdown-menu' @@ -30,21 +30,12 @@ interface SplitButtonBaseProps<T extends string> { size?: 'sm' | 'md' | 'xs' } -// For solid variant with primary theme -interface SplitButtonSolidProps<T extends string> extends SplitButtonBaseProps<T> { - variant?: 'primary' - theme?: 'success' | 'danger' | 'default' +// Base props with all possible variants and themes +export interface SplitButtonProps<T extends string> extends SplitButtonBaseProps<T> { + variant?: 'primary' | 'outline' + theme?: 'default' | 'success' | 'danger' } -// For surface variant with success or danger theme -interface SplitButtonSurfaceProps<T extends string> extends SplitButtonBaseProps<T> { - variant?: 'outline' - theme?: 'success' | 'danger' | 'default' -} - -// Combined discriminated union -export type SplitButtonProps<T extends string> = SplitButtonSolidProps<T> | SplitButtonSurfaceProps<T> - /** * Button with options * - If selectedValue exists, it will behave as a radio button @@ -75,6 +66,19 @@ const SplitButtonInner = forwardRef( }: SplitButtonProps<T>, ref: React.ForwardedRef<HTMLButtonElement> ) => { + const [isOpen, setIsOpen] = useState(false) + + const handleOptionSelect = useCallback( + (optionValue: T) => { + const option = options.find(opt => opt.value === optionValue) + if (!loading && option && !option.disabled) { + handleOptionChange(optionValue) + setIsOpen(false) // Close dropdown after selection + } + }, + [loading, options, handleOptionChange] + ) + return ( <div className={cn('flex', className)}> <Button @@ -90,7 +94,7 @@ const SplitButtonInner = forwardRef( > {children} </Button> - <DropdownMenu.Root> + <DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}> <DropdownMenu.Trigger className={cn(buttonVariants({ theme, variant, size }), 'cn-button-split-dropdown')} disabled={disabled || loading || disableDropdown} @@ -99,18 +103,21 @@ const SplitButtonInner = forwardRef( </DropdownMenu.Trigger> <DropdownMenu.Content className={cn('mt-1 max-w-80', dropdownContentClassName)} align="end"> {selectedValue ? ( - <DropdownMenu.RadioGroup value={String(selectedValue)}> + <DropdownMenu.RadioGroup + value={String(selectedValue)} + onValueChange={value => { + const option = options.find(opt => String(opt.value) === value) + if (option) { + handleOptionSelect(option.value) + } + }} + > {options.map(option => ( <DropdownMenu.RadioItem title={option.label} description={option?.description} value={String(option.value)} key={String(option.value)} - onClick={() => { - if (!loading && !option.disabled) { - handleOptionChange(option.value) - } - }} disabled={loading || option.disabled} /> ))} @@ -122,11 +129,7 @@ const SplitButtonInner = forwardRef( title={option.label} description={option.description} key={String(option.value)} - onClick={() => { - if (!loading && !option.disabled) { - handleOptionChange(option.value) - } - }} + onSelect={() => handleOptionSelect(option.value)} disabled={loading || option.disabled} /> ))} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index 21aa661d3d..50075700e2 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react' -import { BranchTag, Button, IconV2, Layout, StatusBadge, Text, TimeAgoCard } from '@/components' +import { Avatar, BranchTag, Button, IconV2, Layout, StatusBadge, Text, TimeAgoCard } from '@/components' import { cn } from '@utils/cn' import { BranchSelectorContainerProps } from '@views/repo' @@ -91,6 +91,7 @@ export const PullRequestHeader: React.FC<PullRequestTitleProps> = ({ </StatusBadge> <Layout.Horizontal gap="3xs" align="center" wrap="wrap"> + <Avatar name={author?.display_name || author?.email || ''} rounded /> <Text variant="body-single-line-strong" color="foreground-1"> {author?.display_name || author?.email || ''} </Text> From 43b3741350fbe5e6c77146157e19e9e68ff859d9 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Thu, 14 Aug 2025 16:16:53 +0000 Subject: [PATCH 096/180] fix code highlight (#10212) * 6ba5b6 fix code highlight --- .../src/pages-v2/pull-request/pull-request-compare.tsx | 3 ++- apps/gitness/src/utils/path-utils.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx index 0f954b98f6..af64c9f1bf 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx @@ -46,6 +46,7 @@ import { useAPIPath } from '../../hooks/useAPIPath.ts' import { useGitRef } from '../../hooks/useGitRef.ts' import { PathParams } from '../../RouteDefinitions' import { decodeGitContent, normalizeGitRef } from '../../utils/git-utils' +import { getFileExtension } from '../../utils/path-utils.ts' import { useGetRepoLabelAndValuesData } from '../repo/labels/hooks/use-get-repo-label-and-values-data' import { useRepoCommitsStore } from '../repo/stores/repo-commits-store' import { parseSpecificDiff } from './diff-utils' @@ -577,7 +578,7 @@ export const CreatePullRequest = () => { text: item.filePath, data: item.raw, title: item.filePath, - lang: item.filePath.split('.')?.[1], + lang: getFileExtension(item.filePath), addedLines: item.addedLines, deletedLines: item.deletedLines, isBinary: item.isBinary, diff --git a/apps/gitness/src/utils/path-utils.ts b/apps/gitness/src/utils/path-utils.ts index 4cd77cf63d..7f69705d99 100644 --- a/apps/gitness/src/utils/path-utils.ts +++ b/apps/gitness/src/utils/path-utils.ts @@ -32,3 +32,11 @@ export const decodeURIComponentIfValid = (path: string) => { export const removeTrailingSlash = (path: string) => path?.replace(/\/$/, '') export const removeLeadingSlash = (path: string) => path?.replace(/^\//, '') + +export function getFileExtension(filename: string): string { + const lastDotIndex = filename.lastIndexOf('.') + if (lastDotIndex === -1) { + return '' + } + return filename.substring(lastDotIndex + 1) +} From 39c78416361eddfb3c09a78c031f38acd9acfa86 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Thu, 14 Aug 2025 17:51:52 +0000 Subject: [PATCH 097/180] styles-updates (#10211) * e2d662 fix * bde312 Update card-select.ts * f61187 modules icons color update * 073924 Update card-select.ts * ab451a gradient update * 05557a Update node-group.tsx * ff19fc Update pull-request-system-comments.tsx * 27ca03 upd * 1b1364 Update node-group.tsx * 21fe84 Update warning triangle icon * 2246df Fix prertty and portal * d0b51b Update icons * 5d091e fix * 6349cf font fix * a71795 sidebar tokens updated * 8954a1 sidebar tokens updated * e49680 red color update * 1a6d52 Update dialog.ts * d3df4b Update DrawerHeader.tsx * fc2252 Update drawer.ts * 9e2fae Update dialog.tsx * feab02 Update alert-dialog.tsx * 381893 Update alert-dialog.tsx * 64478b Update dialog.ts * e23d00 Update drawer.ts * e92d9d Update drawer.ts * a48e90 slider base upd color * aeac13 badge counter radius updated * 633de3 radius settings across components updated * eac3ee text-2 updated value in light mode --- .../src/content/docs/components/alert-dialog.mdx | 4 +--- .../design-tokens/$themes.json | 1 + .../components/desktop/base/alert.json | 2 +- .../components/desktop/base/avatar.json | 2 +- .../components/desktop/base/badge.json | 4 ++-- .../components/desktop/base/btn.json | 2 +- .../components/desktop/base/calendar.json | 2 +- .../components/desktop/base/card.json | 6 +++--- .../components/desktop/base/dialog.json | 4 ++-- .../components/desktop/base/dropdown.json | 4 ++-- .../components/desktop/base/input.json | 2 +- .../components/desktop/base/popover.json | 2 +- .../components/desktop/base/sidebar.json | 16 ++++++++++------ .../components/desktop/base/tabs.json | 6 +++--- .../components/desktop/base/tag.json | 2 +- .../components/desktop/base/toast.json | 2 +- .../components/desktop/base/tooltip.json | 2 +- .../design-tokens/core/colors_hex.json | 4 ++-- .../design-tokens/core/colors_lch.json | 4 ++-- .../design-tokens/core/dimensions.json | 4 ++++ .../design-tokens/core/typography.json | 2 +- .../design-tokens/mode/dark/default.json | 6 +++--- .../design-tokens/mode/light/default.json | 12 ++++++------ .../mode/light/icon-stroke-width.json | 6 +++--- packages/ui/src/components/dialog.tsx | 4 ++-- .../ui/src/components/drawer/DrawerHeader.tsx | 4 ++-- .../ui/src/components/icon-v2/icon-name-map.ts | 4 ++++ .../ui/src/components/icon-v2/icons/connect.svg | 1 + .../src/components/icon-v2/icons/flag-solid.svg | 1 + .../components/icon-v2/icons/folder-minus.svg | 2 +- .../src/components/icon-v2/icons/folder-plus.svg | 2 +- .../components/icon-v2/icons/folder-settings.svg | 2 +- .../components/icon-v2/icons/folder-warning.svg | 2 +- .../ui/src/components/icon-v2/icons/folder.svg | 2 +- .../ui/src/components/icon-v2/icons/sidebar.svg | 2 +- .../icon-v2/icons/warning-triangle-solid.svg | 2 +- packages/ui/src/components/node-group.tsx | 6 +++--- .../pull-request-system-comments.tsx | 8 ++++---- .../components/card-select.ts | 4 ++-- .../tailwind-utils-config/components/dialog.ts | 4 ---- .../tailwind-utils-config/components/drawer.ts | 4 +--- 41 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 packages/ui/src/components/icon-v2/icons/connect.svg create mode 100644 packages/ui/src/components/icon-v2/icons/flag-solid.svg diff --git a/apps/portal/src/content/docs/components/alert-dialog.mdx b/apps/portal/src/content/docs/components/alert-dialog.mdx index af9a2beb39..9ef438a859 100644 --- a/apps/portal/src/content/docs/components/alert-dialog.mdx +++ b/apps/portal/src/content/docs/components/alert-dialog.mdx @@ -22,9 +22,7 @@ import { Aside } from "@astrojs/starlight/components"; </AlertDialog.Trigger> <AlertDialog.Content title="Delete pipeline"> - <div className="space-y-4"> - <p>Are you sure you want to delete this pipeline? This action cannot be undone.</p> - </div> + <Text>Are you sure you want to delete this pipeline? This action cannot be undone.</Text> </AlertDialog.Content> </AlertDialog.Root> diff --git a/packages/core-design-system/design-tokens/$themes.json b/packages/core-design-system/design-tokens/$themes.json index 1373f500fb..a71176cc98 100644 --- a/packages/core-design-system/design-tokens/$themes.json +++ b/packages/core-design-system/design-tokens/$themes.json @@ -24956,6 +24956,7 @@ "sidebar.header.pb": "dd685714f9bf81e8ee56b25ed42aada8210d29e1", "sidebar.header.gap": "3e73971df988bc4709a9b8fa8980c6daee936b31", "sidebar.footer.pt": "bf7fc9113c8ec0482689d927fdc10c6e1e63254f", + "sidebar.footer.pb": "b323133a58c015571502a3033278dd07edbd776b", "sidebar.footer.gap": "b8b62cd2a9444c08200f639d58aabc01b09eb112", "sidebar.sub-item.px": "a00f03298d388a5a3f882408649cc2a591739981", "sidebar.sub-item.py": "074bac9719bce28e4f9ba9f41715a41b042c7d0a", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/alert.json b/packages/core-design-system/design-tokens/components/desktop/base/alert.json index 2e293bf2ac..1012b9d3a3 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/alert.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/alert.json @@ -18,7 +18,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "min-width": { "$type": "sizing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/avatar.json b/packages/core-design-system/design-tokens/components/desktop/base/avatar.json index cdf7e86787..27a473f96b 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/avatar.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/avatar.json @@ -17,7 +17,7 @@ "radius": { "default": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "rounded": { "$type": "borderRadius", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/badge.json b/packages/core-design-system/design-tokens/components/desktop/base/badge.json index 1853fbc1c3..29487f9d8e 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/badge.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/badge.json @@ -26,7 +26,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "border": { "$type": "borderWidth", @@ -70,7 +70,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" } }, "badge-indicator": { diff --git a/packages/core-design-system/design-tokens/components/desktop/base/btn.json b/packages/core-design-system/design-tokens/components/desktop/base/btn.json index 89b1fcc1f7..8930fdb7f9 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/btn.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/btn.json @@ -47,7 +47,7 @@ "default": { "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" } }, "rounded": { diff --git a/packages/core-design-system/design-tokens/components/desktop/base/calendar.json b/packages/core-design-system/design-tokens/components/desktop/base/calendar.json index 87eece7311..8e2ad09c30 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/calendar.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/calendar.json @@ -8,7 +8,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "padding": { "$type": "spacing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/card.json b/packages/core-design-system/design-tokens/components/desktop/base/card.json index 86b94b85a7..fbfe8609f7 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/card.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/card.json @@ -11,7 +11,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.4}" + "$value": "{rounded.5}" } }, "md": { @@ -25,7 +25,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.3}" + "$value": "{rounded.4}" } }, "sm": { @@ -39,7 +39,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.3}" + "$value": "{rounded.4}" } }, "border": { diff --git a/packages/core-design-system/design-tokens/components/desktop/base/dialog.json b/packages/core-design-system/design-tokens/components/desktop/base/dialog.json index bbe1a30c1e..5d5271d3a0 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/dialog.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/dialog.json @@ -22,7 +22,7 @@ }, "py": { "$type": "spacing", - "$value": "{spacing.4}" + "$value": "{spacing.3-half}" }, "border": { "$type": "borderWidth", @@ -30,7 +30,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.4}" + "$value": "{rounded.5}" }, "safezone": { "$type": "spacing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/dropdown.json b/packages/core-design-system/design-tokens/components/desktop/base/dropdown.json index 07a166494a..fb96561916 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/dropdown.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/dropdown.json @@ -15,7 +15,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.1}" + "$value": "{rounded.2}" } }, "container": { @@ -25,7 +25,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.4}" }, "min-width": { "$type": "sizing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/input.json b/packages/core-design-system/design-tokens/components/desktop/base/input.json index a5fe6c24f0..d58807c05e 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/input.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/input.json @@ -14,7 +14,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}", + "$value": "{rounded.3}", "$description": "6px" }, "md": { diff --git a/packages/core-design-system/design-tokens/components/desktop/base/popover.json b/packages/core-design-system/design-tokens/components/desktop/base/popover.json index 4944f8dcf7..cfefcb1a54 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/popover.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/popover.json @@ -2,7 +2,7 @@ "popover": { "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "py": { "$type": "spacing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/sidebar.json b/packages/core-design-system/design-tokens/components/desktop/base/sidebar.json index 294f1f2e70..51aecd457f 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/sidebar.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/sidebar.json @@ -15,17 +15,17 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.4}" }, "min": { "$type": "sizing", - "$value": "{size.8}" + "$value": "{size.9}" } }, "group": { "py": { "$type": "spacing", - "$value": "{spacing.2-half}" + "$value": "{spacing.1-half}" }, "gap": { "$type": "spacing", @@ -35,15 +35,15 @@ "container": { "py": { "$type": "spacing", - "$value": "{spacing.2-half}" + "$value": "{spacing.1-half}" }, "px": { "$type": "spacing", - "$value": "{spacing.2-half}" + "$value": "{spacing.3}" }, "full-width": { "$type": "sizing", - "$value": "{size.56}" + "$value": "{size.58}" } }, "header": { @@ -65,6 +65,10 @@ "$type": "spacing", "$value": "{spacing.1}" }, + "pb": { + "$type": "spacing", + "$value": "{spacing.1-half}" + }, "gap": { "$type": "spacing", "$value": "{spacing.2-half}" diff --git a/packages/core-design-system/design-tokens/components/desktop/base/tabs.json b/packages/core-design-system/design-tokens/components/desktop/base/tabs.json index 4bb92ee097..13b4f48d95 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/tabs.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/tabs.json @@ -27,7 +27,7 @@ }, "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" } }, "item-overlined": { @@ -53,7 +53,7 @@ }, "rt": { "$type": "borderRadius", - "$value": "{rounded.3}" + "$value": "{rounded.4}" }, "rb": { "$type": "borderRadius", @@ -94,7 +94,7 @@ }, "container-radius": { "$type": "borderRadius", - "$value": "{rounded.3}" + "$value": "{rounded.4}" } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/components/desktop/base/tag.json b/packages/core-design-system/design-tokens/components/desktop/base/tag.json index bfda0e14af..ae1146904f 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/tag.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/tag.json @@ -25,7 +25,7 @@ "radius": { "default": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "full": { "$type": "borderRadius", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/toast.json b/packages/core-design-system/design-tokens/components/desktop/base/toast.json index 9abe9ad65a..92e6e8fc05 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/toast.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/toast.json @@ -2,7 +2,7 @@ "toast": { "radius": { "$type": "borderRadius", - "$value": "{rounded.3}" + "$value": "{rounded.4}" }, "gap": { "$type": "spacing", diff --git a/packages/core-design-system/design-tokens/components/desktop/base/tooltip.json b/packages/core-design-system/design-tokens/components/desktop/base/tooltip.json index 5f13a522dd..8c86ce4e09 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/tooltip.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/tooltip.json @@ -2,7 +2,7 @@ "tooltip": { "radius": { "$type": "borderRadius", - "$value": "{rounded.2}" + "$value": "{rounded.3}" }, "py": { "$type": "spacing", diff --git a/packages/core-design-system/design-tokens/core/colors_hex.json b/packages/core-design-system/design-tokens/core/colors_hex.json index f5a68791f5..5d4a9b1932 100644 --- a/packages/core-design-system/design-tokens/core/colors_hex.json +++ b/packages/core-design-system/design-tokens/core/colors_hex.json @@ -229,10 +229,10 @@ "$value": "#d02f2f" }, "700": { - "$value": "#b42727" + "$value": "#be292a" }, "800": { - "$value": "#9a2020" + "$value": "#9d2624" }, "850": { "$value": "#6f2a2a" diff --git a/packages/core-design-system/design-tokens/core/colors_lch.json b/packages/core-design-system/design-tokens/core/colors_lch.json index edd3cb53ee..5b582468bb 100644 --- a/packages/core-design-system/design-tokens/core/colors_lch.json +++ b/packages/core-design-system/design-tokens/core/colors_lch.json @@ -134,10 +134,10 @@ "$value": "lch(47.29% 74.8 33.74)" }, "700": { - "$value": "lch(40.79% 67.3 33.8)" + "$value": "lch(43% 70 33.8)" }, "800": { - "$value": "lch(34.63% 59.93 33.77)" + "$value": "lch(36% 59 33.77)" }, "850": { "$value": "lch(27.67% 35.3 27.82)" diff --git a/packages/core-design-system/design-tokens/core/dimensions.json b/packages/core-design-system/design-tokens/core/dimensions.json index 52fd6dd4d8..9977e125da 100644 --- a/packages/core-design-system/design-tokens/core/dimensions.json +++ b/packages/core-design-system/design-tokens/core/dimensions.json @@ -315,6 +315,10 @@ "$type": "sizing", "$value": "224px" }, + "58": { + "$type": "sizing", + "$value": "230px" + }, "60": { "$type": "sizing", "$value": "240px" diff --git a/packages/core-design-system/design-tokens/core/typography.json b/packages/core-design-system/design-tokens/core/typography.json index 172ce3295e..6c35e8b6ca 100644 --- a/packages/core-design-system/design-tokens/core/typography.json +++ b/packages/core-design-system/design-tokens/core/typography.json @@ -44,7 +44,7 @@ }, "300": { "$type": "fontWeights", - "$value": "Light" + "$value": "350" }, "400": { "$type": "fontWeights", diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index 226ca7993d..edd2ab4484 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -2221,11 +2221,11 @@ "card": { "from": { "$type": "color", - "$value": "{chrome.950}" + "$value": "{chrome.1000}" }, "to": { "$type": "color", - "$value": "{blue.1000}" + "$value": "{blue.950}" } }, "dialog": { @@ -2790,7 +2790,7 @@ "icon": { "devops": { "$type": "color", - "$value": "{lime.200}" + "$value": "{mint.200}" }, "testing": { "$type": "color", diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index c1023c177e..a2ef19a33a 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -29,7 +29,7 @@ }, "2": { "$type": "color", - "$value": "{tuna.800}", + "$value": "{tuna.850}", "$description": "Primary color for text and icons in general content. Provides optimal readability for extended reading while reducing visual strain.\n\nCommon uses: Body text, primary content, default icons, form inputs." }, "3": { @@ -1556,7 +1556,7 @@ "track": { "base": { "$type": "color", - "$value": "{chrome.150}", + "$value": "{tuna.50}", "$description": "Background color for slider tracks. Represents the full range of available values in an unselected state." }, "progress": { @@ -2239,11 +2239,11 @@ "card": { "from": { "$type": "color", - "$value": "{blue.25}" + "$value": "{blue.50}" }, "to": { "$type": "color", - "$value": "{blue.50}" + "$value": "{blue.25}" } }, "dialog": { @@ -2808,7 +2808,7 @@ "icon": { "devops": { "$type": "color", - "$value": "{lime.500}" + "$value": "{mint.400}" }, "testing": { "$type": "color", @@ -2824,7 +2824,7 @@ }, "platform": { "$type": "color", - "$value": "{purple.500}" + "$value": "{purple.400}" } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json b/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json index fe467e1a16..6a7f363b36 100644 --- a/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json +++ b/packages/core-design-system/design-tokens/mode/light/icon-stroke-width.json @@ -6,15 +6,15 @@ }, "xs": { "$type": "borderWidth", - "$value": "1.3" + "$value": "1.22" }, "sm": { "$type": "borderWidth", - "$value": "1.3" + "$value": "1.2" }, "md": { "$type": "borderWidth", - "$value": "1.1" + "$value": "1.2" }, "lg": { "$type": "borderWidth", diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index 20e26ff055..fade93d072 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -112,12 +112,12 @@ const Header = ({ className, icon, logo, theme = 'default', children, ...props } <div className="cn-modal-dialog-header-title-row"> {icon && ( <div className="cn-modal-dialog-header-icon"> - <IconV2 name={icon} size="lg" /> + <IconV2 name={icon} size="xl" /> </div> )} {logo && ( <div className="cn-modal-dialog-header-logo"> - <LogoV2 name={logo} /> + <LogoV2 name={logo} size="md" /> </div> )} {title} diff --git a/packages/ui/src/components/drawer/DrawerHeader.tsx b/packages/ui/src/components/drawer/DrawerHeader.tsx index ab2b73c4fe..c9995adb14 100644 --- a/packages/ui/src/components/drawer/DrawerHeader.tsx +++ b/packages/ui/src/components/drawer/DrawerHeader.tsx @@ -30,8 +30,8 @@ export type DrawerHeaderProps = DrawerHeaderBaseProps & export const DrawerHeader = ({ className, children, icon, logo, ...props }: DrawerHeaderProps) => { const IconOrLogoComp = - (!!icon && <IconV2 className="cn-drawer-header-icon cn-drawer-header-icon-color" name={icon} skipSize />) || - (!!logo && <LogoV2 className="cn-drawer-header-icon" name={logo} />) || + (!!icon && <IconV2 className="cn-drawer-header-icon cn-drawer-header-icon-color" name={icon} size="xl" />) || + (!!logo && <LogoV2 className="cn-drawer-header-icon" name={logo} size="md" />) || null const { titleChildren, otherChildren } = Children.toArray(children).reduce<{ diff --git a/packages/ui/src/components/icon-v2/icon-name-map.ts b/packages/ui/src/components/icon-v2/icon-name-map.ts index de4e667ea7..70f8e622cc 100644 --- a/packages/ui/src/components/icon-v2/icon-name-map.ts +++ b/packages/ui/src/components/icon-v2/icon-name-map.ts @@ -113,6 +113,7 @@ import CollapseCode from './icons/collapse-code.svg' import CollapseSidebar from './icons/collapse-sidebar.svg' import Collapse from './icons/collapse.svg' import Community from './icons/community.svg' +import Connect from './icons/connect.svg' import ConnectorsSolid from './icons/connectors-solid.svg' import Connectors from './icons/connectors.svg' import ControlSlider from './icons/control-slider.svg' @@ -185,6 +186,7 @@ import FilterSolid from './icons/filter-solid.svg' import Filter from './icons/filter.svg' import Finder from './icons/finder.svg' import FingerprintWindow from './icons/fingerprint-window.svg' +import FlagSolid from './icons/flag-solid.svg' import FlashSolid from './icons/flash-solid.svg' import Flash from './icons/flash.svg' import FolderMinus from './icons/folder-minus.svg' @@ -590,6 +592,7 @@ export const IconNameMapV2 = { 'collapse-sidebar': CollapseSidebar, collapse: Collapse, community: Community, + connect: Connect, 'connectors-solid': ConnectorsSolid, connectors: Connectors, 'control-slider': ControlSlider, @@ -662,6 +665,7 @@ export const IconNameMapV2 = { filter: Filter, finder: Finder, 'fingerprint-window': FingerprintWindow, + 'flag-solid': FlagSolid, 'flash-solid': FlashSolid, flash: Flash, 'folder-minus': FolderMinus, diff --git a/packages/ui/src/components/icon-v2/icons/connect.svg b/packages/ui/src/components/icon-v2/icons/connect.svg new file mode 100644 index 0000000000..09af32dd7d --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/connect.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m2 14 1.62-1.621M6.8 7.6 5.6 8.8m2.8.4-1.2 1.2M14 2l-1.62 1.62m0 0a2.8 2.8 0 0 1 0 3.96l-.78.82-3.98-3.98.8-.8a2.8 2.8 0 0 1 3.96 0m-8.76 4.8a2.8 2.8 0 0 0 3.96 3.96l.8-.8L4.4 7.6z"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/flag-solid.svg b/packages/ui/src/components/icon-v2/icons/flag-solid.svg new file mode 100644 index 0000000000..b664cc920c --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/flag-solid.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" d="m2.435 9.765.69-7.38A.43.43 0 0 1 3.558 2h10.007c.256 0 .456.214.433.462l-.646 6.918a.43.43 0 0 1-.434.385zm0 0L2 14z"/><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m2.435 9.765.69-7.38A.43.43 0 0 1 3.558 2h10.007c.256 0 .456.214.433.462l-.646 6.918a.43.43 0 0 1-.434.385zm0 0L2 14"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-minus.svg b/packages/ui/src/components/icon-v2/icons/folder-minus.svg index 8db71dac8c..c4b9f92ce7 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-minus.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-minus.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 4.4H14m-12 3v5.04c0 .199.161.36.36.36h11.28a.36.36 0 0 0 .36-.36V7.76a.36.36 0 0 0-.36-.36zm0 0V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5H9.2"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 4.4H14m-.36 3a.36.36 0 0 1 .36.36v4.68a.36.36 0 0 1-.36.36H2.36a.36.36 0 0 1-.36-.36V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5H9.2"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-plus.svg b/packages/ui/src/components/icon-v2/icons/folder-plus.svg index 10114dde86..82f0ed94c7 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-plus.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-plus.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 4.4h1.2m0 0H14m-1.2 0V3.2m0 1.2v1.2M2 7.4v5.04c0 .199.161.36.36.36h11.28a.36.36 0 0 0 .36-.36V7.76a.36.36 0 0 0-.36-.36zm0 0V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5H9.2"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 4.4h1.2m0 0H14m-1.2 0V3.2m0 1.2v1.2m.84 1.8a.36.36 0 0 1 .36.36v4.68a.36.36 0 0 1-.36.36H2.36a.36.36 0 0 1-.36-.36V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5H9.2"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-settings.svg b/packages/ui/src/components/icon-v2/icons/folder-settings.svg index 07a57135df..b0a674d9de 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-settings.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-settings.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14 6.2v2.4M2 6.2v5.64c0 .199.161.36.36.36H8.6M2.36 2.6h3.707a.36.36 0 0 1 .234.087L8.2 4.313a.36.36 0 0 0 .234.087h5.207a.36.36 0 0 1 .36.36v1.68a.36.36 0 0 1-.36.36H2.36A.36.36 0 0 1 2 6.44V2.96a.36.36 0 0 1 .36-.36m10.938 8.9q0 .073-.011.146l.326.248a.074.074 0 0 1 .018.096l-.309.52a.08.08 0 0 1-.095.032l-.324-.127a.12.12 0 0 0-.109.012q-.075.05-.155.088a.11.11 0 0 0-.064.085l-.048.336a.08.08 0 0 1-.027.045.1.1 0 0 1-.05.019h-.617a.1.1 0 0 1-.05-.018.08.08 0 0 1-.027-.044l-.049-.335a.11.11 0 0 0-.065-.086 1 1 0 0 1-.154-.088.12.12 0 0 0-.11-.012l-.323.127a.1.1 0 0 1-.053 0 .08.08 0 0 1-.042-.033l-.308-.518a.074.074 0 0 1 .017-.097l.276-.21a.11.11 0 0 0 .043-.099 1 1 0 0 1 0-.174.11.11 0 0 0-.043-.098l-.276-.21a.074.074 0 0 1-.017-.096l.308-.518a.08.08 0 0 1 .095-.032l.324.126a.12.12 0 0 0 .11-.012q.073-.05.155-.088a.11.11 0 0 0 .063-.085l.049-.336a.08.08 0 0 1 .026-.045.1.1 0 0 1 .05-.019h.618q.027 0 .05.018a.08.08 0 0 1 .027.044l.048.335a.115.115 0 0 0 .065.086q.081.038.155.088a.12.12 0 0 0 .109.012l.323-.127a.1.1 0 0 1 .054 0 .08.08 0 0 1 .041.033l.309.518a.074.074 0 0 1-.018.097l-.276.21a.11.11 0 0 0-.044.099q.005.043.005.087"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M13.64 6.8a.36.36 0 0 0 .36-.36V4.76a.36.36 0 0 0-.36-.36H8.433a.36.36 0 0 1-.234-.087L6.3 2.687a.36.36 0 0 0-.234-.087H2.36a.36.36 0 0 0-.36.36v3.48c0 .199.161.36.36.36M14 6.2v2.4M2 6.2v5.64c0 .199.161.36.36.36H8.6m4.698-.7q0 .073-.011.146l.326.248a.074.074 0 0 1 .018.096l-.309.52a.08.08 0 0 1-.095.032l-.324-.127a.12.12 0 0 0-.109.012q-.075.05-.155.088a.11.11 0 0 0-.064.085l-.048.336a.08.08 0 0 1-.027.045.1.1 0 0 1-.05.019h-.617a.1.1 0 0 1-.05-.018.08.08 0 0 1-.027-.044l-.049-.335a.11.11 0 0 0-.065-.086 1 1 0 0 1-.154-.088.12.12 0 0 0-.11-.012l-.323.127a.1.1 0 0 1-.053 0 .08.08 0 0 1-.042-.033l-.308-.518a.074.074 0 0 1 .017-.097l.276-.21a.11.11 0 0 0 .043-.099 1 1 0 0 1 0-.174.11.11 0 0 0-.043-.098l-.276-.21a.074.074 0 0 1-.017-.096l.308-.518a.08.08 0 0 1 .095-.032l.324.126a.12.12 0 0 0 .11-.012q.073-.05.155-.088a.11.11 0 0 0 .063-.085l.049-.336a.08.08 0 0 1 .026-.045.1.1 0 0 1 .05-.019h.618q.027 0 .05.018a.08.08 0 0 1 .027.044l.048.335a.115.115 0 0 0 .065.086q.081.038.155.088a.12.12 0 0 0 .109.012l.323-.127a.1.1 0 0 1 .054 0 .08.08 0 0 1 .041.033l.309.518a.074.074 0 0 1-.018.097l-.276.21a.11.11 0 0 0-.044.099q.005.043.005.087"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-warning.svg b/packages/ui/src/components/icon-v2/icons/folder-warning.svg index 9e2c4b4558..f5dab0a75e 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-warning.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-warning.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 2.9v2.4m0 2.406.006-.007M14 5.3v7.44a.36.36 0 0 1-.36.36H2.36a.36.36 0 0 1-.36-.36V7.7m0 0V3.86a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 5.213a.36.36 0 0 0 .234.087H9.2M2 7.7h7.2"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M11.6 2.9v2.4m0 2.406.006-.007M14 5.3v7.44a.36.36 0 0 1-.36.36H2.36a.36.36 0 0 1-.36-.36V3.86a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 5.213a.36.36 0 0 0 .234.087H9.2"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder.svg b/packages/ui/src/components/icon-v2/icons/folder.svg index 2f924ddc82..71e387d1db 100644 --- a/packages/ui/src/components/icon-v2/icons/folder.svg +++ b/packages/ui/src/components/icon-v2/icons/folder.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M2 7.4V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5h5.207a.36.36 0 0 1 .36.36V7.4m-12 0v5.04c0 .199.161.36.36.36h11.28a.36.36 0 0 0 .36-.36V7.4m-12 0h12"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M2 7.4V3.56a.36.36 0 0 1 .36-.36h3.707a.36.36 0 0 1 .234.087L8.2 4.913A.36.36 0 0 0 8.433 5h5.207a.36.36 0 0 1 .36.36V7.4m-12 0v5.04c0 .199.161.36.36.36h11.28a.36.36 0 0 0 .36-.36V7.4"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/sidebar.svg b/packages/ui/src/components/icon-v2/icons/sidebar.svg index c92a34a473..6476611df5 100644 --- a/packages/ui/src/components/icon-v2/icons/sidebar.svg +++ b/packages/ui/src/components/icon-v2/icons/sidebar.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6 1.75v12m-2.667-12h9.334c.736 0 1.333.597 1.333 1.333v9.334c0 .736-.597 1.333-1.333 1.333H3.333A1.333 1.333 0 0 1 2 12.417V3.083c0-.736.597-1.333 1.333-1.333"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6.5 2v3.75m0 8.25V9.875m-4-4.125V3.333C2.5 2.597 3.097 2 3.833 2h9.334c.736 0 1.333.597 1.333 1.333v9.334c0 .736-.597 1.333-1.333 1.333H3.833A1.333 1.333 0 0 1 2.5 12.667V9.875m0-4.125h4m-4 0v4.125m4-4.125v4.125m0 0h-4"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/warning-triangle-solid.svg b/packages/ui/src/components/icon-v2/icons/warning-triangle-solid.svg index 4ea443c97a..9026916abb 100644 --- a/packages/ui/src/components/icon-v2/icons/warning-triangle-solid.svg +++ b/packages/ui/src/components/icon-v2/icons/warning-triangle-solid.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M6.965 3.599a1.194 1.194 0 0 1 2.07 0l4.804 8.354a1.194 1.194 0 0 1-1.036 1.79H3.196a1.194 1.194 0 0 1-1.035-1.79zm1.376 7.383a.5.5 0 0 0-.707.037l-.005.006a.5.5 0 0 0 .668.738l.074-.069.007-.006a.5.5 0 0 0-.037-.706M8 6.076a.5.5 0 0 0-.5.5v2.389a.5.5 0 0 0 1 0V6.576a.5.5 0 0 0-.5-.5" clip-rule="evenodd"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M6.965 3.227a1.194 1.194 0 0 1 2.07 0l4.804 8.355a1.194 1.194 0 0 1-1.035 1.79H3.196a1.194 1.194 0 0 1-1.035-1.79zm1.376 7.384a.5.5 0 0 0-.706.036l-.006.007a.5.5 0 0 0 .668.737l.074-.068.007-.006a.5.5 0 0 0-.037-.706M8 5.705a.5.5 0 0 0-.5.5v2.388a.5.5 0 0 0 1 0V6.205a.5.5 0 0 0-.5-.5" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/packages/ui/src/components/node-group.tsx b/packages/ui/src/components/node-group.tsx index e8fbdf119e..890ca6d374 100644 --- a/packages/ui/src/components/node-group.tsx +++ b/packages/ui/src/components/node-group.tsx @@ -33,14 +33,14 @@ function Icon({ <div className={cn( { - 'text-icons-8 shadow-commit-list-bullet': simpleNodeIcon, - 'border-cn-borders-4 bg-cn-background-2 text-icons-8 relative flex h-6 w-6 place-content-center place-items-center rounded-full border p-1 layer-medium': + 'text-cn-foreground-1 shadow-commit-list-bullet': simpleNodeIcon, + 'border-cn-borders-3 bg-cn-background-2 text-cn-foreground-1 relative flex h-6 w-6 place-content-center place-items-center rounded-full border p-1 layer-medium': !simpleNodeIcon }, className )} > - {simpleNodeIcon ? <IconV2 name="circle" size="2xs" /> : <>{children}</>} + {simpleNodeIcon ? <IconV2 name="circle" size="xs" /> : <>{children}</>} </div> </div> ) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx index b57611e86a..e31c696d6b 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-system-comments.tsx @@ -284,7 +284,7 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ </> ) }, - icon: <IconV2 name="edit-pencil" size="xs" className="p-0.5" /> + icon: <IconV2 name="edit-pencil" size="xs" /> } case CommentType.REVIEW_DELETE: { @@ -311,7 +311,7 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ </> ) }, - icon: <IconV2 name="edit-pencil" size="xs" className="p-0.5" /> + icon: <IconV2 name="edit-pencil" size="xs" /> } } @@ -341,7 +341,7 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ </Text> ) }, - icon: <IconV2 name="eye" size="xs" className="p-0.5" /> + icon: <IconV2 name="eye" size="xs" /> } } @@ -370,7 +370,7 @@ const PullRequestSystemComments: FC<SystemCommentProps> = ({ </> ) }, - icon: <IconV2 name="edit-pencil" size="xs" className="p-0.5" /> + icon: <IconV2 name="edit-pencil" size="xs" /> } } diff --git a/packages/ui/tailwind-utils-config/components/card-select.ts b/packages/ui/tailwind-utils-config/components/card-select.ts index c4857f22e3..e8d4ec3edb 100644 --- a/packages/ui/tailwind-utils-config/components/card-select.ts +++ b/packages/ui/tailwind-utils-config/components/card-select.ts @@ -41,7 +41,7 @@ export default { gap: 'var(--cn-card-check-md-gap)', '&:hover:not([data-disabled])': { - borderColor: 'var(--cn-border-1)' + borderColor: 'var(--cn-border-brand)' }, '&:where([data-disabled])': { @@ -50,7 +50,7 @@ export default { }, '&:where([data-state="checked"])': { - borderColor: 'var(--cn-border-accent)', + borderColor: 'var(--cn-border-brand)', backgroundImage: 'var(--cn-comp-card-gradient)' }, diff --git a/packages/ui/tailwind-utils-config/components/dialog.ts b/packages/ui/tailwind-utils-config/components/dialog.ts index d3ecab8187..296a98bbba 100644 --- a/packages/ui/tailwind-utils-config/components/dialog.ts +++ b/packages/ui/tailwind-utils-config/components/dialog.ts @@ -80,14 +80,10 @@ export default { '.cn-modal-dialog-header-icon': { '@apply flex items-center justify-center': '', color: 'var(--cn-text-2)', - width: `var(--cn-icon-size-lg)`, - height: `var(--cn-icon-size-lg)`, flexShrink: '0' }, '.cn-modal-dialog-header-logo': { '@apply flex items-center justify-center': '', - width: `var(--cn-icon-size-lg)`, - height: `var(--cn-icon-size-lg)`, flexShrink: '0' }, '.cn-modal-dialog-title': { diff --git a/packages/ui/tailwind-utils-config/components/drawer.ts b/packages/ui/tailwind-utils-config/components/drawer.ts index d72519ef9d..94ef1b3170 100644 --- a/packages/ui/tailwind-utils-config/components/drawer.ts +++ b/packages/ui/tailwind-utils-config/components/drawer.ts @@ -2,7 +2,7 @@ export default { '.cn-drawer': { '&-content': { userSelect: 'auto !important', - backgroundColor: 'var(--cn-bg-2)', + backgroundColor: 'var(--cn-bg-1)', borderColor: 'var(--cn-border-3)', borderRadius: 'var(--cn-drawer-radius)', boxShadow: 'var(--cn-shadow-5)', @@ -91,8 +91,6 @@ export default { '&-icon': { flexShrink: '0', - width: 'var(--cn-icon-size-lg)', - height: 'var(--cn-icon-size-lg)' }, '&-icon-color': { From 3f142e135347881cf9fe3f249da9109652def2ee Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Thu, 14 Aug 2025 18:02:09 +0000 Subject: [PATCH 098/180] fix: ux issues (#10207) * 822e54 fix: ux issues --- packages/ui/locales/en/views.json | 2 +- .../components/pull-request-item-description.tsx | 5 ----- .../repo/pull-request/components/pull-request-list.tsx | 2 +- .../src/views/repo/pull-request/pull-request-list-page.tsx | 7 +++---- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 3d03c94259..ac0a72fb99 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -409,7 +409,7 @@ "createPullRequest": "Create Pull Request" }, "clearFilters": "Clear Filters", - "noPullRequestsInRepo": "Start your contribution journey by creating a new pull request draft.", + "noPullRequestsInRepo": "Start your contribution journey by creating a new pull request.", "noPullRequestsInProject": "There are no pull requests in this project yet.", "noBranches": "No branches yet", "createBranchDescription": "Your branches will appear here once they're created.", diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx index 4236aedc37..b464f65e6c 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-item-description.tsx @@ -14,7 +14,6 @@ interface PullRequestItemDescriptionProps { } export const PullRequestItemDescription: FC<PullRequestItemDescriptionProps> = ({ - reviewRequired, number, tasks, author, @@ -40,10 +39,6 @@ export const PullRequestItemDescription: FC<PullRequestItemDescriptionProps> = ( <span className="inline-block max-w-[200px] truncate align-text-bottom">{author}</span> </Text> - <Separator orientation="vertical" className="h-3.5" /> - - <Text variant="body-single-line-normal">{reviewRequired ? 'Review required' : 'Draft'}</Text> - {/* TODO: where did tasks go? */} {!!tasks && tasks > 0 && ( <div className="flex items-center gap-0.5"> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx index 38d8ad0593..52768e1a7f 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-list.tsx @@ -100,7 +100,7 @@ const EmptyStateView: FC<EmptyStateProps> = ({ /> </StackedList.Item> <NoData - imageName="no-data-folder" + imageName="no-data-pr" title={title} description={description} primaryButton={ diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index 74948f6770..bdf9cd96cf 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -274,16 +274,15 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ </StackedList.Root> ) : ( <NoData - imageName="no-data-folder" + imageName="no-data-pr" title="No pull requests yet" description={ repoId ? [ t( 'views:noData.noPullRequestsInRepo', - `Start your contribution journey by creating a new pull request draft.` - ), - t('views:repos.createPullReq', 'Create Pull request.') + `Start your contribution journey by creating a new pull request.` + ) ] : [t('views:noData.noPullRequestsInProject', `There are no pull requests in this project yet.`)] } From d10f269f0f7848dd78451f9a1611588617722030 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Thu, 14 Aug 2025 20:06:07 +0000 Subject: [PATCH 099/180] add recursive query param to space level rule and tag status check queries (#10214) * 996db8 Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into add-recursive-query-param-to-space-level-status-check-query * 503f89 add recursive query param to space level rule and tag status check queries --- .../project/rules/project-branch-rules-container.tsx | 5 ++++- .../pages-v2/project/rules/project-tag-rules-container.tsx | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx b/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx index 203318dccd..0099585ff5 100644 --- a/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx +++ b/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx @@ -127,9 +127,12 @@ export const ProjectBranchRulesContainer = () => { } } ) + const { data: { body: recentStatusChecks } = {}, error: statusChecksError } = useListStatusCheckRecentSpaceQuery({ space_ref: `${spaceURL}/+`, - queryParams: {} + queryParams: { + recursive: true + } }) const handleRuleUpdate = (data: RepoBranchSettingsFormFields) => { diff --git a/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx b/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx index 2b11e560c6..7541c2d058 100644 --- a/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx +++ b/apps/gitness/src/pages-v2/project/rules/project-tag-rules-container.tsx @@ -121,7 +121,9 @@ export const ProjectTagRulesContainer = () => { const { data: { body: recentStatusChecks } = {}, error: statusChecksError } = useListStatusCheckRecentSpaceQuery({ space_ref: `${spaceRef}/+`, - queryParams: {} + queryParams: { + recursive: true + } }) const { From bd60b73b764896225b3fa48d9474ab6c2dfcee88 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Thu, 14 Aug 2025 20:06:53 +0000 Subject: [PATCH 100/180] Create branch dialogue should stay when api fails (#10213) * 79595e Fixed warning alert text for create by pass branch * 604f03 Fixed lint error * 7bceb1 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * f502a6 Create branch dialogue close fix --- .../components-v2/create-branch-dialog.tsx | 69 +++++++++++-------- .../framework/hooks/useRuleViolationCheck.ts | 44 +++++++++--- .../components/create-branch-dialog.tsx | 62 ++++++++--------- 3 files changed, 101 insertions(+), 74 deletions(-) diff --git a/apps/gitness/src/components-v2/create-branch-dialog.tsx b/apps/gitness/src/components-v2/create-branch-dialog.tsx index c7b236a50b..7abc1f1fe8 100644 --- a/apps/gitness/src/components-v2/create-branch-dialog.tsx +++ b/apps/gitness/src/components-v2/create-branch-dialog.tsx @@ -36,52 +36,61 @@ export const CreateBranchDialog = ({ const [selectedBranchOrTag, setSelectedBranchOrTag] = useState<BranchSelectorListItem | null>(null) const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() - const selectBranchOrTag = useCallback((branchTagName: BranchSelectorListItem) => { + const selectBranchOrTag = useCallback((branchTagName: BranchSelectorListItem, _type: BranchSelectorTab) => { setSelectedBranchOrTag(branchTagName) }, []) + const handleMutationSuccess = useCallback(() => { + setError(undefined) + resetViolation() + onClose() + onSuccess?.() + }, [onClose, onSuccess, resetViolation]) + + const handleMutationError = useCallback( + (err: any) => { + if (err?.violations?.length > 0) { + setAllStates({ + violation: true, + bypassed: true, + bypassable: err?.violations[0]?.bypassable + }) + } else { + setError(err as UsererrorError) + } + }, + [setAllStates] + ) + const { mutateAsync: createBranch, isLoading: isCreatingBranch, reset: resetBranchMutation - } = useCreateBranchMutation( - {}, - { - onSuccess: () => { - onClose() - onSuccess?.() - } - } - ) + } = useCreateBranchMutation({}, { onSuccess: handleMutationSuccess, onError: handleMutationError }) const handleCreateBranch = async (data: CreateBranchFormFields) => { onBranchQueryChange?.(data.name) - try { - await createBranch({ - repo_ref, - body: { - ...data, - bypass_rules: bypassed - } - }) - } catch (_error: any) { - if (_error?.violations?.length > 0) { - setAllStates({ - violation: true, - bypassed: true, - bypassable: _error?.violations[0]?.bypassable - }) - } else { - setError(_error as UsererrorError) + setError(undefined) + resetViolation() + await createBranch({ + repo_ref, + body: { + ...data, + bypass_rules: bypassed } - } + }) } useEffect(() => { - if (!open) { + if (open) { + setError(undefined) + resetViolation() + } else { resetBranchMutation() + setError(undefined) + resetViolation() } - }, [open, resetBranchMutation]) + }, [open]) useEffect(() => { if (preselectedBranchOrTag) { diff --git a/apps/gitness/src/framework/hooks/useRuleViolationCheck.ts b/apps/gitness/src/framework/hooks/useRuleViolationCheck.ts index 924ce49f34..08e71a7718 100644 --- a/apps/gitness/src/framework/hooks/useRuleViolationCheck.ts +++ b/apps/gitness/src/framework/hooks/useRuleViolationCheck.ts @@ -1,4 +1,4 @@ -import { useReducer } from 'react' +import { useCallback, useReducer } from 'react' enum ActionTypes { SET_VIOLATION = 'SET_VIOLATION', @@ -30,9 +30,9 @@ const reducer = (state: ViolationState, action: ViolationAction): ViolationState case ActionTypes.SET_VIOLATION: return { ...state, violation: action.payload } case ActionTypes.SET_BYPASSABLE: - return { ...state, bypassed: action.payload } - case ActionTypes.SET_BYPASSED: return { ...state, bypassable: action.payload } + case ActionTypes.SET_BYPASSED: + return { ...state, bypassed: action.payload } case ActionTypes.SET_ALL_STATES: return { ...state, @@ -47,13 +47,37 @@ const reducer = (state: ViolationState, action: ViolationAction): ViolationState export const useRuleViolationCheck = () => { const [state, dispatch] = useReducer(reducer, initialState) - const setViolation = (value: boolean) => dispatch({ type: ActionTypes.SET_VIOLATION, payload: value }) - const setBypassable = (value: boolean) => dispatch({ type: ActionTypes.SET_BYPASSABLE, payload: value }) - const setBypassed = (value: boolean) => dispatch({ type: ActionTypes.SET_BYPASSED, payload: value }) - const setAllStates = (payload: Partial<ViolationState>) => { - dispatch({ type: ActionTypes.SET_ALL_STATES, payload }) - } - const resetViolation = () => setAllStates({ violation: false, bypassable: false, bypassed: false }) + const setViolation = useCallback( + (value: boolean) => { + dispatch({ type: ActionTypes.SET_VIOLATION, payload: value }) + }, + [dispatch] + ) + + const setBypassable = useCallback( + (value: boolean) => { + dispatch({ type: ActionTypes.SET_BYPASSABLE, payload: value }) + }, + [dispatch] + ) + + const setBypassed = useCallback( + (value: boolean) => { + dispatch({ type: ActionTypes.SET_BYPASSED, payload: value }) + }, + [dispatch] + ) + + const setAllStates = useCallback( + (payload: Partial<ViolationState>) => { + dispatch({ type: ActionTypes.SET_ALL_STATES, payload }) + }, + [dispatch] + ) + + const resetViolation = useCallback(() => { + dispatch({ type: ActionTypes.SET_ALL_STATES, payload: { violation: false, bypassable: false, bypassed: false } }) + }, [dispatch]) return { violation: state.violation, diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index d38edc2902..84af7385c9 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -1,18 +1,7 @@ import { useCallback, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { - Alert, - Button, - ButtonLayout, - ControlGroup, - Dialog, - FormInput, - FormWrapper, - Label, - Message, - MessageTheme -} from '@/components' +import { Alert, Button, ButtonLayout, ControlGroup, Dialog, FormInput, FormWrapper, Label } from '@/components' import { TFunctionWithFallback, useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -67,20 +56,23 @@ export function CreateBranchDialog({ }, [clearErrors, reset]) const handleFormSubmit = async (data: CreateBranchFormFields) => { - await onSubmit(data) - - if (!error && !isCreatingBranch) { - handleClose() + try { + await onSubmit(data) + } catch { + // Parent handles error display; avoid unhandled promise rejection } } useEffect(() => { - reset({ - name: prefilledName || '', - target: selectedBranchOrTag?.name - }) - resetViolation() - }, [open, prefilledName, reset]) + if (open) { + // Reset form when dialog opens + reset({ + name: prefilledName || '', + target: selectedBranchOrTag?.name + }) + resetViolation() + } + }, [open, prefilledName, selectedBranchOrTag, reset, resetViolation]) useEffect(() => { if (selectedBranchOrTag?.name) { @@ -92,7 +84,7 @@ export function CreateBranchDialog({ useEffect(() => { resetViolation() - }, [branchName]) + }, [branchName, resetViolation]) const handleClose = () => { resetForm() @@ -122,17 +114,19 @@ export function CreateBranchDialog({ </ControlGroup> {violation && ( - <Message theme={MessageTheme.ERROR}> - {bypassable - ? t( - 'component:branchDialog.violationMessages.bypassed', - 'Some rules will be bypassed while creating branch' - ) - : t( - 'component:branchDialog.violationMessages.notAllow', - "Some rules don't allow you to create branch" - )} - </Message> + <Alert.Root theme="warning"> + <Alert.Description> + {bypassable + ? t( + 'component:branchDialog.violationMessages.bypassed', + 'Some rules will be bypassed while creating branch' + ) + : t( + 'component:branchDialog.violationMessages.notAllow', + "Some rules don't allow you to create branch" + )} + </Alert.Description> + </Alert.Root> )} {error && ( From c7777bcf7a847348b23222d05ef5921a057feac6 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Thu, 14 Aug 2025 23:00:03 +0000 Subject: [PATCH 101/180] fix: multiple parity items (#10215) * d0d389 fix: pr checks * 77736c feat: CTA on unresolved comments check * 8df8a5 feat: pr commit list links to pr commit changes * a48900 feat: show git lfs tag when applicable in file viewer * 08f9af fix: reop tag link in search results --- .../pull-request-conversation.tsx | 1 + .../views/search-page/search-page-preview.tsx | 1 + .../src/components-v2/file-content-viewer.tsx | 2 + .../pull-request/pull-request-commits.tsx | 4 +- .../pull-request-conversation.tsx | 26 ++++++++++- apps/gitness/src/pages-v2/search-page.tsx | 1 + .../file-viewer-control-bar.tsx | 4 ++ .../conversation/pull-request-panel.tsx | 9 +++- .../sections/pull-request-comment-section.tsx | 46 +++++++++---------- .../search/components/search-results-list.tsx | 16 +++++-- packages/ui/src/views/search/search-page.tsx | 10 ++-- 11 files changed, 85 insertions(+), 35 deletions(-) diff --git a/apps/design-system/src/subjects/views/pull-request-conversation/pull-request-conversation.tsx b/apps/design-system/src/subjects/views/pull-request-conversation/pull-request-conversation.tsx index 3f515114b4..92081068f4 100644 --- a/apps/design-system/src/subjects/views/pull-request-conversation/pull-request-conversation.tsx +++ b/apps/design-system/src/subjects/views/pull-request-conversation/pull-request-conversation.tsx @@ -98,6 +98,7 @@ const PullRequestConversation: FC<PullRequestConversationProps> = ({ state }) => panelProps={{ handleRebaseBranch, handlePrState, + handleViewUnresolvedComments: noop, changesInfo: { header: changesInfo.title, content: changesInfo.statusMessage, diff --git a/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx b/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx index 32574ba6fb..c97fec544e 100644 --- a/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx +++ b/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx @@ -13,6 +13,7 @@ export const SearchPagePreview = () => { return ( <SearchPageView isLoading={false} + toRepo={() => '#'} searchQuery={searchQuery} regexEnabled={regexEnabled} setRegexEnabled={setRegexEnabled} diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index c8746c0cfa..20532adf9c 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -52,6 +52,7 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV const fileName = repoContent?.name || '' const language = filenameToLanguage(fileName) || '' const fileContent = decodeGitContent(repoContent?.content?.data) + const isGitLfsObject = repoContent?.content?.lfs_object_id !== undefined const repoRef = useGetRepoRef() const { fullGitRef, fullResourcePath } = useCodePathDetails() const downloadFile = useDownloadRawFile() @@ -193,6 +194,7 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV handleEditFile={handleEditFile} handleOpenDeleteDialog={() => handleToggleDeleteDialog(true)} refType={selectedRefType} + isGitLfsObject={isGitLfsObject} /> <Tabs.Content diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx index 6e127f3b6a..a5b51747fc 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx @@ -46,7 +46,9 @@ export function PullRequestCommitPage() { return ( <PullRequestCommitsView toCode={({ sha }: { sha: string }) => `${routes.toRepoFiles({ spaceId, repoId })}/${sha}`} - toCommitDetails={({ sha }: { sha: string }) => routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha })} + toCommitDetails={({ sha }: { sha: string }) => + routes.toPullRequestChanges({ spaceId, repoId, pullRequestId, commitSHA: sha }) + } usePullRequestCommitsStore={usePullRequestCommitsStore} /> ) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 6019eff9e0..81960f2afc 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -34,6 +34,7 @@ import { CodeOwnersData, DefaultReviewersDataProps, LatestCodeOwnerApprovalArrType, + PRCommentFilterType, PRPanelData, PullRequestConversationPage as PullRequestConversationView, PullRequestPanelProps, @@ -768,6 +769,27 @@ export default function PullRequestConversationPage() { await performRebase({ body: payload }) }, [pullReqMetadata, performRebase]) + const handleViewUnresolvedComments = useCallback(() => { + const shadowRoot = document.activeElement?.shadowRoot as ShadowRoot + + filtersData.setActivityFilter({ + label: 'Unresolved comments', + value: PRCommentFilterType.UNRESOLVED_COMMENTS + }) + const unresolvedComments = activities?.filter( + activity => !activity.resolved && (activity.type === 'comment' || activity.code_comment) + ) + if (unresolvedComments && unresolvedComments.length > 0) { + const firstUnresolvedCommentId = unresolvedComments[0].id + const firstUnresolvedCommentDiv = shadowRoot?.getElementById + ? shadowRoot.getElementById(`comment-${firstUnresolvedCommentId}`) + : document.getElementById(`comment-${firstUnresolvedCommentId}`) + if (firstUnresolvedCommentDiv) { + firstUnresolvedCommentDiv.scrollIntoView({ behavior: 'smooth', block: 'center' }) + } + } + }, [filtersData, activities]) + /** * Memoize overviewProps */ @@ -826,6 +848,7 @@ export default function PullRequestConversationPage() { return { handleRebaseBranch, handlePrState, + handleViewUnresolvedComments, changesInfo: { header: changesInfo.title, content: changesInfo.statusMessage, @@ -919,7 +942,8 @@ export default function PullRequestConversationPage() { isMerging, isRebasing, setSelectedMergeMethod, - handleMergeMethodSelect + handleMergeMethodSelect, + handleViewUnresolvedComments ]) if (prPanelData?.PRStateLoading || (changesLoading && !!pullReqMetadata?.closed)) { diff --git a/apps/gitness/src/pages-v2/search-page.tsx b/apps/gitness/src/pages-v2/search-page.tsx index cdc933d99b..d2bfd6453f 100644 --- a/apps/gitness/src/pages-v2/search-page.tsx +++ b/apps/gitness/src/pages-v2/search-page.tsx @@ -181,6 +181,7 @@ export default function SearchPage() { : // TODO: get default branch `/repos/${repoRef}/files/refs/heads/main/~/${filePath}` } + toRepo={({ repoPath }) => `/repos/${repoPath}`} // language filter props selectedLanguage={selectedLanguage} onLanguageSelect={language => { diff --git a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx index ce5be9b3d4..14455f6893 100644 --- a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx +++ b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx @@ -8,6 +8,7 @@ import { Separator, StackedList, Tabs, + Tag, Text, ViewTypeValue } from '@/components' @@ -23,6 +24,7 @@ export interface FileViewerControlBarProps { handleEditFile: () => void handleOpenDeleteDialog: () => void refType?: BranchSelectorTab + isGitLfsObject?: boolean } export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ @@ -31,6 +33,7 @@ export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ fileBytesSize, fileContent, url, + isGitLfsObject, handleDownloadFile, handleEditFile, handleOpenDeleteDialog, @@ -43,6 +46,7 @@ export const FileViewerControlBar: FC<FileViewerControlBarProps> = ({ const RightDetails = () => { return ( <Layout.Horizontal gap="xl" align="center"> + {isGitLfsObject && <Tag value="Stored with Git LFS" icon="info-circle" />} <Layout.Horizontal gap="2xs" align="center"> <Text color="foreground-3">{`${fileContent?.split('\n').length || 0} lines`}</Text> <Separator orientation="vertical" className="h-3" /> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 8161f154b1..981f2a28fd 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -283,6 +283,7 @@ export interface PullRequestPanelProps Partial<PullRequestRoutingProps> { handleRebaseBranch: () => void handlePrState: (state: string) => void + handleViewUnresolvedComments: () => void pullReqMetadata?: TypesPullReq checks?: TypesPullReqCheck[] checksInfo: { header: string; content: string; status: EnumCheckStatus } @@ -336,6 +337,7 @@ const PullRequestPanel = ({ onCommitSuggestions, handlePrState, handleRebaseBranch, + handleViewUnresolvedComments, prPanelData, spaceId, repoId, @@ -735,7 +737,12 @@ const PullRequestPanel = ({ )} {(!!prPanelData?.resolvedCommentArr || prPanelData.requiresCommentApproval) && - !pullReqMetadata?.merged && <PullRequestCommentSection commentsInfo={prPanelData.commentsInfoData} />} + !pullReqMetadata?.merged && ( + <PullRequestCommentSection + commentsInfo={prPanelData.commentsInfoData} + handleAction={handleViewUnresolvedComments} + /> + )} <PullRequestCheckSection {...routingProps} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx index e57d8b4c7e..6aab151337 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-comment-section.tsx @@ -1,4 +1,4 @@ -import { Accordion, IconV2, StackedList, Text } from '@/components' +import { Accordion, IconV2, Layout, StackedList, Text } from '@/components' import { LineDescription, LineTitle } from './pull-request-line-title' @@ -11,29 +11,27 @@ const PullRequestCommentSection = ({ commentsInfo, handleAction }: PullRequestMe return ( <Accordion.Item value="item-2"> - <Accordion.Trigger className="py-3 [&>.cn-accordion-trigger-indicator]:hidden"> - <StackedList.Field - className="flex gap-y-1" - title={ - <LineTitle - textClassName={isSuccess ? '' : 'text-cn-foreground-danger'} - text={commentsInfo.header} - icon={ - <IconV2 - size="md" - className={isSuccess ? 'text-cn-foreground-success' : 'text-cn-foreground-danger'} - name={isSuccess ? 'check-circle-solid' : 'warning-triangle'} - /> - } - /> - } - description={!!commentsInfo?.content && <LineDescription text={commentsInfo.content} />} - /> - {commentsInfo.status === 'failed' && !!handleAction && ( - <Text className="pr-2" onClick={() => handleAction()}> - View - </Text> - )} + <Accordion.Trigger className="py-3 [&>.cn-accordion-trigger-indicator]:hidden" onClick={handleAction}> + <Layout.Flex> + <StackedList.Field + className="flex gap-y-1" + title={ + <LineTitle + textClassName={isSuccess ? '' : 'text-cn-foreground-danger'} + text={commentsInfo.header} + icon={ + <IconV2 + size="md" + className={isSuccess ? 'text-cn-foreground-success' : 'text-cn-foreground-danger'} + name={isSuccess ? 'check-circle-solid' : 'warning-triangle'} + /> + } + /> + } + description={!!commentsInfo?.content && <LineDescription text={commentsInfo.content} />} + /> + {commentsInfo.status === 'failed' && !!handleAction && <Text className="pr-2">View</Text>} + </Layout.Flex> </Accordion.Trigger> </Accordion.Item> ) diff --git a/packages/ui/src/views/search/components/search-results-list.tsx b/packages/ui/src/views/search/components/search-results-list.tsx index 27e9c14b2f..e3ed90c152 100644 --- a/packages/ui/src/views/search/components/search-results-list.tsx +++ b/packages/ui/src/views/search/components/search-results-list.tsx @@ -15,6 +15,7 @@ interface SearchResultsListProps { toRepoFileDetails: (params: { repoPath: string; filePath: string; branch: string }) => string searchError?: string isRepoScope: boolean + toRepo: (params: { repoPath?: string }) => string } export interface SearchResultItem { @@ -40,7 +41,8 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ useSearchResultsStore, toRepoFileDetails, searchError, - isRepoScope + isRepoScope, + toRepo }) => { const { t } = useTranslation() const { results } = useSearchResultsStore() @@ -90,9 +92,13 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ disabled={item.matches.length === 0} > <Accordion.Item value={item.file_name}> - <Accordion.Trigger className="py-3 px-4 gap-4"> + <Accordion.Trigger className="gap-4 px-4 py-3"> <Layout.Horizontal className="w-full" gap="md"> - {!isRepoScope ? <Tag value={item.repo_path} icon="repository" /> : null} + {!isRepoScope ? ( + <Link to={toRepo({ repoPath: item.repo_path })}> + <Tag value={item.repo_path} icon="repository" /> + </Link> + ) : null} <Link to={toRepoFileDetails({ repoPath: item.repo_path, @@ -105,7 +111,7 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ </Link> </Layout.Horizontal> </Accordion.Trigger> - <Accordion.Content className="pb-0 bg-cn-background-1 border-t"> + <Accordion.Content className="border-t bg-cn-background-1 pb-0"> {item.matches && item.matches.length >= 1 && ( <Layout.Vertical gap="none" className="mt-1"> {item.matches @@ -144,7 +150,7 @@ export const SearchResultsList: FC<SearchResultsListProps> = ({ {item.matches?.length > DEFAULT_NUM_ITEMS_TO_SHOW && ( <Text variant="caption-single-line-normal" - className="hover:underline bg-cn-background-0 mt-2 pl-4 py-1.5 cursor-pointer" + className="mt-2 cursor-pointer bg-cn-background-0 py-1.5 pl-4 hover:underline" tabIndex={0} onMouseDown={e => { e.preventDefault() diff --git a/packages/ui/src/views/search/search-page.tsx b/packages/ui/src/views/search/search-page.tsx index 1b7acbd241..9e9923546f 100644 --- a/packages/ui/src/views/search/search-page.tsx +++ b/packages/ui/src/views/search/search-page.tsx @@ -60,7 +60,9 @@ export interface SearchPageViewProps { selectedLanguage?: string onLanguageSelect: (language: string) => void onClearFilters: () => void + // routing paths toRepoFileDetails: (params: { repoPath?: string; filePath: string; branch?: string }) => string + toRepo: (params: { repoPath?: string }) => string } export const SearchPageView: FC<SearchPageViewProps> = ({ @@ -83,7 +85,8 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ selectedLanguage, onLanguageSelect, onRepoSelect, - onClearFilters + onClearFilters, + toRepo }) => { const { t } = useTranslation() const { results, semanticResults, page, setPage } = useSearchResultsStore() @@ -173,7 +176,7 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ <Spacer size={6} /> {!isLoading && !semanticEnabled && stats && results && results.length > 0 && ( - <Layout.Horizontal gap="xs"> + <Layout.Horizontal gap="xs" align="center"> <Text variant={'body-normal'}>Results:</Text> <Text variant={'caption-normal'}> {t('views:search.statsText', '{{matchCount}} matches found in {{fileCount}} file(s)', { @@ -184,7 +187,7 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ </Layout.Horizontal> )} {!isLoading && semanticEnabled && stats && semanticResults && semanticResults.length > 0 && ( - <Layout.Horizontal gap="xs"> + <Layout.Horizontal gap="xs" align="center"> <Text variant={'body-normal'}>Results:</Text> <Text variant={'caption-normal'}> {t('views:search.statsText', '{{fileCount}} file(s)', { @@ -210,6 +213,7 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ isDirtyList={isDirtyList} useSearchResultsStore={useSearchResultsStore} toRepoFileDetails={toRepoFileDetails} + toRepo={toRepo} searchError={searchError} isRepoScope={isRepoScope} /> From 576d557423dbb3cf8059369af5084d9789c1dfda Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 15 Aug 2025 00:21:21 +0000 Subject: [PATCH 102/180] Tag creation not allowed even if user is in bypass list (#10216) * 437e18 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * ce5d9c type check error * a02f1d Tag creation not allowed even if user is in bypass list * bbc3ab Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * 79595e Fixed warning alert text for create by pass branch * 604f03 Fixed lint error * 7bceb1 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * f502a6 Create branch dialogue close fix --- .../views/repo-tags/repo-tags-list.tsx | 3 ++ .../repo/repo-tags-list-container.tsx | 25 +++++++-- packages/ui/locales/en/component.json | 9 ++++ packages/ui/locales/fr/component.json | 9 ++++ .../components/create-branch-dialog.tsx | 13 +++-- .../create-tag/create-tag-dialog.tsx | 54 ++++++++++++++++--- 6 files changed, 100 insertions(+), 13 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx index d117fdefcc..66c431334a 100644 --- a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx +++ b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx @@ -61,6 +61,9 @@ export const RepoTagsList = () => { isLoading={false} error="" selectedBranchOrTag={null} + violation={false} + bypassable={false} + resetViolation={noop} branchSelectorRenderer={() => ( <BranchSelectorV2 branchList={branches} diff --git a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx index 6ac09c3529..259616f90b 100644 --- a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx @@ -23,6 +23,7 @@ import { CreateBranchDialog } from '../../components-v2/create-branch-dialog' import { useRoutes } from '../../framework/context/NavigationContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useQueryState } from '../../framework/hooks/useQueryState' +import { useRuleViolationCheck } from '../../framework/hooks/useRuleViolationCheck' import usePaginationQueryStateWithStore from '../../hooks/use-pagination-query-state-with-store' import { PathParams } from '../../RouteDefinitions' import { PageResponseHeader } from '../../types' @@ -48,6 +49,7 @@ export const RepoTagsListContainer = () => { const [deleteTagName, setDeleteTagName] = useState<string | null>(null) const [deleteError, setDeleteError] = useState<DeleteAlertDialogProps['error']>(null) + const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() const { data: { body: tagsList, headers } = {}, @@ -76,6 +78,16 @@ export const RepoTagsListContainer = () => { onSuccess: () => { setOpenCreateTagDialog(false) refetchTags() + }, + onError: (err: any) => { + if (err?.violations?.length > 0) { + const violation = err.violations[0] + setAllStates({ + violation: true, + bypassed: true, + bypassable: violation.bypassable || false + }) + } } } ) @@ -111,7 +123,8 @@ export const RepoTagsListContainer = () => { const onSubmit = (data: CreateTagFormFields) => { createTag({ body: { - ...data + ...data, + bypass_rules: bypassed } }) } @@ -130,8 +143,11 @@ export const RepoTagsListContainer = () => { useEffect(() => { if (!openCreateTagDialog) { resetCreateTagMutation() + resetViolation() + } else { + resetViolation() } - }, [openCreateTagDialog, resetCreateTagMutation]) + }, [openCreateTagDialog, resetCreateTagMutation, resetViolation]) return ( <> @@ -157,8 +173,11 @@ export const RepoTagsListContainer = () => { onClose={() => setOpenCreateTagDialog(false)} onSubmit={onSubmit} isLoading={isCreatingTag} - error={createTagError?.message} + error={violation ? undefined : createTagError?.message} selectedBranchOrTag={selectedBranchOrTag} + violation={violation} + bypassable={bypassable} + resetViolation={resetViolation} branchSelectorRenderer={() => ( <BranchSelectorContainer className={'branch-selector-trigger-as-input'} diff --git a/packages/ui/locales/en/component.json b/packages/ui/locales/en/component.json index a4485a623f..bbc0b78728 100644 --- a/packages/ui/locales/en/component.json +++ b/packages/ui/locales/en/component.json @@ -207,6 +207,7 @@ "notAllow": "Some rules don't allow creating branch " }, "loading": "Creating branch...", + "notAllowed": "Cannot create branch", "default": "Create branch", "bypassButton": "Bypass rules and create branch", "title": "Create a branch", @@ -221,6 +222,14 @@ } } }, + "tagDialog": { + "violationMessages": { + "bypassed": "Some rules will be bypassed while creating tag", + "notAllow": "Some rules don't allow you to create tag" + }, + "notAllowed": "Cannot create tag", + "bypassButton": "Bypass rules and create tag" + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/locales/fr/component.json b/packages/ui/locales/fr/component.json index 327dc5d173..f8faf59cf1 100644 --- a/packages/ui/locales/fr/component.json +++ b/packages/ui/locales/fr/component.json @@ -207,6 +207,7 @@ "notAllow": "Some rules don't allow creating branch " }, "loading": "Creating branch...", + "notAllowed": "Cannot create branch", "default": "Create branch", "bypassButton": "Bypass rules and create branch", "title": "Create a branch", @@ -221,6 +222,14 @@ } } }, + "tagDialog": { + "violationMessages": { + "bypassed": "Some rules will be bypassed while creating tag", + "notAllow": "Some rules don't allow you to create tag" + }, + "notAllowed": "Cannot create tag", + "bypassButton": "Bypass rules and create tag" + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index 84af7385c9..2e968d8d1f 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -144,11 +144,18 @@ export function CreateBranchDialog({ <Dialog.Close onClick={handleClose} loading={isCreatingBranch} disabled={isCreatingBranch}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> - {!bypassable ? ( - <Button type="submit" form="create-branch-form" disabled={isCreatingBranch}> + {!bypassable || !violation ? ( + <Button + type="submit" + form="create-branch-form" + disabled={isCreatingBranch || (!bypassable && violation)} + loading={isCreatingBranch} + > {isCreatingBranch ? t('component:branchDialog.loading', 'Creating branch...') - : t('component:branchDialog.default', 'Create branch')} + : !bypassable && violation + ? t('component:branchDialog.notAllowed', 'Cannot create branch') + : t('component:branchDialog.default', 'Create branch')} </Button> ) : ( <Button type="submit" form="create-branch-form" variant="outline" theme="danger"> diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 403b832b92..46a36023f2 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -21,6 +21,9 @@ interface CreateTagDialogProps { isLoading?: boolean selectedBranchOrTag: BranchSelectorListItem | null branchSelectorRenderer: () => JSX.Element | null + violation?: boolean + bypassable?: boolean + resetViolation: () => void } export const CreateTagDialog: FC<CreateTagDialogProps> = ({ @@ -30,7 +33,10 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ error, isLoading = false, selectedBranchOrTag, - branchSelectorRenderer: BranchSelectorContainer + branchSelectorRenderer: BranchSelectorContainer, + violation = false, + bypassable = false, + resetViolation }) => { const { t } = useTranslation() @@ -40,13 +46,19 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ defaultValues: INITIAL_FORM_VALUES }) - const { register, handleSubmit, setValue, reset, clearErrors } = formMethods + const { register, handleSubmit, setValue, reset, clearErrors, watch } = formMethods const resetForm = useCallback(() => { clearErrors() reset(INITIAL_FORM_VALUES) }, [clearErrors, reset]) + const tagName = watch('name') + + useEffect(() => { + resetViolation() + }, [tagName, resetViolation]) + useEffect(() => { if (open && selectedBranchOrTag) { setValue('target', selectedBranchOrTag.name, { shouldValidate: true }) @@ -61,6 +73,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ const handleClose = () => { resetForm() + resetViolation() onClose() } @@ -95,6 +108,18 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ label={t('views:repos.description', 'Description')} disabled={isLoading} /> + {violation && ( + <Alert.Root theme="warning"> + <Alert.Description> + {bypassable + ? t( + 'component:tagDialog.violationMessages.bypassed', + 'Some rules will be bypassed while creating tag' + ) + : t('component:tagDialog.violationMessages.notAllow', "Some rules don't allow you to create tag")} + </Alert.Description> + </Alert.Root> + )} {error && ( <Alert.Root theme="danger"> @@ -111,11 +136,26 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ <Dialog.Close onClick={handleClose} loading={isLoading} disabled={isLoading}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> - <Button type="submit" form="create-tag-form" disabled={isLoading} loading={isLoading}> - {isLoading - ? t('views:repos.creatingTagButton', 'Creating tag...') - : t('views:repos.createTagButton', 'Create tag')} - </Button> + {!bypassable || !violation ? ( + <Button + type="submit" + form="create-tag-form" + disabled={isLoading || (!bypassable && violation)} + loading={isLoading} + > + {isLoading + ? t('views:repos.creatingTagButton', 'Creating tag...') + : !bypassable && violation + ? t('component:tagDialog.notAllowed', 'Cannot create tag') + : t('views:repos.createTagButton', 'Create tag')} + </Button> + ) : ( + <Button type="submit" form="create-tag-form" variant="outline" theme="danger"> + {isLoading + ? t('views:repos.creatingTagButton', 'Creating tag...') + : t('component:tagDialog.bypassButton', 'Bypass rules and create tag')} + </Button> + )} </ButtonLayout> </Dialog.Footer> </Dialog.Content> From cdf16c1d49f70aea1e3a5cedaecf654c8f816ef9 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Fri, 15 Aug 2025 00:36:44 +0000 Subject: [PATCH 103/180] fix: Remove SSE integration from Pull Requests (#10217) * c555a9 remove SSE integration --- .../pull-request-data-provider.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-data-provider.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-data-provider.tsx index 23784779b0..dd81db1287 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-data-provider.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-data-provider.tsx @@ -1,10 +1,9 @@ -import { FC, HTMLAttributes, PropsWithChildren, useCallback, useEffect } from 'react' +import { FC, HTMLAttributes, PropsWithChildren, useEffect } from 'react' import { useParams } from 'react-router-dom' import { isEqual } from 'lodash-es' import { - TypesPullReq, useFindRepositoryQuery, useGetPullReqQuery, useListCommitsQuery, @@ -13,18 +12,14 @@ import { import { RepoRepositoryOutput } from '@harnessio/ui/views' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' -import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam' -import useSpaceSSE from '../../framework/hooks/useSpaceSSE' import useGetPullRequestTab, { PullRequestTab } from '../../hooks/useGetPullRequestTab' import { PathParams } from '../../RouteDefinitions' -import { SSEEvent } from '../../types' import { normalizeGitRef } from '../../utils/git-utils' import { usePRChecksDecision } from './hooks/usePRChecksDecision' import { extractSpecificViolations, getCommentsInfoData } from './pull-request-utils' import { POLLING_INTERVAL, PR_RULES, usePullRequestProviderStore } from './stores/pull-request-provider-store' const PullRequestDataProvider: FC<PropsWithChildren<HTMLAttributes<HTMLElement>>> = ({ children }) => { - const spaceURL = useGetSpaceURLParam() ?? '' const repoRef = useGetRepoRef() const { pullRequestId, spaceId, repoId } = useParams<PathParams>() const pullRequestTab = useGetPullRequestTab({ spaceId, repoId, pullRequestId }) @@ -85,20 +80,23 @@ const PullRequestDataProvider: FC<PropsWithChildren<HTMLAttributes<HTMLElement>> }) const pullReqChecksDecision = usePRChecksDecision({ repoMetadata, pullReqMetadata: pullReqData }) - const handleEvent = useCallback( - (data: TypesPullReq) => { - if (data && String(data?.number) === pullRequestId) { - refetchPullReq() - } - }, - [pullRequestId, refetchPullReq] - ) - useSpaceSSE({ - space: spaceURL, - events: [SSEEvent.PULLREQ_UPDATED], - onEvent: handleEvent, - shouldRun: !!(spaceURL && pullRequestId) // Ensure shouldRun is true only when space and pullRequestId are valid - }) + /** + * @todo enable it with proper implementation + */ + // const handleEvent = useCallback( + // (data: TypesPullReq) => { + // if (data && String(data?.number) === pullRequestId) { + // refetchPullReq() + // } + // }, + // [pullRequestId, refetchPullReq] + // ) + // useSpaceSSE({ + // space: spaceURL, + // events: [SSEEvent.PULLREQ_UPDATED], + // onEvent: handleEvent, + // shouldRun: !!(spaceURL && pullRequestId) // Ensure shouldRun is true only when space and pullRequestId are valid + // }) useEffect(() => { if (!pullReqData || isEqual(pullReqMetadata, pullReqData)) return From 49d876121b04efd4d19a7953c5a8e52d6a319ebf Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 15 Aug 2025 01:15:53 +0000 Subject: [PATCH 104/180] Create tag and branch - Hz scroll in pop when there is error (#10218) * 51ba59 Fixed lint error * 34a943 Pull from main * 9ab251 Create tag and branch - Hz scroll in pop when there is error * 437e18 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * ce5d9c type check error * a02f1d Tag creation not allowed even if user is in bypass list * bbc3ab Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * 79595e Fixed warning alert text for create by pass branch * 604f03 Fixed lint error * 7bceb1 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * f502a6 Create branch dialogue close fix --- .../repo/repo-branch/components/create-branch-dialog.tsx | 8 ++++---- .../repo-tags/components/create-tag/create-tag-dialog.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index 2e968d8d1f..a43d2e3fa9 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -93,7 +93,7 @@ export function CreateBranchDialog({ return ( <Dialog.Root open={open} onOpenChange={handleClose}> - <Dialog.Content aria-describedby={undefined}> + <Dialog.Content aria-describedby={undefined} className="max-w-2xl"> <Dialog.Header> <Dialog.Title>{t('views:repos.createBranchTitle', 'Create a branch')}</Dialog.Title> </Dialog.Header> @@ -115,7 +115,7 @@ export function CreateBranchDialog({ {violation && ( <Alert.Root theme="warning"> - <Alert.Description> + <Alert.Description className="break-all overflow-hidden"> {bypassable ? t( 'component:branchDialog.violationMessages.bypassed', @@ -131,9 +131,9 @@ export function CreateBranchDialog({ {error && ( <Alert.Root theme="danger"> - <Alert.Title> + <Alert.Description className="break-all overflow-hidden"> {t('views:repos.error', 'Error:')} {error} - </Alert.Title> + </Alert.Description> </Alert.Root> )} </FormWrapper> diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 46a36023f2..2515c38598 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -79,7 +79,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ return ( <Dialog.Root open={open} onOpenChange={handleClose}> - <Dialog.Content> + <Dialog.Content className="max-w-2xl"> <Dialog.Header> <Dialog.Title className="font-medium">{t('views:repos.createTagTitle', 'Create a tag')}</Dialog.Title> </Dialog.Header> @@ -110,7 +110,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ /> {violation && ( <Alert.Root theme="warning"> - <Alert.Description> + <Alert.Description className="break-all overflow-hidden"> {bypassable ? t( 'component:tagDialog.violationMessages.bypassed', @@ -123,9 +123,9 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ {error && ( <Alert.Root theme="danger"> - <Alert.Title> + <Alert.Description className="break-all overflow-hidden"> {t('views:repos.error', 'Error:')} {error} - </Alert.Title> + </Alert.Description> </Alert.Root> )} </FormWrapper> From 0e19d7bf5a812766d1ae5e7ab1d8bd0ee45c8c07 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 15 Aug 2025 05:21:09 +0000 Subject: [PATCH 105/180] File edit - Rendering issue (#10219) * db6948 Added logic for create * 7af193 Edit - Rendering issue * 811ec1 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * 51ba59 Fixed lint error * 34a943 Pull from main * 9ab251 Create tag and branch - Hz scroll in pop when there is error * 437e18 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * ce5d9c type check error * a02f1d Tag creation not allowed even if user is in bypass list * bbc3ab Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * 79595e Fixed warning alert text for create by pass branch * 604f03 Fixed lint error * 7bceb1 Merge remote-tracking branch 'origin/main' into Rk-branch-create-error * f502a6 Create branch dialogue close fix --- apps/gitness/src/components-v2/file-editor.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/gitness/src/components-v2/file-editor.tsx b/apps/gitness/src/components-v2/file-editor.tsx index e2073b1a5f..39f868a179 100644 --- a/apps/gitness/src/components-v2/file-editor.tsx +++ b/apps/gitness/src/components-v2/file-editor.tsx @@ -113,17 +113,15 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch, lo const currentFileName = isNew ? '' : repoDetails?.name || '' setFileName(currentFileName) setLanguage(filenameToLanguage(currentFileName) || '') - setOriginalFileContent(decodeGitContent(repoDetails?.content?.data)) + const decodedContent = decodeGitContent(repoDetails?.content?.data) + setOriginalFileContent(decodedContent) + setContentRevision({ code: decodedContent }) }, [isNew, repoDetails]) useEffect(() => { setDirty(!(!fileName || (isUpdate && contentRevision.code === originalFileContent))) }, [fileName, isUpdate, contentRevision, originalFileContent]) - useEffect(() => { - setContentRevision({ code: originalFileContent }) - }, [originalFileContent]) - const toggleOpenCommitDialog = (value: boolean) => { setIsCommitDialogOpen(value) } @@ -221,7 +219,8 @@ export const FileEditor: FC<FileEditorProps> = ({ repoDetails, defaultBranch, lo <Tabs.Content value="edit" className="grow"> {loading && <Loader />} - {!loading && ( + {!loading && !isNew && !contentRevision.code && <Loader />} + {!loading && (isNew || contentRevision.code) && ( <CodeEditor height="100%" language={language} From cef3fbe4408427abc71b977dc8b869e814de200d Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 15 Aug 2025 06:55:49 +0000 Subject: [PATCH 106/180] fix: multiple p3 fixes across app (#10208) * d3c184 fix: tsc * 89db91 fix: labels created in col * d9ed1b fix: remove exit dialog * 30c8f2 fix: labels design * 0f49a1 fix: labels page fixes * 6cabcd fix: throw error if default br not provided --- .../views/labels/project-labels-list.tsx | 1 - .../views/labels/repo-labels-list.tsx | 1 - apps/gitness/src/framework/routing/types.ts | 3 +- .../labels/project-labels-list-container.tsx | 26 ++++- .../repo/labels/labels-list-container.tsx | 25 ++++- apps/gitness/src/routes.tsx | 3 +- apps/gitness/src/utils/rule-url-utils.ts | 16 ++- .../labels/components/label-cell-content.tsx | 45 -------- .../labels/components/labels-list-view.tsx | 100 ++++++++++++------ .../ui/src/views/labels/labels-list-page.tsx | 8 +- .../repo-create/default-branch-dialog.tsx | 25 +++-- .../repo-settings-general-rules.tsx | 13 ++- 12 files changed, 162 insertions(+), 104 deletions(-) delete mode 100644 packages/ui/src/views/labels/components/label-cell-content.tsx diff --git a/apps/design-system/src/subjects/views/labels/project-labels-list.tsx b/apps/design-system/src/subjects/views/labels/project-labels-list.tsx index 0eb5cee527..350320d499 100644 --- a/apps/design-system/src/subjects/views/labels/project-labels-list.tsx +++ b/apps/design-system/src/subjects/views/labels/project-labels-list.tsx @@ -13,7 +13,6 @@ export const ProjectLabelsList = () => { <> <LabelsListPage useLabelsStore={LabelsListStore.useLabelsStore} - createdIn={''} searchQuery={''} setSearchQuery={noop} labelsListViewProps={{ handleEditLabel: noop, handleDeleteLabel: () => setOpenAlertDeleteDialog(true) }} diff --git a/apps/design-system/src/subjects/views/labels/repo-labels-list.tsx b/apps/design-system/src/subjects/views/labels/repo-labels-list.tsx index c834b2ef0f..c628cc706c 100644 --- a/apps/design-system/src/subjects/views/labels/repo-labels-list.tsx +++ b/apps/design-system/src/subjects/views/labels/repo-labels-list.tsx @@ -13,7 +13,6 @@ export const RepoLabelsList = () => { <> <LabelsListPage useLabelsStore={LabelsListStore.useLabelsStore} - createdIn={''} searchQuery={''} setSearchQuery={noop} isRepository diff --git a/apps/gitness/src/framework/routing/types.ts b/apps/gitness/src/framework/routing/types.ts index 848b2b0a67..890caf3431 100644 --- a/apps/gitness/src/framework/routing/types.ts +++ b/apps/gitness/src/framework/routing/types.ts @@ -92,7 +92,8 @@ export enum RouteConstants { toProjectBranchRuleCreate = 'toProjectBranchRuleCreate', toProjectTagRuleCreate = 'toProjectTagRuleCreate', toProjectRules = 'toProjectRules', - toProjectRuleDetails = 'toProjectRuleDetails' + toProjectRuleDetails = 'toProjectRuleDetails', + toRepoLabelDetails = 'toRepoLabelDetails' } export interface RouteEntry { diff --git a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx index 5af6e53c03..e9070b0f73 100644 --- a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx @@ -1,13 +1,16 @@ import { useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useParams } from 'react-router-dom' import { useDeleteSpaceLabelMutation } from '@harnessio/code-service-client' import { DeleteAlertDialog } from '@harnessio/ui/components' import { ILabelType, LabelsListPage } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../framework/hooks/useGetSpaceParam' +import { useMFEContext } from '../../../framework/hooks/useMFEContext.ts' import { useQueryState } from '../../../framework/hooks/useQueryState' import usePaginationQueryStateWithStore from '../../../hooks/use-pagination-query-state-with-store' +import { PathParams } from '../../../RouteDefinitions.ts' +import { getScopedRuleUrl } from '../../../utils/rule-url-utils.ts' import { useLabelsStore } from '../stores/labels-store' import { useFillLabelStoreWithProjectLabelValuesData } from './hooks/use-fill-label-store-with-project-label-values-data.ts' @@ -17,6 +20,11 @@ export const ProjectLabelsList = () => { const [openAlertDeleteDialog, setOpenAlertDeleteDialog] = useState(false) const [identifier, setIdentifier] = useState<string | null>(null) + const { repoId, spaceId } = useParams<PathParams>() + const { + scope: { accountId, orgIdentifier, projectIdentifier }, + routeUtils + } = useMFEContext() const { page, setPage, deleteLabel: deleteStoreLabel } = useLabelsStore() @@ -54,11 +62,25 @@ export const ProjectLabelsList = () => { <LabelsListPage className="mx-auto max-w-[1040px]" useLabelsStore={useLabelsStore} - createdIn={space_ref} searchQuery={query} setSearchQuery={setQuery} labelsListViewProps={{ handleDeleteLabel: handleOpenDeleteDialog, handleEditLabel }} isRepository={space_ref?.includes('/')} + toRepoLabelDetails={({ labelId, scope }: { labelId: string; scope: number }) => { + getScopedRuleUrl({ + scope, + identifier: labelId, + settingSection: 'labels', + toCODERule: routeUtils?.toCODERule, + toCODEManageRepositories: routeUtils?.toCODEManageRepositories, + accountId, + orgIdentifier, + projectIdentifier, + repoId, + spaceId + }) + return '' + }} /> <DeleteAlertDialog open={openAlertDeleteDialog} diff --git a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx index 22f0e57f72..ec4b3b8b10 100644 --- a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx @@ -6,16 +6,22 @@ import { DeleteAlertDialog } from '@harnessio/ui/components' import { ILabelType, LabelsListPage } from '@harnessio/ui/views' import { useRoutes } from '../../../framework/context/NavigationContext' +import { useMFEContext } from '../../../framework/hooks/useMFEContext.ts' import { useQueryState } from '../../../framework/hooks/useQueryState' import usePaginationQueryStateWithStore from '../../../hooks/use-pagination-query-state-with-store' import { PathParams } from '../../../RouteDefinitions' +import { getScopedRuleUrl } from '../../../utils/rule-url-utils.ts' import { useLabelsStore } from '../../project/stores/labels-store' import { usePopulateLabelStore } from './hooks/use-populate-label-store.ts' export const RepoLabelsList = () => { - const { spaceId } = useParams<PathParams>() + const { repoId, spaceId } = useParams<PathParams>() const routes = useRoutes() const navigate = useNavigate() + const { + routeUtils, + scope: { accountId, orgIdentifier, projectIdentifier } + } = useMFEContext() const [openAlertDeleteDialog, setOpenAlertDeleteDialog] = useState(false) const [identifier, setIdentifier] = useState<string | null>(null) @@ -69,11 +75,26 @@ export const RepoLabelsList = () => { <LabelsListPage // className="max-w-[772px] px-0" useLabelsStore={useLabelsStore} - createdIn={repo_ref} searchQuery={query} setSearchQuery={setQuery} isRepository labelsListViewProps={{ widthType: 'default', handleDeleteLabel: handleOpenDeleteDialog, handleEditLabel }} + toRepoLabelDetails={({ labelId, scope }: { labelId: string; scope: number }) => { + // routes.toRepoLabelDetails({ repoId: repoId ?? '', spaceId: spaceId ?? '', labelId }) + getScopedRuleUrl({ + scope, + identifier: labelId, + settingSection: 'labels', + toCODERule: routeUtils?.toCODERule, + toCODEManageRepositories: routeUtils?.toCODEManageRepositories, + accountId, + orgIdentifier, + projectIdentifier, + repoId, + spaceId + }) + return '' + }} /> <DeleteAlertDialog open={openAlertDeleteDialog} diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 43d8dd2425..2d09b2cee1 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -629,7 +629,8 @@ export const repoRoutes: CustomRouteObject[] = [ path: ':labelId', element: <RepoLabelFormContainer />, handle: { - breadcrumb: ({ labelId }: { labelId: string }) => <span>{labelId}</span> + breadcrumb: ({ labelId }: { labelId: string }) => <span>{labelId}</span>, + routeName: RouteConstants.toRepoLabelDetails } } ] diff --git a/apps/gitness/src/utils/rule-url-utils.ts b/apps/gitness/src/utils/rule-url-utils.ts index 32ed97b7ac..a3aa6a6366 100644 --- a/apps/gitness/src/utils/rule-url-utils.ts +++ b/apps/gitness/src/utils/rule-url-utils.ts @@ -35,10 +35,12 @@ export function getScopedRuleUrl({ repoId, accountId, orgIdentifier, - projectIdentifier + projectIdentifier, + settingSection = 'rules' }: { scope: number identifier: string + settingSection?: string toCODEManageRepositories?: ({ space, ruleId, @@ -74,7 +76,7 @@ export function getScopedRuleUrl({ toCODERule({ repoPath, ruleId: identifier, - settingSection: 'rules' + settingSection }) } else { const url = toRepoBranchRule?.({ spaceId: spaceId ?? '', repoId: repoId ?? '', identifier }) ?? '' @@ -85,7 +87,11 @@ export function getScopedRuleUrl({ if (scope === 1) { if (!toCODEManageRepositories) window.location.href = transformToRuleDetailsUrl(toAccountSettings?.() ?? '', identifier) - toCODEManageRepositories?.({ space: `${accountId ?? ''}`, ruleId: identifier, settingSection: 'rules' }) + toCODEManageRepositories?.({ + space: `${accountId ?? ''}`, + ruleId: identifier, + settingSection + }) } if (scope === 2) { @@ -93,7 +99,7 @@ export function getScopedRuleUrl({ toCODEManageRepositories?.({ space: `${accountId ?? ''}/${orgIdentifier ?? ''}`, ruleId: identifier, - settingSection: 'rules' + settingSection }) } if (scope === 3) { @@ -102,7 +108,7 @@ export function getScopedRuleUrl({ toCODEManageRepositories?.({ space: `${accountId ?? ''}/${orgIdentifier ?? ''}/${projectIdentifier ?? ''}`, ruleId: identifier, - settingSection: 'rules' + settingSection }) } } diff --git a/packages/ui/src/views/labels/components/label-cell-content.tsx b/packages/ui/src/views/labels/components/label-cell-content.tsx deleted file mode 100644 index 423a0dc300..0000000000 --- a/packages/ui/src/views/labels/components/label-cell-content.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { FC } from 'react' - -import { Accordion, Tag } from '@/components' -import { ILabelType, LabelValueType } from '@/views' -import { cn } from '@utils/cn' - -export interface LabelCellContentProps { - label: ILabelType - values?: LabelValueType[] -} - -export const LabelCellContent: FC<LabelCellContentProps> = ({ label, values }) => { - const isWithValues = !!values?.length - - return ( - <Accordion.Root collapsible type="single" indicatorPosition="left" className="min-w-0 overflow-hidden"> - <Accordion.Item value={label.key} disabled={!isWithValues} className="border-none ml-6"> - <Accordion.Trigger className={cn('p-0', { '[&>.cn-accordion-trigger-indicator]:invisible': !isWithValues })}> - <Tag - variant="secondary" - size="md" - theme={label.color} - label={label.key} - value={(values?.length || '').toString()} - /> - </Accordion.Trigger> - - {isWithValues && ( - <Accordion.Content className="flex flex-col gap-y-2.5 pb-0 pl-5 pt-2.5"> - {values.map(item => ( - <Tag - key={item.id} - variant="secondary" - size="md" - theme={item?.color || label.color} - label={label.key} - value={item.value} - /> - ))} - </Accordion.Content> - )} - </Accordion.Item> - </Accordion.Root> - ) -} diff --git a/packages/ui/src/views/labels/components/labels-list-view.tsx b/packages/ui/src/views/labels/components/labels-list-view.tsx index a074e204fe..8e5928d1e6 100644 --- a/packages/ui/src/views/labels/components/labels-list-view.tsx +++ b/packages/ui/src/views/labels/components/labels-list-view.tsx @@ -1,11 +1,9 @@ -import { FC } from 'react' +import { FC, Fragment, useState } from 'react' -import { IconV2, MoreActionsTooltip, NoData, Table, Tag, Text } from '@/components' +import { Button, IconV2, Layout, MoreActionsTooltip, NoData, ScopeTag, Table, Tag, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@/utils' -import { ILabelType, LabelValuesType } from '@/views' - -import { LabelCellContent } from './label-cell-content' +import { ILabelType, LabelValuesType, ScopeType } from '@/views' export interface LabelsListViewProps { labels: ILabelType[] @@ -22,21 +20,21 @@ export interface LabelsListViewProps { * When the widthType is set to 'small', 'name' column is bigger and 'description' column is smaller */ widthType?: 'default' | 'small' - createdIn?: string + toRepoLabelDetails?: ({ labelId, scope }: { labelId: string; scope: number }) => string } -const getDisplayPath = (scope: number, path?: string): string => { - if (!path) return '' - +const getScopeType = (scope: number): ScopeType => { switch (scope) { case 0: - return path + return ScopeType.Repository case 1: - return path.split('/')[0] || '' + return ScopeType.Account case 2: - return path.split('/').slice(0, 2).join('/') + return ScopeType.Organization + case 3: + return ScopeType.Project default: - return path.split('/').slice(0, 3).join('/') + return ScopeType.Account } } @@ -48,9 +46,18 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ handleResetQueryAndPages, values, widthType = 'default', - createdIn + toRepoLabelDetails }) => { const { t } = useTranslation() + const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({}) + + const toggleRow = (key: string, e: React.MouseEvent) => { + e.stopPropagation() + setExpandedRows(prev => ({ + ...prev, + [key]: !prev[key] + })) + } if (!labels.length) { if (searchQuery) { @@ -100,13 +107,12 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ const isSmallWidth = widthType === 'small' return ( - <Table.Root tableClassName="table-fixed" className="mb-8 mt-4"> + <Table.Root tableClassName="table-fixed" className="mb-8 mt-4" size="compact"> <Table.Header> <Table.Row> - <Table.Head className={cn('w-[304px]', { 'w-4/12': isSmallWidth })}> - <Text variant="caption-strong" className="pl-[45px]"> - {t('views:labelData.table.name', 'Name')} - </Text> + <Table.Head className="w-[44px]" /> + <Table.Head className={cn('w-[260px]', { 'w-4/12': isSmallWidth })}> + <Text variant="caption-strong">{t('views:labelData.table.name', 'Name')}</Text> </Table.Head> <Table.Head className="w-[240px]"> <Text variant="caption-strong">{t('views:labelData.table.created', 'Created in')}</Text> @@ -120,22 +126,54 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ <Table.Body> {labels.map(label => ( - <Table.Row key={label.id}> - <Table.Cell className={cn('w-[304px] !py-3', { 'w-4/12': isSmallWidth })}> - <LabelCellContent label={label} values={values?.[label.key]} /> + <Table.Row + className="cursor-pointer" + key={label.id} + onClick={() => { + if (toRepoLabelDetails) { + toRepoLabelDetails({ labelId: label.key, scope: label.scope }) + } + }} + > + <Table.Cell className={cn('w-[44px] align-top', { 'w-4/12': isSmallWidth })}> + {values?.[label.key]?.length > 0 ? ( + <Button variant="ghost" size="2xs" iconOnly onClick={e => toggleRow(label.key, e)}> + <IconV2 name={expandedRows[label.key] ? 'nav-arrow-up' : 'nav-arrow-down'} /> + </Button> + ) : null} </Table.Cell> - <Table.Cell className="w-[240px] !py-3.5 leading-none"> - <Tag - theme="gray" - variant="secondary" - value={getDisplayPath(label.scope, createdIn)} - icon={label.scope === 0 ? 'repository' : 'folder'} - /> + <Table.Cell className="w-[260px] align-top"> + <Layout.Vertical align="start" gap={values?.[label.key]?.length ? 'xs' : 'none'}> + <Tag + variant="secondary" + size="md" + theme={label.color} + label={label.key} + value={(values?.[label.key]?.length || '').toString()} + /> + <Layout.Vertical gap="xs"> + {!!values?.[label.key]?.length && + expandedRows[label.key] && + values?.[label.key].map(item => ( + <Tag + key={item.id} + variant="secondary" + size="md" + theme={item?.color || label.color} + label={label.key} + value={item.value} + /> + ))} + </Layout.Vertical> + </Layout.Vertical> + </Table.Cell> + <Table.Cell className="w-[240px] align-top leading-none"> + <ScopeTag scopedPath={getScopeType(label.scope)} scopeType={getScopeType(label.scope)} /> </Table.Cell> - <Table.Cell className={cn('w-[298px] !py-3', { 'w-5/12': isSmallWidth })}> + <Table.Cell className={cn('w-[298px] align-top', { 'w-5/12': isSmallWidth })}> <span className="line-clamp-3 break-words text-sm text-cn-foreground-3">{label?.description || ''}</span> </Table.Cell> - <Table.Cell className="w-[68px] !py-2"> + <Table.Cell className="w-[68px] align-top"> <MoreActionsTooltip isInTable iconName="more-horizontal" diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index c6ddf360cd..8d5c5e2d8b 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -8,13 +8,13 @@ import { LabelsListView, LabelsListViewProps } from './components/labels-list-vi export interface LabelsListPageProps { useLabelsStore: () => ILabelsStore - createdIn?: string showSpacer?: boolean searchQuery: string | null setSearchQuery: (query: string | null) => void isRepository?: boolean labelsListViewProps: Pick<LabelsListViewProps, 'handleDeleteLabel' | 'handleEditLabel' | 'widthType'> className?: string + toRepoLabelDetails?: ({ labelId, scope }: { labelId: string; scope: number }) => string } export const LabelsListPage: FC<LabelsListPageProps> = ({ @@ -23,8 +23,8 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ setSearchQuery, isRepository = false, labelsListViewProps, - createdIn, - className + className, + toRepoLabelDetails }) => { const { Link } = useRouterContext() const { t } = useTranslation() @@ -99,10 +99,10 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ {...labelsListViewProps} labels={spaceLabels} labelContext={{ space: space_ref, repo: repo_ref }} - createdIn={createdIn} handleResetQueryAndPages={handleResetQueryAndPages} searchQuery={searchQuery} values={spaceValues} + toRepoLabelDetails={toRepoLabelDetails} /> )} diff --git a/packages/ui/src/views/repo/repo-create/default-branch-dialog.tsx b/packages/ui/src/views/repo/repo-create/default-branch-dialog.tsx index 65e7ffbe86..b75210a6a2 100644 --- a/packages/ui/src/views/repo/repo-create/default-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-create/default-branch-dialog.tsx @@ -14,7 +14,12 @@ export const DefaultBranchDialog: FC<DefaultBranchDialogProps> = ({ formMethods const { t } = useTranslation() const [open, setOpen] = useState(false) - const { register, watch, setValue } = formMethods + const { + register, + watch, + setValue, + formState: { errors } + } = formMethods const branchValue = watch('defaultBranch') const customBranchRadio = watch('customBranchRadio') const customBranchInput = watch('customBranchInput') @@ -36,10 +41,12 @@ export const DefaultBranchDialog: FC<DefaultBranchDialogProps> = ({ formMethods } const handleConfirm = () => { - const value = (customBranchRadio === 'custom' ? customBranchInput : customBranchRadio) || 'main' - setValue('defaultBranch', value) - setValue('customBranchInput', customBranchRadio === 'custom' ? value : '') - handleClose() + const value = customBranchRadio === 'custom' ? customBranchInput : customBranchRadio + setValue('defaultBranch', value || '', { shouldValidate: true }) + setValue('customBranchInput', customBranchRadio === 'custom' ? value : '', { shouldValidate: true }) + if (value) { + handleClose() + } } return ( @@ -63,7 +70,7 @@ export const DefaultBranchDialog: FC<DefaultBranchDialogProps> = ({ formMethods </Dialog.Header> <Dialog.Body> - <FormInput.Radio label="Select branch" id="default-branch-radio" {...register('customBranchRadio')}> + <FormInput.Radio id="default-branch-radio" {...register('customBranchRadio')}> <Radio.Item id="default-branch-main" value="main" label="main" /> <Radio.Item id="default-branch-master" value="master" label="master" /> <Radio.Item id="default-branch-custom" value="custom" label="custom" /> @@ -75,6 +82,12 @@ export const DefaultBranchDialog: FC<DefaultBranchDialogProps> = ({ formMethods label="Branch name" {...register('customBranchInput')} placeholder="Enter name to initialize default branch" + required + error={ + customBranchRadio === 'custom' && !customBranchInput + ? errors.defaultBranch?.message?.toString() + : undefined + } autoFocus /> )} diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index dd508211c4..3e556ac2d3 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -45,11 +45,14 @@ const Description: FC<DescriptionProps> = ({ targetPatternsCount, rulesAppliedCo const { t } = useTranslation() return ( <div className="flex items-center gap-1.5 pl-7 text-sm"> - <Text variant="body-normal"> - {targetPatternsCount} {t('views:repos.targetPatterns', 'target patterns')} - </Text> - {/* <span className="pointer-events-none mx-1 h-3 w-px bg-cn-background-3" aria-hidden /> */} - <Separator orientation="vertical" className="h-3" /> + {targetPatternsCount !== 0 ? ( + <> + <Text variant="body-normal"> + {targetPatternsCount} {t('views:repos.targetPatterns', 'target patterns')} + </Text> + <Separator orientation="vertical" className="h-3" /> + </> + ) : null} <Text variant="body-normal"> {rulesAppliedCount} {t('views:repos.rulesApplied', 'rules applied')} </Text> From 07d463257f67830dad0e39ea4501f5a8f7644639 Mon Sep 17 00:00:00 2001 From: Pavel <pavel@pixelpoint.io> Date: Fri, 15 Aug 2025 15:34:17 +0400 Subject: [PATCH 107/180] Refactor UI Components to support Refs (#1893) * refactor: convert components to use forwardRef for better ref handling * fix: add ref prop to LogoV2 component * refactor: convert components to use forwardRef * refactor: update components to use forwardRef * refactor: simplify refs in SplitButton and Tag components * refactor: spread props in forwardRef components for better flexibility * refactor: spread props in AlertDialog, DrawerBody, ScrollArea, TimeAgoCard, and Toggle components for improved flexibility * refactor: convert components to use forwardRef for improved ref handling * feat: add className prop to Tooltip content for custom styling * refactor: use PropsWithoutRef for iconProps in Accordion, Informer, Toggle components for improved type safety * fix tag * refactor: remove spread props from IconV2 and LogoV2 components for cleaner code * refactor: remove unused props from Tooltip component for cleaner code * refactor: simplify FormCaption component and improve color handling * fix pretty * refactor: remove spread props from multiple components for cleaner code * fix: remove unused props * refactor: returned spread props * refactor: remove unused props from FormCaption * refactor: simplify SplitButton component structure and improve dropdown handling --- .../src/content/docs/components/tooltip.mdx | 8 +- .../ui/src/components/accordion/accordion.tsx | 13 +- packages/ui/src/components/alert-dialog.tsx | 48 ++-- packages/ui/src/components/breadcrumb.tsx | 6 +- packages/ui/src/components/button-group.tsx | 76 +++--- packages/ui/src/components/button-layout.tsx | 10 +- .../components/copy-button/copy-button.tsx | 47 ++-- packages/ui/src/components/counter-badge.tsx | 35 +-- packages/ui/src/components/dialog.tsx | 102 +++---- .../src/components/drawer/Drawer.Tagline.tsx | 6 +- .../ui/src/components/drawer/DrawerBody.tsx | 49 ++-- .../ui/src/components/drawer/DrawerFooter.tsx | 6 +- .../ui/src/components/drawer/DrawerHeader.tsx | 68 ++--- packages/ui/src/components/dropdown-menu.tsx | 24 +- .../form-primitives/form-caption.tsx | 85 +++--- .../ui/src/components/icon-v2/icon-v2.tsx | 32 +-- .../components/illustration/illustration.tsx | 56 ++-- packages/ui/src/components/informer.tsx | 4 +- .../ui/src/components/logo-v2/logo-v2.tsx | 10 +- packages/ui/src/components/progress.tsx | 184 ++++++------- packages/ui/src/components/scroll-area.tsx | 169 ++++++------ packages/ui/src/components/sheet.tsx | 40 +-- .../src/components/sidebar/sidebar-item.tsx | 42 +-- packages/ui/src/components/split-button.tsx | 6 +- .../components/status-badge/status-badge.tsx | 49 ++-- packages/ui/src/components/tabs.tsx | 1 + packages/ui/src/components/tag.tsx | 250 ++++++++++-------- packages/ui/src/components/time-ago-card.tsx | 2 +- packages/ui/src/components/toggle-group.tsx | 7 +- packages/ui/src/components/toggle.tsx | 8 +- packages/ui/src/components/tooltip.tsx | 64 ++--- .../components/caption.ts | 25 +- 32 files changed, 801 insertions(+), 731 deletions(-) diff --git a/apps/portal/src/content/docs/components/tooltip.mdx b/apps/portal/src/content/docs/components/tooltip.mdx index 39dd12300e..4823479c2f 100644 --- a/apps/portal/src/content/docs/components/tooltip.mdx +++ b/apps/portal/src/content/docs/components/tooltip.mdx @@ -16,7 +16,7 @@ import { DocsPage } from "../../../components/docs-page"; content="A comprehensive approach to building applications that covers both front-end and back-end technologies." > <Button>Trigger</Button> - </Tooltip.Root> + </Tooltip> </TooltipProvider>`} /> @@ -159,6 +159,12 @@ The provider component that manages tooltips. Typically wrapped around the entir required: true, value: "ReactNode", }, + { + name: "className", + description: "Custom class name for the tooltip content.", + required: false, + value: "string", + }, { name: "title", description: "String to use as the title of the tooltip.", diff --git a/packages/ui/src/components/accordion/accordion.tsx b/packages/ui/src/components/accordion/accordion.tsx index 247b2a0068..6f43e595fa 100644 --- a/packages/ui/src/components/accordion/accordion.tsx +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -1,4 +1,13 @@ -import { ComponentPropsWithoutRef, createContext, ElementRef, forwardRef, Fragment, ReactNode, useContext } from 'react' +import { + ComponentPropsWithoutRef, + createContext, + ElementRef, + forwardRef, + Fragment, + PropsWithoutRef, + ReactNode, + useContext +} from 'react' import { Card } from '@components/card' import { IconPropsV2, IconV2 } from '@components/icon-v2' @@ -93,7 +102,7 @@ AccordionItem.displayName = 'AccordionItem' type AccordionTriggerProps = ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & { prefix?: ReactNode suffix?: ReactNode - indicatorProps?: Omit<IconPropsV2, 'name' | 'fallback'> + indicatorProps?: PropsWithoutRef<Omit<IconPropsV2, 'name' | 'fallback'>> headerClassName?: string } diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx index 073124060c..880bf14200 100644 --- a/packages/ui/src/components/alert-dialog.tsx +++ b/packages/ui/src/components/alert-dialog.tsx @@ -1,4 +1,4 @@ -import { Children, createContext, isValidElement, ReactNode, useContext } from 'react' +import { Children, createContext, forwardRef, isValidElement, ReactNode, useContext } from 'react' import { Button, ButtonLayout, IconV2NamesType } from '@/components' @@ -53,7 +53,7 @@ interface ContentProps { children?: ReactNode } -const Content = ({ title, children }: ContentProps) => { +const Content = forwardRef<HTMLDivElement, ContentProps>(({ title, children }, ref) => { const context = useContext(AlertDialogContext) if (!context) throw new Error('AlertDialog.Content must be used within AlertDialog.Root') @@ -77,7 +77,7 @@ const Content = ({ title, children }: ContentProps) => { }) return ( - <Dialog.Content onOpenAutoFocus={event => event.preventDefault()}> + <Dialog.Content onOpenAutoFocus={event => event.preventDefault()} ref={ref}> <Dialog.Header icon={ context.theme === 'danger' ? 'xmark-circle' : context.theme === 'warning' ? 'warning-triangle' : undefined @@ -97,37 +97,41 @@ const Content = ({ title, children }: ContentProps) => { </Dialog.Footer> </Dialog.Content> ) -} +}) +Content.displayName = 'AlertDialog.Content' -const Cancel = ({ children = 'Cancel', ...props }: { children?: ReactNode }) => { +const Cancel = forwardRef<HTMLButtonElement, { children?: ReactNode }>(({ children = 'Cancel', ...props }, ref) => { const context = useContext(AlertDialogContext) if (!context) throw new Error('AlertDialog.Cancel must be used within AlertDialog.Root') return ( <Dialog.Close asChild> - <Button variant="secondary" disabled={context.loading} onClick={context.onCancel} {...props}> + <Button variant="secondary" disabled={context.loading} onClick={context.onCancel} ref={ref} {...props}> {children} </Button> </Dialog.Close> ) -} +}) Cancel.displayName = 'AlertDialog.Cancel' -const Confirm = ({ children = 'Confirm', ...props }: { children?: ReactNode; disabled?: boolean }) => { - const context = useContext(AlertDialogContext) - if (!context) throw new Error('AlertDialog.Confirm must be used within AlertDialog.Root') - - return ( - <Button - theme={context.theme === 'danger' ? 'danger' : undefined} - loading={context.loading} - onClick={context.onConfirm} - {...props} - > - {context.loading ? 'Loading...' : children} - </Button> - ) -} +const Confirm = forwardRef<HTMLButtonElement, { children?: ReactNode; disabled?: boolean }>( + ({ children = 'Confirm', ...props }, ref) => { + const context = useContext(AlertDialogContext) + if (!context) throw new Error('AlertDialog.Confirm must be used within AlertDialog.Root') + + return ( + <Button + ref={ref} + theme={context.theme === 'danger' ? 'danger' : undefined} + loading={context.loading} + onClick={context.onConfirm} + {...props} + > + {context.loading ? 'Loading...' : children} + </Button> + ) + } +) Confirm.displayName = 'AlertDialog.Confirm' export const AlertDialog = { diff --git a/packages/ui/src/components/breadcrumb.tsx b/packages/ui/src/components/breadcrumb.tsx index 05f4faca32..d2f50bf8d8 100644 --- a/packages/ui/src/components/breadcrumb.tsx +++ b/packages/ui/src/components/breadcrumb.tsx @@ -79,12 +79,12 @@ BreadcrumbSeparator.displayName = 'BreadcrumbSeparator' type BreadcrumbEllipsisProps = ComponentProps<'span'> -const BreadcrumbEllipsis = ({ className, ...props }: BreadcrumbEllipsisProps) => ( - <span role="presentation" aria-hidden="true" className={cn('cn-breadcrumb-ellipsis', className)} {...props}> +const BreadcrumbEllipsis = forwardRef<HTMLSpanElement, BreadcrumbEllipsisProps>(({ className, ...props }, ref) => ( + <span role="presentation" aria-hidden="true" className={cn('cn-breadcrumb-ellipsis', className)} {...props} ref={ref}> <IconV2 name="more-horizontal" skipSize /> <span className="sr-only">More</span> </span> -) +)) BreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis' type BreadcrumbCopyProps = ComponentPropsWithRef<typeof CopyButton> diff --git a/packages/ui/src/components/button-group.tsx b/packages/ui/src/components/button-group.tsx index 206472299e..b392e3af7d 100644 --- a/packages/ui/src/components/button-group.tsx +++ b/packages/ui/src/components/button-group.tsx @@ -1,4 +1,4 @@ -import { ComponentProps, FC, ReactNode } from 'react' +import { ComponentProps, FC, forwardRef, ReactNode } from 'react' import { Button, ButtonProps, DropdownMenu, Tooltip, TooltipProps } from '@/components' import { cn } from '@utils/cn' @@ -61,42 +61,40 @@ const Wrapper: FC<WrapperProps> = ({ children, tooltipProps, dropdownProps, orie return <>{children}</> } -export const ButtonGroup: FC<ButtonGroupProps> = ({ - orientation = 'horizontal', - buttonsProps, - size = 'md', - iconOnly, - className -}) => { - return ( - <div - className={cn( - 'cn-button-group', - orientation === 'vertical' ? 'cn-button-group-vertical' : 'cn-button-group-horizontal', - className - )} - > - {buttonsProps.map((buttonProps, index) => { - const { className, ...restButtonProps } = buttonProps - const tooltipProps = 'tooltipProps' in buttonProps ? buttonProps.tooltipProps : undefined - const dropdownProps = 'dropdownProps' in buttonProps ? buttonProps.dropdownProps : undefined +export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>( + ({ orientation = 'horizontal', buttonsProps, size = 'md', iconOnly, className }, ref) => { + return ( + <div + className={cn( + 'cn-button-group', + orientation === 'vertical' ? 'cn-button-group-vertical' : 'cn-button-group-horizontal', + className + )} + ref={ref} + > + {buttonsProps.map((buttonProps, index) => { + const { className, ...restButtonProps } = buttonProps + const tooltipProps = 'tooltipProps' in buttonProps ? buttonProps.tooltipProps : undefined + const dropdownProps = 'dropdownProps' in buttonProps ? buttonProps.dropdownProps : undefined - return ( - <Wrapper key={index} tooltipProps={tooltipProps} dropdownProps={dropdownProps} orientation={orientation}> - <Button - className={cn( - className, - { 'cn-button-group-first': !index }, - { 'cn-button-group-last': index === buttonsProps.length - 1 } - )} - variant="outline" - size={size} - iconOnly={iconOnly} - {...omit(restButtonProps, ['tooltipProps', 'dropdownProps'])} - /> - </Wrapper> - ) - })} - </div> - ) -} + return ( + <Wrapper key={index} tooltipProps={tooltipProps} dropdownProps={dropdownProps} orientation={orientation}> + <Button + className={cn( + className, + { 'cn-button-group-first': !index }, + { 'cn-button-group-last': index === buttonsProps.length - 1 } + )} + variant="outline" + size={size} + iconOnly={iconOnly} + {...omit(restButtonProps, ['tooltipProps', 'dropdownProps'])} + /> + </Wrapper> + ) + })} + </div> + ) + } +) +ButtonGroup.displayName = 'ButtonGroup' diff --git a/packages/ui/src/components/button-layout.tsx b/packages/ui/src/components/button-layout.tsx index 68fc3e0aeb..a840195a54 100644 --- a/packages/ui/src/components/button-layout.tsx +++ b/packages/ui/src/components/button-layout.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react' +import { forwardRef, HTMLAttributes, PropsWithChildren, ReactNode } from 'react' import { cn } from '@utils/cn' import { cva, VariantProps } from 'class-variance-authority' @@ -36,13 +36,13 @@ const ButtonLayoutRoot = ({ } ButtonLayoutRoot.displayName = 'ButtonLayoutRoot' -const ButtonLayoutPrimary = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-button-layout-primary', className)} {...props} /> +const ButtonLayoutPrimary = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => <div ref={ref} className={cn('cn-button-layout-primary', className)} {...props} /> ) ButtonLayoutPrimary.displayName = 'ButtonLayoutPrimary' -const ButtonLayoutSecondary = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-button-layout-secondary', className)} {...props} /> +const ButtonLayoutSecondary = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => <div ref={ref} className={cn('cn-button-layout-secondary', className)} {...props} /> ) ButtonLayoutSecondary.displayName = 'ButtonLayoutSecondary' diff --git a/packages/ui/src/components/copy-button/copy-button.tsx b/packages/ui/src/components/copy-button/copy-button.tsx index e82d9a2860..8cc17cf0bb 100644 --- a/packages/ui/src/components/copy-button/copy-button.tsx +++ b/packages/ui/src/components/copy-button/copy-button.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { forwardRef } from 'react' import { Button, ButtonSizes, useCopyButton, UseCopyButtonProps, type ButtonVariants } from '@/components' @@ -10,29 +10,26 @@ export interface CopyButtonProps extends Omit<UseCopyButtonProps, 'copyData'> { iconOnly?: boolean } -export const CopyButton: FC<CopyButtonProps> = ({ - name, - className, - buttonVariant = 'outline', - iconSize = 'sm', - size = 'sm', - onClick, - color, - iconOnly = false -}) => { - const { copyButtonProps, CopyIcon } = useCopyButton({ onClick, copyData: name, iconSize, color }) +export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>( + ( + { name, className, buttonVariant = 'outline', iconSize = 'sm', size = 'sm', onClick, color, iconOnly = false }, + ref + ) => { + const { copyButtonProps, CopyIcon } = useCopyButton({ onClick, copyData: name, iconSize, color }) - return ( - <Button - className={className} - type="button" - variant={buttonVariant} - size={size} - iconOnly={iconOnly} - {...copyButtonProps} - > - {CopyIcon} - </Button> - ) -} + return ( + <Button + className={className} + type="button" + variant={buttonVariant} + size={size} + iconOnly={iconOnly} + {...copyButtonProps} + ref={ref} + > + {CopyIcon} + </Button> + ) + } +) CopyButton.displayName = 'CopyButton' diff --git a/packages/ui/src/components/counter-badge.tsx b/packages/ui/src/components/counter-badge.tsx index 6412156336..2b57a8371c 100644 --- a/packages/ui/src/components/counter-badge.tsx +++ b/packages/ui/src/components/counter-badge.tsx @@ -1,3 +1,5 @@ +import { forwardRef } from 'react' + import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' @@ -22,21 +24,24 @@ type CounterBadgeProps = Omit< theme?: VariantProps<typeof counterBadgeVariants>['theme'] } -function CounterBadge({ className, theme = 'default', children, ...props }: CounterBadgeProps) { - return ( - <div - className={cn( - counterBadgeVariants({ - theme - }), - className - )} - {...props} - > - {children} - </div> - ) -} +const CounterBadge = forwardRef<HTMLDivElement, CounterBadgeProps>( + ({ className, theme = 'default', children, ...props }, ref) => { + return ( + <div + ref={ref} + className={cn( + counterBadgeVariants({ + theme + }), + className + )} + {...props} + > + {children} + </div> + ) + } +) CounterBadge.displayName = 'CounterBadge' export { CounterBadge, counterBadgeVariants } diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index fade93d072..741012c2e3 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -87,53 +87,60 @@ interface HeaderProps extends HTMLAttributes<HTMLDivElement> { theme?: 'default' | 'warning' | 'danger' } -const Header = ({ className, icon, logo, theme = 'default', children, ...props }: HeaderProps) => { - if (icon && logo) { - console.warn('Dialog.Header: Cannot use both icon and logo props together') - return null - } - - // Find the title and description from children - let title: React.ReactNode = null - let description: React.ReactNode = null +const Header = forwardRef<HTMLDivElement, HeaderProps>( + ({ className, icon, logo, theme = 'default', children, ...props }, ref) => { + if (icon && logo) { + console.warn('Dialog.Header: Cannot use both icon and logo props together') + return null + } - Children.forEach(children, child => { - if (isValidElement(child)) { - if (child.type === Title) { - title = child - } else if (child.type === Description) { - description = child + // Find the title and description from children + let title: React.ReactNode = null + let description: React.ReactNode = null + + Children.forEach(children, child => { + if (isValidElement(child)) { + if (child.type === Title) { + title = child + } else if (child.type === Description) { + description = child + } } - } - }) - - return ( - <div className={cn(headerVariants({ theme }), className)} {...props}> - <div className="cn-modal-dialog-header-title-row"> - {icon && ( - <div className="cn-modal-dialog-header-icon"> - <IconV2 name={icon} size="xl" /> - </div> - )} - {logo && ( - <div className="cn-modal-dialog-header-logo"> - <LogoV2 name={logo} size="md" /> - </div> - )} - {title} - </div> - {description} - </div> - ) -} + }) -const Title = ({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) => ( - <DialogPrimitive.Title className={cn('cn-modal-dialog-title', className)} {...props} /> + return ( + <div className={cn(headerVariants({ theme }), className)} ref={ref} {...props}> + <div className="cn-modal-dialog-header-title-row"> + {icon && ( + <div className="cn-modal-dialog-header-icon"> + <IconV2 name={icon} size="xl" /> + </div> + )} + {logo && ( + <div className="cn-modal-dialog-header-logo"> + <LogoV2 name={logo} size="md" /> + </div> + )} + {title} + </div> + {description} + </div> + ) + } ) +Header.displayName = 'Dialog.Header' -const Description = ({ className, ...props }: HTMLAttributes<HTMLParagraphElement>) => ( - <DialogPrimitive.Description className={cn('cn-modal-dialog-description', className)} {...props} /> +const Title = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => ( + <DialogPrimitive.Title className={cn('cn-modal-dialog-title', className)} {...props} ref={ref} /> +)) +Title.displayName = 'Dialog.Title' + +const Description = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>( + ({ className, ...props }, ref) => ( + <DialogPrimitive.Description className={cn('cn-modal-dialog-description', className)} {...props} ref={ref} /> + ) ) +Description.displayName = 'Dialog.Description' interface BodyProps { className?: string @@ -141,19 +148,22 @@ interface BodyProps { children: ReactNode } -const Body = ({ className, classNameContent, children, ...props }: BodyProps) => ( +const Body = forwardRef<HTMLDivElement, BodyProps>(({ className, classNameContent, children, ...props }, ref) => ( <ScrollArea className={cn('cn-modal-dialog-body', className)} classNameContent={cn('cn-modal-dialog-body-content', classNameContent)} + ref={ref} {...props} > {children} </ScrollArea> -) +)) +Body.displayName = 'Dialog.Body' -const Footer = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-modal-dialog-footer', className)} {...props} /> -) +const Footer = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => ( + <div className={cn('cn-modal-dialog-footer', className)} {...props} ref={ref} /> +)) +Footer.displayName = 'Dialog.Footer' const Close = forwardRef<HTMLButtonElement, ButtonProps>(({ children, className, ...props }, ref) => ( <DialogPrimitive.Close asChild> diff --git a/packages/ui/src/components/drawer/Drawer.Tagline.tsx b/packages/ui/src/components/drawer/Drawer.Tagline.tsx index 64b7cf372e..3668b58881 100644 --- a/packages/ui/src/components/drawer/Drawer.Tagline.tsx +++ b/packages/ui/src/components/drawer/Drawer.Tagline.tsx @@ -1,8 +1,8 @@ -import { HTMLAttributes } from 'react' +import { forwardRef, HTMLAttributes } from 'react' import { cn } from '@/utils' -export const DrawerTagline = ({ className, ...props }: HTMLAttributes<HTMLSpanElement>) => ( - <span className={cn('cn-drawer-tagline', className)} {...props} /> +export const DrawerTagline = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>( + ({ className, ...props }, ref) => <span className={cn('cn-drawer-tagline', className)} {...props} ref={ref} /> ) DrawerTagline.displayName = 'DrawerTagline' diff --git a/packages/ui/src/components/drawer/DrawerBody.tsx b/packages/ui/src/components/drawer/DrawerBody.tsx index 35260e906f..27d2c78252 100644 --- a/packages/ui/src/components/drawer/DrawerBody.tsx +++ b/packages/ui/src/components/drawer/DrawerBody.tsx @@ -1,28 +1,33 @@ +import { forwardRef } from 'react' + import { ScrollArea, ScrollAreaProps, useScrollArea } from '@/components' import { cn } from '@/utils' -export const DrawerBody = (props: ScrollAreaProps & { scrollAreaClassName?: string }) => { - const { isTop, isBottom, onScrollTop, onScrollBottom } = useScrollArea(props) - const { className, children, scrollAreaClassName, classNameContent, ...restProps } = props +export const DrawerBody = forwardRef<HTMLDivElement, ScrollAreaProps & { scrollAreaClassName?: string }>( + (props, ref) => { + const { isTop, isBottom, onScrollTop, onScrollBottom } = useScrollArea(props) + const { className, children, scrollAreaClassName, classNameContent, ...restProps } = props - return ( - <div - className={cn( - 'cn-drawer-body-wrap', - { 'cn-drawer-body-wrap-top': isTop, 'cn-drawer-body-wrap-bottom': isBottom }, - className - )} - > - <ScrollArea - {...restProps} - onScrollTop={onScrollTop} - onScrollBottom={onScrollBottom} - className={cn('cn-drawer-body', scrollAreaClassName)} - classNameContent={cn('cn-drawer-body-content', classNameContent)} + return ( + <div + className={cn( + 'cn-drawer-body-wrap', + { 'cn-drawer-body-wrap-top': isTop, 'cn-drawer-body-wrap-bottom': isBottom }, + className + )} > - {children} - </ScrollArea> - </div> - ) -} + <ScrollArea + ref={ref} + {...restProps} + onScrollTop={onScrollTop} + onScrollBottom={onScrollBottom} + className={cn('cn-drawer-body', scrollAreaClassName)} + classNameContent={cn('cn-drawer-body-content', classNameContent)} + > + {children} + </ScrollArea> + </div> + ) + } +) DrawerBody.displayName = 'DrawerBody' diff --git a/packages/ui/src/components/drawer/DrawerFooter.tsx b/packages/ui/src/components/drawer/DrawerFooter.tsx index f06e8a7ad7..e210149c81 100644 --- a/packages/ui/src/components/drawer/DrawerFooter.tsx +++ b/packages/ui/src/components/drawer/DrawerFooter.tsx @@ -1,8 +1,8 @@ -import { HTMLAttributes } from 'react' +import { forwardRef, HTMLAttributes } from 'react' import { cn } from '@/utils' -export const DrawerFooter = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-drawer-footer', className)} {...props} /> +export const DrawerFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => <div className={cn('cn-drawer-footer', className)} ref={ref} {...props} /> ) DrawerFooter.displayName = 'DrawerFooter' diff --git a/packages/ui/src/components/drawer/DrawerHeader.tsx b/packages/ui/src/components/drawer/DrawerHeader.tsx index c9995adb14..1fbe59ace2 100644 --- a/packages/ui/src/components/drawer/DrawerHeader.tsx +++ b/packages/ui/src/components/drawer/DrawerHeader.tsx @@ -1,4 +1,4 @@ -import { Children, HTMLAttributes, ReactNode } from 'react' +import { Children, forwardRef, HTMLAttributes, ReactNode } from 'react' import { IconV2, IconV2NamesType, LogoV2, LogoV2NamesType } from '@/components' import { cn, getComponentDisplayName } from '@/utils' @@ -28,39 +28,41 @@ type DrawerHeaderNoIconOrLogoProps = { export type DrawerHeaderProps = DrawerHeaderBaseProps & (DrawerHeaderIconOnlyProps | DrawerHeaderLogoOnlyProps | DrawerHeaderNoIconOrLogoProps) -export const DrawerHeader = ({ className, children, icon, logo, ...props }: DrawerHeaderProps) => { - const IconOrLogoComp = - (!!icon && <IconV2 className="cn-drawer-header-icon cn-drawer-header-icon-color" name={icon} size="xl" />) || - (!!logo && <LogoV2 className="cn-drawer-header-icon" name={logo} size="md" />) || - null +export const DrawerHeader = forwardRef<HTMLDivElement, DrawerHeaderProps>( + ({ className, children, icon, logo, ...props }, ref) => { + const IconOrLogoComp = + (!!icon && <IconV2 className="cn-drawer-header-icon cn-drawer-header-icon-color" name={icon} size="xl" />) || + (!!logo && <LogoV2 className="cn-drawer-header-icon" name={logo} size="md" />) || + null - const { titleChildren, otherChildren } = Children.toArray(children).reduce<{ - titleChildren: ReactNode[] - otherChildren: ReactNode[] - }>( - (acc, child) => { - const displayName = getComponentDisplayName(child) + const { titleChildren, otherChildren } = Children.toArray(children).reduce<{ + titleChildren: ReactNode[] + otherChildren: ReactNode[] + }>( + (acc, child) => { + const displayName = getComponentDisplayName(child) - if (displayName === DrawerPrimitive.Title.displayName || displayName === DrawerTagline.displayName) { - acc.titleChildren.push(child) - } else { - acc.otherChildren.push(child) - } - return acc - }, - { titleChildren: [], otherChildren: [] } - ) + if (displayName === DrawerPrimitive.Title.displayName || displayName === DrawerTagline.displayName) { + acc.titleChildren.push(child) + } else { + acc.otherChildren.push(child) + } + return acc + }, + { titleChildren: [], otherChildren: [] } + ) - return ( - <div className={cn('cn-drawer-header', className)} {...props}> - {(!!titleChildren.length || !!IconOrLogoComp) && ( - <div className="cn-drawer-header-top"> - {IconOrLogoComp} - <div className="cn-drawer-header-title">{titleChildren}</div> - </div> - )} - {otherChildren} - </div> - ) -} + return ( + <div className={cn('cn-drawer-header', className)} ref={ref} {...props}> + {(!!titleChildren.length || !!IconOrLogoComp) && ( + <div className="cn-drawer-header-top"> + {IconOrLogoComp} + <div className="cn-drawer-header-title">{titleChildren}</div> + </div> + )} + {otherChildren} + </div> + ) + } +) DrawerHeader.displayName = 'DrawerHeader' diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index 58a7104f77..e40fb0174d 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -402,20 +402,22 @@ const DropdownMenuSeparator = forwardRef< )) DropdownMenuSeparator.displayName = displayNames.separator -const DropdownMenuHeader = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-dropdown-menu-header', className)} {...props} /> +const DropdownMenuHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => <div className={cn('cn-dropdown-menu-header', className)} ref={ref} {...props} /> ) DropdownMenuHeader.displayName = displayNames.header -const DropdownMenuFooter = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-dropdown-menu-footer', className)} {...props} /> +const DropdownMenuFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => <div className={cn('cn-dropdown-menu-footer', className)} ref={ref} {...props} /> ) DropdownMenuFooter.displayName = displayNames.footer -const DropdownMenuSpinner = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('cn-dropdown-menu-spinner', className)} {...props}> - <IconV2 className="animate-spin" name="loader" /> - </div> +const DropdownMenuSpinner = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( + ({ className, ...props }, ref) => ( + <div className={cn('cn-dropdown-menu-spinner', className)} ref={ref} {...props}> + <IconV2 className="animate-spin" name="loader" /> + </div> + ) ) DropdownMenuSpinner.displayName = displayNames.spinner @@ -430,7 +432,11 @@ const DropdownMenuNoOptions = ({ className, children, ...props }: Omit<TextProps } DropdownMenuNoOptions.displayName = displayNames.noOptions -const DropdownMenuSlot = (props: HTMLAttributes<HTMLDivElement>) => <div {...props} /> +const DropdownMenuSlot = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>((props, ref) => ( + <div ref={ref} {...props}> + {props.children} + </div> +)) DropdownMenuSlot.displayName = displayNames.slot const DropdownMenu = { diff --git a/packages/ui/src/components/form-primitives/form-caption.tsx b/packages/ui/src/components/form-primitives/form-caption.tsx index 4b66ab39b5..9b7e405a5d 100644 --- a/packages/ui/src/components/form-primitives/form-caption.tsx +++ b/packages/ui/src/components/form-primitives/form-caption.tsx @@ -1,56 +1,53 @@ -import { PropsWithChildren } from 'react' +import { forwardRef, PropsWithChildren } from 'react' import { IconV2 } from '@components/icon-v2' -import { cn } from '@utils/cn' -import { cva, VariantProps } from 'class-variance-authority' - -const formCaptionVariants = cva('cn-caption', { - variants: { - theme: { - default: '', - success: 'cn-caption-success', - danger: 'cn-caption-danger', - warning: 'cn-caption-warning' - } - }, - defaultVariants: { - theme: 'default' - } -}) +import { Text, textVariants } from '@components/text' +import { VariantProps } from 'class-variance-authority' + +type ThemeVariants = 'default' | 'success' | 'danger' | 'warning' +type TextColorVariants = VariantProps<typeof textVariants>['color'] type FormCaptionProps = { - theme?: VariantProps<typeof formCaptionVariants>['theme'] + theme?: ThemeVariants className?: string disabled?: boolean } -export const FormCaption = ({ - theme = 'default', - className, - disabled, - children -}: PropsWithChildren<FormCaptionProps>) => { - /** - * Return null if no message, errorMessage, or warningMessage is provided - */ - if (!children) { - return null - } - - const canShowIcon = theme === 'danger' || theme === 'warning' +export const FormCaption = forwardRef<HTMLParagraphElement, PropsWithChildren<FormCaptionProps>>( + ({ theme = 'default', className, disabled, children }, ref) => { + /** + * Return null if no message, errorMessage, or warningMessage is provided + */ + if (!children) { + return null + } - /** - * cross-circle - danger - * triangle-warning - warning - */ - const effectiveIconName = theme === 'danger' ? 'xmark-circle' : 'warning-triangle' + const canShowIcon = theme === 'danger' || theme === 'warning' + + /** + * cross-circle - danger + * triangle-warning - warning + */ + const effectiveIconName = theme === 'danger' ? 'xmark-circle' : 'warning-triangle' + + const getColor = (theme: ThemeVariants, disabled?: boolean): TextColorVariants => { + if (disabled) return 'disabled' + const colorMap: Record<ThemeVariants, TextColorVariants> = { + default: 'foreground-3', + success: 'success', + danger: 'danger', + warning: 'warning' + } + return colorMap[theme] + } - return ( - <p className={cn(formCaptionVariants({ theme }), { 'cn-caption-disabled': disabled }, className)}> - {canShowIcon && <IconV2 name={effectiveIconName} size="md" />} - <span>{children}</span> - </p> - ) -} + return ( + <Text color={getColor(theme, disabled)} className={className} ref={ref}> + {canShowIcon && <IconV2 name={effectiveIconName} size="md" />} + <span>{children}</span> + </Text> + ) + } +) FormCaption.displayName = 'FormCaption' diff --git a/packages/ui/src/components/icon-v2/icon-v2.tsx b/packages/ui/src/components/icon-v2/icon-v2.tsx index b5e4ace43f..e7ad3aaa33 100644 --- a/packages/ui/src/components/icon-v2/icon-v2.tsx +++ b/packages/ui/src/components/icon-v2/icon-v2.tsx @@ -1,4 +1,4 @@ -import { FC, SVGProps } from 'react' +import { forwardRef, SVGProps } from 'react' import { cn } from '@utils/cn' import { cva, VariantProps } from 'class-variance-authority' @@ -41,24 +41,26 @@ interface IconFallbackPropsV2 extends BaseIconPropsV2 { export type IconPropsV2 = IconDefaultPropsV2 | IconFallbackPropsV2 -const IconV2: FC<IconPropsV2> = ({ name, size = 'sm', className, skipSize = false, fallback }) => { - const Component = name ? IconNameMapV2[name] : undefined - const sizeClasses = skipSize ? '' : iconVariants({ size }) +const IconV2 = forwardRef<SVGSVGElement, IconPropsV2>( + ({ name, size = 'sm', className, skipSize = false, fallback }, ref) => { + const Component = name ? IconNameMapV2[name] : undefined + const sizeClasses = skipSize ? '' : iconVariants({ size }) - if (!Component && fallback) { - console.warn(`Icon "${name}" not found, falling back to "${fallback}".`) - const FallbackComponent = IconNameMapV2[fallback] + if (!Component && fallback) { + console.warn(`Icon "${name}" not found, falling back to "${fallback}".`) + const FallbackComponent = IconNameMapV2[fallback] - return <FallbackComponent className={cn(sizeClasses, className)} /> - } + return <FallbackComponent className={cn(sizeClasses, className)} ref={ref} /> + } - if (!Component) { - console.warn(`Icon "${name}" not found in IconNameMapV2.`) - return null - } + if (!Component) { + console.warn(`Icon "${name}" not found in IconNameMapV2.`) + return null + } - return <Component className={cn(sizeClasses, className)} /> -} + return <Component className={cn(sizeClasses, className)} ref={ref} /> + } +) export { IconV2 } diff --git a/packages/ui/src/components/illustration/illustration.tsx b/packages/ui/src/components/illustration/illustration.tsx index 7d5463cd6d..de3d4525eb 100644 --- a/packages/ui/src/components/illustration/illustration.tsx +++ b/packages/ui/src/components/illustration/illustration.tsx @@ -1,4 +1,4 @@ -import { FC, SVGProps } from 'react' +import { forwardRef, SVGProps } from 'react' import { useTheme } from '@/context' import { cn } from '@utils/cn' @@ -14,39 +14,33 @@ export interface IllustrationProps extends SVGProps<SVGSVGElement> { themeDependent?: boolean } -const Illustration: FC<IllustrationProps> = ({ - name, - size = 112, - height, - width, - className, - themeDependent = false -}) => { - const { isLightTheme } = useTheme() - - const isLightIconAvailable = !!IllustrationsNameMap[`${name}-light` as keyof typeof IllustrationsNameMap] - - const Component = - themeDependent && isLightTheme && isLightIconAvailable - ? IllustrationsNameMap[`${name}-light` as keyof typeof IllustrationsNameMap] - : IllustrationsNameMap[name] - - if (!Component) { - console.warn(`Icon "${name}" not found in IllustrationsNameMap.`) - return null - } +const Illustration = forwardRef<SVGSVGElement, IllustrationProps>( + ({ name, size = 112, height, width, className, themeDependent = false }, ref) => { + const { isLightTheme } = useTheme() - const shouldInvert = themeDependent && isLightTheme && !isLightIconAvailable + const isLightIconAvailable = !!IllustrationsNameMap[`${name}-light` as keyof typeof IllustrationsNameMap] - const sizeProps = { - width: width || size, - height: height || size, - style: { minWidth: `${width || size}px`, minHeight: `${height || size}px` } - } + const Component = + themeDependent && isLightTheme && isLightIconAvailable + ? IllustrationsNameMap[`${name}-light` as keyof typeof IllustrationsNameMap] + : IllustrationsNameMap[name] - return <Component className={cn({ invert: shouldInvert }, className)} {...sizeProps} /> -} + if (!Component) { + console.warn(`Icon "${name}" not found in IllustrationsNameMap.`) + return null + } -export { Illustration } + const shouldInvert = themeDependent && isLightTheme && !isLightIconAvailable + const sizeProps = { + width: width || size, + height: height || size, + style: { minWidth: `${width || size}px`, minHeight: `${height || size}px` } + } + + return <Component ref={ref} className={cn({ invert: shouldInvert }, className)} {...sizeProps} /> + } +) Illustration.displayName = 'Illustration' + +export { Illustration } diff --git a/packages/ui/src/components/informer.tsx b/packages/ui/src/components/informer.tsx index b17520b7ff..4ef3399938 100644 --- a/packages/ui/src/components/informer.tsx +++ b/packages/ui/src/components/informer.tsx @@ -1,3 +1,5 @@ +import { PropsWithoutRef } from 'react' + import { IconPropsV2, IconV2, Tooltip, TooltipProps } from '@/components' import { cn } from '@/utils' @@ -13,7 +15,7 @@ export interface InformerProps extends Omit<TooltipProps, 'children'> { export const Informer = ({ className, disabled, iconProps, ...props }: InformerProps) => ( <Tooltip {...props}> <button className={cn({ 'pointer-events-none': disabled }, className)} disabled={disabled}> - <IconV2 name="info-circle" {...iconProps} /> + <IconV2 name="info-circle" {...(iconProps as PropsWithoutRef<IconPropsV2>)} /> </button> </Tooltip> ) diff --git a/packages/ui/src/components/logo-v2/logo-v2.tsx b/packages/ui/src/components/logo-v2/logo-v2.tsx index 7b5088fdb2..999742bf72 100644 --- a/packages/ui/src/components/logo-v2/logo-v2.tsx +++ b/packages/ui/src/components/logo-v2/logo-v2.tsx @@ -1,4 +1,4 @@ -import { FC, SVGProps } from 'react' +import { forwardRef, SVGProps } from 'react' import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' @@ -37,7 +37,7 @@ interface LogoFallbackPropsV2 extends BaseLogoPropsV2 { export type LogoPropsV2 = LogoDefaultPropsV2 | LogoFallbackPropsV2 -const LogoV2: FC<LogoPropsV2> = ({ name, size, className, skipSize = false, fallback }) => { +const LogoV2 = forwardRef<SVGSVGElement, LogoPropsV2>(({ name, size, className, skipSize = false, fallback }, ref) => { const Component = name ? LogoNameMapV2[name] : undefined const sizeClasses = skipSize ? '' : logoVariants({ size }) @@ -45,7 +45,7 @@ const LogoV2: FC<LogoPropsV2> = ({ name, size, className, skipSize = false, fall console.warn(`Logo "${name}" not found, falling back to "${fallback}".`) const FallbackComponent = LogoNameMapV2[fallback] - return <FallbackComponent className={cn(sizeClasses, className)} /> + return <FallbackComponent className={cn(sizeClasses, className)} ref={ref} /> } if (!Component) { @@ -53,8 +53,8 @@ const LogoV2: FC<LogoPropsV2> = ({ name, size, className, skipSize = false, fall return null } - return <Component className={cn(sizeClasses, className)} /> -} + return <Component className={cn(sizeClasses, className)} ref={ref} /> +}) export { LogoV2 } diff --git a/packages/ui/src/components/progress.tsx b/packages/ui/src/components/progress.tsx index cb70f184c2..a4a68592bc 100644 --- a/packages/ui/src/components/progress.tsx +++ b/packages/ui/src/components/progress.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from 'react' +import { forwardRef, useMemo } from 'react' import { cn } from '@/utils/cn' import { Text } from '@components/text' @@ -58,110 +58,116 @@ interface IndeterminateProgressProps extends CommonProgressProps { type ProgressProps = DeterminateProgressProps | IndeterminateProgressProps -const Progress: FC<ProgressProps> = ({ - id: defaultId, - variant = 'default', - state, - size = 'md', - hideIcon = false, - label, - description, - subtitle, - hidePercentage = false, - value = 0, - className, - hideContainer = false -}) => { - const percentageValue = Math.min(Math.max(0, value), 1) * 100 || 0 - - const id = useMemo(() => defaultId || `progress-${generateAlphaNumericHash(10)}`, [defaultId]) - - const getIcon = () => { - if (hideIcon || variant === 'indeterminate') return null - const iconName: IconV2NamesType = getIconName(state) - return <IconV2 className="cn-progress-icon" name={iconName} skipSize /> - } +const Progress = forwardRef<HTMLProgressElement, ProgressProps>( + ( + { + id: defaultId, + variant = 'default', + state, + size = 'md', + hideIcon = false, + label, + description, + subtitle, + hidePercentage = false, + value = 0, + className, + hideContainer = false + }, + ref + ) => { + const percentageValue = Math.min(Math.max(0, value), 1) * 100 || 0 + + const id = useMemo(() => defaultId || `progress-${generateAlphaNumericHash(10)}`, [defaultId]) + + const getIcon = () => { + if (hideIcon || variant === 'indeterminate') return null + const iconName: IconV2NamesType = getIconName(state) + return <IconV2 className="cn-progress-icon" name={iconName} skipSize /> + } + + const getProgress = () => { + if (variant === 'indeterminate') { + return ( + <> + <progress ref={ref} className="cn-progress-root" id={id} /> + <div className="cn-progress-overlay-box"> + <div className="cn-progress-overlay"> + <div className="cn-progress-indeterminate-fake" /> + </div> + </div> + </> + ) + } - const getProgress = () => { - if (variant === 'indeterminate') { return ( <> - <progress className="cn-progress-root" id={id} /> - <div className="cn-progress-overlay-box"> - <div className="cn-progress-overlay"> - <div className="cn-progress-indeterminate-fake" /> + <progress + ref={ref} + className="cn-progress-root" + id={id} + value={percentageValue} + max={hideContainer ? percentageValue : 100} + /> + + {state === 'processing' && ( + <div className="cn-progress-overlay-box"> + <div className="cn-progress-overlay"> + <div + className="cn-progress-processing-fake" + style={{ transform: `translateX(-${100 - percentageValue}%)` }} + /> + </div> </div> - </div> + )} </> ) } return ( - <> - <progress - className="cn-progress-root" - id={id} - value={percentageValue} - max={hideContainer ? percentageValue : 100} - /> - - {state === 'processing' && ( - <div className="cn-progress-overlay-box"> - <div className="cn-progress-overlay"> - <div - className="cn-progress-processing-fake" - style={{ transform: `translateX(-${100 - percentageValue}%)` }} - /> + <div className={cn(progressVariants({ size, state }), className)}> + {(label || !hidePercentage || !hideIcon) && ( + <label className="cn-progress-header" htmlFor={id}> + <div className="cn-progress-header-left"> + {label && ( + <Text variant="body-strong" color="foreground-1" truncate> + {label} + </Text> + )} </div> - </div> + <div className="cn-progress-header-right"> + {!hidePercentage && variant === 'default' && ( + <Text variant="body-strong" color="foreground-1"> + {percentageValue}% + </Text> + )} + {getIcon()} + </div> + </label> )} - </> - ) - } - return ( - <div className={cn(progressVariants({ size, state }), className)}> - {(label || !hidePercentage || !hideIcon) && ( - <label className="cn-progress-header" htmlFor={id}> - <div className="cn-progress-header-left"> - {label && ( - <Text variant="body-strong" color="foreground-1" truncate> - {label} - </Text> - )} - </div> - <div className="cn-progress-header-right"> - {!hidePercentage && variant === 'default' && ( - <Text variant="body-strong" color="foreground-1"> - {percentageValue}% - </Text> - )} - {getIcon()} - </div> - </label> - )} - - <div className="cn-progress-container">{getProgress()}</div> + <div className="cn-progress-container">{getProgress()}</div> - {(description || subtitle) && ( - <div className="cn-progress-footer"> - <div className="cn-progress-description-wrap"> - {description && ( - <Text className="cn-progress-description" variant="body-strong" color="foreground-3" truncate> - {description} + {(description || subtitle) && ( + <div className="cn-progress-footer"> + <div className="cn-progress-description-wrap"> + {description && ( + <Text className="cn-progress-description" variant="body-strong" color="foreground-3" truncate> + {description} + </Text> + )} + </div> + {subtitle && ( + <Text className="cn-progress-subtitle" align="right" variant="body-normal" color="foreground-3" truncate> + {subtitle} </Text> )} </div> - {subtitle && ( - <Text className="cn-progress-subtitle" align="right" variant="body-normal" color="foreground-3" truncate> - {subtitle} - </Text> - )} - </div> - )} - </div> - ) -} + )} + </div> + ) + } +) Progress.displayName = 'Progress' diff --git a/packages/ui/src/components/scroll-area.tsx b/packages/ui/src/components/scroll-area.tsx index 5d951f932f..962789eade 100644 --- a/packages/ui/src/components/scroll-area.tsx +++ b/packages/ui/src/components/scroll-area.tsx @@ -1,4 +1,4 @@ -import { FC, HTMLAttributes, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react' +import { forwardRef, HTMLAttributes, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react' import { cn } from '@utils/cn' @@ -22,91 +22,98 @@ export type ScrollAreaProps = { } & ScrollAreaIntersectionProps & HTMLAttributes<HTMLDivElement> -const ScrollArea: FC<ScrollAreaProps> = ({ - children, - onScrollTop, - onScrollBottom, - onScrollLeft, - onScrollRight, - - rootMargin = '0px', - threshold = 0.1, - - direction, - className, - classNameContent, - - ...rest -}) => { - const viewportRef = useRef<HTMLDivElement | null>(null) - - const topMarkerRef = useRef<HTMLDivElement | null>(null) - const bottomMarkerRef = useRef<HTMLDivElement | null>(null) - const leftMarkerRef = useRef<HTMLDivElement | null>(null) - const rightMarkerRef = useRef<HTMLDivElement | null>(null) - - const createObserver = useCallback( - ( - markerRef: RefObject<HTMLElement>, - callback?: (entry: IntersectionObserverEntry) => void - ): IntersectionObserver | null => { - if (!markerRef.current || !callback) return null - - if (typeof rootMargin === 'object') { - const { top, right, bottom, left } = rootMargin - rootMargin = `${top || '0px'} ${right || '0px'} ${bottom || '0px'} ${left || '0px'}` - } +const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>( + ( + { + children, + onScrollTop, + onScrollBottom, + onScrollLeft, + onScrollRight, - const observer = new IntersectionObserver( - ([entry]) => { - callback(entry) - }, - { - root: viewportRef.current, - rootMargin, - threshold - } - ) + rootMargin = '0px', + threshold = 0.1, + + direction, + className, + classNameContent, - observer.observe(markerRef.current) - return observer + ...rest }, - [rootMargin, threshold] - ) + ref + ) => { + const viewportRef = useRef<HTMLDivElement | null>(null) + + const topMarkerRef = useRef<HTMLDivElement | null>(null) + const bottomMarkerRef = useRef<HTMLDivElement | null>(null) + const leftMarkerRef = useRef<HTMLDivElement | null>(null) + const rightMarkerRef = useRef<HTMLDivElement | null>(null) + + const createObserver = useCallback( + ( + markerRef: RefObject<HTMLElement>, + callback?: (entry: IntersectionObserverEntry) => void + ): IntersectionObserver | null => { + if (!markerRef.current || !callback) return null + + if (typeof rootMargin === 'object') { + const { top, right, bottom, left } = rootMargin + rootMargin = `${top || '0px'} ${right || '0px'} ${bottom || '0px'} ${left || '0px'}` + } - useEffect(() => { - const observers: IntersectionObserver[] = [] - - const configs: [RefObject<HTMLElement>, IntersectionObserverEntryCallback | undefined][] = [ - [topMarkerRef, onScrollTop], - [bottomMarkerRef, onScrollBottom], - [leftMarkerRef, onScrollLeft], - [rightMarkerRef, onScrollRight] - ] - - for (const [ref, cb] of configs) { - const obs = createObserver(ref, cb) - if (obs) observers.push(obs) - } - - return () => { - observers.forEach(o => o.disconnect()) - } - }, [createObserver, onScrollTop, onScrollBottom, onScrollLeft, onScrollRight]) - - return ( - <div className={cn('cn-scroll-area', className)} dir={direction} ref={viewportRef} {...rest}> - <div className={cn('cn-scroll-area-content', classNameContent)}> - {children} - - {onScrollTop && <div className="cn-scroll-area-marker cn-scroll-area-marker-top" ref={topMarkerRef} />} - {onScrollBottom && <div className="cn-scroll-area-marker cn-scroll-area-marker-bottom" ref={bottomMarkerRef} />} - {onScrollLeft && <div className="cn-scroll-area-marker cn-scroll-area-marker-left" ref={leftMarkerRef} />} - {onScrollRight && <div className="cn-scroll-area-marker cn-scroll-area-marker-right" ref={rightMarkerRef} />} + const observer = new IntersectionObserver( + ([entry]) => { + callback(entry) + }, + { + root: viewportRef.current, + rootMargin, + threshold + } + ) + + observer.observe(markerRef.current) + return observer + }, + [rootMargin, threshold] + ) + + useEffect(() => { + const observers: IntersectionObserver[] = [] + + const configs: [RefObject<HTMLElement>, IntersectionObserverEntryCallback | undefined][] = [ + [topMarkerRef, onScrollTop], + [bottomMarkerRef, onScrollBottom], + [leftMarkerRef, onScrollLeft], + [rightMarkerRef, onScrollRight] + ] + + for (const [ref, cb] of configs) { + const obs = createObserver(ref, cb) + if (obs) observers.push(obs) + } + + return () => { + observers.forEach(o => o.disconnect()) + } + }, [createObserver, onScrollTop, onScrollBottom, onScrollLeft, onScrollRight]) + + return ( + <div className={cn('cn-scroll-area', className)} dir={direction} ref={viewportRef}> + <div className={cn('cn-scroll-area-content', classNameContent)} ref={ref} {...rest}> + {children} + + {onScrollTop && <div className="cn-scroll-area-marker cn-scroll-area-marker-top" ref={topMarkerRef} />} + {onScrollBottom && ( + <div className="cn-scroll-area-marker cn-scroll-area-marker-bottom" ref={bottomMarkerRef} /> + )} + {onScrollLeft && <div className="cn-scroll-area-marker cn-scroll-area-marker-left" ref={leftMarkerRef} />} + {onScrollRight && <div className="cn-scroll-area-marker cn-scroll-area-marker-right" ref={rightMarkerRef} />} + </div> </div> - </div> - ) -} + ) + } +) ScrollArea.displayName = 'ScrollArea' diff --git a/packages/ui/src/components/sheet.tsx b/packages/ui/src/components/sheet.tsx index 41f69f5d50..72145fbd06 100644 --- a/packages/ui/src/components/sheet.tsx +++ b/packages/ui/src/components/sheet.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { ComponentPropsWithoutRef, ElementRef, forwardRef, HTMLAttributes, MouseEvent } from 'react' import { usePortal, useTheme } from '@/context' import * as SheetPrimitive from '@radix-ui/react-dialog' @@ -14,12 +14,12 @@ const SheetTrigger = SheetPrimitive.Trigger const SheetPortal = SheetPrimitive.Portal -interface SheetOverlayProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> { +interface SheetOverlayProps extends ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> { modal?: boolean - handleClose?: ((event: React.MouseEvent<HTMLDivElement>) => void) | (() => void) + handleClose?: ((event: MouseEvent<HTMLDivElement>) => void) | (() => void) } -const SheetOverlay = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Overlay>, SheetOverlayProps>( +const SheetOverlay = forwardRef<ElementRef<typeof SheetPrimitive.Overlay>, SheetOverlayProps>( ({ className, modal, handleClose, ...props }, ref) => { const { isLightTheme } = useTheme() @@ -67,7 +67,7 @@ const sheetVariants = cva('fixed z-50 gap-4 bg-cn-background p-6 shadow-lg trans }) interface SheetContentProps - extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, + extends ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, VariantProps<typeof sheetVariants> { hideCloseButton?: boolean handleClose?: () => void @@ -76,7 +76,7 @@ interface SheetContentProps closeClassName?: string } -const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>( +const SheetContent = forwardRef<ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>( ( { side = 'right', @@ -128,27 +128,31 @@ const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Con ) SheetContent.displayName = SheetPrimitive.Content.displayName -const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} /> -) +const SheetHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => ( + <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} ref={ref} /> +)) SheetHeader.displayName = 'SheetHeader' -const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( - <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} /> -) +const SheetFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => ( + <div + className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} + {...props} + ref={ref} + /> +)) SheetFooter.displayName = 'SheetFooter' -const SheetTitle = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Title>, - React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> +const SheetTitle = forwardRef< + ElementRef<typeof SheetPrimitive.Title>, + ComponentPropsWithoutRef<typeof SheetPrimitive.Title> >(({ className, ...props }, ref) => ( <SheetPrimitive.Title ref={ref} className={cn('text-cn-foreground text-lg font-semibold', className)} {...props} /> )) SheetTitle.displayName = SheetPrimitive.Title.displayName -const SheetDescription = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Description>, - React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> +const SheetDescription = forwardRef< + ElementRef<typeof SheetPrimitive.Description>, + ComponentPropsWithoutRef<typeof SheetPrimitive.Description> >(({ className, ...props }, ref) => ( <SheetPrimitive.Description ref={ref} className={cn('text-cn-foreground-3 text-sm', className)} {...props} /> )) diff --git a/packages/ui/src/components/sidebar/sidebar-item.tsx b/packages/ui/src/components/sidebar/sidebar-item.tsx index 10b6fd210e..2c319bf602 100644 --- a/packages/ui/src/components/sidebar/sidebar-item.tsx +++ b/packages/ui/src/components/sidebar/sidebar-item.tsx @@ -382,21 +382,29 @@ export const SidebarItem = forwardRef<HTMLButtonElement | HTMLAnchorElement, Sid }) as SidebarItemComponent SidebarItem.displayName = 'SidebarItem' -export const SidebarMenuSubItem = ({ title, className, ...props }: NavLinkProps & { title: string }) => { - const { NavLink } = useRouterContext() - - return ( - <NavLink - className={({ isActive }) => - cn('cn-sidebar-submenu-item', { 'cn-sidebar-submenu-item-active': isActive }, className) - } - role="menuitem" - {...props} - > - <Text className="cn-sidebar-submenu-item-content" variant="body-single-line-normal" color="foreground-2" truncate> - {title} - </Text> - </NavLink> - ) -} +export const SidebarMenuSubItem = forwardRef<HTMLAnchorElement, NavLinkProps & { title: string }>( + ({ title, className, ...props }, ref) => { + const { NavLink } = useRouterContext() + + return ( + <NavLink + className={({ isActive }) => + cn('cn-sidebar-submenu-item', { 'cn-sidebar-submenu-item-active': isActive }, className) + } + role="menuitem" + {...props} + ref={ref} + > + <Text + className="cn-sidebar-submenu-item-content" + variant="body-single-line-normal" + color="foreground-2" + truncate + > + {title} + </Text> + </NavLink> + ) + } +) SidebarMenuSubItem.displayName = SUBMENU_ITEM_DISPLAY_NAME diff --git a/packages/ui/src/components/split-button.tsx b/packages/ui/src/components/split-button.tsx index d5d55dbc47..e389336fac 100644 --- a/packages/ui/src/components/split-button.tsx +++ b/packages/ui/src/components/split-button.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, MouseEvent, ReactNode, useCallback, useState } from 'react' +import { ForwardedRef, forwardRef, MouseEvent, ReactNode, useCallback, useState } from 'react' import { Button, buttonVariants } from '@/components/button' import { DropdownMenu } from '@components/dropdown-menu' @@ -64,7 +64,7 @@ const SplitButtonInner = forwardRef( dropdownContentClassName, size = 'md' }: SplitButtonProps<T>, - ref: React.ForwardedRef<HTMLButtonElement> + ref: ForwardedRef<HTMLButtonElement> ) => { const [isOpen, setIsOpen] = useState(false) @@ -145,5 +145,5 @@ const SplitButtonInner = forwardRef( SplitButtonInner.displayName = 'SplitButton' export const SplitButton = SplitButtonInner as <T extends string>( - props: SplitButtonProps<T> & { ref?: React.ForwardedRef<HTMLButtonElement> } + props: SplitButtonProps<T> & { ref?: ForwardedRef<HTMLButtonElement> } ) => JSX.Element diff --git a/packages/ui/src/components/status-badge/status-badge.tsx b/packages/ui/src/components/status-badge/status-badge.tsx index 2fdf19598d..72833d1ed1 100644 --- a/packages/ui/src/components/status-badge/status-badge.tsx +++ b/packages/ui/src/components/status-badge/status-badge.tsx @@ -1,3 +1,5 @@ +import { forwardRef } from 'react' + import { IconNameMapV2, IconV2 } from '@components/icon-v2' import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' @@ -59,29 +61,32 @@ type StatusBadgeOtherThemeProps = BadgeBaseProps & { // Combined props using discriminated union export type StatusBadgeProps = StatusBadgeOtherThemeProps | StatusBadgeStatusVariantProps -function StatusBadge({ className, variant, size, pulse, theme = 'muted', children, icon, ...props }: StatusBadgeProps) { - const isStatusVariant = variant === 'status' +const StatusBadge = forwardRef<HTMLDivElement, StatusBadgeProps>( + ({ className, variant, size, pulse, theme = 'muted', children, icon, ...props }, ref) => { + const isStatusVariant = variant === 'status' - return ( - <div - className={cn( - statusBadgeVariants({ - variant, - size, - theme - }), - className - )} - {...props} - > - {isStatusVariant && ( - <span className={cn('cn-badge-indicator rounded-full', { 'animate-pulse': pulse })} aria-hidden="true" /> - )} - {icon && <IconV2 skipSize className="cn-badge-icon" name={icon} />} - {children} - </div> - ) -} + return ( + <div + ref={ref} + className={cn( + statusBadgeVariants({ + variant, + size, + theme + }), + className + )} + {...props} + > + {isStatusVariant && ( + <span className={cn('cn-badge-indicator rounded-full', { 'animate-pulse': pulse })} aria-hidden="true" /> + )} + {icon && <IconV2 skipSize className="cn-badge-icon" name={icon} />} + {children} + </div> + ) + } +) StatusBadge.displayName = 'StatusBadge' diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 476e976dba..65678c622f 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -227,6 +227,7 @@ const TabsTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, TabsTrigge }} {...(linkProps as Omit<NavLinkProps, 'to' | 'className'>)} {...(_restProps as Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'className'>)} + ref={ref as Ref<HTMLAnchorElement>} > <TabTriggerContent /> </NavLink> diff --git a/packages/ui/src/components/tag.tsx b/packages/ui/src/components/tag.tsx index ce89d4890b..fc37ca4727 100644 --- a/packages/ui/src/components/tag.tsx +++ b/packages/ui/src/components/tag.tsx @@ -1,3 +1,5 @@ +import { forwardRef } from 'react' + import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' @@ -56,126 +58,144 @@ type TagProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'role' | 'tabIndex'> onActionClick?: () => void } -function Tag({ - variant, - size, - theme, - rounded, - icon, - label, - value, - className, - disabled = false, - enableHover = false, - title, - actionIcon, - onActionClick, - ...props -}: TagProps) { - if (label && value) { +const Tag = forwardRef<HTMLDivElement, TagProps>( + ( + { + variant, + size, + theme, + rounded, + icon, + label, + value, + className, + disabled = false, + enableHover = false, + title, + actionIcon, + onActionClick, + ...props + }, + ref + ) => { + if (label && value) { + return ( + <TagSplit + ref={ref} + {...{ + variant, + size, + theme, + rounded, + icon, + actionIcon, + onActionClick, + label: label, + value, + disabled, + enableHover: Boolean(props.onClick), + onClick: props.onClick + }} + /> + ) + } + return ( - <TagSplit - {...{ - variant, - size, - theme, - rounded, - icon, - actionIcon, - onActionClick, - label: label, - value, - disabled, - enableHover: Boolean(props.onClick), - onClick: props.onClick - }} - /> + <div + ref={ref} + tabIndex={-1} + className={cn( + tagVariants({ variant, size, theme, rounded }), + { + 'text-cn-foreground-disabled cursor-not-allowed': disabled, + 'cn-tag-hoverable': !disabled && (enableHover || props.onClick), + 'cursor-pointer': !disabled && props.onClick + }, + className + )} + {...props} + > + {icon && ( + <IconV2 size="xs" name={icon} className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} /> + )} + <span + className={cn('cn-tag-text', { 'text-cn-foreground-disabled': disabled })} + title={title || value || label} + > + {value || label} + </span> + + {actionIcon && ( + <Button + iconOnly + disabled={disabled} + rounded={rounded} + className="cn-tag-action-icon-button" + variant="transparent" + size={size === 'sm' ? '3xs' : '2xs'} + onClick={e => { + e.stopPropagation() + e.preventDefault() + onActionClick?.() + }} + > + <IconV2 + skipSize + name={actionIcon} + className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} + /> + </Button> + )} + </div> ) } +) +Tag.displayName = 'Tag' - return ( - <div - tabIndex={-1} - className={cn( - tagVariants({ variant, size, theme, rounded }), - { - 'text-cn-foreground-disabled cursor-not-allowed': disabled, - 'cn-tag-hoverable': !disabled && (enableHover || props.onClick), - 'cursor-pointer': !disabled && props.onClick - }, - className - )} - {...props} - > - {icon && ( - <IconV2 size="xs" name={icon} className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} /> - )} - <span className={cn('cn-tag-text', { 'text-cn-foreground-disabled': disabled })} title={title || value || label}> - {value || label} - </span> - - {actionIcon && ( - <Button - iconOnly - disabled={disabled} - rounded={rounded} - className="cn-tag-action-icon-button" - variant="transparent" - size={size === 'sm' ? '3xs' : '2xs'} - onClick={e => { - e.stopPropagation() - e.preventDefault() - onActionClick?.() - }} - > - <IconV2 - skipSize - name={actionIcon} - className={cn('cn-tag-icon', { 'text-cn-foreground-disabled': disabled })} - /> - </Button> - )} - </div> - ) -} - -function TagSplit({ - variant, - size, - theme, - rounded, - icon, - actionIcon, - onActionClick, - value, - label = '', - disabled = false, - enableHover = false, - onClick -}: TagProps) { - const sharedProps = { variant, size, theme, rounded, icon, disabled, onClick } +const TagSplit = forwardRef<HTMLDivElement, TagProps>( + ( + { + variant, + size, + theme, + rounded, + icon, + actionIcon, + onActionClick, + value, + label = '', + disabled = false, + enableHover = false, + onClick + }, + ref + ) => { + const sharedProps = { variant, size, theme, rounded, icon, disabled, onClick } - return ( - <div - className={cn('cn-tag-split flex w-fit items-center justify-center', { - 'cursor-not-allowed': disabled, - 'cn-tag-split-hoverable': !disabled && enableHover - })} - > - {/* LEFT TAG - should never have a Reset Icon */} - <Tag {...sharedProps} icon={icon} value={label} className="cn-tag-split-left" /> + return ( + <div + className={cn('cn-tag-split flex w-fit items-center justify-center', { + 'cursor-not-allowed': disabled, + 'cn-tag-split-hoverable': !disabled && enableHover + })} + ref={ref} + > + {/* LEFT TAG - should never have a Reset Icon */} + <Tag {...sharedProps} icon={icon} value={label} className="cn-tag-split-left" /> - {/* RIGHT TAG - should never have a tag Icon */} - <Tag - {...sharedProps} - icon={icon} - actionIcon={actionIcon} - onActionClick={onActionClick} - value={value} - className="cn-tag-split-right" - /> - </div> - ) -} + {/* RIGHT TAG - should never have a tag Icon */} + <Tag + {...sharedProps} + icon={icon} + actionIcon={actionIcon} + onActionClick={onActionClick} + value={value} + className="cn-tag-split-right" + /> + </div> + ) + } +) +TagSplit.displayName = 'TagSplit' export { Tag, type TagProps } diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index cbe139eb01..1e160e4101 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -169,7 +169,7 @@ export const TimeAgoCard = memo( if (timestamp === null || timestamp === undefined) { return ( - <Text as="span" {...textProps}> + <Text as="span" {...textProps} ref={ref}> Unknown time </Text> ) diff --git a/packages/ui/src/components/toggle-group.tsx b/packages/ui/src/components/toggle-group.tsx index 713369b38e..c94a65a77a 100644 --- a/packages/ui/src/components/toggle-group.tsx +++ b/packages/ui/src/components/toggle-group.tsx @@ -4,6 +4,7 @@ import { ElementRef, FC, forwardRef, + PropsWithoutRef, ReactNode, useCallback, useContext, @@ -56,14 +57,14 @@ export type ToggleGroupItemPropsBase = { tooltipProps?: ToggleTooltipProps disabled?: boolean suffixIcon?: IconV2NamesType - suffixIconProps?: IconPropsV2 + suffixIconProps?: PropsWithoutRef<IconPropsV2> text?: string } type TogglePropsIconOnly = ToggleGroupItemPropsBase & { iconOnly: true prefixIcon: IconV2NamesType - prefixIconProps: IconPropsV2 + prefixIconProps: PropsWithoutRef<IconPropsV2> suffixIcon: never suffixIconProps: never } @@ -71,7 +72,7 @@ type TogglePropsIconOnly = ToggleGroupItemPropsBase & { type TogglePropsNotIconOnly = ToggleGroupItemPropsBase & { iconOnly?: false prefixIcon?: IconV2NamesType - prefixIconProps?: IconPropsV2 + prefixIconProps?: PropsWithoutRef<IconPropsV2> } export type ToggleGroupItemProps = TogglePropsIconOnly | TogglePropsNotIconOnly diff --git a/packages/ui/src/components/toggle.tsx b/packages/ui/src/components/toggle.tsx index 30a2e862be..b37add2b9d 100644 --- a/packages/ui/src/components/toggle.tsx +++ b/packages/ui/src/components/toggle.tsx @@ -1,4 +1,4 @@ -import { ElementRef, FC, forwardRef, ReactNode, useCallback, useState } from 'react' +import { ElementRef, FC, forwardRef, PropsWithoutRef, ReactNode, useCallback, useState } from 'react' import { Button, ButtonProps, IconPropsV2, IconV2, IconV2NamesType, Tooltip, TooltipProps } from '@/components' import * as TogglePrimitive from '@radix-ui/react-toggle' @@ -40,7 +40,7 @@ type TogglePropsBase = Pick<ButtonProps, 'rounded' | 'iconOnly' | 'disabled'> & text?: string size?: VariantProps<typeof toggleVariants>['size'] suffixIcon?: IconV2NamesType - suffixIconProps?: Omit<IconPropsV2, 'name'> + suffixIconProps?: PropsWithoutRef<Omit<IconPropsV2, 'name'>> selected?: boolean defaultValue?: boolean tooltipProps?: ToggleTooltipProps @@ -50,7 +50,7 @@ type TogglePropsBase = Pick<ButtonProps, 'rounded' | 'iconOnly' | 'disabled'> & type TogglePropsIconOnly = TogglePropsBase & { iconOnly: true prefixIcon: IconV2NamesType - prefixIconProps?: Omit<IconPropsV2, 'name'> + prefixIconProps?: PropsWithoutRef<Omit<IconPropsV2, 'name'>> suffixIcon?: never suffixIconProps?: never } @@ -58,7 +58,7 @@ type TogglePropsIconOnly = TogglePropsBase & { type TogglePropsNotIconOnly = TogglePropsBase & { iconOnly?: false prefixIcon?: IconV2NamesType - prefixIconProps?: Omit<IconPropsV2, 'name'> + prefixIconProps?: PropsWithoutRef<Omit<IconPropsV2, 'name'>> } export type ToggleProps = TogglePropsIconOnly | TogglePropsNotIconOnly diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx index 19d7522e17..98fa1a67f1 100644 --- a/packages/ui/src/components/tooltip.tsx +++ b/packages/ui/src/components/tooltip.tsx @@ -1,7 +1,8 @@ -import { ComponentProps, FC, ReactNode } from 'react' +import { ComponentProps, forwardRef, ReactNode } from 'react' import { usePortal } from '@/context' import * as TooltipPrimitive from '@radix-ui/react-tooltip' +import { cn } from '@utils/cn' import { Illustration } from './illustration' @@ -15,36 +16,39 @@ export type TooltipProps = { hideArrow?: boolean delay?: TooltipPrimitiveRootType['delayDuration'] open?: boolean -} & Pick<TooltipPrimitiveContentType, 'side' | 'align'> +} & Pick<TooltipPrimitiveContentType, 'side' | 'align' | 'className'> -export const Tooltip: FC<TooltipProps> = ({ - children, - title, - content, - hideArrow = false, - delay = 500, - side = 'top', - align = 'center', - open -}) => { - const { portalContainer } = usePortal() - return ( - <TooltipPrimitive.Root delayDuration={delay} open={open}> - <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger> - <TooltipPrimitive.Portal container={portalContainer}> - <TooltipPrimitive.Content className="cn-tooltip" side={side} align={align} sideOffset={4}> - {!!title && <span className="cn-tooltip-title">{title}</span>} - <div>{content}</div> - {!hideArrow && ( - <TooltipPrimitive.Arrow width={20} height={8} asChild> - <Illustration className="cn-tooltip-arrow" name="tooltip-arrow" /> - </TooltipPrimitive.Arrow> - )} - </TooltipPrimitive.Content> - </TooltipPrimitive.Portal> - </TooltipPrimitive.Root> - ) -} +export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>( + ( + { children, title, content, hideArrow = false, delay = 500, side = 'top', align = 'center', open, className }, + ref + ) => { + const { portalContainer } = usePortal() + return ( + <TooltipPrimitive.Root delayDuration={delay} open={open}> + <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger> + <TooltipPrimitive.Portal container={portalContainer}> + <TooltipPrimitive.Content + ref={ref} + className={cn('cn-tooltip', className)} + side={side} + align={align} + sideOffset={4} + > + {!!title && <span className="cn-tooltip-title">{title}</span>} + <div>{content}</div> + {!hideArrow && ( + <TooltipPrimitive.Arrow width={20} height={8} asChild> + <Illustration className="cn-tooltip-arrow" name="tooltip-arrow" /> + </TooltipPrimitive.Arrow> + )} + </TooltipPrimitive.Content> + </TooltipPrimitive.Portal> + </TooltipPrimitive.Root> + ) + } +) +Tooltip.displayName = 'Tooltip' export const TooltipProvider = (props: ComponentProps<typeof TooltipPrimitive.Provider>) => ( <TooltipPrimitive.Provider skipDelayDuration={0} {...props} /> diff --git a/packages/ui/tailwind-utils-config/components/caption.ts b/packages/ui/tailwind-utils-config/components/caption.ts index 4ef45dbbba..2009af7e85 100644 --- a/packages/ui/tailwind-utils-config/components/caption.ts +++ b/packages/ui/tailwind-utils-config/components/caption.ts @@ -1,29 +1,6 @@ -import { CSSRuleObject } from 'tailwindcss/types/config' - -const themes = ['danger', 'warning', 'success'] as const - -const createCaptionThemeStyles = () => { - const styles: CSSRuleObject = {} - - themes.forEach(theme => { - styles[`&:where(.cn-caption-${theme})`] = { - color: `var(--cn-text-${theme})` - } - }) - - return styles -} - export default { '.cn-caption': { - color: 'var(--cn-text-3)', gap: 'var(--cn-spacing-1)', - '@apply w-full inline-flex items-center font-body-normal': '', - - '&:where(.cn-caption-disabled)': { - color: 'var(--cn-state-disabled-text)' - }, - - ...createCaptionThemeStyles() + '@apply w-full inline-flex items-center': '' } } From ee2a1bde638aaba878c6a5fadcf62e22afbdf588 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:56:08 +0400 Subject: [PATCH 108/180] add repo general settings design review (#2067) * add repo general settings design review * fixes * fixes --- .../repo/repo-settings-general-container.tsx | 20 +-- packages/ui/locales/en/component.json | 3 + packages/ui/locales/fr/component.json | 3 + .../dialogs/delete-alert-dialog.tsx | 65 +++++-- .../branch-selector-v2/branch-selector.tsx | 3 +- .../repo-settings-general-delete.tsx | 39 ++-- .../repo-settings-general-features.tsx | 58 +++--- .../components/repo-settings-general-form.tsx | 170 ++++++++---------- .../repo-settings-general-security.tsx | 127 ++++++------- .../repo-settings-general-page.tsx | 82 +++++---- .../components/dialog.ts | 2 +- 11 files changed, 286 insertions(+), 286 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx b/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx index bfdab0dc2c..62db29e3b1 100644 --- a/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx @@ -22,7 +22,6 @@ import { useUpdateSecuritySettingsMutation } from '@harnessio/code-service-client' import { DeleteAlertDialog, ExitConfirmDialog } from '@harnessio/ui/components' -import { wrapConditionalObjectElement } from '@harnessio/ui/utils' import { AccessLevel, ErrorTypes, @@ -324,18 +323,13 @@ export const RepoSettingsGeneralPageContainer = () => { <DeleteAlertDialog open={isRepoAlertDeleteDialogOpen} onClose={closeAlertDeleteDialog} - {...wrapConditionalObjectElement( - { - identifier: repoRef, - deleteFn: handleDeleteRepository, - isLoading: isDeletingRepo, - error: apiError?.type === ErrorTypes.DELETE_REPO ? apiError : null, - type: 'repository', - withForm: true, - deletionKeyword: repoDataStore?.name - }, - isRepoAlertDeleteDialogOpen - )} + withForm + identifier={repoRef} + deleteFn={handleDeleteRepository} + isLoading={isDeletingRepo} + error={apiError?.type === ErrorTypes.DELETE_REPO ? apiError : null} + type="repository" + deletionKeyword={repoDataStore?.name} /> </> ) diff --git a/packages/ui/locales/en/component.json b/packages/ui/locales/en/component.json index bbc0b78728..dd708d0e08 100644 --- a/packages/ui/locales/en/component.json +++ b/packages/ui/locales/en/component.json @@ -81,6 +81,9 @@ "description": "This will permanently remove all data. This action cannot be undone.", "title": "Are you sure?", "inputLabel": "To confirm, type", + "validation": { + "mismatch": "" + }, "violationMessages": { "bypassed": "Some rules will be bypassed while deleting {{type}}", "notAllow": "Some rules don't allow you to delete {{type}}" diff --git a/packages/ui/locales/fr/component.json b/packages/ui/locales/fr/component.json index f8faf59cf1..3204e9aeeb 100644 --- a/packages/ui/locales/fr/component.json +++ b/packages/ui/locales/fr/component.json @@ -81,6 +81,9 @@ "description": "This will permanently remove all data. This action cannot be undone.", "title": "Êtes-vous sûr ?", "inputLabel": "To confirm, type", + "validation": { + "mismatch": "" + }, "violationMessages": { "bypassed": "", "notAllow": "" diff --git a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx index a8b03449c8..bbaa2864f8 100644 --- a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx +++ b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx @@ -1,6 +1,6 @@ import { ChangeEvent, FC, useMemo, useState } from 'react' -import { Alert, AlertDialog, Fieldset, Input, Message, MessageTheme } from '@/components' +import { Alert, AlertDialog, Message, MessageTheme, Text, TextInput } from '@/components' import { useTranslation } from '@/context' import { getErrorMessage } from '@utils/utils' @@ -35,19 +35,34 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ }) => { const { t } = useTranslation() const [verification, setVerification] = useState('') + const [validationError, setValidationError] = useState(false) const handleChangeVerification = (event: ChangeEvent<HTMLInputElement>) => { setVerification(event.target.value) + if (validationError) { + setValidationError(false) + } } - const isDisabled = isLoading || (withForm && verification !== deletionKeyword) - const handleDelete = () => { - if (isDisabled) return + if (withForm && verification !== deletionKeyword) { + setValidationError(true) + } + + if (isLoading || (withForm && verification !== deletionKeyword)) return + setValidationError(false) deleteFn(identifier!) } + const handleOpenChange = () => { + setTimeout(() => { + setVerification('') + setValidationError(false) + }, 300) + onClose() + } + const displayMessageContent = useMemo(() => { if (message) return message @@ -65,21 +80,35 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ }, [type, t, message, identifier]) return ( - <AlertDialog.Root theme="danger" open={open} onOpenChange={onClose} onConfirm={handleDelete} loading={isLoading}> + <AlertDialog.Root + theme="danger" + open={open} + onOpenChange={handleOpenChange} + onConfirm={handleDelete} + loading={isLoading} + > <AlertDialog.Content title={t('component:deleteDialog.title', 'Are you sure?')}> - <div className="overflow-hidden break-words text-wrap">{displayMessageContent}</div> + <Text className="break-words" wrap="wrap"> + {displayMessageContent} + </Text> + {withForm && ( - <Fieldset> - <Input - id="verification" - value={verification} - placeholder="" - onChange={handleChangeVerification} - label={`${t('component:deleteDialog.inputLabel', `To confirm, type`)} "${deletionKeyword}"`} - disabled={isLoading} - autoFocus - /> - </Fieldset> + <TextInput + id="verification" + value={verification} + placeholder="" + onChange={handleChangeVerification} + label={`${t('component:deleteDialog.inputLabel', `To confirm, type`)} "${deletionKeyword}"`} + disabled={isLoading} + autoFocus + error={ + validationError + ? t('component:deleteDialog.validation.mismatch', `Please type "${deletionKeyword}" to confirm`, { + deletionKeyword + }) + : '' + } + /> )} {violation && ( @@ -97,7 +126,7 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ )} {!!error && ( - <Alert.Root className="mt-4" theme="danger"> + <Alert.Root theme="danger"> <Alert.Title>Failed to perform delete operation</Alert.Title> <Alert.Description> {getErrorMessage(error as Error, 'Failed to perform delete operation')} diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx index 6bf53f6869..d0ae425084 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx +++ b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx @@ -2,6 +2,7 @@ import { FC } from 'react' import { Button, DropdownMenu, IconV2, Text, type ButtonSizes } from '@/components' import { BranchData, BranchSelectorListItem, BranchSelectorTab } from '@/views' +import { cn } from '@utils/cn' import { BranchSelectorDropdown } from './branch-selector-dropdown' @@ -54,7 +55,7 @@ export const BranchSelectorV2: FC<BranchSelectorProps> = ({ return ( <DropdownMenu.Root> <DropdownMenu.Trigger asChild> - <Button className={className} variant="outline" size={buttonSize} disabled={disabled}> + <Button className={cn('min-w-0', className)} variant="outline" size={buttonSize} disabled={disabled}> {!hideIcon && <IconV2 name={isTag ? 'tag' : 'git-branch'} size="sm" />} <Text className="truncate"> {branchPrefix diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx index 14d72ca76e..447dab8c2f 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-delete.tsx @@ -1,15 +1,6 @@ import { FC } from 'react' -import { - Button, - ButtonLayout, - FormSeparator, - Layout, - PermissionIdentifier, - ResourceType, - Spacer, - Text -} from '@/components' +import { Button, ButtonLayout, FormSeparator, Layout, PermissionIdentifier, ResourceType, Text } from '@/components' import { useComponents, useTranslation } from '@/context' import { ErrorTypes } from '@/views' @@ -24,10 +15,10 @@ export const RepoSettingsGeneralDelete: FC<{ const { RbacButton } = useComponents() const { t } = useTranslation() return ( - <Layout.Flex direction="column" gap="xl"> + <Layout.Vertical gap="xl"> <Layout.Vertical gap="xl"> <Layout.Vertical gap="xs"> - <Text variant="heading-subsection"> + <Text variant="heading-subsection" as="h3"> {archived ? t('views:repos.unarchiveRepo', 'Unarchive this repository') : t('views:repos.archiveRepo', 'Archive this repository')} @@ -41,6 +32,7 @@ export const RepoSettingsGeneralDelete: FC<{ )} </Text> </Layout.Vertical> + <ButtonLayout horizontalAlign="start"> <Button type="button" @@ -59,17 +51,15 @@ export const RepoSettingsGeneralDelete: FC<{ : t('views:repos.archiveRepoButton', 'Archive Repository')} </Button> </ButtonLayout> - {apiError && apiError.type === ErrorTypes.ARCHIVE_REPO && ( - <> - <Spacer size={2} /> - <Text color="danger">{apiError.message}</Text> - </> - )} + + {apiError && apiError.type === ErrorTypes.ARCHIVE_REPO && <Text color="danger">{apiError.message}</Text>} </Layout.Vertical> <FormSeparator /> <Layout.Vertical gap="xl"> <Layout.Vertical gap="xs"> - <Text variant="heading-subsection">{t('views:repos.deleteRepo', 'Delete repository')}</Text> + <Text variant="heading-subsection" as="h3"> + {t('views:repos.deleteRepo', 'Delete repository')} + </Text> <Text as="span" variant="body-normal"> {t( 'views:repos.deleteRepoDescription', @@ -77,6 +67,7 @@ export const RepoSettingsGeneralDelete: FC<{ )} </Text> </Layout.Vertical> + <ButtonLayout horizontalAlign="start"> <RbacButton type="button" @@ -91,13 +82,9 @@ export const RepoSettingsGeneralDelete: FC<{ {t('views:repos.deleteRepoButton', 'Delete Repository')} </RbacButton> </ButtonLayout> - {apiError && apiError.type === ErrorTypes.DELETE_REPO && ( - <> - <Spacer size={2} /> - <Text color="danger">{apiError.message}</Text> - </> - )} + + {apiError && apiError.type === ErrorTypes.DELETE_REPO && <Text color="danger">{apiError.message}</Text>} </Layout.Vertical> - </Layout.Flex> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx index 3a6dbc5b44..4e15c26b7a 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-features.tsx @@ -1,7 +1,7 @@ import { FC, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { Checkbox, ControlGroup, Fieldset, Layout, Message, MessageTheme, Skeleton, Spacer, Text } from '@/components' +import { Checkbox, ControlGroup, Layout, Message, MessageTheme, Skeleton, Text } from '@/components' import { useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -62,35 +62,33 @@ export const RepoSettingsFeaturesForm: FC<RepoSettingsFeaturesFormProps> = ({ : '' return ( - <Fieldset> - <Layout.Vertical gap="xl"> - <Text variant="heading-subsection">{t('views:repos.features', 'Features')}</Text> - {isLoadingFeaturesSettings ? ( - <Skeleton.Form /> - ) : ( - <ControlGroup> - <Checkbox - checked={watch('gitLfsEnabled')} - id="git-lfs-enabled" - onCheckedChange={onCheckboxChange} - disabled={isDisabled} - title={tooltipMessage} - label={t('views:repos.gitLfs', 'Git LFS')} - caption={t('views:repos.gitLfsDescription', 'Enable Git Large File Storage (LFS) for this repository.')} - /> - {errors.gitLfsEnabled && ( - <Message theme={MessageTheme.ERROR}>{errors.gitLfsEnabled.message?.toString()}</Message> - )} - </ControlGroup> - )} + <Layout.Vertical gap="xl"> + <Text variant="heading-subsection" as="h3"> + {t('views:repos.features', 'Features')} + </Text> - {!!apiError && (apiError.type === ErrorTypes.FETCH_GENERAL || apiError.type === ErrorTypes.UPDATE_GENERAL) && ( - <> - <Spacer size={2} /> - <Text color="danger">{apiError.message}</Text> - </> - )} - </Layout.Vertical> - </Fieldset> + {isLoadingFeaturesSettings ? ( + <Skeleton.Form /> + ) : ( + <ControlGroup> + <Checkbox + checked={watch('gitLfsEnabled')} + id="git-lfs-enabled" + onCheckedChange={onCheckboxChange} + disabled={isDisabled} + title={tooltipMessage} + label={t('views:repos.gitLfs', 'Git LFS')} + caption={t('views:repos.gitLfsDescription', 'Enable Git Large File Storage (LFS) for this repository.')} + /> + {errors.gitLfsEnabled && ( + <Message theme={MessageTheme.ERROR}>{errors.gitLfsEnabled.message?.toString()}</Message> + )} + </ControlGroup> + )} + + {!!apiError && (apiError.type === ErrorTypes.FETCH_GENERAL || apiError.type === ErrorTypes.UPDATE_GENERAL) && ( + <Text color="danger">{apiError.message}</Text> + )} + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx index fbbd54463c..55ee296e89 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-form.tsx @@ -5,12 +5,10 @@ import { Button, ButtonLayout, ControlGroup, - Fieldset, FormInput, FormWrapper, IconV2, Label, - Layout, Radio, Skeleton, Text @@ -52,7 +50,14 @@ export const RepoSettingsGeneralForm: FC<{ } }) - const { register, handleSubmit, setValue, watch, reset } = formMethods + const { + register, + handleSubmit, + setValue, + watch, + reset, + formState: { isDirty } + } = formMethods useEffect(() => { // Don't reset the form during updates to prevent UI flakiness @@ -98,100 +103,79 @@ export const RepoSettingsGeneralForm: FC<{ } const onSubmit: SubmitHandler<RepoUpdateData> = data => { + if (!isDirty && branchValue === repoData.defaultBranch) return + setIsSubmitted(true) handleRepoUpdate(data) } + if (isLoadingRepoData) { + return <Skeleton.Form /> + } + return ( - <Fieldset> - {isLoadingRepoData ? ( - <Skeleton.Form /> - ) : ( - <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> - {/* NAME */} - <Fieldset> - <FormInput.Text - id="name" - {...register('name')} - placeholder={t('views:repos.repoNamePlaceholder', 'Enter repository name')} - disabled - label={t('views:repos.name', 'Name')} - /> - </Fieldset> - - {/* DESCRIPTION */} - <Fieldset> - <FormInput.Textarea - id="description" - {...register('description')} - placeholder={t('views:repos.repoDescriptionPlaceholder', 'Enter a description of this repository...')} - label={t('views:repos.description', 'Description')} - optional - resizable - className="min-h-[136px]" - /> - </Fieldset> - - {/* BRANCH */} - <Fieldset className="w-[298px]"> - <ControlGroup> - <Layout.Vertical gap="xs"> - <Label>{t('views:repos.defaultBranch', 'Default Branch')}</Label> - <BranchSelector - onSelectBranchorTag={value => { - handleSelectChange('branch', value.name) - }} - isBranchOnly={true} - dynamicWidth={true} - selectedBranch={{ name: branchValue, sha: '' }} - isUpdating={isUpdatingRepoData} - disabled={isUpdatingRepoData} - /> - </Layout.Vertical> - </ControlGroup> - </Fieldset> - - <Fieldset> - <FormInput.Radio label={t('views:repos.visibility', 'Visibility')} id="visibility" {...register('access')}> - <Radio.Item - id="access-public" - value="1" - label={t('views:repos.public', 'Public')} - caption={t('views:repos.publicDescription', 'Anyone with access to Harness can clone this repo.')} - /> - <Radio.Item - id="access-private" - value="2" - label={t('views:repos.private', 'Private')} - caption={t( - 'views:repos.privateDescription', - 'You can choose who can see and commit to this repository.' - )} - /> - </FormInput.Radio> - </Fieldset> - - {/* SUBMIT BUTTONS */} - <Fieldset> - <ControlGroup> - <ButtonLayout horizontalAlign="start"> - {!isSubmitted || !isRepoUpdateSuccess ? ( - <Button type="submit" disabled={isUpdatingRepoData}> - {!isUpdatingRepoData ? t('views:repos.save', 'Save') : t('views:repos.saving', 'Saving...')} - </Button> - ) : ( - <Button variant="primary" theme="success" type="button" className="pointer-events-none"> - Saved - <IconV2 name="check" size="xs" /> - </Button> - )} - </ButtonLayout> - </ControlGroup> - </Fieldset> - - {!!apiError && errorTypes.has(apiError.type) && <Text color="danger">{apiError.message}</Text>} - </FormWrapper> - )} - </Fieldset> + <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> + <FormInput.Text + id="name" + {...register('name')} + placeholder={t('views:repos.repoNamePlaceholder', 'Enter repository name')} + disabled + label={t('views:repos.name', 'Name')} + /> + + <FormInput.Textarea + id="description" + {...register('description')} + placeholder={t('views:repos.repoDescriptionPlaceholder', 'Enter a description of this repository...')} + label={t('views:repos.description', 'Description')} + optional + resizable + rows={6} + /> + + <ControlGroup> + <Label>{t('views:repos.defaultBranch', 'Default Branch')}</Label> + <BranchSelector + onSelectBranchorTag={value => { + handleSelectChange('branch', value.name) + }} + isBranchOnly + selectedBranch={{ name: branchValue, sha: '' }} + isUpdating={isUpdatingRepoData} + disabled={isUpdatingRepoData} + className="w-fit max-w-full" + /> + </ControlGroup> + + <FormInput.Radio label={t('views:repos.visibility', 'Visibility')} id="visibility" {...register('access')}> + <Radio.Item + id="access-public" + value="1" + label={t('views:repos.public', 'Public')} + caption={t('views:repos.publicDescription', 'Anyone with access to Harness can clone this repo.')} + /> + <Radio.Item + id="access-private" + value="2" + label={t('views:repos.private', 'Private')} + caption={t('views:repos.privateDescription', 'You can choose who can see and commit to this repository.')} + /> + </FormInput.Radio> + + <ButtonLayout horizontalAlign="start"> + {!isSubmitted || !isRepoUpdateSuccess ? ( + <Button type="submit" disabled={isUpdatingRepoData}> + {!isUpdatingRepoData ? t('views:repos.save', 'Save') : t('views:repos.saving', 'Saving...')} + </Button> + ) : ( + <Button variant="primary" theme="success" type="button" className="pointer-events-none"> + Saved + <IconV2 name="check" size="xs" /> + </Button> + )} + </ButtonLayout> + + {!!apiError && errorTypes.has(apiError.type) && <Text color="danger">{apiError.message}</Text>} + </FormWrapper> ) } diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx index 6f06e9e2b7..0f4bf6727f 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-security.tsx @@ -1,7 +1,7 @@ import { FC, useEffect } from 'react' import { useForm } from 'react-hook-form' -import { Checkbox, ControlGroup, Fieldset, Layout, Message, MessageTheme, Skeleton, Spacer, Text } from '@/components' +import { Checkbox, ControlGroup, Layout, Message, MessageTheme, Skeleton, Text } from '@/components' import { useTranslation } from '@/context' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -79,82 +79,85 @@ export const RepoSettingsSecurityForm: FC<RepoSettingsSecurityFormProps> = ({ setValue('vulnerabilityScanning', vulnerabilityScanning) }, [securityScanning, verifyCommitterIdentity, vulnerabilityScanning, setValue]) - const isDisabled = apiError && (apiError.type === 'fetchSecurity' || apiError.type === 'updateSecurity') + const isDisabled = !!(apiError && (apiError.type === 'fetchSecurity' || apiError.type === 'updateSecurity')) const tooltipMessage = isDisabled ? t('views:repos.settingsToolTip', 'Cannot change settings while loading or updating.') : '' return ( - <Fieldset className="gap-y-6"> - <Layout.Vertical gap="xl"> - <Text variant="heading-subsection">{t('views:repos.security', 'Security')}</Text> - {isLoadingSecuritySettings ? ( - <Skeleton.Form linesCount={2} /> - ) : ( + <Layout.Vertical gap="xl"> + <Text variant="heading-subsection" as="h3"> + {t('views:repos.security', 'Security')} + </Text> + + {isLoadingSecuritySettings ? ( + <Skeleton.Form linesCount={2} /> + ) : ( + <Layout.Vertical gap="sm"> <ControlGroup> - <Layout.Vertical gap="sm"> - <Checkbox - checked={watch('secretScanning')} - id="secret-scanning" - onCheckedChange={onSecurityScanningCheckboxChange} - disabled={isDisabled ?? false} - title={tooltipMessage} - label={t('views:repos.secretScanning', 'Secret scanning')} - caption={t( - 'views:repos.secretScanningDescription', - 'Block commits containing secrets like passwords, API keys and tokens.' - )} - /> - {errors.secretScanning && ( - <Message theme={MessageTheme.ERROR}>{errors.secretScanning.message?.toString()}</Message> + <Checkbox + checked={watch('secretScanning')} + id="secret-scanning" + onCheckedChange={onSecurityScanningCheckboxChange} + disabled={isDisabled} + title={tooltipMessage} + label={t('views:repos.secretScanning', 'Secret scanning')} + caption={t( + 'views:repos.secretScanningDescription', + 'Block commits containing secrets like passwords, API keys and tokens.' )} + error={!!errors.secretScanning} + /> + {errors.secretScanning && ( + <Message theme={MessageTheme.ERROR}>{errors.secretScanning.message?.toString()}</Message> + )} + </ControlGroup> - {showVulnerabilityScanning ? ( - <> - <Checkbox - checked={watch('vulnerabilityScanning')} - id="vulnerability-scanning" - onCheckedChange={onVulnerabilityScanningCheckboxChange} - disabled={isDisabled ?? false} - title={tooltipMessage} - label={t('views:repos.vulnerabilityScanning', 'Vulnerability scanning')} - caption={t( - 'views:repos.vulnerabilityScanningDescription', - 'Scan incoming commits for known vulnerabilities.' - )} - /> - {errors.vulnerabilityScanning && ( - <Message theme={MessageTheme.ERROR}>{errors.vulnerabilityScanning.message?.toString()}</Message> - )} - </> - ) : null} - + {showVulnerabilityScanning && ( + <ControlGroup> <Checkbox - checked={watch('verifyCommitterIdentity')} - id="verify-committer-identity" - onCheckedChange={onVerifyCommitterIdentityCheckboxChange} - disabled={isDisabled ?? false} + checked={watch('vulnerabilityScanning')} + id="vulnerability-scanning" + onCheckedChange={onVulnerabilityScanningCheckboxChange} + disabled={isDisabled} title={tooltipMessage} - label={t('views:repos.verifyCommitterIdentity', 'Verify committer identity')} + label={t('views:repos.vulnerabilityScanning', 'Vulnerability scanning')} caption={t( - 'views:repos.verifyCommitterIdentityDescription', - 'Block commits not committed by the user pushing the changes.' + 'views:repos.vulnerabilityScanningDescription', + 'Scan incoming commits for known vulnerabilities.' )} + error={!!errors.vulnerabilityScanning} /> - {errors.verifyCommitterIdentity && ( - <Message theme={MessageTheme.ERROR}>{errors.verifyCommitterIdentity.message?.toString()}</Message> + {errors.vulnerabilityScanning && ( + <Message theme={MessageTheme.ERROR}>{errors.vulnerabilityScanning.message?.toString()}</Message> + )} + </ControlGroup> + )} + + <ControlGroup> + <Checkbox + checked={watch('verifyCommitterIdentity')} + id="verify-committer-identity" + onCheckedChange={onVerifyCommitterIdentityCheckboxChange} + disabled={isDisabled} + title={tooltipMessage} + label={t('views:repos.verifyCommitterIdentity', 'Verify committer identity')} + caption={t( + 'views:repos.verifyCommitterIdentityDescription', + 'Block commits not committed by the user pushing the changes.' )} - </Layout.Vertical> + error={!!errors.verifyCommitterIdentity} + /> + {errors.verifyCommitterIdentity && ( + <Message theme={MessageTheme.ERROR}>{errors.verifyCommitterIdentity.message?.toString()}</Message> + )} </ControlGroup> - )} - </Layout.Vertical> - - {!!apiError && (apiError.type === ErrorTypes.FETCH_SECURITY || apiError.type === ErrorTypes.UPDATE_SECURITY) && ( - <> - <Spacer size={2} /> - <Text color="danger">{apiError.message}</Text> - </> + </Layout.Vertical> + )} + + {(apiError?.type === ErrorTypes.FETCH_SECURITY || apiError?.type === ErrorTypes.UPDATE_SECURITY) && ( + <Text color="danger">{apiError.message}</Text> )} - </Fieldset> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx b/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx index b2eed2d67c..805bfb8bc1 100644 --- a/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx +++ b/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Fieldset, FormSeparator, Layout, Text } from '@/components' +import { FormSeparator, Layout, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' import { BranchSelectorContainerProps } from '@/views/repo/components' @@ -56,50 +56,48 @@ export const RepoSettingsGeneralPage: FC<RepoSettingsGeneralPageProps> = ({ return ( <SandboxLayout.Content className="max-w-[635px]"> <Layout.Vertical gap="xl"> - <Text as="h1" variant="heading-section"> + <Text as="h2" variant="heading-section"> {t('views:repos.generalSettings', 'General settings')} </Text> - <Fieldset> - <Layout.Vertical gap="xl"> - <RepoSettingsGeneralForm - repoData={repoData} - handleRepoUpdate={handleRepoUpdate} - apiError={apiError} - isLoadingRepoData={loadingStates.isLoadingRepoData} - isUpdatingRepoData={loadingStates.isUpdatingRepoData} - isRepoUpdateSuccess={isRepoUpdateSuccess} - branchSelectorRenderer={branchSelectorRenderer} - /> - <FormSeparator /> - <RepoSettingsSecurityForm - showVulnerabilityScanning={showVulnerabilityScanning} - securityScanning={securityScanning} - verifyCommitterIdentity={verifyCommitterIdentity} - vulnerabilityScanning={vulnerabilityScanning === 'detect'} - handleUpdateSecuritySettings={handleUpdateSecuritySettings} - apiError={apiError} - isUpdatingSecuritySettings={loadingStates.isUpdatingSecuritySettings} - isLoadingSecuritySettings={loadingStates.isLoadingSecuritySettings} - /> - <FormSeparator /> - <RepoSettingsFeaturesForm - gitLfsEnabled={gitLfsEnabled} - handleUpdateFeaturesSettings={handleUpdateFeaturesSettings} - apiError={apiError} - isUpdatingFeaturesSettings={loadingStates.isUpdatingFeaturesSettings} - isLoadingFeaturesSettings={loadingStates.isLoadingFeaturesSettings} - /> - <FormSeparator /> - <RepoSettingsGeneralDelete - archived={repoData?.archived} - apiError={apiError} - openRepoAlertDeleteDialog={openRepoAlertDeleteDialog} - openRepoArchiveDialog={openRepoArchiveDialog} - isUpdatingArchive={loadingStates.isUpdatingArchive} - /> - </Layout.Vertical> - </Fieldset> + <Layout.Vertical gap="xl"> + <RepoSettingsGeneralForm + repoData={repoData} + handleRepoUpdate={handleRepoUpdate} + apiError={apiError} + isLoadingRepoData={loadingStates.isLoadingRepoData} + isUpdatingRepoData={loadingStates.isUpdatingRepoData} + isRepoUpdateSuccess={isRepoUpdateSuccess} + branchSelectorRenderer={branchSelectorRenderer} + /> + <FormSeparator /> + <RepoSettingsSecurityForm + showVulnerabilityScanning={showVulnerabilityScanning} + securityScanning={securityScanning} + verifyCommitterIdentity={verifyCommitterIdentity} + vulnerabilityScanning={vulnerabilityScanning === 'detect'} + handleUpdateSecuritySettings={handleUpdateSecuritySettings} + apiError={apiError} + isUpdatingSecuritySettings={loadingStates.isUpdatingSecuritySettings} + isLoadingSecuritySettings={loadingStates.isLoadingSecuritySettings} + /> + <FormSeparator /> + <RepoSettingsFeaturesForm + gitLfsEnabled={gitLfsEnabled} + handleUpdateFeaturesSettings={handleUpdateFeaturesSettings} + apiError={apiError} + isUpdatingFeaturesSettings={loadingStates.isUpdatingFeaturesSettings} + isLoadingFeaturesSettings={loadingStates.isLoadingFeaturesSettings} + /> + <FormSeparator /> + <RepoSettingsGeneralDelete + archived={repoData?.archived} + apiError={apiError} + openRepoAlertDeleteDialog={openRepoAlertDeleteDialog} + openRepoArchiveDialog={openRepoArchiveDialog} + isUpdatingArchive={loadingStates.isUpdatingArchive} + /> + </Layout.Vertical> </Layout.Vertical> </SandboxLayout.Content> ) diff --git a/packages/ui/tailwind-utils-config/components/dialog.ts b/packages/ui/tailwind-utils-config/components/dialog.ts index 296a98bbba..9e7a7d3a26 100644 --- a/packages/ui/tailwind-utils-config/components/dialog.ts +++ b/packages/ui/tailwind-utils-config/components/dialog.ts @@ -5,7 +5,7 @@ export default { '@apply fixed inset-0 z-50': '', '&[data-state="open"]': { - animation: 'cn-overlay-fadeIn 0.2s ease-out forwards', + animation: 'cn-overlay-fadeIn 0.2s ease-out forwards' }, '&[data-state="closed"]': { From 6af864acca4b5a3fdc22b3ddbb5fa08d7bb79595 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:40:20 +0400 Subject: [PATCH 109/180] Settings container refactoring (#2070) * add settings layout rafactoring * fix layout --- .../views/labels/project-labels-list.tsx | 6 +- .../labels/project-labels-list-container.tsx | 7 +- .../rules/project-branch-rules-container.tsx | 39 ++-- .../repo/labels/labels-list-container.tsx | 1 - packages/ui/src/components/layout.tsx | 19 +- packages/ui/src/styles/styles.css | 4 + .../labels/components/labels-list-view.tsx | 6 +- .../ui/src/views/labels/labels-list-page.tsx | 84 ++++----- .../layouts/content-layout-with-sidebar.tsx | 27 ++- .../layouts/webhooks-settings-layout.tsx | 1 - .../profile-settings-general-page.tsx | 173 ++++++++---------- .../profile-settings-keys-page.tsx | 10 +- .../profile-settings-layout.tsx | 8 +- .../project-general/project-general-page.tsx | 2 +- .../components/repo-branch-rules-fields.tsx | 2 +- .../repo-branch-settings-rules-page.tsx | 12 +- .../repo/repo-commits/repo-commits-view.tsx | 4 +- .../repo-settings-general-rules.tsx | 17 +- .../repo-settings-general-page.tsx | 87 +++++---- .../repo-settings/repo-settings-layout.tsx | 2 +- .../repo-settings-rules-page.tsx | 70 +++---- .../components/repo-tag-rules-fields.tsx | 2 +- .../repo-tag-settings-rules-page.tsx | 11 +- .../repo-webhook-create-page.tsx | 75 ++++---- .../repo-webhook-execution-details-page.tsx | 119 ++++++------ .../repo-webhook-executions-list-page.tsx | 34 ++-- .../webhook-list/repo-webhook-list-page.tsx | 55 +++--- 27 files changed, 431 insertions(+), 446 deletions(-) diff --git a/apps/design-system/src/subjects/views/labels/project-labels-list.tsx b/apps/design-system/src/subjects/views/labels/project-labels-list.tsx index 350320d499..059b7498b2 100644 --- a/apps/design-system/src/subjects/views/labels/project-labels-list.tsx +++ b/apps/design-system/src/subjects/views/labels/project-labels-list.tsx @@ -4,13 +4,13 @@ import { LabelsListStore } from '@subjects/stores/labels-store' import { noop } from '@utils/viewUtils' import { DeleteAlertDialog } from '@harnessio/ui/components' -import { LabelsListPage } from '@harnessio/ui/views' +import { LabelsListPage, SandboxLayout } from '@harnessio/ui/views' export const ProjectLabelsList = () => { const [openAlertDeleteDialog, setOpenAlertDeleteDialog] = useState(false) return ( - <> + <SandboxLayout.Content> <LabelsListPage useLabelsStore={LabelsListStore.useLabelsStore} searchQuery={''} @@ -25,6 +25,6 @@ export const ProjectLabelsList = () => { deleteFn={noop} isLoading={false} /> - </> + </SandboxLayout.Content> ) } diff --git a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx index e9070b0f73..9a624f20c3 100644 --- a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx @@ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom' import { useDeleteSpaceLabelMutation } from '@harnessio/code-service-client' import { DeleteAlertDialog } from '@harnessio/ui/components' -import { ILabelType, LabelsListPage } from '@harnessio/ui/views' +import { ILabelType, LabelsListPage, SandboxLayout } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../framework/hooks/useGetSpaceParam' import { useMFEContext } from '../../../framework/hooks/useMFEContext.ts' @@ -58,9 +58,8 @@ export const ProjectLabelsList = () => { } return ( - <> + <SandboxLayout.Content> <LabelsListPage - className="mx-auto max-w-[1040px]" useLabelsStore={useLabelsStore} searchQuery={query} setSearchQuery={setQuery} @@ -90,6 +89,6 @@ export const ProjectLabelsList = () => { deleteFn={handleDeleteLabel} isLoading={isDeletingSpaceLabel} /> - </> + </SandboxLayout.Content> ) } diff --git a/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx b/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx index 0099585ff5..b719d5d973 100644 --- a/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx +++ b/apps/gitness/src/pages-v2/project/rules/project-branch-rules-container.tsx @@ -18,7 +18,8 @@ import { getBranchRules, MergeStrategy, RepoBranchSettingsFormFields, - RepoBranchSettingsRulesPage + RepoBranchSettingsRulesPage, + SandboxLayout } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../framework/hooks/useGetSpaceParam' @@ -244,22 +245,24 @@ export const ProjectBranchRulesContainer = () => { ? t('views:pullRequests.selectUsersUGAndServiceAccounts', 'Select users, user groups and service accounts') : t('views:pullRequests.selectUsers', 'Select users') return ( - <RepoBranchSettingsRulesPage - handleRuleUpdate={handleRuleUpdate} - apiErrors={errors} - isLoading={addingRule || updatingRule} - useRepoRulesStore={useProjectRulesStore} - useBranchRulesStore={useBranchRulesStore} - handleCheckboxChange={handleCheckboxChange} - handleSubmenuChange={handleSubmenuChange} - handleSelectChangeForRule={handleSelectChangeForRule} - handleInputChange={handleInputChange} - handleInitialRules={handleInitialRules} - setPrincipalsSearchQuery={setPrincipalsSearchQuery} - principalsSearchQuery={principalsSearchQuery} - isSubmitSuccess={isSubmitSuccess} - bypassListPlaceholder={searchPlaceholder} - projectScope - /> + <SandboxLayout.Content> + <RepoBranchSettingsRulesPage + handleRuleUpdate={handleRuleUpdate} + apiErrors={errors} + isLoading={addingRule || updatingRule} + useRepoRulesStore={useProjectRulesStore} + useBranchRulesStore={useBranchRulesStore} + handleCheckboxChange={handleCheckboxChange} + handleSubmenuChange={handleSubmenuChange} + handleSelectChangeForRule={handleSelectChangeForRule} + handleInputChange={handleInputChange} + handleInitialRules={handleInitialRules} + setPrincipalsSearchQuery={setPrincipalsSearchQuery} + principalsSearchQuery={principalsSearchQuery} + isSubmitSuccess={isSubmitSuccess} + bypassListPlaceholder={searchPlaceholder} + projectScope + /> + </SandboxLayout.Content> ) } diff --git a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx index ec4b3b8b10..1f65fcdf0e 100644 --- a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx @@ -73,7 +73,6 @@ export const RepoLabelsList = () => { return ( <> <LabelsListPage - // className="max-w-[772px] px-0" useLabelsStore={useLabelsStore} searchQuery={query} setSearchQuery={setQuery} diff --git a/packages/ui/src/components/layout.tsx b/packages/ui/src/components/layout.tsx index a81a7629d8..7daf1edff7 100644 --- a/packages/ui/src/components/layout.tsx +++ b/packages/ui/src/components/layout.tsx @@ -46,6 +46,10 @@ const flexVariants = cva('flex', { nowrap: 'flex-nowrap', wrap: 'flex-wrap', 'wrap-reverse': 'flex-wrap-reverse' + }, + grow: { + true: 'flex-grow', + false: 'flex-grow-0' } }, defaultVariants: { @@ -129,7 +133,9 @@ interface LayoutProps { as?: ElementType } -interface FlexProps extends LayoutProps, HTMLAttributes<HTMLDivElement>, VariantProps<typeof flexVariants> {} +interface FlexProps extends LayoutProps, HTMLAttributes<HTMLDivElement>, VariantProps<typeof flexVariants> { + grow?: boolean +} interface GridProps extends LayoutProps, HTMLAttributes<HTMLDivElement>, VariantProps<typeof gridVariants> { columns?: string | number @@ -138,11 +144,18 @@ interface GridProps extends LayoutProps, HTMLAttributes<HTMLDivElement>, Variant } const Flex = forwardRef<HTMLDivElement, FlexProps>( - ({ children, className, direction, align, justify, gap, gapX, gapY, wrap, as: Comp = 'div', ...props }, ref) => { + ( + { children, className, direction, align, justify, gap, gapX, gapY, wrap, grow, as: Comp = 'div', ...props }, + ref + ) => { return ( <Comp ref={ref} - className={cn(flexVariants({ direction, align, justify, wrap }), gapVariants({ gap, gapX, gapY }), className)} + className={cn( + flexVariants({ direction, align, justify, wrap, grow }), + gapVariants({ gap, gapX, gapY }), + className + )} {...props} > {children} diff --git a/packages/ui/src/styles/styles.css b/packages/ui/src/styles/styles.css index a6b695a856..598efc60de 100644 --- a/packages/ui/src/styles/styles.css +++ b/packages/ui/src/styles/styles.css @@ -618,3 +618,7 @@ mark { .width-popover-max-width { max-width: var(--radix-popover-content-available-width); } + +.settings-form-width { + width: 570px; +} diff --git a/packages/ui/src/views/labels/components/labels-list-view.tsx b/packages/ui/src/views/labels/components/labels-list-view.tsx index 8e5928d1e6..48f9b788e4 100644 --- a/packages/ui/src/views/labels/components/labels-list-view.tsx +++ b/packages/ui/src/views/labels/components/labels-list-view.tsx @@ -64,7 +64,7 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ return ( <NoData withBorder - className="min-h-0 py-cn-xl" + className="py-cn-xl min-h-0" imageName="no-search-magnifying-glass" title={t('views:noData.noResults', 'No search results')} description={[ @@ -87,7 +87,7 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ return ( <NoData withBorder - className="min-h-0 py-cn-3xl" + className="py-cn-3xl min-h-0" imageName="no-data-branches" title={t('views:noData.labels', 'No labels yet')} description={[t('views:noData.createLabel', 'Create a new label to get started.')]} @@ -107,7 +107,7 @@ export const LabelsListView: FC<LabelsListViewProps> = ({ const isSmallWidth = widthType === 'small' return ( - <Table.Root tableClassName="table-fixed" className="mb-8 mt-4" size="compact"> + <Table.Root tableClassName="table-fixed" size="compact"> <Table.Header> <Table.Row> <Table.Head className="w-[44px]" /> diff --git a/packages/ui/src/views/labels/labels-list-page.tsx b/packages/ui/src/views/labels/labels-list-page.tsx index 8d5c5e2d8b..9a2accb4c9 100644 --- a/packages/ui/src/views/labels/labels-list-page.tsx +++ b/packages/ui/src/views/labels/labels-list-page.tsx @@ -1,8 +1,9 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, Checkbox, IconV2, ListActions, Pagination, SearchInput, Skeleton, Text } from '@/components' +import { Button, Checkbox, IconV2, Layout, ListActions, Pagination, SearchInput, Skeleton, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { ILabelsStore, SandboxLayout } from '@/views' +import { ILabelsStore } from '@/views' +import { cn } from '@utils/cn' import { LabelsListView, LabelsListViewProps } from './components/labels-list-view' @@ -54,59 +55,58 @@ export const LabelsListPage: FC<LabelsListPageProps> = ({ } return ( - // <SandboxLayout.Main> - <SandboxLayout.Content className={className}> - <Text as="h1" variant="heading-section" className="mb-6"> + <Layout.Vertical className={cn('grow', className)} gapY="xl"> + <Text as="h1" variant="heading-section"> {t('views:labelData.title', 'Labels')} </Text> - {isRepository && ( - <div className="mb-[18px]"> + <Layout.Vertical grow gapY="md"> + {isRepository && ( <Checkbox id="parent-labels" checked={getParentScopeLabels} onCheckedChange={setGetParentScopeLabels} label={t('views:labelData.showParentLabels', 'Show labels from parent scopes')} /> - </div> - )} + )} - {(!!spaceLabels.length || isDirtyList) && ( - <ListActions.Root> - <ListActions.Left> - <SearchInput - inputContainerClassName="max-w-80" - defaultValue={searchQuery || ''} - onChange={handleSearchChange} - placeholder={t('views:repos.search', 'Search')} - /> - </ListActions.Left> - <ListActions.Right> - <Button asChild> - <Link to="create"> - <IconV2 name="plus" /> - {t('views:labelData.createLabel', 'Create Label')} - </Link> - </Button> - </ListActions.Right> - </ListActions.Root> - )} + {(!!spaceLabels.length || isDirtyList) && ( + <ListActions.Root> + <ListActions.Left> + <SearchInput + inputContainerClassName="max-w-80" + defaultValue={searchQuery || ''} + onChange={handleSearchChange} + placeholder={t('views:repos.search', 'Search')} + /> + </ListActions.Left> + <ListActions.Right> + <Button asChild> + <Link to="create"> + <IconV2 name="plus" /> + {t('views:labelData.createLabel', 'Create Label')} + </Link> + </Button> + </ListActions.Right> + </ListActions.Root> + )} - {isLoading && <Skeleton.Table countRows={5} countColumns={3} />} + {isLoading && <Skeleton.Table countRows={5} countColumns={3} />} - {!isLoading && ( - <LabelsListView - {...labelsListViewProps} - labels={spaceLabels} - labelContext={{ space: space_ref, repo: repo_ref }} - handleResetQueryAndPages={handleResetQueryAndPages} - searchQuery={searchQuery} - values={spaceValues} - toRepoLabelDetails={toRepoLabelDetails} - /> - )} + {!isLoading && ( + <LabelsListView + {...labelsListViewProps} + labels={spaceLabels} + labelContext={{ space: space_ref, repo: repo_ref }} + handleResetQueryAndPages={handleResetQueryAndPages} + searchQuery={searchQuery} + values={spaceValues} + toRepoLabelDetails={toRepoLabelDetails} + /> + )} + </Layout.Vertical> <Pagination totalItems={totalItems} pageSize={pageSize} currentPage={page} goToPage={setPage} /> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/layouts/content-layout-with-sidebar.tsx b/packages/ui/src/views/layouts/content-layout-with-sidebar.tsx index dcfd6bff16..5e759eb519 100644 --- a/packages/ui/src/views/layouts/content-layout-with-sidebar.tsx +++ b/packages/ui/src/views/layouts/content-layout-with-sidebar.tsx @@ -4,6 +4,8 @@ import { Layout, Link, ScrollArea, Separator, Text } from '@/components' import { useRouterContext } from '@/context' import { cn } from '@utils/cn' +import { SandboxLayout } from './SandboxLayout' + export interface SidebarMenuItemSubItem { id: string | number title: string @@ -33,8 +35,6 @@ export interface ContentLayoutWithSidebarProps { export const ContentLayoutWithSidebar: FC<ContentLayoutWithSidebarProps> = ({ children, sidebarMenu, - sidebarContainerClassName, - sidebarViewportClassName, showBackButton = false, backButtonLabel = 'Back', backButtonTo @@ -42,20 +42,18 @@ export const ContentLayoutWithSidebar: FC<ContentLayoutWithSidebarProps> = ({ const { NavLink } = useRouterContext() return ( - <div className="relative mx-auto ml-2 flex w-full items-start gap-x-[28px] pr-4"> - <div - className={cn( - 'nested-sidebar-height top-[var(--cn-page-nav-height)] sticky w-[270px]', - sidebarContainerClassName - )} + <SandboxLayout.Content className="gap-x-cn-4xl relative flex-row"> + <Layout.Grid + className={cn('top-[var(--cn-breadcrumbs-height)] sticky w-[228px] h-fit -mt-7 pt-7 shrink-0')} + gapY="lg" > {showBackButton && ( - <Link size="sm" prefixIcon to={backButtonTo?.() ?? ''} className="mt-7 px-5"> + <Link size="sm" prefixIcon to={backButtonTo?.() ?? ''}> {backButtonLabel} </Link> )} - <ScrollArea className={cn('pb-11 !px-5 h-full', sidebarViewportClassName)}> + <ScrollArea className="h-full pb-11"> {sidebarMenu.map((group, group_idx) => ( <Fragment key={group.groupId}> {group_idx > 0 && <Separator className="mb-2" />} @@ -86,10 +84,9 @@ export const ContentLayoutWithSidebar: FC<ContentLayoutWithSidebarProps> = ({ </Fragment> ))} </ScrollArea> - </div> - <div className="flex h-[fill-available] flex-1 [&:has(>:first-child.peer)]:self-center [&>*]:flex-1"> - {children} - </div> - </div> + </Layout.Grid> + + {children} + </SandboxLayout.Content> ) } diff --git a/packages/ui/src/views/layouts/webhooks-settings-layout.tsx b/packages/ui/src/views/layouts/webhooks-settings-layout.tsx index e939ee9cd9..05ef0983ce 100644 --- a/packages/ui/src/views/layouts/webhooks-settings-layout.tsx +++ b/packages/ui/src/views/layouts/webhooks-settings-layout.tsx @@ -19,7 +19,6 @@ export function WebhookSettingsLayout() { return ( <ContentLayoutWithSidebar sidebarMenu={getNavItems(t)} - sidebarViewportClassName="pt-5" showBackButton backButtonLabel="Back to webhooks" backButtonTo={() => '../settings/webhooks'} diff --git a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx index 641dde9e0c..12dcdc9cbd 100644 --- a/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx +++ b/packages/ui/src/views/profile-settings/profile-settings-general-page.tsx @@ -6,18 +6,17 @@ import { Avatar, Button, ButtonLayout, - ControlGroup, - Fieldset, FormInput, FormSeparator, FormWrapper, IconV2, + Layout, Legend, Skeleton, Text } from '@/components' import { TFunctionWithFallback, useTranslation } from '@/context' -import { IProfileSettingsStore, ProfileSettingsErrorType, SandboxLayout } from '@/views' +import { IProfileSettingsStore, ProfileSettingsErrorType } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -180,8 +179,8 @@ export const SettingsAccountGeneralPage: FC<SettingsAccountGeneralPageProps> = ( ) return ( - <SandboxLayout.Content className="max-w-[476px] px-0"> - <Text as="h1" variant="heading-section" className="mb-10"> + <Layout.Vertical className="settings-form-width" gap="xl"> + <Text as="h1" variant="heading-section"> {t('views:profileSettings.accountSettings', 'Account settings')} </Text> @@ -192,63 +191,55 @@ export const SettingsAccountGeneralPage: FC<SettingsAccountGeneralPageProps> = ( <FormWrapper {...profileFormMethods} onSubmit={handleProfileSubmit(onProfileSubmit)}> <Legend title={t('views:profileSettings.personalInfo', 'Personal information')} /> <Avatar name={userData?.name} src="/images/anon.jpg" size="lg" rounded /> - <Fieldset> - <FormInput.Text - id="name" - {...registerProfile('name')} - placeholder={t('views:profileSettings.enterNamePlaceholder', 'Enter your name')} - label={t('views:profileSettings.name', 'Name')} - error={profileErrors?.name?.message?.toString()} - disabled={isUpdatingUser} - /> - </Fieldset> - <Fieldset> - <FormInput.Text - id="username" - {...registerProfile('username')} - placeholder={t('views:profileSettings.enterUsernamePlaceholder', 'Enter your username')} - disabled - label={t('views:profileSettings.username', 'Username')} - caption={t( - 'views:profileSettings.enterUsernameCaption', - 'This username will be shown across the platform.' - )} - /> - </Fieldset> - <Fieldset> - <FormInput.Text - id="email" - {...registerProfile('email')} - placeholder="name@domain.com" - label={t('views:profileSettings.accountEmail', 'Account email')} - disabled={isUpdatingUser} - /> - </Fieldset> + <FormInput.Text + id="name" + {...registerProfile('name')} + placeholder={t('views:profileSettings.enterNamePlaceholder', 'Enter your name')} + label={t('views:profileSettings.name', 'Name')} + error={profileErrors?.name?.message?.toString()} + disabled={isUpdatingUser} + /> + <FormInput.Text + id="username" + {...registerProfile('username')} + placeholder={t('views:profileSettings.enterUsernamePlaceholder', 'Enter your username')} + disabled + label={t('views:profileSettings.username', 'Username')} + caption={t( + 'views:profileSettings.enterUsernameCaption', + 'This username will be shown across the platform.' + )} + /> + <FormInput.Text + id="email" + {...registerProfile('email')} + placeholder="name@domain.com" + label={t('views:profileSettings.accountEmail', 'Account email')} + disabled={isUpdatingUser} + /> {renderErrorMessage(ProfileSettingsErrorType.PROFILE, error?.message || '')} - <ControlGroup type="button"> - <ButtonLayout horizontalAlign="start"> - {!profileSubmitted ? ( - <Button - type="submit" - disabled={!isProfileValid || isUpdatingUser || !Object.keys(profileDirtyFields).length} - > - {isUpdatingUser - ? t('views:profileSettings.updatingProfileButton', 'Updating...') - : t('views:profileSettings.updateProfileButton', 'Update Profile')} - </Button> - ) : ( - <Button className="pointer-events-none" variant="ghost" type="button" theme="success"> - {t('views:profileSettings.updatedButton', 'Updated')}   - <IconV2 name="check" size="xs" /> - </Button> - )} - </ButtonLayout> - </ControlGroup> + <ButtonLayout horizontalAlign="start"> + {!profileSubmitted ? ( + <Button + type="submit" + disabled={!isProfileValid || isUpdatingUser || !Object.keys(profileDirtyFields).length} + > + {isUpdatingUser + ? t('views:profileSettings.updatingProfileButton', 'Updating...') + : t('views:profileSettings.updateProfileButton', 'Update Profile')} + </Button> + ) : ( + <Button className="pointer-events-none" variant="ghost" type="button" theme="success"> + {t('views:profileSettings.updatedButton', 'Updated')}   + <IconV2 name="check" size="xs" /> + </Button> + )} + </ButtonLayout> </FormWrapper> - <FormSeparator className="border-cn-borders-4 my-7" /> + <FormSeparator /> <FormWrapper {...passwordFormMethods} onSubmit={handlePasswordSubmit(onPasswordSubmit)}> <Legend @@ -258,48 +249,42 @@ export const SettingsAccountGeneralPage: FC<SettingsAccountGeneralPageProps> = ( 'Minimum of 6 characters long containing at least one number and a mixture of uppercase and lowercase letters.' )} /> - <Fieldset> - <FormInput.Text - id="newPassword" - type="password" - {...registerPassword('newPassword')} - placeholder={t('views:profileSettings.enterPasswordPlaceholder', 'Enter a new password')} - label={t('views:profileSettings.newPassword', 'New password')} - disabled={isUpdatingPassword} - /> - </Fieldset> - <Fieldset> - <FormInput.Text - id="confirmPassword" - type="password" - {...registerPassword('confirmPassword')} - placeholder={t('views:profileSettings.confirmPasswordPlaceholder', 'Confirm your new password')} - label={t('views:profileSettings.confirmPassword', 'Confirm password')} - disabled={isUpdatingPassword} - /> - </Fieldset> + <FormInput.Text + id="newPassword" + type="password" + {...registerPassword('newPassword')} + placeholder={t('views:profileSettings.enterPasswordPlaceholder', 'Enter a new password')} + label={t('views:profileSettings.newPassword', 'New password')} + disabled={isUpdatingPassword} + /> + <FormInput.Text + id="confirmPassword" + type="password" + {...registerPassword('confirmPassword')} + placeholder={t('views:profileSettings.confirmPasswordPlaceholder', 'Confirm your new password')} + label={t('views:profileSettings.confirmPassword', 'Confirm password')} + disabled={isUpdatingPassword} + /> {renderErrorMessage(ProfileSettingsErrorType.PASSWORD, error?.message || '')} - <ControlGroup type="button"> - <ButtonLayout> - {!passwordSubmitted ? ( - <Button type="submit" disabled={isUpdatingPassword}> - {isUpdatingPassword - ? t('views:profileSettings.updating', 'Updating...') - : t('views:profileSettings.updatePassword', 'Update password')} - </Button> - ) : ( - <Button className="pointer-events-none" variant="ghost" type="button" theme="success"> - {t('views:profileSettings.updatedButton', 'Updated')}   - <IconV2 name="check" size="xs" /> - </Button> - )} - </ButtonLayout> - </ControlGroup> + <ButtonLayout> + {!passwordSubmitted ? ( + <Button type="submit" disabled={isUpdatingPassword}> + {isUpdatingPassword + ? t('views:profileSettings.updating', 'Updating...') + : t('views:profileSettings.updatePassword', 'Update password')} + </Button> + ) : ( + <Button className="pointer-events-none" variant="ghost" type="button" theme="success"> + {t('views:profileSettings.updatedButton', 'Updated')}   + <IconV2 name="check" size="xs" /> + </Button> + )} + </ButtonLayout> </FormWrapper> </> )} - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/profile-settings/profile-settings-keys-page.tsx b/packages/ui/src/views/profile-settings/profile-settings-keys-page.tsx index 8f4bd94d6f..af846c9688 100644 --- a/packages/ui/src/views/profile-settings/profile-settings-keys-page.tsx +++ b/packages/ui/src/views/profile-settings/profile-settings-keys-page.tsx @@ -1,8 +1,8 @@ import { FC } from 'react' -import { Alert, Button, Fieldset, FormSeparator, Layout, Legend, Spacer, Text } from '@/components' +import { Alert, Button, Fieldset, FormSeparator, Layout, Legend, Text } from '@/components' import { useTranslation } from '@/context' -import { ApiErrorType, SandboxLayout } from '@/views' +import { ApiErrorType } from '@/views' import { ProfileKeysList } from './components/profile-settings-keys-list' import { ProfileTokensList } from './components/profile-settings-tokens-list' @@ -42,11 +42,11 @@ const SettingsAccountKeysPage: FC<SettingsAccountKeysPageProps> = ({ const { t } = useTranslation() return ( - <SandboxLayout.Content className="px-0"> + <Layout.Vertical gap="xl" grow> <Text as="h1" variant="heading-section"> {t('views:profileSettings.keysAndTokens', 'Keys and Tokens')} </Text> - <Spacer size={10} /> + <Layout.Vertical gap="2xl"> <Fieldset className="gap-y-5"> <div className="flex items-end justify-between"> @@ -103,7 +103,7 @@ const SettingsAccountKeysPage: FC<SettingsAccountKeysPageProps> = ({ )} </Fieldset> </Layout.Vertical> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/profile-settings/profile-settings-layout.tsx b/packages/ui/src/views/profile-settings/profile-settings-layout.tsx index fab18f65d5..3bac2071a5 100644 --- a/packages/ui/src/views/profile-settings/profile-settings-layout.tsx +++ b/packages/ui/src/views/profile-settings/profile-settings-layout.tsx @@ -4,7 +4,7 @@ import { ContentLayoutWithSidebar } from '@/views' const getNavItems = (t: TFunctionWithFallback) => [ { groupId: 0, - title: t('views:profileSettings.accountSettings', 'Account settings'), + // title: t('views:profileSettings.accountSettings', 'Account settings'), items: [ { id: 0, title: t('views:repos.generalTab', 'General'), to: 'general' }, { id: 1, title: t('views:repos.keysTab', 'Keys and tokens'), to: 'keys' } @@ -17,11 +17,7 @@ export function ProfileSettingsLayout() { const { t } = useTranslation() return ( - <ContentLayoutWithSidebar - sidebarMenu={getNavItems(t)} - sidebarViewportClassName="pt-7" - sidebarContainerClassName="top-[var(--cn-breadcrumbs-height)]" - > + <ContentLayoutWithSidebar sidebarMenu={getNavItems(t)}> <Outlet /> </ContentLayoutWithSidebar> ) diff --git a/packages/ui/src/views/project/project-general/project-general-page.tsx b/packages/ui/src/views/project/project-general/project-general-page.tsx index c444d5dc7a..78f6743401 100644 --- a/packages/ui/src/views/project/project-general/project-general-page.tsx +++ b/packages/ui/src/views/project/project-general/project-general-page.tsx @@ -101,7 +101,7 @@ export const ProjectSettingsGeneralPage = ({ return ( <SandboxLayout.Main> - <SandboxLayout.Content className="mx-auto max-w-[38.125rem] pt-[3.25rem]"> + <SandboxLayout.Content className="mx-auto max-w-[38.125rem]"> <Text as="h2" variant="heading-section" className="mb-10"> {t('views:projectSettings.general.mainTitle', 'Project Settings')} </Text> diff --git a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx index 92c7f1b623..50e100a715 100644 --- a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx +++ b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-fields.tsx @@ -214,7 +214,7 @@ export const BranchSettingsRuleBypassListField: FC< <ControlGroup> <FormInput.Checkbox {...register!('repo_owners')} - id="edit-permissons" + id="edit-permissions" label={t( 'views:repos.editPermissionsCheckboxDescription', 'Allow users with edit permission on the repository to bypass' diff --git a/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx index fecef95a76..93c3c02d16 100644 --- a/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-branch-rules/repo-branch-settings-rules-page.tsx @@ -8,12 +8,14 @@ import { Fieldset, FormSeparator, FormWrapper, + Layout, MultiSelectOption, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { IProjectRulesStore, IRepoStore, repoBranchSettingsFormSchema, SandboxLayout } from '@/views' +import { IProjectRulesStore, IRepoStore, repoBranchSettingsFormSchema } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' +import { cn } from '@utils/index' import { BranchSettingsRuleBypassListField, @@ -135,11 +137,11 @@ export const RepoBranchSettingsRulesPage: FC<RepoBranchSettingsRulesPageProps> = apiErrors?.principals || apiErrors?.statusChecks || apiErrors?.addRule || apiErrors?.updateRule || null return ( - <SandboxLayout.Content className={`max-w-[612px] ml-3 ${projectScope ? 'mx-auto' : ''}`}> - <Text as="h1" variant="heading-section" className="mb-4"> + <Layout.Vertical className={cn('settings-form-width', { 'mx-auto': projectScope })} gapY="md"> + <Text as="h1" variant="heading-section"> {presetRuleData ? t('views:repos.updateBranchRule', 'Update branch rule') - : t('views:repos.createBranchRule', 'Create a branch rule')} + : t('views:repos.createBranchRule', 'Create branch rule')} </Text> <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> @@ -202,6 +204,6 @@ export const RepoBranchSettingsRulesPage: FC<RepoBranchSettingsRulesPageProps> = {!!apiErrorsValue && <span className="text-2 text-cn-foreground-danger">{apiErrorsValue}</span>} </FormWrapper> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index 9fe6f574e0..049f052439 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -53,12 +53,12 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ return ( <SandboxLayout.Main> <SandboxLayout.Content> - <Layout.Flex direction="column" gapY="xl" className="grow"> + <Layout.Flex direction="column" gapY="xl" grow> <Text variant="heading-section" as="h2"> Commits </Text> - <Layout.Flex direction="column" gapY="md" className="grow"> + <Layout.Flex direction="column" gapY="md" grow> <div> <BranchSelectorContainer /> </div> diff --git a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx index 3e556ac2d3..29d904a3bc 100644 --- a/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx +++ b/packages/ui/src/views/repo/repo-settings/components/repo-settings-general-rules.tsx @@ -4,6 +4,7 @@ import { Fragment } from 'react/jsx-runtime' import { Alert, IconV2, + Layout, ListActions, MoreActionsTooltip, NoData, @@ -11,7 +12,6 @@ import { SearchInput, Separator, Skeleton, - Spacer, SplitButton, StackedList, Tag, @@ -124,13 +124,13 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ }, [rulesSearchQuery, rules]) return ( - <> - <ListActions.Root className="mt-1"> + <Layout.Vertical gapY="sm" grow> + <ListActions.Root> <ListActions.Left> <SearchInput id="search" defaultValue={rulesSearchQuery} - inputContainerClassName="max-w-80" + inputContainerClassName="w-80" placeholder={t('views:repos.search', 'Search')} onChange={handleSearchChange} /> @@ -170,12 +170,8 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ {isShowRulesContent ? ( <> - <Spacer size={3} /> {isLoading ? ( - <> - <Spacer size={7} /> - <Skeleton.List /> - </> + <Skeleton.List /> ) : rules?.length !== 0 ? ( <StackedList.Root> {rules?.map((rule, idx) => @@ -278,7 +274,6 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ </> ) : ( <> - <Spacer size={3} /> <NoData withBorder className="py-cn-3xl min-h-0" @@ -314,6 +309,6 @@ export const RepoSettingsGeneralRules: FC<RepoSettingsGeneralRulesProps> = ({ /> </> )} - </> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx b/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx index 805bfb8bc1..9dbd3641fb 100644 --- a/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx +++ b/packages/ui/src/views/repo/repo-settings/repo-settings-general-page.tsx @@ -2,7 +2,6 @@ import { FC } from 'react' import { FormSeparator, Layout, Text } from '@/components' import { useTranslation } from '@/context' -import { SandboxLayout } from '@/views' import { BranchSelectorContainerProps } from '@/views/repo/components' import { RepoSettingsGeneralDelete } from './components/repo-settings-general-delete' @@ -54,51 +53,49 @@ export const RepoSettingsGeneralPage: FC<RepoSettingsGeneralPageProps> = ({ useRepoRulesStore() return ( - <SandboxLayout.Content className="max-w-[635px]"> - <Layout.Vertical gap="xl"> - <Text as="h2" variant="heading-section"> - {t('views:repos.generalSettings', 'General settings')} - </Text> + <Layout.Vertical className="settings-form-width" gap="xl"> + <Text as="h1" variant="heading-section"> + {t('views:repos.generalSettings', 'General settings')} + </Text> - <Layout.Vertical gap="xl"> - <RepoSettingsGeneralForm - repoData={repoData} - handleRepoUpdate={handleRepoUpdate} - apiError={apiError} - isLoadingRepoData={loadingStates.isLoadingRepoData} - isUpdatingRepoData={loadingStates.isUpdatingRepoData} - isRepoUpdateSuccess={isRepoUpdateSuccess} - branchSelectorRenderer={branchSelectorRenderer} - /> - <FormSeparator /> - <RepoSettingsSecurityForm - showVulnerabilityScanning={showVulnerabilityScanning} - securityScanning={securityScanning} - verifyCommitterIdentity={verifyCommitterIdentity} - vulnerabilityScanning={vulnerabilityScanning === 'detect'} - handleUpdateSecuritySettings={handleUpdateSecuritySettings} - apiError={apiError} - isUpdatingSecuritySettings={loadingStates.isUpdatingSecuritySettings} - isLoadingSecuritySettings={loadingStates.isLoadingSecuritySettings} - /> - <FormSeparator /> - <RepoSettingsFeaturesForm - gitLfsEnabled={gitLfsEnabled} - handleUpdateFeaturesSettings={handleUpdateFeaturesSettings} - apiError={apiError} - isUpdatingFeaturesSettings={loadingStates.isUpdatingFeaturesSettings} - isLoadingFeaturesSettings={loadingStates.isLoadingFeaturesSettings} - /> - <FormSeparator /> - <RepoSettingsGeneralDelete - archived={repoData?.archived} - apiError={apiError} - openRepoAlertDeleteDialog={openRepoAlertDeleteDialog} - openRepoArchiveDialog={openRepoArchiveDialog} - isUpdatingArchive={loadingStates.isUpdatingArchive} - /> - </Layout.Vertical> + <Layout.Vertical gap="xl"> + <RepoSettingsGeneralForm + repoData={repoData} + handleRepoUpdate={handleRepoUpdate} + apiError={apiError} + isLoadingRepoData={loadingStates.isLoadingRepoData} + isUpdatingRepoData={loadingStates.isUpdatingRepoData} + isRepoUpdateSuccess={isRepoUpdateSuccess} + branchSelectorRenderer={branchSelectorRenderer} + /> + <FormSeparator /> + <RepoSettingsSecurityForm + showVulnerabilityScanning={showVulnerabilityScanning} + securityScanning={securityScanning} + verifyCommitterIdentity={verifyCommitterIdentity} + vulnerabilityScanning={vulnerabilityScanning === 'detect'} + handleUpdateSecuritySettings={handleUpdateSecuritySettings} + apiError={apiError} + isUpdatingSecuritySettings={loadingStates.isUpdatingSecuritySettings} + isLoadingSecuritySettings={loadingStates.isLoadingSecuritySettings} + /> + <FormSeparator /> + <RepoSettingsFeaturesForm + gitLfsEnabled={gitLfsEnabled} + handleUpdateFeaturesSettings={handleUpdateFeaturesSettings} + apiError={apiError} + isUpdatingFeaturesSettings={loadingStates.isUpdatingFeaturesSettings} + isLoadingFeaturesSettings={loadingStates.isLoadingFeaturesSettings} + /> + <FormSeparator /> + <RepoSettingsGeneralDelete + archived={repoData?.archived} + apiError={apiError} + openRepoAlertDeleteDialog={openRepoAlertDeleteDialog} + openRepoArchiveDialog={openRepoArchiveDialog} + isUpdatingArchive={loadingStates.isUpdatingArchive} + /> </Layout.Vertical> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-settings/repo-settings-layout.tsx b/packages/ui/src/views/repo/repo-settings/repo-settings-layout.tsx index 001ff6c65e..2d63b88685 100644 --- a/packages/ui/src/views/repo/repo-settings/repo-settings-layout.tsx +++ b/packages/ui/src/views/repo/repo-settings/repo-settings-layout.tsx @@ -56,7 +56,7 @@ export function RepoSettingsLayout() { const { t } = useTranslation() return ( - <ContentLayoutWithSidebar sidebarMenu={getNavItems(t)} sidebarViewportClassName="pt-7"> + <ContentLayoutWithSidebar sidebarMenu={getNavItems(t)}> <Outlet /> </ContentLayoutWithSidebar> ) diff --git a/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx index 40aa9a3c05..79d82c4e58 100644 --- a/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-settings/repo-settings-rules-page.tsx @@ -1,6 +1,5 @@ -import { Checkbox, Spacer, Text } from '@/components' +import { Checkbox, Layout, Text } from '@/components' import { useTranslation } from '@/context' -import { SandboxLayout } from '@views/layouts/SandboxLayout' import { RepoSettingsGeneralRules } from './components/repo-settings-general-rules' import { ErrorTypes, IRepoStore } from './types' @@ -46,46 +45,47 @@ export const RepoSettingsRulesPage: React.FC<RepoSettingsRulesPageProps> = ({ const { t } = useTranslation() return ( - <SandboxLayout.Content> - <Text as="h1" variant="heading-section" className="mb-2"> - Rules - </Text> - {!projectScope ? ( - <Text className="max-w-[570px]"> - {t( - 'views:repos.rulesDescription', - 'Define standards and automate workflows to ensure better collaboration and control in your repository.' - )} + <Layout.Vertical gap="xl" grow> + <Layout.Grid gapY="xs"> + <Text as="h1" variant="heading-section"> + Rules </Text> - ) : null} - {showParentScopeLabelsCheckbox && ( - <div> + {!projectScope && ( + <Text className="settings-form-width"> + {t( + 'views:repos.rulesDescription', + 'Define standards and automate workflows to ensure better collaboration and control in your repository.' + )} + </Text> + )} + </Layout.Grid> + + <Layout.Vertical grow> + {showParentScopeLabelsCheckbox && ( <Checkbox id="parent-labels" checked={parentScopeLabelsChecked} onCheckedChange={onParentScopeLabelsChange} label={t('views:rules.showParentRules', 'Show rules from parent scopes')} - className="mt-6" /> - </div> - )} - <Spacer size={3} /> + )} - <RepoSettingsGeneralRules - isLoading={isRulesLoading} - rules={rules} - apiError={apiError} - handleRuleClick={handleRuleClick} - openRulesAlertDeleteDialog={openRulesAlertDeleteDialog} - rulesSearchQuery={rulesSearchQuery} - setRulesSearchQuery={setRulesSearchQuery} - projectScope={projectScope} - toRepoBranchRuleCreate={toRepoBranchRuleCreate} - toRepoTagRuleCreate={toRepoTagRuleCreate} - ruleTypeFilter={ruleTypeFilter} - setRuleTypeFilter={setRuleTypeFilter} - toProjectRuleDetails={toProjectRuleDetails} - /> - </SandboxLayout.Content> + <RepoSettingsGeneralRules + isLoading={isRulesLoading} + rules={rules} + apiError={apiError} + handleRuleClick={handleRuleClick} + openRulesAlertDeleteDialog={openRulesAlertDeleteDialog} + rulesSearchQuery={rulesSearchQuery} + setRulesSearchQuery={setRulesSearchQuery} + projectScope={projectScope} + toRepoBranchRuleCreate={toRepoBranchRuleCreate} + toRepoTagRuleCreate={toRepoTagRuleCreate} + ruleTypeFilter={ruleTypeFilter} + setRuleTypeFilter={setRuleTypeFilter} + toProjectRuleDetails={toProjectRuleDetails} + /> + </Layout.Vertical> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/repo-tag-rules/components/repo-tag-rules-fields.tsx b/packages/ui/src/views/repo/repo-tag-rules/components/repo-tag-rules-fields.tsx index 23aa1efd02..0feaa77578 100644 --- a/packages/ui/src/views/repo/repo-tag-rules/components/repo-tag-rules-fields.tsx +++ b/packages/ui/src/views/repo/repo-tag-rules/components/repo-tag-rules-fields.tsx @@ -190,7 +190,7 @@ export const TagSettingsRuleBypassListField: FC< <ControlGroup> <FormInput.Checkbox {...register!('repo_owners')} - id="edit-permissons" + id="edit-permissions" label={t( 'views:repos.editPermissionsCheckboxDescription', 'Allow users with edit permission on the repository to bypass' diff --git a/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx b/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx index d661649c64..2bce3c085d 100644 --- a/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx +++ b/packages/ui/src/views/repo/repo-tag-rules/repo-tag-settings-rules-page.tsx @@ -1,10 +1,11 @@ import { FC, useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' -import { Button, ButtonLayout, ControlGroup, Fieldset, FormSeparator, FormWrapper, Text } from '@/components' +import { Button, ButtonLayout, ControlGroup, Fieldset, FormSeparator, FormWrapper, Layout, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { IProjectRulesStore, IRepoStore, SandboxLayout } from '@/views' +import { IProjectRulesStore, IRepoStore } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' +import { cn } from '@utils/cn' import { combineAndNormalizePrincipalsAndGroups } from '../repo-branch-rules/utils' import { @@ -118,8 +119,8 @@ export const RepoTagSettingsRulesPage: FC<RepoTagSettingsRulesPageProps> = ({ apiErrors?.principals || apiErrors?.statusChecks || apiErrors?.addRule || apiErrors?.updateRule || null return ( - <SandboxLayout.Content className={`max-w-[612px] ml-3 ${projectScope ? 'mx-auto' : ''}`}> - <Text as="h1" variant="heading-section" color="foreground-1" className="mb-4"> + <Layout.Vertical className={cn('settings-form-width', { 'mx-auto': projectScope })} gapY="md"> + <Text as="h1" variant="heading-section"> {presetRuleData ? t('views:repos.updateTagRule', 'Update tag rule') : t('views:repos.createTagRule', 'Create tag rule')} @@ -170,6 +171,6 @@ export const RepoTagSettingsRulesPage: FC<RepoTagSettingsRulesPageProps> = ({ {!!apiErrorsValue && <span className="text-2 text-cn-foreground-danger">{apiErrorsValue}</span>} </FormWrapper> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx index 034682ce73..64e90dc353 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-create/repo-webhook-create-page.tsx @@ -1,9 +1,9 @@ import { FC, useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' -import { Alert, Button, ButtonLayout, Fieldset, FormSeparator, FormWrapper, Text } from '@/components' +import { Alert, Button, ButtonLayout, Fieldset, FormSeparator, FormWrapper, Layout, Text } from '@/components' import { useTranslation } from '@/context' -import { SandboxLayout, WebhookStore } from '@/views' +import { WebhookStore } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' import { createWebhookFormSchema } from '@views/repo/webhooks/webhook-create/components/create-webhooks-form-schema' @@ -25,7 +25,6 @@ interface RepoWebhooksCreatePageProps { onFormCancel: () => void apiError?: string | null isLoading: boolean - // preSetWebHookData: CreateWebhookFormFields | null useWebhookStore: () => WebhookStore } @@ -99,38 +98,30 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ } return ( - <SandboxLayout.Content className="max-w-[632px]"> - <Text as="h1" variant="heading-section" className="mb-2"> + <Layout.Vertical className="settings-form-width" gapY="md"> + <Text as="h1" variant="heading-section"> {preSetWebhookData ? t('views:repos.editWebhookTitle', 'Order Status Update Webhook') : t('views:repos.createWebhookTitle', 'Create a webhook')} </Text> - <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)} className="gap-y-6"> - <Fieldset> - <WebhookToggleField register={register} setValue={setValue} watch={watch} /> - </Fieldset> + + <FormWrapper {...formMethods} onSubmit={handleSubmit(onSubmit)}> + <WebhookToggleField register={register} setValue={setValue} watch={watch} /> <FormSeparator /> - {preSetWebhookData ? ( + + {preSetWebhookData && ( <Text as="h2" variant="heading-subsection"> {t('views:repos.webhookDetails', 'Details')} </Text> - ) : null} - <Fieldset> - <WebhookNameField register={register} /> - </Fieldset> - <Fieldset> - <WebhookDescriptionField register={register} /> - </Fieldset> - <Fieldset> - <WebhookPayloadUrlField register={register} /> - </Fieldset> + )} + + <WebhookNameField register={register} /> + <WebhookDescriptionField register={register} /> + <WebhookPayloadUrlField register={register} /> + <WebhookSecretField register={register} /> + <WebhookSSLVerificationField register={register} /> + <Fieldset> - <WebhookSecretField register={register} /> - </Fieldset> - <Fieldset className="mt-5"> - <WebhookSSLVerificationField register={register} /> - </Fieldset> - <Fieldset className="mt-5"> <WebhookTriggerField register={register} /> {triggerValue === TriggerEventsEnum.SELECTED_EVENTS && ( <> @@ -155,22 +146,20 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ )} </Fieldset> - <Fieldset className="mt-7"> - <ButtonLayout horizontalAlign="start"> - <Button type="submit" disabled={isLoading}> - {isLoading - ? preSetWebhookData - ? t('views:repos.updatingWebhook', 'Updating Webhook...') - : t('views:repos.creatingWebhook', 'Creating Webhook...') - : preSetWebhookData - ? t('views:repos.updateWebhook', 'Update Webhook') - : t('views:repos.createWebhook', 'Create Webhook')} - </Button> - <Button type="button" variant="outline" onClick={onFormCancel}> - {t('views:repos.cancel', 'Cancel')} - </Button> - </ButtonLayout> - </Fieldset> + <ButtonLayout horizontalAlign="start"> + <Button type="submit" disabled={isLoading}> + {isLoading + ? preSetWebhookData + ? t('views:repos.updatingWebhook', 'Updating Webhook...') + : t('views:repos.creatingWebhook', 'Creating Webhook...') + : preSetWebhookData + ? t('views:repos.updateWebhook', 'Update Webhook') + : t('views:repos.createWebhook', 'Create Webhook')} + </Button> + <Button type="button" variant="outline" onClick={onFormCancel}> + {t('views:repos.cancel', 'Cancel')} + </Button> + </ButtonLayout> {!!apiError && ( <Alert.Root theme="danger"> @@ -178,6 +167,6 @@ export const RepoWebhooksCreatePage: FC<RepoWebhooksCreatePageProps> = ({ </Alert.Root> )} </FormWrapper> - </SandboxLayout.Content> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx index 5b6397079d..dfd44d4193 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page.tsx @@ -1,8 +1,8 @@ import { FC, useEffect, useMemo, useState } from 'react' -import { Button, ListActions, Spacer, StatusBadge, Text, TimeAgoCard } from '@/components' +import { Button, Layout, ListActions, StatusBadge, Text, TimeAgoCard } from '@/components' import { ModeType, useTheme, useTranslation } from '@/context' -import { SandboxLayout, WebhookStore } from '@/views' +import { WebhookStore } from '@/views' import { formatDuration } from '@utils/TimeUtils' import { CodeEditor } from '@harnessio/yaml-editor' @@ -86,64 +86,64 @@ export const RepoWebhookExecutionDetailsPage: FC<RepoWebhookExecutionDetailsPage } return ( - <SandboxLayout.Main> - <SandboxLayout.Content maxWidth="5xl" className="ml-0.5"> - <ListActions.Root> - <ListActions.Left> - <Text variant="heading-section">#{executionId}</Text> - <StatusBadge - variant="status" - theme={ - execution?.result === 'success' - ? 'success' - : ['fatal_error', 'retriable_error'].includes(execution?.result ?? '') - ? 'danger' - : 'muted' - } - > - {execution?.result === 'success' - ? 'Success' + <Layout.Vertical gap="xl" grow> + <ListActions.Root> + <ListActions.Left> + <Text as="h1" variant="heading-section"> + #{executionId} + </Text> + <StatusBadge + variant="status" + theme={ + execution?.result === 'success' + ? 'success' : ['fatal_error', 'retriable_error'].includes(execution?.result ?? '') - ? 'Failed' - : 'Invalid'} - </StatusBadge> - </ListActions.Left> - <ListActions.Right> - <Button onClick={handleRetriggerExecution} disabled={isLoading}> - {isLoading ? 'Re-triggering Execution' : 'Re-trigger Execution'} - </Button> - </ListActions.Right> - </ListActions.Root> - - <Spacer size={5} /> - <div className="flex items-center gap-10"> - <div className="flex items-center gap-1"> - <Text variant="body-single-line-normal" className="text-cn-foreground-3"> - Trigger Event: - </Text> - <Text className="text-cn-foreground-1" variant="body-single-line-normal"> - {' '} - {events.find(event => event.id === execution?.trigger_type)?.event || execution?.trigger_type} - </Text> - </div> - <div className="flex items-center gap-1"> - <Text className="flex items-center text-cn-foreground-3" variant="body-single-line-normal"> - Time: - </Text> - <TimeAgoCard timestamp={execution?.created} /> - </div> - <div className="flex items-center gap-1"> - <Text className="text-cn-foreground-3" variant="body-single-line-normal"> - Duration: - </Text> - <Text className="text-cn-foreground-1" variant="body-single-line-normal"> - {formatDuration(execution?.duration ?? 0, 'ns')} - </Text> - </div> + ? 'danger' + : 'muted' + } + > + {execution?.result === 'success' + ? 'Success' + : ['fatal_error', 'retriable_error'].includes(execution?.result ?? '') + ? 'Failed' + : 'Invalid'} + </StatusBadge> + </ListActions.Left> + <ListActions.Right> + <Button onClick={handleRetriggerExecution} disabled={isLoading}> + {isLoading ? 'Re-triggering Execution' : 'Re-trigger Execution'} + </Button> + </ListActions.Right> + </ListActions.Root> + + <div className="flex items-center gap-10"> + <div className="flex items-center gap-1"> + <Text variant="body-single-line-normal" className="text-cn-foreground-3"> + Trigger Event: + </Text> + <Text className="text-cn-foreground-1" variant="body-single-line-normal"> + {' '} + {events.find(event => event.id === execution?.trigger_type)?.event || execution?.trigger_type} + </Text> + </div> + <div className="flex items-center gap-1"> + <Text className="text-cn-foreground-3 flex items-center" variant="body-single-line-normal"> + Time: + </Text> + <TimeAgoCard timestamp={execution?.created} /> </div> - <Spacer size={8} /> + <div className="flex items-center gap-1"> + <Text className="text-cn-foreground-3" variant="body-single-line-normal"> + Duration: + </Text> + <Text className="text-cn-foreground-1" variant="body-single-line-normal"> + {formatDuration(execution?.duration ?? 0, 'ns')} + </Text> + </div> + </div> + + <div> <WebhookExecutionEditorControlBar view={view} onChangeView={onChangeView} /> - {/* <div className="rounded-b-3 border-cn-borders-3 border-x border-b"> */} <CodeEditor height="500px" language={view === 'payload' ? 'json' : 'html'} @@ -156,8 +156,7 @@ export const RepoWebhookExecutionDetailsPage: FC<RepoWebhookExecutionDetailsPage }} className="rounded-b-3 border-cn-borders-3 max-w-full" /> - {/* </div> */} - </SandboxLayout.Content> - </SandboxLayout.Main> + </div> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx index 547a43c116..351b299ead 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page.tsx @@ -1,8 +1,18 @@ import { FC, useMemo } from 'react' -import { FormSeparator, NoData, Pagination, Skeleton, StatusBadge, Table, Text, TimeAgoCard } from '@/components' +import { + FormSeparator, + Layout, + NoData, + Pagination, + Skeleton, + StatusBadge, + Table, + Text, + TimeAgoCard +} from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { SandboxLayout, WebhookStore } from '@/views' +import { WebhookStore } from '@/views' import { getBranchAndTagEvents, @@ -29,17 +39,19 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ }, [t]) return ( - <SandboxLayout.Main> - <SandboxLayout.Content> - <Text as="h1" variant="heading-section" className="mb-2"> + <Layout.Vertical gap="xl" grow> + <Layout.Grid gap="xs"> + <Text as="h1" variant="heading-section"> Order Status Update Webhook </Text> - <Text className="max-w-[570px]"> + <Text className="settings-form-width"> This webhook triggers every time an order status is updated, sending data to the specified endpoint for real-time tracking. </Text> - <FormSeparator className="my-6" /> - <Text as="h2" variant="heading-subsection" className="mb-4"> + </Layout.Grid> + <FormSeparator /> + <Layout.Vertical grow> + <Text as="h2" variant="heading-subsection"> Executions </Text> {isLoading ? ( @@ -58,7 +70,7 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ <Table.Head className="w-[136px]"> <Text variant="caption-strong">Status</Text> </Table.Head> - <Table.Head className="flex justify-end w-[176px]"> + <Table.Head className="flex w-[176px] justify-end"> <Text variant="caption-strong">Last triggered at</Text> </Table.Head> </Table.Row> @@ -122,8 +134,8 @@ const RepoWebhookExecutionsPage: FC<RepoWebhookExecutionsPageProps> = ({ ]} /> )} - </SandboxLayout.Content> - </SandboxLayout.Main> + </Layout.Vertical> + </Layout.Vertical> ) } diff --git a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx index 651c3c0310..470aea212e 100644 --- a/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx +++ b/packages/ui/src/views/repo/webhooks/webhook-list/repo-webhook-list-page.tsx @@ -1,8 +1,7 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, IconV2, ListActions, SearchInput, Skeleton, Spacer, Text } from '@/components' +import { Button, IconV2, Layout, ListActions, SearchInput, Skeleton, Text } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { SandboxLayout } from '@/views' import { RepoWebhookList } from './components/repo-webhook-list' import { RepoWebhookListPageProps } from './types' @@ -42,39 +41,35 @@ const RepoWebhookListPage: FC<RepoWebhookListPageProps> = ({ } return ( - <SandboxLayout.Content> + <Layout.Vertical gap="xl" grow> <Text as="h1" variant="heading-section"> Webhooks </Text> - <Spacer size={6} /> {error ? ( - <span className="text-2 text-cn-foreground-danger">{error || 'Something went wrong'}</span> + <Text color="danger">{error || 'Something went wrong'}</Text> ) : ( - <> + <Layout.Vertical grow> {(!!webhooks?.length || (!webhooks?.length && isDirtyList)) && ( - <> - <ListActions.Root> - <ListActions.Left> - <SearchInput - id="search" - defaultValue={searchQuery || ''} - inputContainerClassName="max-w-80" - placeholder={t('views:repos.search', 'Search')} - onChange={handleSearchChange} - /> - </ListActions.Left> - <ListActions.Right> - <Button asChild> - <Link to="create"> - <IconV2 name="plus" /> - {t('views:webhookData.create', 'Create Webhook')} - </Link> - </Button> - </ListActions.Right> - </ListActions.Root> - <Spacer size={4.5} /> - </> + <ListActions.Root> + <ListActions.Left> + <SearchInput + id="search" + defaultValue={searchQuery || ''} + inputContainerClassName="w-80" + placeholder={t('views:repos.search', 'Search')} + onChange={handleSearchChange} + /> + </ListActions.Left> + <ListActions.Right> + <Button asChild> + <Link to="create"> + <IconV2 name="plus" /> + {t('views:webhookData.create', 'Create Webhook')} + </Link> + </Button> + </ListActions.Right> + </ListActions.Root> )} {webhookLoading ? ( @@ -95,9 +90,9 @@ const RepoWebhookListPage: FC<RepoWebhookListPageProps> = ({ toRepoWebhookCreate={toRepoWebhookCreate} /> )} - </> + </Layout.Vertical> )} - </SandboxLayout.Content> + </Layout.Vertical> ) } From dcff10fb2810fa6492da02a24eb04a86a23ea9eb Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 15 Aug 2025 18:33:05 +0000 Subject: [PATCH 110/180] fix: multiple p2, p3 bugs across app (#10220) * 28f746 fix: multiple p2/p3's across app --- .../repo/repo-settings-rules-list-container.tsx | 16 +++++----------- packages/ui/locales/en/views.json | 4 ++-- packages/ui/src/components/alert-dialog.tsx | 2 +- .../components/repo-branch-rules-data.tsx | 6 +++--- .../repo/repo-branch/components/branch-list.tsx | 2 +- .../repo-branch/components/divergence-gauge.tsx | 6 +++--- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx b/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx index 1f75c6deb4..0f2af77eee 100644 --- a/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-settings-rules-list-container.tsx @@ -11,7 +11,6 @@ import { useRepoRuleListQuery } from '@harnessio/code-service-client' import { DeleteAlertDialog } from '@harnessio/ui/components' -import { wrapConditionalObjectElement } from '@harnessio/ui/utils' import { ErrorTypes, RepoSettingsRulesPage } from '@harnessio/ui/views' import { useRoutes } from '../../framework/context/NavigationContext' @@ -146,16 +145,11 @@ export const RepoSettingsRulesListContainer = () => { <DeleteAlertDialog open={isRuleAlertDeleteDialogOpen} onClose={closeAlertDeleteDialog} - {...wrapConditionalObjectElement( - { - identifier: alertDeleteParams, - deleteFn: handleDeleteRule, - isLoading: isDeletingRule, - error: apiError?.type === ErrorTypes.DELETE_RULE ? apiError : null, - type: 'rule' - }, - isRuleAlertDeleteDialogOpen - )} + identifier={alertDeleteParams} + deleteFn={handleDeleteRule} + isLoading={isDeletingRule} + error={apiError?.type === ErrorTypes.DELETE_RULE ? apiError : null} + type="rule" /> </> ) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index ac0a72fb99..603b98eb79 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -88,8 +88,8 @@ "RequireMinimumDefaultReviewerCountDescription": "Require a minimum number of default reviewers", "RequireCodeReview": "Require a minimum number of reviewers", "RequireCodeReviewDescription": "Require approval on pull requests from a minimum number of reviewers", - "AutoAddCodeOwners": "Add Code Owners as reviewers", - "AutoAddCodeOwnersDescription": "Automatically add relevant Code Owners as reviewers", + "AutoAddCodeOwners": "Add code owners as reviewers", + "AutoAddCodeOwnersDescription": "Automatically add relevant code owners as reviewers", "RequireCodeOwners": "Require review from code owners", "RequireCodeOwnersDescription": "Require approval on pull requests from one reviewer for each Code Owner rule", "RequestApprovalRule": "Request approval of new changes", diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx index 880bf14200..7b1f09d9c0 100644 --- a/packages/ui/src/components/alert-dialog.tsx +++ b/packages/ui/src/components/alert-dialog.tsx @@ -84,7 +84,7 @@ const Content = forwardRef<HTMLDivElement, ContentProps>(({ title, children }, r } theme={context.theme} > - <Dialog.Title>{title}</Dialog.Title> + <Dialog.Title className="!pt-0">{title}</Dialog.Title> </Dialog.Header> <Dialog.Body>{otherChildren}</Dialog.Body> diff --git a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-data.tsx b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-data.tsx index ca35a17aa3..9133aac648 100644 --- a/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-data.tsx +++ b/packages/ui/src/views/repo/repo-branch-rules/components/repo-branch-rules-data.tsx @@ -86,15 +86,15 @@ export const getBranchRules = (t: TFunctionWithFallback): BranchRuleType[] => [ }, { id: BranchRuleId.AUTO_ADD_CODE_OWNERS, - label: t('views:repos.AutoAddCodeOwners', 'Add Code Owners as reviewers'), - description: t('views:repos.AutoAddCodeOwnersDescription', 'Automatically add relevant Code Owners as reviewers') + label: t('views:repos.AutoAddCodeOwners', 'Add code owners as reviewers'), + description: t('views:repos.AutoAddCodeOwnersDescription', 'Automatically add relevant code owners as reviewers') }, { id: BranchRuleId.REQUIRE_CODE_OWNERS, label: t('views:repos.RequireCodeOwners', 'Require review from code owners'), description: t( 'views:repos.RequireCodeOwnersDescription', - 'Require approval on pull requests from one reviewer for each Code Owner rule' + 'Require approval on pull requests from one reviewer for each code owner rule' ) }, { diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index b68721a824..4babfed94f 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -190,7 +190,7 @@ export const BranchesList: FC<BranchListPageProps> = ({ )} {/* calculated divergence bar & default branch */} <Table.Cell className="content-center"> - <div className="flex size-full items-center justify-center"> + <div className="flex size-full items-center justify-start"> {branch?.behindAhead?.default ? ( <Tag variant="outline" size="md" value={t('views:repos.default', 'Default')} theme="gray" rounded /> ) : ( diff --git a/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx b/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx index 8b956adbf1..cb07730ea9 100644 --- a/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx @@ -26,7 +26,7 @@ export const DivergenceGauge = ({ behindAhead, className }: GaugeProps) => { return ( <div className={cn('flex w-full flex-col gap-[4px]', className)}> - <div className="mx-auto grid w-28 grid-flow-col grid-cols-[1fr_auto_1fr] items-center justify-center gap-x-1.5"> + <div className="grid w-28 grid-flow-col grid-cols-[1fr_auto_1fr] items-center justify-start gap-x-1.5"> <span className="truncate text-right text-2 leading-none text-cn-foreground-3"> {behindAhead.behind ?? 0} <span className="sr-only"> @@ -45,8 +45,8 @@ export const DivergenceGauge = ({ behindAhead, className }: GaugeProps) => { </div> {/* Both behind and ahead are 0, don't show the progress bar */} {/* TODO: replace with meter component when available */} - {behindAhead?.behind === 0 && behindAhead?.ahead == 0 ? null : ( - <div className="mx-auto flex w-28 items-center justify-center"> + {behindAhead?.behind === 0 && behindAhead?.ahead === 0 ? null : ( + <div className="flex w-28 items-center justify-start"> <div className="flex w-1/2 flex-row-reverse justify-start"> <div style={{ width: `${adjustedBehindPercentage}%` }}> <Progress From 2610035393427b1f2d0f6edfe3702d5dfde418cb Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 15 Aug 2025 21:49:31 +0000 Subject: [PATCH 111/180] Fixed favorite to unfavorite and unfavorite to favorite calls (#10223) * 25cb2a Branches - Renamed New PR to Compare * eac680 Fixed favorite to unfavorite and unfavorite to favorite --- packages/ui/locales/en/views.json | 1 + packages/ui/locales/fr/views.json | 1 + packages/ui/src/components/favorite.tsx | 3 ++- .../ui/src/views/repo/repo-branch/components/branch-list.tsx | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 603b98eb79..233ecd206f 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -142,6 +142,7 @@ "ahead": "Ahead", "pullRequest": "Pull Request", "newPullReq": "New pull request", + "compare": "Compare", "viewRules": "View Rules", "browse": "Browse", "deleteBranch": "Delete Branch", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index bc162fa1ae..ee003dc42e 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -142,6 +142,7 @@ "ahead": "En avance", "pullRequest": "Pull Request", "newPullReq": "Nouvelle requête de tirage", + "compare": "Compare", "viewRules": "Voir les règles", "browse": "Browse", "deleteBranch": "Delete Branch", diff --git a/packages/ui/src/components/favorite.tsx b/packages/ui/src/components/favorite.tsx index 14ac03d49d..800d046195 100644 --- a/packages/ui/src/components/favorite.tsx +++ b/packages/ui/src/components/favorite.tsx @@ -13,12 +13,13 @@ const Favorite: FC<FavoriteIconProps> = ({ isFavorite = false, onFavoriteToggle size="sm" variant="transparent" selectedVariant="primary" + selected={isFavorite} prefixIcon={isFavorite ? 'star-solid' : 'star'} prefixIconProps={{ className: isFavorite ? 'text-cn-icon-yellow' : 'text-cn-foreground-2', size: '2xs' }} - onChange={(selected: boolean) => onFavoriteToggle(!selected)} + onChange={(selected: boolean) => onFavoriteToggle(selected)} /> ) diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 4babfed94f..99ada0e05a 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -238,11 +238,11 @@ export const BranchesList: FC<BranchListPageProps> = ({ <MoreActionsTooltip iconName="more-horizontal" actions={[ - // Don't show New Pull Request option for default branch + // Don't show Compare option for default branch ...(!branch?.behindAhead?.default ? [ { - title: t('views:repos.newPullReq', 'New pull request'), + title: t('views:repos.compare', 'Compare'), to: toPullRequestCompare?.({ diffRefs: `${defaultBranch}...${branch.name}` }) || '', iconName: 'git-pull-request' } as ActionData From 5cef06130eab2610f29451e214cfe5de2962402f Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 15 Aug 2025 23:45:42 +0000 Subject: [PATCH 112/180] add undo and redo to pr comment box (#10224) * add undo and redo to pr comment box --- packages/ui/locales/en/views.json | 2 +- packages/ui/locales/fr/views.json | 2 +- .../conversation/pull-request-comment-box.tsx | 80 ++++++++++++++++--- .../repo-tags/components/repo-tags-list.tsx | 2 +- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 233ecd206f..cb912dded9 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -134,7 +134,7 @@ "createRuleButton": "Create Rule", "updatingRuleButton": "Updating Rule...", "creatingRuleButton": "Creating Rule...", - "createBranch": "Create branch", + "createBranch": "Create Branch", "branch": "Branch", "update": "Updated", "checkStatus": "Check status", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index ee003dc42e..be4c17a0a6 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -134,7 +134,7 @@ "createRuleButton": "Créer une règle de branche", "updatingRuleButton": "Mise à jour de la règle...", "creatingRuleButton": "Création de la règle...", - "createBranch": "Create branch", + "createBranch": "Create Branch", "branch": "Branche", "update": "Mettre à jour", "checkStatus": "Vérifier le statut", diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index f564d3deb0..b414116b66 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -68,6 +68,11 @@ interface ToolbarItem { size?: number } +interface CommentHistory { + comment: string + selection: TextSelection +} + export interface PullRequestCommentBoxProps { className?: string comment: string @@ -142,13 +147,17 @@ export const PullRequestCommentBox = ({ const [textSelection, setTextSelection] = useState({ start: 0, end: 0 }) const [showAiLoader, setShowAiLoader] = useState(false) const dropZoneRef = useRef<HTMLDivElement>(null) - - const [commentError, setCommentError] = useState<string | null>(null) - const [isLoading, setIsLoading] = useState(false) - + const [commentError, setCommentError] = useState<string | null>(null) const [localPrincipalsMentionMap, setLocalPrincipalsMentionMap] = useState<PrincipalsMentionMap>({}) + const initialCommentHistory: CommentHistory = { + comment: comment, + selection: { start: 0, end: 0 } + } + const [commentHistory, setCommentHistory] = useState<CommentHistory[]>([initialCommentHistory]) + const [commentFuture, setCommentFuture] = useState<CommentHistory[]>([]) + const { principalsMentionMap, setPrincipalsMentionMap @@ -168,7 +177,7 @@ export const PullRequestCommentBox = ({ const clearComment = () => { if (!preserveCommentOnSave) { - setComment('') // Clear the comment box after saving + setCommentAndTextSelection('', { start: 0, end: 0 }) // Clear the comment box after saving } } @@ -477,16 +486,57 @@ export const PullRequestCommentBox = ({ textAreaRef.current && textAreaRef.current.focus() } - const setCommentAndTextSelection = (comment: string, textSelection: TextSelection) => { + const handleUndo = (e: KeyboardEvent<HTMLTextAreaElement>): void => { + e.preventDefault() + + if (commentHistory.length === 1) { + return + } + + const current = commentHistory.pop() ?? initialCommentHistory + const undo = commentHistory.pop() ?? current + + !isUndefined(undo) && + !isUndefined(current) && + setCommentAndTextSelection(undo.comment, undo.selection, [...commentHistory, undo], [...commentFuture, current]) + } + + const handleRedo = (e: KeyboardEvent<HTMLTextAreaElement>): void => { + e.preventDefault() + + if (commentFuture.length === 0) { + return + } + + const redo = commentFuture.pop() + + !isUndefined(redo) && + setCommentAndTextSelection(redo.comment, redo.selection, [...commentHistory, redo], commentFuture) + } + + const setCommentAndTextSelection = ( + comment: string, + textSelection: TextSelection, + replaceHistory?: CommentHistory[], + replaceFuture?: CommentHistory[] + ): void => { setTextSelection(textSelection) setComment(comment) + + const current = { + comment: comment, + selection: textSelection + } + + setCommentHistory(replaceHistory ?? [...commentHistory, current]) + setCommentFuture(replaceFuture ? replaceFuture : []) } - const onCommentChange = (e: ChangeEvent<HTMLTextAreaElement>) => { + const onCommentChange = (e: ChangeEvent<HTMLTextAreaElement>): void => { setCommentAndTextSelection(e.target.value, { start: e.target.selectionStart, end: e.target.selectionEnd }) } - const onCommentSelect = () => { + const onCommentSelect = (): void => { if (textAreaRef.current) { const target = textAreaRef.current @@ -494,7 +544,7 @@ export const PullRequestCommentBox = ({ } } - const onMouseUp = () => { + const onMouseUp = (): void => { if (textAreaRef.current) { const target = textAreaRef.current @@ -502,7 +552,7 @@ export const PullRequestCommentBox = ({ } } - const onKeyUp = (e: KeyboardEvent<HTMLTextAreaElement>) => { + const onKeyUp = (e: KeyboardEvent<HTMLTextAreaElement>): void => { switch (e.code) { case 'ArrowUp': case 'ArrowDown': @@ -529,9 +579,15 @@ export const PullRequestCommentBox = ({ } } - const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { + const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>): void => { if (e.key === 'Enter' && e.metaKey) { - handleSaveComment() + return handleSaveComment() + } + if (e.key === 'z' && e.metaKey && e.shiftKey) { + return handleRedo(e) + } + if (e.key === 'z' && e.metaKey) { + return handleUndo(e) } } diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index ce813d89d0..b6cca9fac0 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -44,7 +44,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ const getTableActions = (tag: CommitTagType): ActionData[] => [ { iconName: 'git-branch', - title: t('views:repos.createBranch', 'Create branch'), + title: t('views:repos.createBranch', 'Create Branch'), onClick: () => onOpenCreateBranchDialog(tag) }, { From 57bb66cbd9f1d2804fb36d0e741d3a950a7c43c5 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Sat, 16 Aug 2025 20:53:08 +0000 Subject: [PATCH 113/180] fix: label issues in prs (#10225) * 198e2e fix: pr comments sort order * 0afe9c fix: scroll to comment should work in mfe * 30b10a fix: labels filters in non-repo scope pr list * 075a54 fix: parent scope label colors in pr page --- apps/gitness/src/AppMFE.tsx | 2 +- ...el-store-with-project-label-values-data.ts | 6 ++- .../pull-request/pull-request-list.tsx | 2 + .../pull-request-conversation.tsx | 6 ++- .../labels/label-value-selector.tsx | 9 +++- .../conversation/pull-request-overview.tsx | 47 ++++++++++++------- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/apps/gitness/src/AppMFE.tsx b/apps/gitness/src/AppMFE.tsx index fab720d6ad..f50331dcd3 100644 --- a/apps/gitness/src/AppMFE.tsx +++ b/apps/gitness/src/AppMFE.tsx @@ -147,7 +147,7 @@ export default function AppMFE({ const { t } = useTranslationStore() return ( - <div ref={shadowRef}> + <div ref={shadowRef} id="code-mfe-root"> <ShadowRootWrapper> {/* Radix UI elements need to be rendered inside the following div with the theme class */} <div className={theme.toLowerCase()} ref={portalRef}> diff --git a/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts b/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts index 6b43c1f24a..8528aa85a5 100644 --- a/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts +++ b/apps/gitness/src/pages-v2/project/labels/hooks/use-fill-label-store-with-project-label-values-data.ts @@ -18,12 +18,14 @@ export interface UseFillLabelStoreWithProjectLabelValuesDataProps { queryPage?: number query?: string enabled?: boolean + inherited?: boolean } export const useFillLabelStoreWithProjectLabelValuesData = ({ queryPage, query, - enabled = true + enabled = true, + inherited = false }: UseFillLabelStoreWithProjectLabelValuesDataProps) => { const space_ref = useGetSpaceURLParam() const [isLoadingValues, setIsLoadingValues] = useState(false) @@ -34,7 +36,7 @@ export const useFillLabelStoreWithProjectLabelValuesData = ({ const { data: { body: labels, headers } = {}, isLoading: isLoadingSpaceLabels } = useListSpaceLabelsQuery( { space_ref: `${space_ref}/+`, - queryParams: { page: queryPage || 1, limit: 10, query: query ?? '', inherited: getParentScopeLabels } + queryParams: { page: queryPage || 1, limit: 10, query: query ?? '', inherited: inherited || getParentScopeLabels } }, { enabled } ) diff --git a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx index 2de5c2a09d..4aff31d7e8 100644 --- a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx @@ -28,6 +28,7 @@ import { getPullRequestUrl, getScopeType } from '../../../utils/scope-url-utils' import { buildPRFilters } from '../../pull-request/pull-request-utils' import { usePullRequestListStore } from '../../pull-request/stores/pull-request-list-store' import { usePopulateLabelStore } from '../../repo/labels/hooks/use-populate-label-store' +import { useFillLabelStoreWithProjectLabelValuesData } from '../labels/hooks/use-fill-label-store-with-project-label-values-data' import { useLabelsStore } from '../stores/labels-store' export default function PullRequestListPage() { @@ -54,6 +55,7 @@ export default function PullRequestListPage() { const { navigate } = useRouterContext() usePopulateLabelStore({ queryPage, query: labelsQuery, enabled: populateLabelStore, inherited: true }) + useFillLabelStoreWithProjectLabelValuesData({ queryPage, query: labelsQuery, inherited: true }) const getApiPath = useAPIPath() const { accountId, orgIdentifier, projectIdentifier } = scope diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 81960f2afc..1c99314eb5 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -531,7 +531,11 @@ export default function PullRequestConversationPage() { if (!commentId || isScrolledToComment || prPanelData.PRStateLoading || activityData?.length === 0) return // Slight timeout so the UI has time to expand/hydrate const timeoutId = setTimeout(() => { - const elem = document.getElementById(`comment-${commentId}`) + const mfeRoot = document.getElementById('code-mfe-root') + const shadowRoot = mfeRoot?.shadowRoot as ShadowRoot + const elem = mfeRoot + ? shadowRoot?.getElementById(`comment-${commentId}`) + : document.getElementById(`comment-${commentId}`) if (!elem) return elem.scrollIntoView({ behavior: 'smooth', block: 'center' }) setIsScrolledToComment(true) diff --git a/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx b/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx index d96b0e7210..34e87531a7 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/label-value-selector.tsx @@ -4,7 +4,7 @@ import { Button, DropdownMenu, IconV2, SearchBox, Tag } from '@/components' import { useTranslation } from '@/context' import { useDebounceSearch } from '@/hooks' import { wrapConditionalObjectElement } from '@/utils' -import { HandleAddLabelType, TypesLabelValueInfo } from '@/views' +import { EnumLabelColor, HandleAddLabelType, TypesLabelValueInfo } from '@/views' import { LabelsWithValueType } from './pull-request-labels-header' @@ -119,7 +119,12 @@ export const LabelValueSelector: FC<LabelValueSelectorProps> = ({ label, handleA <DropdownMenu.Item key={value.id} onSelect={handleOnSelect(value)} - tag={{ variant: 'secondary', size: 'sm', theme: label.color, value: value.value ?? '' }} + tag={{ + variant: 'secondary', + size: 'sm', + theme: value.color as EnumLabelColor, + value: value.value ?? '' + }} checkmark={label.selectedValueId === value.id} /> ))} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx index 8093f5198c..c532f24c6d 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx @@ -88,23 +88,36 @@ export const PullRequestOverview: FC<PullRequestOverviewProps> = ({ const mappedData: CommentItem<TypesPullReqActivity>[][] = useMemo(() => { if (!data) return [] - const sortedData: TypesPullReqActivity[] = orderBy(data, 'created', dateOrderSort.value as orderSortDate) - - return Array.from( - sortedData - .reduce<Map<number, CommentItem<TypesPullReqActivity>[]>>((acc, activity) => { - const parentId = activity.parent_id || activity.id - - if (!parentId) return acc - - if (!acc.has(parentId)) acc.set(parentId, []) - - acc.get(parentId)?.push(activityToCommentItem(activity)) - - return acc - }, new Map()) - .values() - ) + // Separate parent comments and child comments + const parentComments = data.filter(activity => !activity.parent_id) + const childComments = data.filter(activity => activity.parent_id) + + // Sort parent comments by the chosen order + const sortedParentComments = orderBy(parentComments, 'created', dateOrderSort.value as orderSortDate) + + // Sort child comments always in ascending order (oldest first) + const sortedChildComments = orderBy(childComments, 'created', orderSortDate.ASC) + + // Group comments into threads + const threadMap = new Map<number, CommentItem<TypesPullReqActivity>[]>() + + // Add parent comments first + sortedParentComments.forEach(activity => { + const parentId = activity.id + if (parentId) { + threadMap.set(parentId, [activityToCommentItem(activity)]) + } + }) + + // Add child comments to their respective parent threads + sortedChildComments.forEach(activity => { + const parentId = activity.parent_id + if (parentId && threadMap.has(parentId)) { + threadMap.get(parentId)?.push(activityToCommentItem(activity)) + } + }) + + return Array.from(threadMap.values()) }, [data, dateOrderSort]) /** From 7f45ed0b4566706bd1eddaabf09ca1e47f24a965 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Sat, 16 Aug 2025 22:52:57 +0000 Subject: [PATCH 114/180] Fix: multiple p3's across app (#10227) * 017838 fix: tsc/lint * 655842 fix: logs * dd85a4 fix: file click on commit details * 5d7d02 fix: query state on create branch --- .../commit-details/commit-details-store.ts | 2 ++ .../repo/repo-commit-details-diff.tsx | 19 ++++++++++-- .../repo/stores/commit-details-store.ts | 4 ++- .../ui/src/components/path-breadcrumbs.tsx | 8 ++++- .../branch-selector-dropdown.tsx | 1 - .../components/commit-changes.tsx | 30 ++++++++++++------- .../components/commit-diff.tsx | 7 +++-- .../views/repo/repo-commit-details/types.ts | 2 ++ 8 files changed, 55 insertions(+), 18 deletions(-) diff --git a/apps/design-system/src/subjects/views/commit-details/commit-details-store.ts b/apps/design-system/src/subjects/views/commit-details/commit-details-store.ts index a1da6c870d..f6fdfed9a1 100644 --- a/apps/design-system/src/subjects/views/commit-details/commit-details-store.ts +++ b/apps/design-system/src/subjects/views/commit-details/commit-details-store.ts @@ -3,6 +3,8 @@ import { noop } from '@utils/viewUtils' import { DiffFileEntry, ICommitDetailsStore } from '@harnessio/ui/views' export const commitDetailsStore: ICommitDetailsStore = { + commitSHA: '', + setCommitSHA: noop, diffs: [ { blocks: [ diff --git a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx index 6e64a915c3..c85a274b67 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx @@ -15,6 +15,7 @@ import { CommitDiff, CommitSidebar } from '@harnessio/ui/views' import Explorer from '../../components-v2/FileExplorer' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' +import { useIsMFE } from '../../framework/hooks/useIsMFE' import useCodePathDetails from '../../hooks/useCodePathDetails' import { PathParams } from '../../RouteDefinitions' import { normalizeGitRef } from '../../utils/git-utils' @@ -28,9 +29,10 @@ import { useCommitDetailsStore } from './stores/commit-details-store' */ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: boolean }) => { const repoRef = useGetRepoRef() - const { commitSHA } = useParams<PathParams>() + const { repoId, spaceId, commitSHA } = useParams<PathParams>() + const isMfe = useIsMFE() const { fullGitRef } = useCodePathDetails() - const { setDiffs, setDiffStats } = useCommitDetailsStore() + const { setDiffs, setDiffStats, setCommitSHA } = useCommitDetailsStore() const defaultCommitRange = compact(commitSHA?.split(/~1\.\.\.|\.\.\./g)) const diffApiPath = `${defaultCommitRange[0]}~1...${defaultCommitRange[defaultCommitRange.length - 1]}` @@ -46,6 +48,12 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool } }, [diffStats, setDiffStats]) + useEffect(() => { + if (commitSHA) { + setCommitSHA(commitSHA) + } + }, [commitSHA, setCommitSHA]) + const { data: currentCommitDiffData } = useGetCommitDiffQuery({ repo_ref: repoRef, commit_sha: commitSHA || '', @@ -106,7 +114,12 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool </CommitSidebar> )} - <CommitDiff useCommitDetailsStore={useCommitDetailsStore} /> + <CommitDiff + useCommitDetailsStore={useCommitDetailsStore} + toRepoFileDetails={({ path }: { path: string }) => { + return isMfe ? `/repos/${repoId}/${path}` : `/${spaceId}/repos/${repoId}/${path}` + }} + /> </Layout.Flex> ) } diff --git a/apps/gitness/src/pages-v2/repo/stores/commit-details-store.ts b/apps/gitness/src/pages-v2/repo/stores/commit-details-store.ts index 54fb260669..ddaf4f613d 100644 --- a/apps/gitness/src/pages-v2/repo/stores/commit-details-store.ts +++ b/apps/gitness/src/pages-v2/repo/stores/commit-details-store.ts @@ -7,8 +7,10 @@ export const useCommitDetailsStore = create<ICommitDetailsStore>(set => ({ commitData: null, diffStats: null, isVerified: false, + commitSHA: '', setDiffs: diffs => set({ diffs }), setCommitData: commitData => set({ commitData }), setDiffStats: diffStats => set({ diffStats }), - setIsVerified: isVerified => set({ isVerified }) + setIsVerified: isVerified => set({ isVerified }), + setCommitSHA: commitSHA => set({ commitSHA }) })) diff --git a/packages/ui/src/components/path-breadcrumbs.tsx b/packages/ui/src/components/path-breadcrumbs.tsx index 2f2f7132df..d5afe6bbc0 100644 --- a/packages/ui/src/components/path-breadcrumbs.tsx +++ b/packages/ui/src/components/path-breadcrumbs.tsx @@ -122,7 +122,13 @@ export const PathBreadcrumbs = ({ items, isEdit, isNew, ...props }: PathBreadcru {isRenderInput && renderInput()} {items.length > 0 && !isRenderInput && ( - <CopyButton name={items.map(item => item.path).join('/')} className="ml-cn-2xs" /> + <CopyButton + name={items + .slice(1) + .map(item => item.path) + .join('/')} + className="ml-cn-2xs" + /> )} </Layout.Flex> ) diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx index 834fdb4956..3979922f70 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx +++ b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx @@ -64,7 +64,6 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ value={activeTab} onValueChange={value => { setActiveTab(value as BranchSelectorTab) - setSearchQuery('') }} > <Tabs.List className="-mx-3 px-3" activeClassName="bg-cn-background-3" variant="overlined"> diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx index 3d83fa839a..398e8f06c0 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-changes.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react' -import { Accordion, Button, CopyButton, Layout, StackedList, StatusBadge, Text } from '@/components' +import { Accordion, Button, CopyButton, Layout, Link, StackedList, StatusBadge } from '@/components' import { useTranslation } from '@/context' import { DiffModeEnum } from '@git-diff-view/react' import PullRequestDiffViewer from '@views/repo/pull-request/components/pull-request-diff-viewer' @@ -25,14 +25,18 @@ interface HeaderProps { interface DataProps { data: HeaderProps[] diffMode: DiffModeEnum + toRepoFileDetails?: ({ path }: { path: string }) => string + commitSHA: string } -const LineTitle: FC<HeaderProps> = ({ text, numAdditions, numDeletions }) => { +const LineTitle: FC< + HeaderProps & { toRepoFileDetails?: ({ path }: { path: string }) => string; commitSHA: string } +> = ({ text, numAdditions, numDeletions, toRepoFileDetails, commitSHA }) => { return ( <div className="flex w-full max-w-full items-center gap-2"> - <Text variant="body-strong" truncate color="foreground-1"> + <Link to={toRepoFileDetails?.({ path: `files/${commitSHA}/~/${text}` }) ?? ''} variant="secondary"> {text} - </Text> + </Link> <CopyButton name={text} color="gray" buttonVariant="ghost" className="relative z-10" /> {!!numAdditions && ( <StatusBadge variant="outline" size="sm" theme="success"> @@ -54,7 +58,9 @@ const CommitsAccordion: FC<{ diffMode: DiffModeEnum openItems: string[] onToggle: () => void -}> = ({ header, diffMode, openItems, onToggle }) => { + toRepoFileDetails?: ({ path }: { path: string }) => string + commitSHA: string +}> = ({ header, diffMode, openItems, onToggle, toRepoFileDetails, commitSHA }) => { const { t: _ts } = useTranslation() const { highlight, wrap, fontsize } = useDiffConfig() @@ -84,10 +90,12 @@ const CommitsAccordion: FC<{ indicatorPosition="left" > <Accordion.Item value={header?.text ?? ''} className="border-none"> - <div className="py-cn-xs px-cn-sm relative"> - <Accordion.Trigger className="py-cn-xs px-cn-sm rounded-t-3 absolute inset-0 z-0 hover:cursor-pointer [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" /> - <StackedList.Field className="grid pl-5" title={<LineTitle {...header} />} disableTruncate /> - </div> + <Accordion.Trigger className="[&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center !py-cn-xs !px-cn-sm hover:cursor-pointer"> + <StackedList.Field + title={<LineTitle {...header} toRepoFileDetails={toRepoFileDetails} commitSHA={commitSHA} />} + disableTruncate + /> + </Accordion.Trigger> <Accordion.Content className="pb-0"> <div className="rounded-b-3 overflow-hidden border-t bg-transparent"> {(fileDeleted || isDiffTooLarge || fileUnchanged || header?.isBinary) && !showHiddenDiff ? ( @@ -143,7 +151,7 @@ const CommitsAccordion: FC<{ ) } -export const CommitChanges: FC<DataProps> = ({ data, diffMode }) => { +export const CommitChanges: FC<DataProps> = ({ data, diffMode, commitSHA, toRepoFileDetails }) => { const [openItems, setOpenItems] = useState<string[]>([]) useEffect(() => { @@ -172,6 +180,8 @@ export const CommitChanges: FC<DataProps> = ({ data, diffMode }) => { diffMode={diffMode} openItems={openItems} onToggle={() => toggleOpen(item.text)} + toRepoFileDetails={toRepoFileDetails} + commitSHA={commitSHA} /> ) })} diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx index ad04a4fcdd..6e4761a928 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx @@ -5,10 +5,11 @@ import { CommitChanges } from './commit-changes' export interface CommitDiffsViewProps { useCommitDetailsStore: () => ICommitDetailsStore + toRepoFileDetails?: ({ path }: { path: string }) => string } -export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsStore }) => { - const { diffs, diffStats } = useCommitDetailsStore() +export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsStore, toRepoFileDetails }) => { + const { diffs, diffStats, commitSHA } = useCommitDetailsStore() return ( <Layout.Flex direction="column" className="w-full pb-cn-xl min-h-[calc(100vh-var(--cn-page-nav-height))]" gapY="sm"> @@ -31,6 +32,8 @@ export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsSto isBinary: item.isBinary }))} diffMode={2} + toRepoFileDetails={toRepoFileDetails} + commitSHA={commitSHA} /> </Layout.Flex> ) diff --git a/packages/ui/src/views/repo/repo-commit-details/types.ts b/packages/ui/src/views/repo/repo-commit-details/types.ts index fe88d1c0e9..0f098bb7ab 100644 --- a/packages/ui/src/views/repo/repo-commit-details/types.ts +++ b/packages/ui/src/views/repo/repo-commit-details/types.ts @@ -5,8 +5,10 @@ export interface ICommitDetailsStore { commitData: TypesCommit | null diffStats: TypesDiffStats | null isVerified?: boolean + commitSHA: string setDiffs: (diffs: DiffFileEntry[]) => void setCommitData: (commitData: TypesCommit) => void setDiffStats: (diffStats: TypesDiffStats) => void setIsVerified: (isVerified: boolean) => void + setCommitSHA: (commitSHA: string) => void } From 3e8cbe7fd341d648c32d1f70f48a8d56cf1901c9 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Sat, 16 Aug 2025 23:47:45 +0000 Subject: [PATCH 115/180] fix: pr compare logic (#10228) * e8da56 fix: type issues * 53f48d fix type issues * ff3e05 fix error handling * d2f8bc fix: pr compare logic --- .../views/repo-tags/repo-tags-list.tsx | 3 +- .../branch-selector-container.tsx | 12 +- .../components-v2/create-branch-dialog.tsx | 2 +- .../pull-request/pull-request-compare.tsx | 169 +++++++++--------- .../repo/repo-tags-list-container.tsx | 2 +- packages/ui/locales/en/views.json | 2 +- packages/ui/locales/fr/views.json | 2 +- .../branch-selector-v2/branch-selector.tsx | 16 +- .../components/branch-selector-v2/types.ts | 4 +- .../compare/pull-request-compare-page.tsx | 86 ++++----- .../ui/src/views/repo/repo-branch/types.ts | 2 +- .../create-tag/create-tag-dialog.tsx | 6 +- 12 files changed, 156 insertions(+), 150 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx index 66c431334a..3079778dde 100644 --- a/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx +++ b/apps/design-system/src/subjects/views/repo-tags/repo-tags-list.tsx @@ -17,7 +17,7 @@ import { tagsStore } from './repo-tags-store' export const RepoTagsList = () => { const [openCreateTagDialog, setOpenCreateTagDialog] = useState(false) const [openCreateBranchDialog, setOpenCreateBranchDialog] = useState(false) - const [selectedTagInList, setSelectedTagInList] = useState<BranchSelectorListItem | null>(null) + const [selectedTagInList, setSelectedTagInList] = useState<BranchSelectorListItem>() const [preSelectedTab, setPreSelectedTab] = useState<BranchSelectorTab>(BranchSelectorTab.BRANCHES) const [openDeleteTagDialog, setOpenDeleteTagDialog] = useState(false) @@ -60,7 +60,6 @@ export const RepoTagsList = () => { onSubmit={noop} isLoading={false} error="" - selectedBranchOrTag={null} violation={false} bypassable={false} resetViolation={noop} diff --git a/apps/gitness/src/components-v2/branch-selector-container.tsx b/apps/gitness/src/components-v2/branch-selector-container.tsx index ff55a51570..6a90517af6 100644 --- a/apps/gitness/src/components-v2/branch-selector-container.tsx +++ b/apps/gitness/src/components-v2/branch-selector-container.tsx @@ -10,7 +10,7 @@ import { PathParams } from '../RouteDefinitions' import { orderSortDate } from '../types' interface BranchSelectorContainerProps { - selectedBranch?: BranchSelectorListItem | null + selectedBranch?: BranchSelectorListItem onSelectBranchorTag: (branchTag: BranchSelectorListItem, type: BranchSelectorTab) => void isBranchOnly?: boolean dynamicWidth?: boolean @@ -22,6 +22,7 @@ interface BranchSelectorContainerProps { branchPrefix?: string isUpdating?: boolean disabled?: boolean + autoSelectDefaultBranch?: boolean } export const BranchSelectorContainer: React.FC<BranchSelectorContainerProps> = ({ selectedBranch, @@ -35,7 +36,8 @@ export const BranchSelectorContainer: React.FC<BranchSelectorContainerProps> = ( branchPrefix, className, isUpdating, - disabled + disabled, + autoSelectDefaultBranch = true }) => { const repoRef = useGetRepoRef() const { spaceId, repoId } = useParams<PathParams>() @@ -71,7 +73,7 @@ export const BranchSelectorContainer: React.FC<BranchSelectorContainerProps> = ( useEffect(() => { // Only auto-select default branch if no branch is currently selected // This prevents the flakiness when the form is being updated - if (repository && !selectedBranch?.name && !isUpdating) { + if (repository && !selectedBranch?.name && !isUpdating && autoSelectDefaultBranch) { const defaultBranch = branches?.find(branch => branch.name === repository.default_branch) onSelectBranchorTag( @@ -79,7 +81,7 @@ export const BranchSelectorContainer: React.FC<BranchSelectorContainerProps> = ( BranchSelectorTab.BRANCHES ) } - }, [repository, selectedBranch?.name, branches, onSelectBranchorTag, isUpdating]) + }, [repository, selectedBranch?.name, branches, onSelectBranchorTag, isUpdating, autoSelectDefaultBranch]) useEffect(() => { refetchBranches() @@ -112,7 +114,7 @@ export const BranchSelectorContainer: React.FC<BranchSelectorContainerProps> = ( className={className} branchList={branchList} tagList={tagList} - selectedBranchorTag={selectedBranch ?? { name: '', sha: '', default: false }} + selectedBranchorTag={selectedBranch} repoId={repoId ?? ''} spaceId={spaceId ?? ''} searchQuery={branchTagQuery ?? ''} diff --git a/apps/gitness/src/components-v2/create-branch-dialog.tsx b/apps/gitness/src/components-v2/create-branch-dialog.tsx index 7abc1f1fe8..643aad802f 100644 --- a/apps/gitness/src/components-v2/create-branch-dialog.tsx +++ b/apps/gitness/src/components-v2/create-branch-dialog.tsx @@ -33,7 +33,7 @@ export const CreateBranchDialog = ({ }: CreateBranchDialogProps) => { const repo_ref = useGetRepoRef() const [error, setError] = useState<UsererrorError>() - const [selectedBranchOrTag, setSelectedBranchOrTag] = useState<BranchSelectorListItem | null>(null) + const [selectedBranchOrTag, setSelectedBranchOrTag] = useState<BranchSelectorListItem>() const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() const selectBranchOrTag = useCallback((branchTagName: BranchSelectorListItem, _type: BranchSelectorTab) => { diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx index af64c9f1bf..edfed84792 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx @@ -24,7 +24,6 @@ import { import { IconV2 } from '@harnessio/ui/components' import { BranchSelectorListItem, - BranchSelectorTab, CommitSelectorListItem, CompareFormFields, HandleAddLabelType, @@ -81,11 +80,11 @@ export const CreatePullRequest = () => { const navigate = useNavigate() const [apiError, setApiError] = useState<string | null>(null) const repoRef = useGetRepoRef() - const [selectedTargetBranch, setSelectedTargetBranch] = useState<BranchSelectorListItem | null>( - diffTargetBranch ? { name: diffTargetBranch, sha: '' } : null + const [selectedTargetBranch, setSelectedTargetBranch] = useState<BranchSelectorListItem | undefined>( + diffTargetBranch ? { name: diffTargetBranch, sha: '' } : undefined ) - const [selectedSourceBranch, setSelectedSourceBranch] = useState<BranchSelectorListItem | null>( - diffSourceBranch ? { name: diffSourceBranch, sha: '' } : null + const [selectedSourceBranch, setSelectedSourceBranch] = useState<BranchSelectorListItem | undefined>( + diffSourceBranch ? { name: diffSourceBranch, sha: '' } : undefined ) const [prBranchCombinationExists, setPrBranchCombinationExists] = useState<{ number: number @@ -107,22 +106,26 @@ export const CreatePullRequest = () => { const [cachedDiff, setCachedDiff] = useAtom(changesInfoAtom) const [mergeability, setMergeabilty] = useState<boolean>() const [jumpToDiff, setJumpToDiff] = useState('') - const diffApiPath = useMemo( - () => - // show range of commits and user selected subrange - commitRange.length > 0 - ? `${commitRange[0]}~1...${commitRange[commitRange.length - 1]}` - : // show range of commits and user did not select a subrange - `${normalizeGitRef(targetRef)}...${normalizeGitRef(sourceRef)}`, - [commitRange, targetRef, sourceRef] + const diffApiPath = useMemo(() => { + if (!targetRef || !sourceRef) return '' + // show range of commits and user selected subrange + return commitRange.length > 0 + ? `${commitRange[0]}~1...${commitRange[commitRange.length - 1]}` + : // show range of commits and user did not select a subrange + `${normalizeGitRef(targetRef)}...${normalizeGitRef(sourceRef)}` + }, [commitRange, targetRef, sourceRef]) + + const { data: { body: prTemplateData } = {} } = useGetContentQuery( + { + path: '.harness/pull_request_template.md', + repo_ref: repoRef, + queryParams: { include_commit: false, git_ref: normalizeGitRef(diffTargetBranch || '') } + }, + { + enabled: !!repoRef && !!diffTargetBranch + } ) - const { data: { body: prTemplateData } = {} } = useGetContentQuery({ - path: '.harness/pull_request_template.md', - repo_ref: repoRef, - queryParams: { include_commit: false, git_ref: normalizeGitRef(diffTargetBranch || '') } - }) - useEffect(() => { if (prTemplateData?.content?.data) { setPrTemplate(decodeGitContent(prTemplateData?.content?.data)) @@ -184,10 +187,10 @@ export const CreatePullRequest = () => { useEffect(() => { // Set isBranchSelected to false if source and target branches are the same, otherwise true - if (selectedSourceBranch && selectedTargetBranch && selectedSourceBranch.name === selectedTargetBranch.name) { - setIsBranchSelected(false) - } else { + if (selectedSourceBranch && selectedTargetBranch && selectedSourceBranch.name !== selectedTargetBranch.name) { setIsBranchSelected(true) + } else { + setIsBranchSelected(false) } }, [selectedSourceBranch, selectedTargetBranch, setIsBranchSelected]) @@ -246,7 +249,7 @@ export const CreatePullRequest = () => { useEffect(() => { if (repoMetadata?.default_branch) { setSelectedTargetBranch({ name: diffTargetBranch || repoMetadata.default_branch, sha: '' }) - setSelectedSourceBranch({ name: diffSourceBranch || '', sha: '' }) + // setSelectedSourceBranch({ name: diffSourceBranch || '', sha: '' }) } }, [repoMetadata, diffTargetBranch, diffSourceBranch]) @@ -310,34 +313,37 @@ export const CreatePullRequest = () => { useEffect(() => { // useMergeCheckMutation setApiError(null) + setMergeabilty(undefined) // reset mergeability state when diffRefs change, to hide the last status + if (!targetRef || !sourceRef) return mergeCheck({ queryParams: {}, repo_ref: repoRef, range: diffApiPath }) .then(({ body: value }) => { setMergeabilty(value?.mergeable) }) .catch(err => { - if (err.message !== "head branch doesn't contain any new commits.") { - setApiError('Error in merge check') - } else { - setApiError("head branch doesn't contain any new commits.") - } + setApiError(err.message || 'Error in merge check') setMergeabilty(false) }) - }, [repoRef, diffApiPath]) + }, [repoRef, diffApiPath, targetRef, sourceRef]) const { data: { body: diffStats } = {} } = useDiffStatsQuery( { queryParams: {}, repo_ref: repoRef, range: diffApiPath }, { enabled: !!repoRef && !!diffApiPath } ) - const { data: { body: pullReqData } = {} } = useGetPullReqByBranchesQuery({ - repo_ref: repoRef, - source_branch: selectedSourceBranch?.name || repoMetadata?.default_branch || '', - target_branch: selectedTargetBranch?.name || repoMetadata?.default_branch || '', - queryParams: { - include_checks: true, - include_rules: true + const { data: { body: pullReqData } = {} } = useGetPullReqByBranchesQuery( + { + repo_ref: repoRef, + source_branch: sourceRef || '', + target_branch: targetRef || '', + queryParams: { + include_checks: true, + include_rules: true + } + }, + { + enabled: !!repoRef && !!sourceRef && !!targetRef } - }) + ) useEffect(() => { if (pullReqData?.number && pullReqData.title) { @@ -353,19 +359,24 @@ export const CreatePullRequest = () => { const [query, setQuery] = useQueryState('query') // TODO:handle pagination in compare commit tab - const { data: { body: commitData, headers } = {}, isFetching: isFetchingCommits } = useListCommitsQuery({ - repo_ref: repoRef, - - queryParams: { - // TODO: add query when commit list api has query abilities - // query: query??'', - page: 0, - limit: 20, - after: normalizeGitRef(selectedTargetBranch?.name), - git_ref: normalizeGitRef(selectedSourceBranch?.name), - include_stats: true + const { data: { body: commitData, headers } = {}, isFetching: isFetchingCommits } = useListCommitsQuery( + { + repo_ref: repoRef, + + queryParams: { + // TODO: add query when commit list api has query abilities + // query: query??'', + page: 0, + limit: 20, + after: normalizeGitRef(selectedTargetBranch?.name), + git_ref: normalizeGitRef(selectedSourceBranch?.name), + include_stats: true + } + }, + { + enabled: !!repoRef && !!sourceRef && !!targetRef } - }) + ) const { setCommits, setSelectedCommit } = useRepoCommitsStore() useEffect(() => { @@ -384,50 +395,39 @@ export const CreatePullRequest = () => { [commitData, setSelectedCommit] ) - const selectBranchorTag = useCallback( - (branchTagName: BranchSelectorListItem, type: BranchSelectorTab, sourceBranch: boolean) => { - if (type === BranchSelectorTab.BRANCHES) { - if (sourceBranch) { - setSelectedSourceBranch(branchTagName) - } else { - setSelectedTargetBranch(branchTagName) - } - } else if (type === BranchSelectorTab.TAGS) { - if (sourceBranch) { - setSelectedSourceBranch(branchTagName) - } else { - setSelectedTargetBranch(branchTagName) - } - } - - // Update URL when either branch changes - use branchTagName directly - const targetName = sourceBranch ? selectedTargetBranch?.name || diffTargetBranch : branchTagName.name + const handleSelectSourceBranchOrTag = useCallback( + (branchTagName: BranchSelectorListItem) => { + setSelectedSourceBranch(branchTagName) - const sourceName = sourceBranch ? branchTagName.name : selectedSourceBranch?.name || diffSourceBranch + if (targetRef && branchTagName.name) { + navigate( + routes.toPullRequestCompare({ + spaceId, + repoId, + diffRefs: `${targetRef}...${branchTagName.name}` + }), + { replace: true } + ) + } + }, + [setSelectedSourceBranch, targetRef, sourceRef, navigate, routes, spaceId, repoId] + ) - if (targetName && sourceName) { + const handleSelectTargetBranchOrTag = useCallback( + (branchTagName: BranchSelectorListItem) => { + setSelectedTargetBranch(branchTagName) + if (sourceRef && branchTagName.name) { navigate( routes.toPullRequestCompare({ spaceId, repoId, - diffRefs: `${targetName}...${sourceName}` + diffRefs: `${branchTagName.name}...${sourceRef}` }), { replace: true } ) } }, - [ - setSelectedSourceBranch, - setSelectedTargetBranch, - selectedSourceBranch, - selectedTargetBranch, - diffTargetBranch, - diffSourceBranch, - navigate, - routes, - spaceId, - repoId - ] + [setSelectedTargetBranch, sourceRef, targetRef, navigate, routes, spaceId, repoId] ) const handleAddReviewer = (id?: number) => { @@ -624,15 +624,16 @@ export const CreatePullRequest = () => { branchSelectorRenderer={ <> <BranchSelectorContainer - onSelectBranchorTag={(branchTagName, type) => selectBranchorTag(branchTagName, type, false)} + onSelectBranchorTag={branchTagName => handleSelectTargetBranchOrTag(branchTagName)} selectedBranch={selectedTargetBranch} branchPrefix="base" /> <IconV2 name="arrow-left" /> <BranchSelectorContainer - onSelectBranchorTag={(branchTagName, type) => selectBranchorTag(branchTagName, type, true)} + onSelectBranchorTag={branchTagName => handleSelectSourceBranchOrTag(branchTagName)} selectedBranch={selectedSourceBranch} branchPrefix="compare" + autoSelectDefaultBranch={false} /> </> } diff --git a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx index 259616f90b..e834c67d75 100644 --- a/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-tags-list-container.tsx @@ -39,7 +39,7 @@ export const RepoTagsListContainer = () => { const { queryPage } = usePaginationQueryStateWithStore({ page, setPage }) - const [selectedBranchOrTag, setSelectedBranchOrTag] = useState<BranchSelectorListItem | null>(null) + const [selectedBranchOrTag, setSelectedBranchOrTag] = useState<BranchSelectorListItem>() const [selectedTagInList, setSelectedTagInList] = useState<BranchSelectorListItem | null>(null) const [preSelectedTab, setPreSelectedTab] = useState<BranchSelectorTab>(BranchSelectorTab.BRANCHES) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index cb912dded9..2735ee2229 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -141,7 +141,6 @@ "behind": "Behind", "ahead": "Ahead", "pullRequest": "Pull Request", - "newPullReq": "New pull request", "compare": "Compare", "viewRules": "View Rules", "browse": "Browse", @@ -321,6 +320,7 @@ "creatingWebhook": "Creating Webhook...", "updateWebhook": "Update Webhook", "createWebhook": "Create Webhook", + "newPullReq": "New pull request", "createBranchButton": "Create Branch", "newBranch": "New branch", "emptyRepo": "This repository is empty.", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index be4c17a0a6..ce332cc9da 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -141,7 +141,6 @@ "behind": "En retard", "ahead": "En avance", "pullRequest": "Pull Request", - "newPullReq": "Nouvelle requête de tirage", "compare": "Compare", "viewRules": "Voir les règles", "browse": "Browse", @@ -315,6 +314,7 @@ "creatingWebhook": "Creating webhook...", "updateWebhook": "Update webhook", "createWebhook": "Create webhook", + "newPullReq": "Nouvelle requête de tirage", "createBranchButton": "Créer une branche", "newBranch": "Nouvelle branche", "emptyRepo": "Ce référentiel est vide.", diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx index d0ae425084..1f09791dd7 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx +++ b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector.tsx @@ -9,7 +9,7 @@ import { BranchSelectorDropdown } from './branch-selector-dropdown' interface BranchSelectorProps { branchList: BranchData[] tagList: BranchSelectorListItem[] - selectedBranchorTag: BranchSelectorListItem + selectedBranchorTag?: BranchSelectorListItem repoId: string spaceId: string branchPrefix?: string @@ -57,11 +57,15 @@ export const BranchSelectorV2: FC<BranchSelectorProps> = ({ <DropdownMenu.Trigger asChild> <Button className={cn('min-w-0', className)} variant="outline" size={buttonSize} disabled={disabled}> {!hideIcon && <IconV2 name={isTag ? 'tag' : 'git-branch'} size="sm" />} - <Text className="truncate"> - {branchPrefix - ? `${branchPrefix}: ${selectedBranch?.name || selectedBranchorTag.name}` - : selectedBranch?.name || selectedBranchorTag.name} - </Text> + {selectedBranch || selectedBranchorTag ? ( + <Text className="truncate"> + {branchPrefix + ? `${branchPrefix}: ${selectedBranch?.name || selectedBranchorTag?.name}` + : selectedBranch?.name || selectedBranchorTag?.name} + </Text> + ) : ( + <Text className="truncate">Select a branch or tag</Text> + )} <IconV2 name="nav-arrow-down" size="xs" className="ml-auto" /> </Button> </DropdownMenu.Trigger> diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/types.ts b/packages/ui/src/views/repo/components/branch-selector-v2/types.ts index 90760b7592..4d6ba03def 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/types.ts +++ b/packages/ui/src/views/repo/components/branch-selector-v2/types.ts @@ -18,7 +18,7 @@ export const getBranchSelectorLabels = (t: TFunctionWithFallback) => ({ }) export interface BranchSelectorDropdownProps { - selectedBranch: BranchSelectorListItem + selectedBranch?: BranchSelectorListItem branchList: BranchSelectorListItem[] tagList: BranchSelectorListItem[] onSelectBranch?: (branchTag: BranchSelectorListItem, type: BranchSelectorTab) => void @@ -41,7 +41,7 @@ export interface BranchSelectorProps extends BranchSelectorDropdownProps { } export interface BranchSelectorContainerProps { - selectedBranch?: BranchSelectorListItem | null + selectedBranch?: BranchSelectorListItem onSelectBranchorTag: (branchTag: BranchSelectorListItem, type: BranchSelectorTab) => void isBranchOnly?: boolean dynamicWidth?: boolean diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index c538a44f27..3c4735260a 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -111,7 +111,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ isLoading, isSuccess, onFormDraftSubmit, - mergeability = false, + mergeability, handleAiPullRequestSummary, diffData, @@ -237,59 +237,59 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ <Layout.Horizontal align="center" gap="xs"> {branchSelectorRenderer} - {isBranchSelected && - !isLoading && ( // Only render this block if isBranchSelected is true - <Layout.Horizontal gap="xs" align="center"> - {mergeability ? ( - <> - <IconV2 className="text-icons-success" name="check" size="2xs" /> - <Text variant="body-single-line-normal" color="success"> - {t('views:pullRequests.compareChangesAbleToMerge', 'Able to merge.')}{' '} - <Text variant="body-single-line-normal" as="span" color="foreground-2"> + {mergeability !== undefined && !isLoading && ( + <Layout.Horizontal gap="xs" align="center"> + {mergeability === true ? ( + <> + <IconV2 className="text-icons-success" name="check" size="2xs" /> + <Text variant="body-single-line-normal" color="success"> + {t('views:pullRequests.compareChangesAbleToMerge', 'Able to merge.')}{' '} + <Text variant="body-single-line-normal" as="span" color="foreground-2"> + {t( + 'views:pullRequests.compareChangesAbleToMergeDescription', + 'These branches can be automatically merged.' + )} + </Text> + </Text> + </> + ) : null} + {mergeability === false ? ( + <> + {apiError === "head branch doesn't contain any new commits." ? ( + <> + <IconV2 name="xmark" size="2xs" className="text-icons-1" /> + <Text variant="body-single-line-normal"> {t( - 'views:pullRequests.compareChangesAbleToMergeDescription', - 'These branches can be automatically merged.' + 'views:pullRequests.compareChangesApiError', + 'Head branch doesn’t contain any new commits.' )} </Text> - </Text> - </> - ) : ( - <> - {apiError === "head branch doesn't contain any new commits." ? ( - <> - <IconV2 name="xmark" size="2xs" className="text-icons-1" /> - <Text variant="body-single-line-normal"> + </> + ) : ( + <> + <IconV2 className="text-icons-danger" name="xmark" size="2xs" /> + <Text variant="body-single-line-normal" color="danger"> + {t('views:pullRequests.compareChangesCantMerge', 'Can’t be merged.')}{' '} + <span className="text-cn-foreground-2"> {t( - 'views:pullRequests.compareChangesApiError', - 'Head branch doesn’t contain any new commits.' + 'views:pullRequests.compareChangesCantMergeDescription', + 'You can still create the pull request.' )} - </Text> - </> - ) : ( - <> - <IconV2 className="text-icons-danger" name="xmark" size="2xs" /> - <Text variant="body-single-line-normal" color="danger"> - {t('views:pullRequests.compareChangesCantMerge', 'Can’t be merged.')}{' '} - <span className="text-cn-foreground-2"> - {t( - 'views:pullRequests.compareChangesCantMergeDescription', - 'You can still create the pull request.' - )} - </span> - </Text> - </> - )} - </> - )} - </Layout.Horizontal> - )} + </span> + </Text> + </> + )} + </> + ) : null} + </Layout.Horizontal> + )} </Layout.Horizontal> </Layout.Vertical> {!prBranchCombinationExists && ( <Layout.Horizontal align="center" justify="between" - className="mt-5 rounded-md border border-cn-borders-2 bg-cn-background-2 py-3 px-4" + className="mt-5 rounded-md border border-cn-borders-2 bg-cn-background-2 px-4 py-3" > <Text variant="body-normal" color="foreground-1"> {isBranchSelected ? ( diff --git a/packages/ui/src/views/repo/repo-branch/types.ts b/packages/ui/src/views/repo/repo-branch/types.ts index ad378d84cc..ba2c253865 100644 --- a/packages/ui/src/views/repo/repo-branch/types.ts +++ b/packages/ui/src/views/repo/repo-branch/types.ts @@ -74,7 +74,7 @@ export interface CreateBranchDialogProps { onSubmit: (formValues: CreateBranchFormFields) => Promise<void> error?: string isCreatingBranch?: boolean - selectedBranchOrTag: BranchSelectorListItem | null + selectedBranchOrTag?: BranchSelectorListItem renderProp: React.ReactNode prefilledName?: string resetViolation: () => void diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 2515c38598..6d737e2f73 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -19,7 +19,7 @@ interface CreateTagDialogProps { onSubmit: (data: CreateTagFormFields) => void error?: string isLoading?: boolean - selectedBranchOrTag: BranchSelectorListItem | null + selectedBranchOrTag?: BranchSelectorListItem branchSelectorRenderer: () => JSX.Element | null violation?: boolean bypassable?: boolean @@ -110,7 +110,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ /> {violation && ( <Alert.Root theme="warning"> - <Alert.Description className="break-all overflow-hidden"> + <Alert.Description className="overflow-hidden break-all"> {bypassable ? t( 'component:tagDialog.violationMessages.bypassed', @@ -123,7 +123,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ {error && ( <Alert.Root theme="danger"> - <Alert.Description className="break-all overflow-hidden"> + <Alert.Description className="overflow-hidden break-all"> {t('views:repos.error', 'Error:')} {error} </Alert.Description> </Alert.Root> From 17a5403ef005bd6cfa852d586532f22644379e55 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Sun, 17 Aug 2025 00:26:20 +0000 Subject: [PATCH 116/180] Parity Gap - History commits after file name changes (#10226) * b64971 Merge remote-tracking branch 'origin/main' into rk-bugs-bash * 8c061c addressed pr comment * 01def1 Updated with Accordian * 6507dc Merge remote-tracking branch 'origin/main' into rk-bugs-bash * 824ca0 Rename file history * 25cb2a Branches - Renamed New PR to Compare * eac680 Fixed favorite to unfavorite and unfavorite to favorite --- .../src/components-v2/file-content-viewer.tsx | 200 +++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index 20532adf9c..2ebf9a986e 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -1,8 +1,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' -import { OpenapiGetContentOutput, TypesCommit, useListCommitsQuery } from '@harnessio/code-service-client' import { + OpenapiGetContentOutput, + TypesCommit, + TypesRenameDetails, + useListCommitsQuery +} from '@harnessio/code-service-client' +import { + Accordion, FileViewerControlBar, getIsMarkdown, IconV2, @@ -12,6 +18,7 @@ import { ScrollArea, Skeleton, Tabs, + Text, ViewTypeValue } from '@harnessio/ui/components' import { cn } from '@harnessio/ui/utils' @@ -38,6 +45,149 @@ const getDefaultView = (language?: string): ViewTypeValue => { return getIsMarkdown(language) ? 'preview' : 'code' } +type RenameHistorySectionProps = { + titlePath: string + repoRef: string + spaceId: string + repoId: string + routes: any + gitRef: string +} + +const RenameHistorySection: React.FC<RenameHistorySectionProps> = ({ + titlePath, + repoRef, + spaceId, + repoId, + routes, + gitRef +}) => { + const [accordionValue, setAccordionValue] = useState<string | undefined>(undefined) + const { data, isFetching } = useListCommitsQuery({ + repo_ref: repoRef, + queryParams: { + git_ref: gitRef, + path: titlePath || '', + limit: 20 + } + }) + + return ( + <div className="space-y-cn-sm"> + <Accordion.Root + type="single" + collapsible + onValueChange={value => setAccordionValue(Array.isArray(value) ? value[0] : value)} + > + <Accordion.Item value="rename-history" style={{ border: 'none' }}> + <Accordion.Trigger indicatorProps={{ className: 'hidden' }}> + <div className="flex items-center gap-cn-xs"> + <IconV2 name="nav-arrow-down" size="xs" className="text-cn-foreground-3" /> + <IconV2 name="git-commit" size="xs" className="text-cn-foreground-3" /> + <Text variant="body-single-line-normal" color="foreground-2"> + Renamed from {titlePath} - {accordionValue ? 'Hide History' : 'Show History'} + </Text> + </div> + </Accordion.Trigger> + <Accordion.Content> + <div className="mt-cn-md"> + {isFetching ? ( + <Skeleton.List /> + ) : data?.body?.commits?.length ? ( + <CommitsList + className="mt-cn-md" + toCommitDetails={({ sha }: { sha: string }) => + routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha }) + } + toCode={({ sha }: { sha: string }) => `${routes.toRepoFiles({ spaceId, repoId })}/${sha}`} + data={data.body.commits.map((item: TypesCommit) => ({ + sha: item.sha, + parent_shas: item.parent_shas, + title: item.title, + message: item.message, + author: item.author, + committer: item.committer + }))} + /> + ) : ( + <Layout.Vertical className="mt-cn-md p-cn-sm"> + <Text variant="body-single-line-normal" color="foreground-3"> + No previous commits found + </Text> + </Layout.Vertical> + )} + </div> + </Accordion.Content> + </Accordion.Item> + </Accordion.Root> + </div> + ) +} + +interface RenameHistoryDetectorProps { + repoRef: string + spaceId: string + repoId: string + routes: any + currentPath: string + gitRef: string +} + +const RenameHistoryDetector: React.FC<RenameHistoryDetectorProps> = ({ + repoRef, + spaceId, + repoId, + routes, + currentPath, + gitRef +}) => { + const [oldPath, setOldPath] = useState<string | null>(null) + + // Try to detect if this file was renamed by looking at the first commit + const { data: firstCommit } = useListCommitsQuery({ + repo_ref: repoRef, + queryParams: { + git_ref: gitRef, + path: currentPath, + limit: 1 + } + }) + + const { data: renameInfo } = useListCommitsQuery( + { + repo_ref: repoRef, + queryParams: { + git_ref: firstCommit?.body?.commits?.[0]?.sha || '', + path: currentPath, + limit: 1 + } + }, + { enabled: !!firstCommit?.body?.commits?.[0]?.sha } + ) + + useEffect(() => { + if (renameInfo?.body?.rename_details && renameInfo.body.rename_details.length > 0) { + const renameDetail = renameInfo.body.rename_details[0] + if (renameDetail.old_path && renameDetail.commit_sha_before) { + setOldPath(renameDetail.old_path) + } + } + }, [renameInfo]) + + if (!oldPath) return null + + return ( + <RenameHistorySection + titlePath={oldPath} + repoRef={repoRef} + spaceId={spaceId} + repoId={repoId} + routes={routes} + gitRef={gitRef} + /> + ) +} + interface FileContentViewerProps { repoContent?: OpenapiGetContentOutput loading?: boolean @@ -79,6 +229,19 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV } }) + const [cachedRenameDetails, setCachedRenameDetails] = useState<TypesRenameDetails[] | null>(null) + + useEffect(() => { + if (commitData?.rename_details && commitData.rename_details.length > 0) { + setCachedRenameDetails(commitData.rename_details) + } + }, [commitData?.rename_details]) + + const renameDetailsToRender = useMemo(() => { + if (commitData?.rename_details && commitData.rename_details.length > 0) return commitData.rename_details + return cachedRenameDetails || null + }, [commitData?.rename_details, cachedRenameDetails]) + // TODO: temporary solution for matching themes const monacoTheme = (theme ?? '').startsWith('dark') ? 'dark' : 'light' @@ -289,6 +452,41 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV committer: item.committer }))} /> + + {/* Show rename history if available */} + {renameDetailsToRender && renameDetailsToRender.length > 0 && ( + <Layout.Vertical className="mt-cn-md"> + {renameDetailsToRender.map((detail, index) => { + if (!detail.old_path) return null + return ( + <RenameHistorySection + key={index} + titlePath={detail.old_path} + repoRef={repoRef} + spaceId={spaceId || ''} + repoId={repoId || ''} + routes={routes} + gitRef={fullGitRef} + /> + ) + })} + </Layout.Vertical> + )} + + {/* Always show rename history if we know the file was renamed, even without rename_details */} + {!renameDetailsToRender && fullResourcePath && ( + <Layout.Vertical className="mt-cn-md"> + <RenameHistoryDetector + repoRef={repoRef} + spaceId={spaceId || ''} + repoId={repoId || ''} + routes={routes} + currentPath={fullResourcePath} + gitRef={fullGitRef} + /> + </Layout.Vertical> + )} + <Pagination indeterminate hasNext={xNextPage > 0} From 7746b29c00c5f9a89d3b53e4c67eafd8eb53218b Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:08:11 +0400 Subject: [PATCH 117/180] fix sticky sidebars (#2072) * fix sticky content * fixes --- .../commit-details-diff-view-wrapper.tsx | 6 +- .../view-preview/repo-files-view-wrapper.tsx | 76 ++++++++++++------- .../chat/chat-empty-preview-wrapper.tsx | 2 +- .../components/chat/chat-preview-wrapper.tsx | 2 +- packages/ui/src/components/chat/chat.tsx | 2 +- .../components/manage-navigation/index.tsx | 8 +- packages/ui/src/styles/styles.css | 2 +- .../components/commit-diff.tsx | 6 +- .../components/commit-sidebar.tsx | 2 +- .../views/repo/repo-summary/repo-summary.tsx | 4 +- .../components/dialog.ts | 16 ++-- 11 files changed, 73 insertions(+), 53 deletions(-) diff --git a/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx b/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx index 0221155fab..a0eae357ea 100644 --- a/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx +++ b/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx @@ -5,20 +5,20 @@ import { repoFilesStore } from '@subjects/views/repo-files/components/repo-files import { renderEntries } from '@utils/fileViewUtils' import { noop } from '@utils/viewUtils' -import { FileExplorer } from '@harnessio/ui/components' +import { FileExplorer, Layout } from '@harnessio/ui/components' import { CommitDiff, CommitSidebar, ICommitDetailsStore } from '@harnessio/ui/views' export const CommitDetailsDiffViewWrapper: FC = () => { const useCommitDetailsStore = useCallback((): ICommitDetailsStore => commitDetailsStore, []) return ( - <> + <Layout.Flex gapX="xl"> <CommitSidebar navigateToFile={() => {}} filesList={repoFilesStore.filesList}> <FileExplorer.Root onValueChange={noop} value={[]}> {renderEntries(repoFilesStore.filesTreeData, '')} </FileExplorer.Root> </CommitSidebar> <CommitDiff useCommitDetailsStore={useCommitDetailsStore} /> - </> + </Layout.Flex> ) } diff --git a/apps/design-system/src/pages/view-preview/repo-files-view-wrapper.tsx b/apps/design-system/src/pages/view-preview/repo-files-view-wrapper.tsx index fd20b4fb2d..c42f47329f 100644 --- a/apps/design-system/src/pages/view-preview/repo-files-view-wrapper.tsx +++ b/apps/design-system/src/pages/view-preview/repo-files-view-wrapper.tsx @@ -1,39 +1,59 @@ -import { FC, HTMLAttributes, PropsWithChildren } from 'react' +import { FC, HTMLAttributes, PropsWithChildren, useRef, useState } from 'react' import { repoFilesStore } from '@subjects/views/repo-files/components/repo-files-store' import { renderEntries } from '@utils/fileViewUtils' import { noop } from '@utils/viewUtils' -import { FileExplorer } from '@harnessio/ui/components' -import { BranchSelectorV2, RepoSidebar as RepoSidebarView } from '@harnessio/ui/views' +import { FileExplorer, Layout } from '@harnessio/ui/components' +import { + BranchSelectorV2, + DraggableSidebarDivider, + RepoSidebar as RepoSidebarView, + SIDEBAR_MAX_WIDTH, + SIDEBAR_MIN_WIDTH +} from '@harnessio/ui/views' export const RepoFilesViewWrapper: FC<PropsWithChildren<HTMLAttributes<HTMLElement>>> = ({ children }) => { + const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const containerRef = useRef<HTMLDivElement>(null) + return ( - <div className="flex flex-1"> - <RepoSidebarView - navigateToNewFile={noop} - navigateToFile={noop} - filesList={repoFilesStore.filesList} - branchSelectorRenderer={() => ( - <BranchSelectorV2 - repoId="canary" - spaceId="org" - branchList={[]} - tagList={[]} - selectedBranchorTag={{ name: 'main', sha: 'sha' }} - selectedBranch={{ name: 'main', sha: 'sha' }} - onSelectBranch={noop} - isBranchOnly={false} - dynamicWidth={false} - setSearchQuery={noop} - /> - )} + <Layout.Flex className="flex-1" ref={containerRef}> + <div + className="shrink-0 overflow-hidden" + style={{ + width: `${sidebarWidth}px`, + minWidth: `${SIDEBAR_MIN_WIDTH}px`, + maxWidth: `${SIDEBAR_MAX_WIDTH}px` + }} > - <FileExplorer.Root onValueChange={noop} value={[]}> - {renderEntries(repoFilesStore.filesTreeData, '')} - </FileExplorer.Root> - </RepoSidebarView> - <div className="min-h-[calc(100vh-var(--cn-page-nav-height))]">{children}</div> - </div> + <RepoSidebarView + navigateToNewFile={noop} + navigateToFile={noop} + filesList={repoFilesStore.filesList} + branchSelectorRenderer={() => ( + <BranchSelectorV2 + repoId="canary" + spaceId="org" + branchList={[]} + tagList={[]} + selectedBranchorTag={{ name: 'main', sha: 'sha' }} + selectedBranch={{ name: 'main', sha: 'sha' }} + onSelectBranch={noop} + isBranchOnly={false} + dynamicWidth={false} + setSearchQuery={noop} + /> + )} + > + <FileExplorer.Root onValueChange={noop} value={[]}> + {renderEntries(repoFilesStore.filesTreeData, '')} + </FileExplorer.Root> + </RepoSidebarView> + </div> + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> + + {children} + </Layout.Flex> ) } diff --git a/packages/ui/src/components/chat/chat-empty-preview-wrapper.tsx b/packages/ui/src/components/chat/chat-empty-preview-wrapper.tsx index 51377201d2..7be1228ed4 100644 --- a/packages/ui/src/components/chat/chat-empty-preview-wrapper.tsx +++ b/packages/ui/src/components/chat/chat-empty-preview-wrapper.tsx @@ -4,7 +4,7 @@ import { Chat } from '@/components' export const ChatEmptyPreviewWrapper: FC = () => { return ( - <div className="border-cn-borders-4 h-[calc(100vh-var(--cn-page-nav-height))] border-r"> + <div className="border-cn-borders-4 h-[calc(100vh-var(--cn-page-nav-full-height))] border-r"> <Chat.Root> <Chat.Body> <Chat.EmptyState /> diff --git a/packages/ui/src/components/chat/chat-preview-wrapper.tsx b/packages/ui/src/components/chat/chat-preview-wrapper.tsx index 4484f2fae5..cfc219095d 100644 --- a/packages/ui/src/components/chat/chat-preview-wrapper.tsx +++ b/packages/ui/src/components/chat/chat-preview-wrapper.tsx @@ -19,7 +19,7 @@ const diffData = export const ChatPreviewWrapper: FC = () => { return ( - <div className="border-cn-borders-4 h-[calc(100vh-var(--cn-page-nav-height))] border-r"> + <div className="border-cn-borders-4 h-[calc(100vh-var(--cn-page-nav-full-height))] border-r"> <Chat.Root> <Chat.Header onClose={() => {}} /> <Chat.Body> diff --git a/packages/ui/src/components/chat/chat.tsx b/packages/ui/src/components/chat/chat.tsx index bcd65f305d..609292b9c1 100644 --- a/packages/ui/src/components/chat/chat.tsx +++ b/packages/ui/src/components/chat/chat.tsx @@ -28,7 +28,7 @@ const Body: FC<PropsWithChildren<HTMLAttributes<HTMLElement>>> = ({ children }) } const Footer: FC<PropsWithChildren<HTMLAttributes<HTMLElement>>> = ({ children }) => { - return <div className="sticky bottom-0 bg-cn-background-1 px-6 py-3">{children}</div> + return <div className="sticky bottom-0 z-10 bg-cn-background-1 px-6 py-3">{children}</div> } interface MessageProps extends PropsWithChildren<HTMLAttributes<HTMLElement>> { diff --git a/packages/ui/src/components/manage-navigation/index.tsx b/packages/ui/src/components/manage-navigation/index.tsx index 82692980ad..5a991333d9 100644 --- a/packages/ui/src/components/manage-navigation/index.tsx +++ b/packages/ui/src/components/manage-navigation/index.tsx @@ -165,14 +165,10 @@ export const ManageNavigation = ({ <Layout.Flex justify="between" gapX="xs" + align="center" className="hover:bg-cn-background-3 w-full grow cursor-grab rounded active:cursor-grabbing" > - <Button - {...attributes} - {...listeners} - variant="transparent" - className="w-full justify-start" - > + <Button {...attributes} {...listeners} variant="transparent" className="justify-start"> <IconV2 name="grip-dots" size="xs" /> <Text color="inherit">{item.title}</Text> </Button> diff --git a/packages/ui/src/styles/styles.css b/packages/ui/src/styles/styles.css index 598efc60de..c89ba76a05 100644 --- a/packages/ui/src/styles/styles.css +++ b/packages/ui/src/styles/styles.css @@ -601,7 +601,7 @@ mark { } .nested-sidebar-height { - height: calc(100vh - var(--cn-page-nav-height) - var(--cn-inset-layout-indent) - var(--cn-inset-border-width) * 2); + height: calc(100vh - var(--cn-breadcrumbs-height) - var(--cn-inset-layout-indent) - var(--cn-inset-border-width) * 2); } .repo-files-height { diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx index 6e4761a928..08c705ee9e 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx @@ -12,7 +12,11 @@ export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsSto const { diffs, diffStats, commitSHA } = useCommitDetailsStore() return ( - <Layout.Flex direction="column" className="w-full pb-cn-xl min-h-[calc(100vh-var(--cn-page-nav-height))]" gapY="sm"> + <Layout.Flex + direction="column" + className="pb-cn-xl min-h-[calc(100vh-var(--cn-page-nav-full-height))] w-full" + gapY="sm" + > {/* TODO: add goToDiff handler */} <ChangedFilesShortInfo diffData={diffs} diffStats={diffStats} goToDiff={() => {}} /> diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx index 2b6d38e13f..de87a33b18 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx @@ -10,7 +10,7 @@ interface CommitsSidebarProps { export const CommitSidebar = ({ navigateToFile, filesList, children }: CommitsSidebarProps) => { return ( - <div className="nested-sidebar-height pt-cn-md -mt-cn-md sticky top-[var(--cn-page-nav-height)]"> + <div className="nested-sidebar-height pt-cn-md -mt-cn-md sticky top-[var(--cn-breadcrumbs-height)]"> <Layout.Flex direction="column" className="max-h-full overflow-hidden" gapY="sm"> <SearchFiles navigateToFile={navigateToFile} filesList={filesList} contentClassName="width-popover-max-width" /> diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 28ba92f27e..b9172b7d4f 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -186,8 +186,8 @@ export function RepoSummaryView({ )} <ListActions.Root className="flex-wrap gap-y-2"> - <ListActions.Left> - <ButtonLayout className="w-full " horizontalAlign="start"> + <ListActions.Left className="max-w-full"> + <ButtonLayout className="grid w-full grid-cols-[auto,1fr]" horizontalAlign="start"> {cloneElement(branchSelectorRenderer, { className: 'w-full max-w-fit' })} <SearchFiles navigateToFile={navigateToFile} diff --git a/packages/ui/tailwind-utils-config/components/dialog.ts b/packages/ui/tailwind-utils-config/components/dialog.ts index 9e7a7d3a26..1dfae60cfa 100644 --- a/packages/ui/tailwind-utils-config/components/dialog.ts +++ b/packages/ui/tailwind-utils-config/components/dialog.ts @@ -15,12 +15,13 @@ export default { }, '.cn-modal-dialog-content': { + '--cn-dialog-width': 'min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-sm))', backgroundColor: 'var(--cn-bg-2)', borderRadius: 'var(--cn-dialog-radius)', border: '1px solid var(--cn-border-3)', borderWidth: 'var(--cn-dialog-border)', boxShadow: 'var(--cn-shadow-5)', - width: 'var(--cn-modal-width-sm)', + width: 'var(--cn-dialog-width)', maxWidth: 'calc(100vw - (var(--cn-dialog-safezone) * 2))', maxHeight: 'calc(100vh - (var(--cn-dialog-safezone) * 2))', paddingTop: 'var(--cn-dialog-px)', @@ -37,14 +38,11 @@ export default { animation: 'cn-dialog-slideOut 0.2s ease-in forwards' }, - '&.cn-modal-dialog-sm': { - width: `min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-sm))` - }, '&.cn-modal-dialog-md': { - width: `min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-md))` + '--cn-dialog-width': `min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-md))` }, '&.cn-modal-dialog-lg': { - width: `min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-lg))` + '--cn-dialog-width': `min(calc(100vw - (var(--cn-dialog-safezone) * 2)), var(--cn-dialog-lg))` } }, @@ -102,6 +100,8 @@ export default { // Body Component '.cn-modal-dialog-body': { '--cn-modal-dialog-scroll-compensation': '4px', + display: 'grid', + gridTemplateColumns: 'calc(var(--cn-dialog-width) - var(--cn-dialog-px)*2)', paddingInline: 'var(--cn-modal-dialog-scroll-compensation)', paddingBottom: 'var(--cn-modal-dialog-scroll-compensation)', marginInline: 'calc(var(--cn-modal-dialog-scroll-compensation) * -1)', @@ -110,8 +110,8 @@ export default { height: '100%', '&-content': { - display: 'grid', - alignContent: 'start', + display: 'flex', + flexDirection: 'column', gap: 'var(--cn-layout-xl)' } }, From 6c5546b37b2462c979edf75484828ca6112a0437 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Mon, 18 Aug 2025 15:27:52 +0300 Subject: [PATCH 118/180] Fix Create Branch dialog and remove redundant list props (#2075) --- .../subjects/views/repo-branches/index.tsx | 7 +---- .../src/pages-v2/repo/repo-branch-list.tsx | 20 ++----------- .../form-primitives/form-caption.tsx | 3 +- .../create-pipeline-dialog.tsx | 1 + .../profile-settings-token-create-dialog.tsx | 2 +- .../components/invite-member-dialog.tsx | 2 +- .../pull-request-header-edit-dialog.tsx | 4 ++- .../components/create-branch-dialog.tsx | 28 ++++++++++++++----- .../ui/src/views/repo/repo-branch/types.ts | 7 ----- .../create-tag/create-tag-dialog.tsx | 2 +- 10 files changed, 33 insertions(+), 43 deletions(-) diff --git a/apps/design-system/src/subjects/views/repo-branches/index.tsx b/apps/design-system/src/subjects/views/repo-branches/index.tsx index ea6448e4f8..0c3d57239e 100644 --- a/apps/design-system/src/subjects/views/repo-branches/index.tsx +++ b/apps/design-system/src/subjects/views/repo-branches/index.tsx @@ -7,25 +7,20 @@ import { IBranchSelectorStore, RepoBranchListView } from '@harnessio/ui/views' import { repoBranchesStore } from './repo-branches-store' export function RepoBranchesView() { - const [isCreateBranchDialogOpen, setCreateBranchDialogOpen] = useState(false) + const [_isCreateBranchDialogOpen, setCreateBranchDialogOpen] = useState(false) const useRepoBranchesStore = useCallback((): IBranchSelectorStore => repoBranchesStore, []) return ( <RepoBranchListView isLoading={false} - isCreatingBranch={false} - onSubmit={async () => {}} useRepoBranchesStore={useRepoBranchesStore} - isCreateBranchDialogOpen={isCreateBranchDialogOpen} setCreateBranchDialogOpen={setCreateBranchDialogOpen} searchQuery={''} setSearchQuery={noop} - createBranchError={undefined} toPullRequest={() => ''} toBranchRules={() => ''} toPullRequestCompare={() => ''} onDeleteBranch={noop} - setCreateBranchSearchQuery={noop} /> ) } diff --git a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx index a40199705c..132b8306a5 100644 --- a/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-branch-list.tsx @@ -6,13 +6,12 @@ import { isEmpty } from 'lodash-es' import { useCalculateCommitDivergenceMutation, - useCreateBranchMutation, useDeleteBranchMutation, useFindRepositoryQuery, useListBranchesQuery } from '@harnessio/code-service-client' import { DeleteAlertDialog } from '@harnessio/ui/components' -import { CreateBranchFormFields, RepoBranchListView } from '@harnessio/ui/views' +import { RepoBranchListView } from '@harnessio/ui/views' import { CreateBranchDialog } from '../../components-v2/create-branch-dialog' import { useRoutes } from '../../framework/context/NavigationContext' @@ -44,7 +43,6 @@ export function RepoBranchesListPage() { } = useRepoBranchesStore() const [query, setQuery] = useQueryState('query') - const [createBranchSearchQuery, setCreateBranchSearchQuery] = useState('') const { queryPage } = usePaginationQueryStateWithStore({ page, setPage }) const [isCreateBranchDialogOpen, setCreateBranchDialogOpen] = useState(false) @@ -57,7 +55,7 @@ export function RepoBranchesListPage() { queryParams: { page: queryPage, limit: 10, - query: (createBranchSearchQuery || query) ?? '', + query: query ?? '', order: orderSortDate.DESC, sort: 'date', include_commit: true, @@ -107,15 +105,6 @@ export function RepoBranchesListPage() { reset: resetDeleteBranch } = useDeleteBranchMutation({ repo_ref: repoRef }) - const { mutateAsync: saveBranch, isLoading: isCreatingBranch, error: createBranchError } = useCreateBranchMutation({}) - - const onSubmit = async (formValues: CreateBranchFormFields) => { - const { name, target } = formValues - await saveBranch({ repo_ref: repoRef, body: { name, target, bypass_rules: false } }) - handleInvalidateBranchList() - setCreateBranchDialogOpen(false) - } - const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck() const handleDeleteBranch = (branch_name: string) => { @@ -200,14 +189,10 @@ export function RepoBranchesListPage() { <> <RepoBranchListView isLoading={isLoadingBranches || isLoadingDivergence} - isCreatingBranch={isCreatingBranch} - onSubmit={onSubmit} useRepoBranchesStore={useRepoBranchesStore} - isCreateBranchDialogOpen={isCreateBranchDialogOpen} setCreateBranchDialogOpen={setCreateBranchDialogOpen} searchQuery={query} setSearchQuery={setQuery} - createBranchError={createBranchError?.message} // toBranchRules={() => routes.toRepoBranchRules({ spaceId, repoId })} toPullRequestCompare={({ diffRefs }: { diffRefs: string }) => routes.toPullRequestCompare({ spaceId, repoId, diffRefs }) @@ -217,7 +202,6 @@ export function RepoBranchesListPage() { } toCode={({ branchName }: { branchName: string }) => `${routes.toRepoFiles({ spaceId, repoId })}/${branchName}`} onDeleteBranch={handleSetDeleteBranch} - setCreateBranchSearchQuery={setCreateBranchSearchQuery} /> <CreateBranchDialog diff --git a/packages/ui/src/components/form-primitives/form-caption.tsx b/packages/ui/src/components/form-primitives/form-caption.tsx index 9b7e405a5d..0ddd844131 100644 --- a/packages/ui/src/components/form-primitives/form-caption.tsx +++ b/packages/ui/src/components/form-primitives/form-caption.tsx @@ -1,5 +1,6 @@ import { forwardRef, PropsWithChildren } from 'react' +import { cn } from '@/utils' import { IconV2 } from '@components/icon-v2' import { Text, textVariants } from '@components/text' import { VariantProps } from 'class-variance-authority' @@ -42,7 +43,7 @@ export const FormCaption = forwardRef<HTMLParagraphElement, PropsWithChildren<Fo } return ( - <Text color={getColor(theme, disabled)} className={className} ref={ref}> + <Text color={getColor(theme, disabled)} className={cn('cn-caption', className)} ref={ref}> {canShowIcon && <IconV2 name={effectiveIconName} size="md" />} <span>{children}</span> </Text> diff --git a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx index 1d1ed2589e..b7f30a6365 100644 --- a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx +++ b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx @@ -144,6 +144,7 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { onCancel() reset() }} + disabled={isLoadingBranchNames} > Cancel </Dialog.Close> diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx index fd10ae9cc4..7d21e5f6a6 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-token-create-dialog.tsx @@ -171,7 +171,7 @@ export const ProfileSettingsTokenCreateDialog: FC<ProfileSettingsTokenCreateDial </Dialog.Body> <Dialog.Footer> <ButtonLayout> - <Dialog.Close onClick={onClose}> + <Dialog.Close onClick={onClose} disabled={isLoading}> {createdTokenData ? t('views:profileSettings.gotItButton', 'Got It') : t('views:profileSettings.cancel', 'Cancel')} diff --git a/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx b/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx index 997c80651e..b052459b3e 100644 --- a/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx +++ b/packages/ui/src/views/project/project-members/components/invite-member-dialog.tsx @@ -127,7 +127,7 @@ export const InviteMemberDialog: FC<InviteMemberDialogProps> = ({ <Dialog.Footer> <ButtonLayout> - <Dialog.Close onClick={onClose} loading={isInvitingMember}> + <Dialog.Close onClick={onClose} disabled={isInvitingMember}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> <Button type="submit" form="new-member-form" disabled={isInvitingMember || !isValid}> diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx index bf8ce232c9..cca36f738f 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header-edit-dialog.tsx @@ -152,7 +152,9 @@ export const PullRequestHeaderEditDialog: FC<PullRequestHeaderEditDialogProps> = <Dialog.Footer> <ButtonLayout> - <Dialog.Close onClick={handleDialogClose}>Cancel</Dialog.Close> + <Dialog.Close onClick={handleDialogClose} disabled={isDisabled}> + Cancel + </Dialog.Close> <Button type="submit" form="edit-pr-title-form" disabled={isDisabled}> {isLoading ? 'Saving...' : 'Save'} </Button> diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index a43d2e3fa9..03ed49d89a 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Alert, Button, ButtonLayout, ControlGroup, Dialog, FormInput, FormWrapper, Label } from '@/components' @@ -31,7 +31,7 @@ export function CreateBranchDialog({ open, onClose, onSubmit, - isCreatingBranch, + isCreatingBranch = false, violation, bypassable, resetViolation, @@ -41,6 +41,7 @@ export function CreateBranchDialog({ prefilledName }: CreateBranchDialogProps) { const { t } = useTranslation() + const [isLoading, setIsLoading] = useState(isCreatingBranch) const formMethods = useForm<CreateBranchFormFields>({ resolver: zodResolver(createBranchFormSchema(t)), @@ -74,6 +75,19 @@ export function CreateBranchDialog({ } }, [open, prefilledName, selectedBranchOrTag, reset, resetViolation]) + useEffect(() => { + if (isCreatingBranch) { + setIsLoading(isCreatingBranch) + return + } + + const t = setTimeout(() => { + setIsLoading(isCreatingBranch) + }, 300) + + return () => clearTimeout(t) + }, [isCreatingBranch]) + useEffect(() => { if (selectedBranchOrTag?.name) { setValue('target', selectedBranchOrTag.name) @@ -141,17 +155,17 @@ export function CreateBranchDialog({ <Dialog.Footer> <ButtonLayout> - <Dialog.Close onClick={handleClose} loading={isCreatingBranch} disabled={isCreatingBranch}> + <Dialog.Close onClick={handleClose} disabled={isLoading}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> {!bypassable || !violation ? ( <Button type="submit" form="create-branch-form" - disabled={isCreatingBranch || (!bypassable && violation)} - loading={isCreatingBranch} + disabled={isLoading || (!bypassable && violation)} + loading={isLoading} > - {isCreatingBranch + {isLoading ? t('component:branchDialog.loading', 'Creating branch...') : !bypassable && violation ? t('component:branchDialog.notAllowed', 'Cannot create branch') @@ -159,7 +173,7 @@ export function CreateBranchDialog({ </Button> ) : ( <Button type="submit" form="create-branch-form" variant="outline" theme="danger"> - {isCreatingBranch + {isLoading ? t('component:branchDialog.loading', 'Creating branch...') : t('component:branchDialog.bypassButton', 'Bypass rules and create branch')} </Button> diff --git a/packages/ui/src/views/repo/repo-branch/types.ts b/packages/ui/src/views/repo/repo-branch/types.ts index ba2c253865..9700638270 100644 --- a/packages/ui/src/views/repo/repo-branch/types.ts +++ b/packages/ui/src/views/repo/repo-branch/types.ts @@ -1,5 +1,3 @@ -import { Dispatch, SetStateAction } from 'react' - import { z } from 'zod' import { PullRequestType } from '../pull-request/pull-request.types' @@ -55,15 +53,10 @@ export interface BranchListPageProps extends Partial<RoutingProps> { export interface RepoBranchListViewProps extends Partial<RoutingProps> { isLoading: boolean useRepoBranchesStore: () => IBranchSelectorStore - isCreateBranchDialogOpen: boolean setCreateBranchDialogOpen: (isOpen: boolean) => void - onSubmit: (formValues: CreateBranchFormFields) => Promise<void> - isCreatingBranch: boolean - createBranchError?: string searchQuery: string | null setSearchQuery: (query: string | null) => void onDeleteBranch: (branchName: string) => void - setCreateBranchSearchQuery: Dispatch<SetStateAction<string>> } export interface CreateBranchDialogProps { diff --git a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx index 6d737e2f73..cdfa1b55f2 100644 --- a/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/create-tag/create-tag-dialog.tsx @@ -133,7 +133,7 @@ export const CreateTagDialog: FC<CreateTagDialogProps> = ({ <Dialog.Footer> <ButtonLayout> - <Dialog.Close onClick={handleClose} loading={isLoading} disabled={isLoading}> + <Dialog.Close onClick={handleClose} disabled={isLoading}> {t('views:repos.cancel', 'Cancel')} </Dialog.Close> {!bypassable || !violation ? ( From da1778f2c4ddcd9d9c5fafd2c075f207cd87a4d1 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Mon, 18 Aug 2025 16:26:31 +0300 Subject: [PATCH 119/180] Fix Delete alert Dialog (#2076) --- packages/ui/src/components/alert-dialog.tsx | 2 +- .../dialogs/delete-alert-dialog.tsx | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/alert-dialog.tsx b/packages/ui/src/components/alert-dialog.tsx index 7b1f09d9c0..880bf14200 100644 --- a/packages/ui/src/components/alert-dialog.tsx +++ b/packages/ui/src/components/alert-dialog.tsx @@ -84,7 +84,7 @@ const Content = forwardRef<HTMLDivElement, ContentProps>(({ title, children }, r } theme={context.theme} > - <Dialog.Title className="!pt-0">{title}</Dialog.Title> + <Dialog.Title>{title}</Dialog.Title> </Dialog.Header> <Dialog.Body>{otherChildren}</Dialog.Body> diff --git a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx index bbaa2864f8..a64cacec46 100644 --- a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx +++ b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx @@ -66,13 +66,30 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ const displayMessageContent = useMemo(() => { if (message) return message + const replaceText = '__IDENTIFIER__' + if (type) { - return t( + const text = t( 'component:deleteDialog.descriptionWithType', - `This will permanently delete your ${type} ${identifier} and remove all data. This action cannot be undone.`, - { type: type, identifier: identifier } + `This will permanently delete your ${type} ${replaceText} and remove all data. This action cannot be undone.`, + { type: type, identifier: replaceText } + ) + + const parts = text.split(replaceText) + + return ( + <> + {parts[0]} + {identifier && ( + <Text as="span" variant="body-strong"> + {identifier} + </Text> + )} + {parts[1]} + </> ) } + return t( 'component:deleteDialog.description', `This will permanently remove all data. This action cannot be undone.` From bbb51030d732ecee1abc55f239e7ec9c91a2d2c9 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:27:23 +0400 Subject: [PATCH 120/180] fix review requested button styles (#2074) * fix review requested button styles * fixes --- packages/ui/src/components/button-group.tsx | 8 +++--- .../pull-request/pull-request-list-page.tsx | 26 ++++++++----------- .../components/button-group.ts | 6 ----- .../components/button.ts | 23 +++++++++++----- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/ui/src/components/button-group.tsx b/packages/ui/src/components/button-group.tsx index b392e3af7d..6643d073ba 100644 --- a/packages/ui/src/components/button-group.tsx +++ b/packages/ui/src/components/button-group.tsx @@ -6,7 +6,7 @@ import omit from 'lodash-es/omit' type ButtonGroupTooltipProps = Pick<TooltipProps, 'title' | 'content' | 'side' | 'align'> -type BaseButtonProps = Omit<ButtonProps, 'variant' | 'size' | 'theme' | 'asChild' | 'rounded' | 'type'> +type BaseButtonProps = Omit<ButtonProps, 'size' | 'theme' | 'asChild' | 'rounded' | 'type'> type ButtonWithTooltip = BaseButtonProps & { tooltipProps: ButtonGroupTooltipProps @@ -73,7 +73,7 @@ export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>( ref={ref} > {buttonsProps.map((buttonProps, index) => { - const { className, ...restButtonProps } = buttonProps + const { className, variant, ...restButtonProps } = buttonProps const tooltipProps = 'tooltipProps' in buttonProps ? buttonProps.tooltipProps : undefined const dropdownProps = 'dropdownProps' in buttonProps ? buttonProps.dropdownProps : undefined @@ -85,7 +85,7 @@ export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>( { 'cn-button-group-first': !index }, { 'cn-button-group-last': index === buttonsProps.length - 1 } )} - variant="outline" + variant={variant ?? 'outline'} size={size} iconOnly={iconOnly} {...omit(restButtonProps, ['tooltipProps', 'dropdownProps'])} @@ -98,3 +98,5 @@ export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>( } ) ButtonGroup.displayName = 'ButtonGroup' + +export type { BaseButtonProps as ButtonGroupBaseButtonProps } diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index bdf9cd96cf..aa80868042 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -3,6 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Button, ButtonGroup, + ButtonGroupBaseButtonProps, IconV2, Layout, ListActions, @@ -393,8 +394,13 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ onFilterChange?.(_filterValues) } - const activeClass = 'bg-cn-background-primary text-cn-foreground-primary' - const inactiveClass = '' + const getToggleCommonProps = (filterGroup: PRFilterGroupTogglerOptions): ButtonGroupBaseButtonProps => { + return { + onClick: () => onFilterGroupChange(filterGroup), + className: activeFilterGrp === filterGroup ? 'z-[2]' : '', + variant: activeFilterGrp === filterGroup ? 'primary' : 'outline' + } + } return ( <SandboxLayout.Main> @@ -435,21 +441,11 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ {isProjectLevel && ( <ButtonGroup buttonsProps={[ - { - children: 'All', - onClick: () => onFilterGroupChange(PRFilterGroupTogglerOptions.All), - className: activeFilterGrp === PRFilterGroupTogglerOptions.All ? activeClass : inactiveClass - }, - { - children: 'Created', - onClick: () => onFilterGroupChange(PRFilterGroupTogglerOptions.Created), - className: activeFilterGrp === PRFilterGroupTogglerOptions.Created ? activeClass : inactiveClass - }, + { children: 'All', ...getToggleCommonProps(PRFilterGroupTogglerOptions.All) }, + { children: 'Created', ...getToggleCommonProps(PRFilterGroupTogglerOptions.Created) }, { children: 'Review Requested', - onClick: () => onFilterGroupChange(PRFilterGroupTogglerOptions.ReviewRequested), - className: - activeFilterGrp === PRFilterGroupTogglerOptions.ReviewRequested ? activeClass : inactiveClass + ...getToggleCommonProps(PRFilterGroupTogglerOptions.ReviewRequested) } ]} size="sm" diff --git a/packages/ui/tailwind-utils-config/components/button-group.ts b/packages/ui/tailwind-utils-config/components/button-group.ts index 390d882d5a..58bac4ffe3 100644 --- a/packages/ui/tailwind-utils-config/components/button-group.ts +++ b/packages/ui/tailwind-utils-config/components/button-group.ts @@ -47,11 +47,5 @@ export default { } } }, - - '& .cn-button': { - '&:hover, &:focus': { - 'z-index': '1', - } - } } } diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index 0b359597a2..bfd975840c 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -52,15 +52,26 @@ function createButtonVariantStyles() { `var(--cn-set-${themeStyle}-${variant}-border, var(--cn-set-${themeStyle}-${variant}-bg))` // Hover styles - style[`&:hover:not(:disabled, .cn-button-disabled)`] = { - backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-hover, var(--cn-set-${themeStyle}-${variant}-bg))` - } + style[`&:hover:not(:disabled, .cn-button-disabled)`] = + variant === 'surface' + ? { + backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-hover, var(--cn-set-${themeStyle}-${variant}-bg))` + } + : { + backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-hover, var(--cn-set-${themeStyle}-${variant}-bg))`, + borderColor: `var(--cn-set-${themeStyle}-${variant}-bg-hover, var(--cn-set-${themeStyle}-${variant}-bg))` + } // Active styles style[`&:active:not(:disabled, .cn-button-disabled), &:where(.cn-button-active), &:where([data-state=open])`] = - { - backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-selected, var(--cn-set-${themeStyle}-${variant}-bg))` - } + variant === 'surface' + ? { + backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-selected, var(--cn-set-${themeStyle}-${variant}-bg))` + } + : { + backgroundColor: `var(--cn-set-${themeStyle}-${variant}-bg-selected, var(--cn-set-${themeStyle}-${variant}-bg))`, + borderColor: `var(--cn-set-${themeStyle}-${variant}-bg-selected, var(--cn-set-${themeStyle}-${variant}-bg))` + } separatorStyles[`&:where(.cn-button-split-dropdown.cn-button-${variant}.cn-button-${theme})`] = { '&::before': { From 56b1a211d2bbb0b98c3e223b37f997420cd1276d Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Mon, 18 Aug 2025 14:10:59 +0000 Subject: [PATCH 121/180] Code editor highlight (#10222) * fad9af improve * fc33fe cleanup * a46650 code editor highlight --- apps/gitness/src/AppMFE.tsx | 2 +- .../src/components-v2/file-content-viewer.tsx | 10 +++++- apps/gitness/src/pages-v2/search-page.tsx | 4 +-- .../demo-shadowdom-codeeditor.tsx | 1 + .../yaml-editor/src/components/CodeEditor.css | 6 +++- .../yaml-editor/src/components/CodeEditor.tsx | 5 +++ .../yaml-editor/src/hooks/useHighlight.tsx | 36 +++++++++++++++++++ 7 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 packages/yaml-editor/src/hooks/useHighlight.tsx diff --git a/apps/gitness/src/AppMFE.tsx b/apps/gitness/src/AppMFE.tsx index f50331dcd3..7521c7f34e 100644 --- a/apps/gitness/src/AppMFE.tsx +++ b/apps/gitness/src/AppMFE.tsx @@ -62,7 +62,7 @@ function MFERouteRenderer({ renderUrl, parentLocationPath, onRouteChange }: MFER // Notify parent about route change useEffect(() => { if (canNavigate) { - onRouteChange?.(decodeURIComponentIfValid(`${renderUrl}${location.pathname}`)) + onRouteChange?.(decodeURIComponentIfValid(`${renderUrl}${location.pathname}${location.search}`)) } }, [location.pathname]) diff --git a/apps/gitness/src/components-v2/file-content-viewer.tsx b/apps/gitness/src/components-v2/file-content-viewer.tsx index 2ebf9a986e..7314e1043c 100644 --- a/apps/gitness/src/components-v2/file-content-viewer.tsx +++ b/apps/gitness/src/components-v2/file-content-viewer.tsx @@ -1,6 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' +import { isNull } from 'lodash-es' + import { OpenapiGetContentOutput, TypesCommit, @@ -21,6 +23,7 @@ import { Text, ViewTypeValue } from '@harnessio/ui/components' +import { useRouterContext } from '@harnessio/ui/context' import { cn } from '@harnessio/ui/utils' import { CommitsList, FileReviewError, monacoThemes } from '@harnessio/ui/views' import { CodeEditor } from '@harnessio/yaml-editor' @@ -218,6 +221,10 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV const { gitRefName } = useGitRef() + const { useSearchParams } = useRouterContext() + const [searchParams] = useSearchParams() + const keyword = searchParams.get('keyword') + const fileError = !repoContent || !repoContent.content || !repoContent.content.data const { data: { body: commitData, headers } = {}, isFetching: isFetchingCommits } = useListCommitsQuery({ @@ -309,7 +316,7 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV } const Loader = () => ( - <Layout.Flex align="center" justify="center" className="rounded-b-3 flex h-full rounded-t-none border border-t-0"> + <Layout.Flex align="center" justify="center" className="flex h-full rounded-b-3 rounded-t-none border border-t-0"> <IconV2 className="animate-spin" name="loader" size="lg" /> </Layout.Flex> ) @@ -412,6 +419,7 @@ export default function FileContentViewer({ repoContent, loading }: FileContentV enableLinesSelection={true} onSelectedLineChange={setSelectedLine} selectedLine={selectedLine} + highlightKeyword={!isNull(keyword) ? keyword : undefined} /> )} </Tabs.Content> diff --git a/apps/gitness/src/pages-v2/search-page.tsx b/apps/gitness/src/pages-v2/search-page.tsx index d2bfd6453f..a2fd292856 100644 --- a/apps/gitness/src/pages-v2/search-page.tsx +++ b/apps/gitness/src/pages-v2/search-page.tsx @@ -177,9 +177,9 @@ export default function SearchPage() { searchError={searchError?.message?.toString()} toRepoFileDetails={({ repoPath, filePath, branch }) => repoPath && branch - ? `/repos/${repoPath}/files/refs/heads/${branch}/~/${filePath}` + ? `/repos/${repoPath}/files/refs/heads/${branch}/~/${filePath}?keyword=${searchQuery}` : // TODO: get default branch - `/repos/${repoRef}/files/refs/heads/main/~/${filePath}` + `/repos/${repoRef}/files/refs/heads/main/~/${filePath}?keyword=${searchQuery}` } toRepo={({ repoPath }) => `/repos/${repoPath}`} // language filter props diff --git a/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx b/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx index ae0d7635b9..2bb559b003 100644 --- a/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx +++ b/packages/yaml-editor/playground/src/demo-shadowdom-codeeditor/demo-shadowdom-codeeditor.tsx @@ -80,6 +80,7 @@ export const DemoCodeEditorShadowDom: React.FC<React.PropsWithChildren<React.HTM setBounds(undefined) } }} + highlightKeyword="Services" /> </div> )} diff --git a/packages/yaml-editor/src/components/CodeEditor.css b/packages/yaml-editor/src/components/CodeEditor.css index 56c50fd445..6392ff907d 100644 --- a/packages/yaml-editor/src/components/CodeEditor.css +++ b/packages/yaml-editor/src/components/CodeEditor.css @@ -1,6 +1,10 @@ .CodeEditor_HighlightedLine { opacity: 0.1; - background-color: var(--cn-yellow-300) !important; + background-color: var(--cn-yellow-300, orange) !important; +} + +.CodeEditor_HighlightedText { + background-color: var(--cn-yellow-100, orange) !important; } .CodeEditor_HighlightedGlyphMargin { diff --git a/packages/yaml-editor/src/components/CodeEditor.tsx b/packages/yaml-editor/src/components/CodeEditor.tsx index 4f6d956af8..ac88a17daa 100644 --- a/packages/yaml-editor/src/components/CodeEditor.tsx +++ b/packages/yaml-editor/src/components/CodeEditor.tsx @@ -4,6 +4,7 @@ import Editor, { EditorProps, loader, Monaco, useMonaco } from '@monaco-editor/r import * as monaco from 'monaco-editor' import { MonacoCommonDefaultOptions } from '../constants/monaco-common-default-options' +import { useHighlight } from '../hooks/useHighlight' import { useLinesSelection } from '../hooks/useLinesSelection' import { useTheme } from '../hooks/useTheme' import { ThemeDefinition } from '../types/themes' @@ -34,6 +35,7 @@ export interface CodeEditorProps<_> { selectedLine?: number onSelectedLineChange?: (line: number | undefined) => void onSelectedLineButtonClick?: (ev: HTMLDivElement | undefined) => void + highlightKeyword?: string } export function CodeEditor<T>({ @@ -47,6 +49,7 @@ export function CodeEditor<T>({ selectedLine, onSelectedLineChange, onSelectedLineButtonClick, + highlightKeyword, height = '75vh', className }: CodeEditorProps<T>): JSX.Element { @@ -115,6 +118,8 @@ export function CodeEditor<T>({ onSelectedLineButtonClick }) + useHighlight({ editor, keyword: highlightKeyword }) + const mergedOptions = useMemo(() => { return { ...defaultOptions, diff --git a/packages/yaml-editor/src/hooks/useHighlight.tsx b/packages/yaml-editor/src/hooks/useHighlight.tsx new file mode 100644 index 0000000000..e3903311b1 --- /dev/null +++ b/packages/yaml-editor/src/hooks/useHighlight.tsx @@ -0,0 +1,36 @@ +import { useEffect } from 'react' + +import * as monaco from 'monaco-editor' + +export interface UseHighlightProps { + editor?: monaco.editor.IStandaloneCodeEditor + keyword?: string +} + +export function useHighlight(props: UseHighlightProps) { + const { editor, keyword } = props + + useEffect(() => { + if (!editor || !keyword) return + + const editorModel = editor.getModel() as monaco.editor.ITextModel + const keywordMatches: monaco.editor.FindMatch[] = editorModel.findMatches(keyword, false, false, false, null, false) + + if (keywordMatches.length > 0) { + keywordMatches.forEach((match: monaco.editor.FindMatch): void => { + editorModel.deltaDecorations( + [], + [ + { + range: match.range, + options: { + isWholeLine: false, + inlineClassName: 'CodeEditor_HighlightedText' + } + } + ] + ) + }) + } + }, [editor, keyword]) +} From 109c2c65058c345baa4bb5eda805a926fbc5927a Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Mon, 18 Aug 2025 17:15:16 +0300 Subject: [PATCH 122/180] Fix Tooltip max-height and add scroll (#2077) --- packages/ui/tailwind-utils-config/components/tooltip.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/tailwind-utils-config/components/tooltip.ts b/packages/ui/tailwind-utils-config/components/tooltip.ts index e2deb4a7d9..b484f00e9f 100644 --- a/packages/ui/tailwind-utils-config/components/tooltip.ts +++ b/packages/ui/tailwind-utils-config/components/tooltip.ts @@ -2,12 +2,13 @@ export default { '.cn-tooltip': { minWidth: 'var(--cn-tooltip-min)', maxWidth: 'var(--cn-tooltip-max)', + maxHeight: 'calc(var(--radix-tooltip-content-available-height) - 4px)', borderRadius: 'var(--cn-tooltip-radius)', border: 'var(--cn-tooltip-border) solid var(--cn-set-gray-solid-bg)', background: 'var(--cn-set-gray-solid-bg)', padding: 'var(--cn-tooltip-py) var(--cn-tooltip-px)', color: 'var(--cn-set-gray-solid-text)', - '@apply flex flex-col z-50 font-body-normal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95': + '@apply overflow-auto flex flex-col z-50 font-body-normal data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95': '', '&-title': { From ec231813c84330f870f89040fdaed2664ad9231e Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Mon, 18 Aug 2025 15:48:51 +0000 Subject: [PATCH 123/180] fix: hide pr suggest btn on conv view (#10229) * ddc049 fix: more robust logic * 9562ad fix: hide pr suggest btn on conv view --- .../conversation/pull-request-comment-box.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index b414116b66..01c8a4ccc9 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -409,14 +409,19 @@ export const PullRequestCommentBox = ({ } const toolbar: ToolbarItem[] = useMemo(() => { - const initial: ToolbarItem[] = handleAiPullRequestSummary + const aiButton: ToolbarItem[] = handleAiPullRequestSummary ? [{ icon: 'ai' as IconV2NamesType, variant: 'ai', action: ToolbarAction.AI_SUMMARY }] : [] + const suggestionButton: ToolbarItem[] = + diff !== '' && lineNumber !== undefined && lineFromNumber !== undefined + ? [{ icon: 'suggestion', action: ToolbarAction.SUGGESTION }] + : [] + // TODO: Design system: Update icons once they are available in IconV2 return [ - ...initial, - { icon: 'suggestion', action: ToolbarAction.SUGGESTION }, + ...aiButton, + ...suggestionButton, { icon: 'header', action: ToolbarAction.HEADER }, { icon: 'bold', action: ToolbarAction.BOLD }, { icon: 'italic', action: ToolbarAction.ITALIC }, @@ -425,7 +430,7 @@ export const PullRequestCommentBox = ({ { icon: 'list-select', action: ToolbarAction.CHECK_LIST }, { icon: 'code', action: ToolbarAction.CODE_BLOCK } ] - }, []) + }, [diff, lineNumber, lineFromNumber, handleAiPullRequestSummary]) const handleActionClick = (type: ToolbarAction, comment: string, textSelection: TextSelection) => { switch (type) { @@ -612,7 +617,7 @@ export const PullRequestCommentBox = ({ > <Tabs.Root defaultValue={TABS_KEYS.WRITE} value={activeTab} onValueChange={handleTabChange}> <Tabs.List - className="-mx-4 px-4 mb-cn-md" + className="-mx-4 mb-cn-md px-4" activeClassName={inReplyMode ? 'bg-cn-background-2' : 'bg-cn-background-1'} variant="overlined" > @@ -633,7 +638,7 @@ export const PullRequestCommentBox = ({ resizable ref={textAreaRef} placeholder={textareaPlaceholder ?? 'Add your comment here'} - className="text-cn-foreground-1 min-h-24 pb-8" + className="min-h-24 pb-8 text-cn-foreground-1" autoFocus={!!autofocus || !!inReplyMode} principalProps={principalProps} setPrincipalsMentionMap={setPrincipalsMentionMap} @@ -661,7 +666,7 @@ export const PullRequestCommentBox = ({ <Layout.Flex align="center" - className="bg-cn-background-1 absolute bottom-[1px] left-[1px] w-[calc(100%-20px)] rounded" + className="absolute bottom-px left-px w-[calc(100%-20px)] rounded bg-cn-background-1" > {toolbar.map((item, index) => { const isFirst = index === 0 From 8c7aa6b012a65d79b14fc190770b09c8fedc3421 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Mon, 18 Aug 2025 17:01:11 +0000 Subject: [PATCH 124/180] PR Files - Fixed since last view changed (#10230) * 13b02b Fixed since last view changed --- packages/ui/locales/en/views.json | 1 + packages/ui/locales/fr/views.json | 1 + .../components/pull-request-accordian.tsx | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 2735ee2229..54f1dffeaa 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -620,6 +620,7 @@ "searchLabels": "Search", "noLabels": "No labels found", "editLabels": "Edit labels", + "changedSinceLastView": "Changed since last viewed", "markViewed": "Viewed", "deletedComment": "This comment was deleted.", "reviewers": "Reviewers", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index ce332cc9da..24ff4e68a8 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -612,6 +612,7 @@ "searchLabels": "Rechercher des étiquettes..", "noLabels": "No labels found", "editLabels": "Edit labels", + "changedSinceLastView": "Modifié depuis la dernière consultation", "markViewed": "Vue", "deletedComment": "Ce commentaire a été supprimé.", "reviewers": "Réviseurs", diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index cb479cfd6f..624de82bba 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { Accordion, Button, Checkbox, CopyButton, CounterBadge, IconV2, Layout, Link, Text } from '@/components' +import { Accordion, Button, Checkbox, CopyButton, CounterBadge, IconV2, Layout, Link, Tag, Text } from '@/components' import { useTranslation } from '@/context' import { CommentItem, @@ -17,6 +17,7 @@ import { } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' import PullRequestDiffViewer from '@views/repo/pull-request/components/pull-request-diff-viewer' +import { FILE_VIEWED_OBSOLETE_SHA } from '@views/repo/pull-request/details/pull-request-utils' import { useDiffConfig } from '@views/repo/pull-request/hooks/useDiffConfig' import { parseStartingLineIfOne, PULL_REQUEST_LARGE_DIFF_CHANGES_LIMIT } from '@views/repo/pull-request/utils' @@ -98,7 +99,7 @@ export const LineTitle: React.FC<LineTitleProps> = ({ currentRefForDiff }) => { const { t } = useTranslation() - const { text, addedLines, deletedLines, filePath, checksumAfter } = header + const { text, addedLines, deletedLines, filePath, checksumAfter, fileViews } = header return ( <div className="flex items-center justify-between gap-x-3"> <div className="inline-flex items-center gap-x-4"> @@ -129,7 +130,11 @@ export const LineTitle: React.FC<LineTitleProps> = ({ {deletedLines != null && deletedLines > 0 && <CounterBadge theme="danger">-{deletedLines}</CounterBadge>} </div> </div> - <div className="inline-flex items-center gap-x-6"> + <div className="inline-flex items-center gap-x-2"> + {/* Show "Changed since last viewed" tag when file is obsolete */} + {fileViews?.get(filePath) === FILE_VIEWED_OBSOLETE_SHA && ( + <Tag value={t('views:pullRequests.changedSinceLastView')} theme="orange" /> + )} {showViewed ? ( <Button variant="ghost" size="sm" className="gap-x-2.5 px-2.5 py-1.5" onClick={e => e.stopPropagation()}> <Checkbox From 9fd9fe601330e6cd888cca53baa3ac0d1f67f66c Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:25:45 +0400 Subject: [PATCH 125/180] add branches page design review (#2052) --- .../src/content/docs/components/button.mdx | 15 +- apps/portal/src/styles.css | 2 +- packages/ui/locales/en/views.json | 25 +- packages/ui/locales/fr/views.json | 17 +- packages/ui/src/components/button.tsx | 41 ++-- packages/ui/src/components/chat/chat.tsx | 4 +- .../ui/src/components/chat_deprecated.tsx | 2 +- .../ui/src/components/icon-v2/icon-v2.tsx | 4 +- packages/ui/src/components/input-otp.tsx | 2 +- .../src/components/more-actions-tooltip.tsx | 2 +- packages/ui/src/components/no-data.tsx | 49 ++-- .../pipeline-nodes/splitview-stage-node.tsx | 2 +- .../components/pipeline-nodes/step-node.tsx | 2 +- packages/ui/src/components/progress.tsx | 9 +- packages/ui/src/components/repo-subheader.tsx | 2 +- packages/ui/src/components/table.tsx | 18 +- .../execution-list/execution-list.tsx | 2 +- .../pipelines/pipeline-list/pipeline-list.tsx | 2 +- .../components/profile-settings-keys-list.tsx | 6 +- .../branch-selector-dropdown.tsx | 2 +- .../components/branch-selector-v2/types.ts | 2 +- .../pull-request/pull-request-list-page.tsx | 16 +- .../repo-branch/components/branch-list.tsx | 218 ++++++------------ .../components/create-branch-dialog.tsx | 2 +- .../components/divergence-gauge.tsx | 78 +++---- .../repo-branch/repo-branch-list-view.tsx | 118 +++++++--- .../repo/repo-commits/repo-commits-view.tsx | 13 +- .../repo/repo-files/file-review-error.tsx | 11 +- .../ui/src/views/repo/repo-list/repo-list.tsx | 26 +-- .../repo/repo-summary/repo-empty-view.tsx | 10 +- .../views/repo/repo-summary/repo-summary.tsx | 2 +- .../repo-tags/components/repo-tags-list.tsx | 2 +- packages/ui/tailwind-design-system.ts | 12 +- 33 files changed, 346 insertions(+), 372 deletions(-) diff --git a/apps/portal/src/content/docs/components/button.mdx b/apps/portal/src/content/docs/components/button.mdx index 38696ea27c..3e6d7fc04d 100644 --- a/apps/portal/src/content/docs/components/button.mdx +++ b/apps/portal/src/content/docs/components/button.mdx @@ -371,11 +371,17 @@ Transparent buttons have no background or border and don't accept theme props. <Button variant="secondary" size="sm" iconOnly> <IconV2 name="git-branch" skipSize /> </Button> + <Button variant="transparent" size="sm" iconOnly> + <IconV2 name="xmark" skipSize /> + </Button> <Button variant="outline" size="xs" iconOnly> <IconV2 name="check" skipSize /> </Button> - <Button variant="transparent" size="sm" iconOnly> - <IconV2 name="xmark" skipSize /> + <Button variant="outline" size="2xs" iconOnly> + <IconV2 name="check" skipSize /> + </Button> + <Button variant="outline" size="3xs" iconOnly> + <IconV2 name="check" skipSize /> </Button> <Button variant="outline" size="2xs" iconOnly> <IconV2 name="xmark" skipSize /> @@ -490,9 +496,10 @@ This ensures type safety and provides proper development-time feedback on invali }, { name: "size", - description: "Size of the button.", + description: + "Size of the button. '2xs' and '3xs' set iconOnly props to true.", required: false, - value: "'md' | 'sm' | 'xs'", + value: "'md' | 'sm' | 'xs' | '2xs' | '3xs'", defaultValue: "'md'", }, { diff --git a/apps/portal/src/styles.css b/apps/portal/src/styles.css index 1d764506ea..4eb25c1d72 100644 --- a/apps/portal/src/styles.css +++ b/apps/portal/src/styles.css @@ -22,7 +22,7 @@ main { } code { - @apply bg-cn-background-11 !important; + @apply bg-cn-background-softgray !important; } .content-panel { diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 54f1dffeaa..ccb489a991 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -8,7 +8,12 @@ "commits": "Commits", "tags": "Tags", "pullRequests": "Pull requests", - "branches": "Branches", + "branches": { + "title": "Branches", + "commitsBehind": "commits behind", + "commitsAhead": "commits ahead", + "createBranch": "Create Branch" + }, "search": "Search", "settings": "Settings", "descriptionPlaceholder": "Enter a description", @@ -134,7 +139,6 @@ "createRuleButton": "Create Rule", "updatingRuleButton": "Updating Rule...", "creatingRuleButton": "Creating Rule...", - "createBranch": "Create Branch", "branch": "Branch", "update": "Updated", "checkStatus": "Check status", @@ -142,7 +146,6 @@ "ahead": "Ahead", "pullRequest": "Pull Request", "compare": "Compare", - "viewRules": "View Rules", "browse": "Browse", "deleteBranch": "Delete Branch", "createBranchDialog": { @@ -316,12 +319,15 @@ "editWebhookTitle": "Order Status Update Webhook", "createWebhookTitle": "Create a webhook", "webhookDetails": "Details", - "updatingWebhook": "Updating Webhook...", - "creatingWebhook": "Creating Webhook...", - "updateWebhook": "Update Webhook", - "createWebhook": "Create Webhook", + "updatingWebhook": "Updating webhook...", + "creatingWebhook": "Creating webhook...", + "updateWebhook": "Update webhook", + "createWebhook": "Create webhook", + "updateRule": "Update rule", + "updatingRule": "Updating rule...", + "createBranchButton": "Create branch", "newPullReq": "New pull request", - "createBranchButton": "Create Branch", + "viewRules": "View Rules", "newBranch": "New branch", "emptyRepo": "This repository is empty.", "afterComparingOpenPullRequest": "After comparing changes, you may open a pull request to contribute your changes upstream.", @@ -409,7 +415,6 @@ "button": { "createPullRequest": "Create Pull Request" }, - "clearFilters": "Clear Filters", "noPullRequestsInRepo": "Start your contribution journey by creating a new pull request.", "noPullRequestsInProject": "There are no pull requests in this project yet.", "noBranches": "No branches yet", @@ -417,6 +422,7 @@ "startBranchDescription": "Start branching to see your work organized.", "noCommitsHistory": "No commits history", "noCommitsHistoryDescription": "There isn't any commit history to show here for the selected user, time range, or current page.", + "clearFilters": "Clear Filters", "noRepos": "No repositories yet", "noReposProject": "There are no repositories in this project yet.", "createOrImportRepos": "Create new or import an existing repository.", @@ -528,6 +534,7 @@ "title": "Labels", "showParentLabels": "Show labels from parent scopes", "createLabel": "Create Label", + "newLabel": "Create Label", "create": "Create Labels" }, "projectSettings": { diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index 24ff4e68a8..e27503df78 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -7,8 +7,13 @@ "pipelines": "Pipelines", "commits": "Commits", "tags": "Étiquettes", - "pullRequests": "Requêtes de tirage", - "branches": "Branches", + "pullRequests": "Pull requests", + "branches": { + "title": "Branches", + "commitsBehind": "commits en retard", + "commitsAhead": "commits en avance", + "createBranch": "Create Branch" + }, "search": "Rechercher", "settings": "Paramètres", "descriptionPlaceholder": "Enter a description", @@ -134,15 +139,13 @@ "createRuleButton": "Créer une règle de branche", "updatingRuleButton": "Mise à jour de la règle...", "creatingRuleButton": "Création de la règle...", - "createBranch": "Create Branch", "branch": "Branche", "update": "Mettre à jour", "checkStatus": "Vérifier le statut", - "behind": "En retard", + "behind": "Behind", "ahead": "En avance", "pullRequest": "Pull Request", "compare": "Compare", - "viewRules": "Voir les règles", "browse": "Browse", "deleteBranch": "Delete Branch", "createBranchDialog": { @@ -316,6 +319,7 @@ "createWebhook": "Create webhook", "newPullReq": "Nouvelle requête de tirage", "createBranchButton": "Créer une branche", + "viewRules": "Voir les règles", "newBranch": "Nouvelle branche", "emptyRepo": "Ce référentiel est vide.", "afterComparingOpenPullRequest": "Après avoir comparé les modifications, vous pouvez ouvrir une demande d'extraction pour contribuer à vos modifications en amont.", @@ -401,7 +405,6 @@ "button": { "createPullRequest": "Créer une demande de tirage" }, - "clearFilters": "Effacer les filtres", "noPullRequestsInRepo": "Commencez votre parcours de contribution en créant un brouillon de demande de tirage.", "noPullRequestsInProject": "There are no pull requests in this project yet.", "noBranches": "Pas encore de branches", @@ -409,6 +412,7 @@ "startBranchDescription": "Commencez à créer des branches pour voir votre travail organisé.", "noCommitsHistory": "No commits history", "noCommitsHistoryDescription": "There isn't any commit history to show here for the selected user, time range, or current page.", + "clearFilters": "Effacer les filtres", "noRepos": "Pas encore de dépôts", "noReposProject": "Il n'y a pas encore de dépôts dans ce projet.", "createOrImportRepos": "Créer un nouveau dépôt ou importer un dépôt existant.", @@ -520,6 +524,7 @@ "title": "Labels", "showParentLabels": "Show labels from parent scopes", "createLabel": "Create label", + "newLabel": "Create Label", "create": "Create labels" }, "projectSettings": { diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index 7a03c08957..d5ccd3252a 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -2,9 +2,10 @@ import { ButtonHTMLAttributes, forwardRef } from 'react' import { Slot } from '@radix-ui/react-slot' import { cn } from '@utils/cn' +import { filterChildrenByDisplayNames } from '@utils/utils' import { cva, type VariantProps } from 'class-variance-authority' -import { IconV2 } from './icon-v2' +import { IconV2, IconV2DisplayName } from './icon-v2' const buttonVariants = cva('cn-button', { variants: { @@ -18,11 +19,11 @@ const buttonVariants = cva('cn-button', { transparent: 'cn-button-transparent' }, size: { - '3xs': 'cn-button-3xs', - '2xs': 'cn-button-2xs', - xs: 'cn-button-xs', + md: '', sm: 'cn-button-sm', - md: '' + xs: 'cn-button-xs', + '2xs': 'cn-button-2xs', + '3xs': 'cn-button-3xs' }, rounded: { true: 'cn-button-rounded' @@ -105,45 +106,41 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>( size = 'md', theme = 'default', rounded, - iconOnly, + iconOnly: _iconOnly = false, asChild = false, loading, disabled, - children, + children: _children, type = 'button', ...props }, ref ) => { const Comp = asChild ? Slot : 'button' + const microSize = size === '2xs' || size === '3xs' + const iconOnly = _iconOnly || microSize - const _children = loading ? ( + const filteredChildren = iconOnly ? filterChildrenByDisplayNames(_children, [IconV2DisplayName])[0] : _children + + const children = loading ? ( <> - {loading && <IconV2 className="animate-spin" name="loader" />} - {children} + <IconV2 className="animate-spin" name="loader" /> + {/* When button state is 'loading' and iconOnly is true, we show only 1 icon */} + {!iconOnly && filteredChildren} </> ) : ( - children + filteredChildren ) return ( <Comp - className={cn( - buttonVariants({ - variant: variant || 'primary', - size: size || 'md', - theme: theme || 'default', - rounded, - iconOnly, - className - }) - )} + className={cn(buttonVariants({ variant, size, theme, rounded, iconOnly, className }))} ref={ref} disabled={disabled || loading} type={type} {...props} > - {_children} + {children} </Comp> ) } diff --git a/packages/ui/src/components/chat/chat.tsx b/packages/ui/src/components/chat/chat.tsx index 609292b9c1..f690b91d40 100644 --- a/packages/ui/src/components/chat/chat.tsx +++ b/packages/ui/src/components/chat/chat.tsx @@ -55,7 +55,7 @@ const Message: FC<MessageProps> = ({ self, avatar, actions, children }) => { > <div className={cn('text-2 text-cn-foreground-1 leading-relaxed', { - 'px-3.5 py-2 bg-cn-background-8 rounded-[8px_8px_2px_8px]': self + 'px-3.5 py-2 bg-cn-background-softgray rounded-[8px_8px_2px_8px]': self })} > {children} @@ -70,7 +70,7 @@ const CodeBlock: FC<PropsWithChildren<{ className?: string }>> = ({ children, cl return ( <code className={cn( - 'inline-block rounded-[3px] border border-cn-borders-2 bg-cn-background-8 px-1.5 text-2 leading-[18px]', + 'inline-block rounded-[3px] border border-cn-borders-2 bg-cn-background-softgray px-1.5 text-2 leading-[18px]', className )} > diff --git a/packages/ui/src/components/chat_deprecated.tsx b/packages/ui/src/components/chat_deprecated.tsx index 44e3405fa1..d364bc519d 100644 --- a/packages/ui/src/components/chat_deprecated.tsx +++ b/packages/ui/src/components/chat_deprecated.tsx @@ -43,7 +43,7 @@ const Message: React.FC<MessageProps> = ({ self, time, avatar, actions, children > <div className={cn('text-cn-foreground-3 leading-relaxed', { - 'px-3 py-2 bg-cn-background-8 rounded-lg': self + 'px-3 py-2 bg-cn-background-softgray rounded-lg': self })} > {children} diff --git a/packages/ui/src/components/icon-v2/icon-v2.tsx b/packages/ui/src/components/icon-v2/icon-v2.tsx index e7ad3aaa33..b342e03207 100644 --- a/packages/ui/src/components/icon-v2/icon-v2.tsx +++ b/packages/ui/src/components/icon-v2/icon-v2.tsx @@ -5,6 +5,8 @@ import { cva, VariantProps } from 'class-variance-authority' import { IconNameMapV2 } from './icon-name-map' +export const IconV2DisplayName = 'IconV2' + export type IconV2NamesType = keyof typeof IconNameMapV2 export const iconVariants = cva('cn-icon', { @@ -64,4 +66,4 @@ const IconV2 = forwardRef<SVGSVGElement, IconPropsV2>( export { IconV2 } -IconV2.displayName = 'IconV2' +IconV2.displayName = IconV2DisplayName diff --git a/packages/ui/src/components/input-otp.tsx b/packages/ui/src/components/input-otp.tsx index 65f6ade090..730c0121b1 100644 --- a/packages/ui/src/components/input-otp.tsx +++ b/packages/ui/src/components/input-otp.tsx @@ -48,7 +48,7 @@ const InputOTPSlot = forwardRef<HTMLDivElement, InputOTPSlotProps>(({ index, cla {char} {hasFakeCaret && ( <div className="pointer-events-none absolute inset-0 flex items-center justify-center"> - <div className="animate-caret-blink h-4 w-px bg-cn-background-primary duration-1000" /> + <div className="animate-caret-blink h-4 w-px bg-cn-background-accent duration-1000" /> </div> )} </div> diff --git a/packages/ui/src/components/more-actions-tooltip.tsx b/packages/ui/src/components/more-actions-tooltip.tsx index bdac4ccbeb..152671ea90 100644 --- a/packages/ui/src/components/more-actions-tooltip.tsx +++ b/packages/ui/src/components/more-actions-tooltip.tsx @@ -31,7 +31,7 @@ export interface MoreActionsTooltipProps { export const MoreActionsTooltip: FC<MoreActionsTooltipProps> = ({ actions, iconName = 'more-vert', - sideOffset = 7, + sideOffset = 2, alignOffset = 0, className }) => { diff --git a/packages/ui/src/components/no-data.tsx b/packages/ui/src/components/no-data.tsx index d5c02e89f1..db16790fc0 100644 --- a/packages/ui/src/components/no-data.tsx +++ b/packages/ui/src/components/no-data.tsx @@ -1,25 +1,34 @@ import { Dispatch, FC, ReactNode, SetStateAction } from 'react' -import { Button, ButtonProps, Illustration, IllustrationsNameType, Layout, SplitButton, Text } from '@/components' +import { + Button, + ButtonProps, + IconPropsV2, + IconV2, + Illustration, + IllustrationsNameType, + Layout, + SplitButton, + Text +} from '@/components' import { useRouterContext } from '@/context' import { cn } from '@utils/cn' +import omit from 'lodash-es/omit' export interface NoDataProps { title: string imageName?: IllustrationsNameType imageSize?: number description: string[] - primaryButton?: { + primaryButton?: ButtonProps & { + icon?: IconPropsV2['name'] label: ReactNode | string - onClick?: () => void to?: string - props?: ButtonProps } - secondaryButton?: { + secondaryButton?: ButtonProps & { + icon?: IconPropsV2['name'] label: ReactNode | string to?: string - onClick?: () => void - props?: ButtonProps } withBorder?: boolean loadState?: string @@ -41,7 +50,6 @@ export const NoData: FC<NoDataProps> = ({ title, description, primaryButton, - secondaryButton, withBorder = false, textWrapperClassName, @@ -76,26 +84,29 @@ export const NoData: FC<NoDataProps> = ({ <Layout.Horizontal gap="sm"> {primaryButton && (primaryButton.to ? ( - <Button asChild onClick={() => primaryButton?.onClick?.()} {...primaryButton.props}> - <NavLink to={primaryButton.to}>{primaryButton.label}</NavLink> + <Button asChild {...omit(primaryButton, ['to', 'label', 'icon'])}> + <NavLink to={primaryButton.to}> + {primaryButton.icon && <IconV2 name={primaryButton.icon} size="sm" />} + {primaryButton.label} + </NavLink> </Button> ) : ( - <Button onClick={() => primaryButton?.onClick?.()} {...primaryButton.props}> + <Button {...omit(primaryButton, ['label', 'icon'])}> + {primaryButton.icon && <IconV2 name={primaryButton.icon} size="sm" />} {primaryButton.label} </Button> ))} {secondaryButton && (secondaryButton.to ? ( - <Button - variant="outline" - asChild - onClick={() => secondaryButton?.onClick?.()} - {...secondaryButton.props} - > - <NavLink to={secondaryButton.to}>{secondaryButton.label}</NavLink> + <Button variant="outline" asChild {...omit(secondaryButton, ['to', 'label', 'icon'])}> + <NavLink to={secondaryButton.to}> + {secondaryButton.icon && <IconV2 name={secondaryButton.icon} size="sm" />} + {secondaryButton.label} + </NavLink> </Button> ) : ( - <Button variant="outline" onClick={() => secondaryButton?.onClick?.()} {...secondaryButton.props}> + <Button variant="outline" {...omit(secondaryButton, ['label', 'icon'])}> + {secondaryButton.icon && <IconV2 name={secondaryButton.icon} size="sm" />} {secondaryButton.label} </Button> ))} diff --git a/packages/ui/src/components/pipeline-nodes/splitview-stage-node.tsx b/packages/ui/src/components/pipeline-nodes/splitview-stage-node.tsx index 55574936da..22365c59de 100644 --- a/packages/ui/src/components/pipeline-nodes/splitview-stage-node.tsx +++ b/packages/ui/src/components/pipeline-nodes/splitview-stage-node.tsx @@ -56,7 +56,7 @@ export function SplitView_StageNode(props: SplitView_StageNodeProps) { <ExecutionStatus executionStatus={executionStatus} /> <div - className={cn('bg-cn-background-8 rounded-md', { + className={cn('bg-cn-background-softgray rounded-md', { 'unified-pipeline-studio_card-wrapper ': executionStatus === 'executing' })} > diff --git a/packages/ui/src/components/pipeline-nodes/step-node.tsx b/packages/ui/src/components/pipeline-nodes/step-node.tsx index 1e435b70ed..b6de881dc3 100644 --- a/packages/ui/src/components/pipeline-nodes/step-node.tsx +++ b/packages/ui/src/components/pipeline-nodes/step-node.tsx @@ -56,7 +56,7 @@ export function StepNode(props: StepNodeProps) { <ExecutionStatus executionStatus={executionStatus} /> <div - className={cn('bg-cn-background-8 rounded-md', { + className={cn('bg-cn-background-softgray rounded-md', { 'unified-pipeline-studio_card-wrapper ': executionStatus === 'executing' })} > diff --git a/packages/ui/src/components/progress.tsx b/packages/ui/src/components/progress.tsx index a4a68592bc..9b9e49a572 100644 --- a/packages/ui/src/components/progress.tsx +++ b/packages/ui/src/components/progress.tsx @@ -1,4 +1,4 @@ -import { forwardRef, useMemo } from 'react' +import { forwardRef, HTMLAttributes, useMemo } from 'react' import { cn } from '@/utils/cn' import { Text } from '@components/text' @@ -35,7 +35,7 @@ const getIconName = (state: VariantProps<typeof progressVariants>['state']): Ico return 'clock' } -interface CommonProgressProps extends VariantProps<typeof progressVariants> { +interface CommonProgressProps extends HTMLAttributes<HTMLDivElement>, VariantProps<typeof progressVariants> { id?: string label?: string description?: string @@ -72,7 +72,8 @@ const Progress = forwardRef<HTMLProgressElement, ProgressProps>( hidePercentage = false, value = 0, className, - hideContainer = false + hideContainer = false, + ...props }, ref ) => { @@ -125,7 +126,7 @@ const Progress = forwardRef<HTMLProgressElement, ProgressProps>( } return ( - <div className={cn(progressVariants({ size, state }), className)}> + <div className={cn(progressVariants({ size, state }), className)} {...props}> {(label || !hidePercentage || !hideIcon) && ( <label className="cn-progress-header" htmlFor={id}> <div className="cn-progress-header-left"> diff --git a/packages/ui/src/components/repo-subheader.tsx b/packages/ui/src/components/repo-subheader.tsx index 3b8cffe07d..4abe4fe990 100644 --- a/packages/ui/src/components/repo-subheader.tsx +++ b/packages/ui/src/components/repo-subheader.tsx @@ -58,7 +58,7 @@ export const RepoSubheader = ({ {t('views:repos.pullRequests', 'Pull requests')} </Tabs.Trigger> <Tabs.Trigger value={RepoTabsKeys.BRANCHES} disabled={isRepoEmpty}> - {t('views:repos.branches', 'Branches')} + {t('views:repos.branches.title', 'Branches')} </Tabs.Trigger> {showSearchTab && ( <Tabs.Trigger value={RepoTabsKeys.SEARCH} disabled={isRepoEmpty}> diff --git a/packages/ui/src/components/table.tsx b/packages/ui/src/components/table.tsx index d8f75de385..8251591d7f 100644 --- a/packages/ui/src/components/table.tsx +++ b/packages/ui/src/components/table.tsx @@ -137,15 +137,29 @@ export interface TableHeadProps extends ThHTMLAttributes<HTMLTableCellElement> { tooltipProps?: Omit<TooltipProps, 'children'> containerProps?: FlexProps hideDivider?: boolean + contentClassName?: string } const TableHead = forwardRef<HTMLTableCellElement, TableHeadProps>( - ({ className, sortDirection, sortable, children, tooltipProps, hideDivider, containerProps, ...props }, ref) => { + ( + { + className, + sortDirection, + sortable, + children, + tooltipProps, + hideDivider, + containerProps, + contentClassName, + ...props + }, + ref + ) => { const Title = () => ( <Text variant="caption-strong" color="foreground-1" - className={cn({ 'underline decoration-dashed': !!tooltipProps?.content })} + className={cn({ 'underline decoration-dashed': !!tooltipProps?.content }, contentClassName)} > {children} </Text> diff --git a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx index faf15dcef3..17b7298a8e 100644 --- a/packages/ui/src/views/pipelines/execution-list/execution-list.tsx +++ b/packages/ui/src/views/pipelines/execution-list/execution-list.tsx @@ -34,7 +34,7 @@ const Description = ({ </div> )} {sha && ( - <div className="flex h-4 items-center gap-1 rounded bg-cn-background-8 px-1.5 text-1 text-cn-foreground-1"> + <div className="flex h-4 items-center gap-1 rounded bg-cn-background-softgray px-1.5 text-1 text-cn-foreground-1"> <IconV2 className="text-icons-9" size="2xs" name="git-commit" /> {sha?.slice(0, 7)} </div> diff --git a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx index 325bd9a1dd..9465909b09 100644 --- a/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx +++ b/packages/ui/src/views/pipelines/pipeline-list/pipeline-list.tsx @@ -26,7 +26,7 @@ const Description = ({ sha, description, version }: { sha?: string; description? </div> )} {sha && ( - <div className="bg-cn-background-8 text-1 text-cn-foreground-1 flex h-4 items-center gap-1 rounded px-1.5"> + <div className="bg-cn-background-softgray text-1 text-cn-foreground-1 flex h-4 items-center gap-1 rounded px-1.5"> <IconV2 className="text-icons-9" size="2xs" name="git-commit" /> {sha?.slice(0, 7)} </div> diff --git a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx index ec422cb844..1d079231d8 100644 --- a/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx +++ b/packages/ui/src/views/profile-settings/components/profile-settings-keys-list.tsx @@ -37,7 +37,11 @@ export const ProfileKeysList: FC<ProfileKeysListProps> = ({ publicKeys, isLoadin <Table.Row key={key.identifier}> <Table.Cell className="content-center"> <div className="inline-flex items-center gap-x-2.5"> - <IconV2 name="ssh-key" size="lg" className="rounded-md bg-cn-background-8 text-cn-foreground-2" /> + <IconV2 + name="ssh-key" + size="lg" + className="rounded-md bg-cn-background-softgray text-cn-foreground-2" + /> <div className="flex flex-col"> <span className="block w-[200px] truncate font-medium text-cn-foreground-1"> {key.identifier} diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx index 3979922f70..8a9c7e3da0 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx +++ b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx @@ -68,7 +68,7 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ > <Tabs.List className="-mx-3 px-3" activeClassName="bg-cn-background-3" variant="overlined"> <Tabs.Trigger value="branches" onClick={() => setActiveTab(BranchSelectorTab.BRANCHES)}> - {t('views:repos.branches', 'Branches')} + {t('views:repos.branches.title', 'Branches')} </Tabs.Trigger> <Tabs.Trigger value="tags" onClick={() => setActiveTab(BranchSelectorTab.TAGS)}> diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/types.ts b/packages/ui/src/views/repo/components/branch-selector-v2/types.ts index 4d6ba03def..d8c54cfc36 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/types.ts +++ b/packages/ui/src/views/repo/components/branch-selector-v2/types.ts @@ -8,7 +8,7 @@ export enum BranchSelectorTab { export const getBranchSelectorLabels = (t: TFunctionWithFallback) => ({ [BranchSelectorTab.BRANCHES]: { - label: t('views:repos.branches', 'Branches'), + label: t('views:repos.branches.title', 'Branches'), searchPlaceholder: t('views:repos.findBranch', 'Find a branch') }, [BranchSelectorTab.TAGS]: { diff --git a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx index aa80868042..e15bc7e9c5 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx +++ b/packages/ui/src/views/repo/pull-request/pull-request-list-page.tsx @@ -260,12 +260,8 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ t('views:noData.changeSearch', 'or search for a different keyword.') ]} secondaryButton={{ - label: ( - <> - <IconV2 name="trash" /> - <Text>{t('views:noData.clearFilters', 'Clear filters')}</Text> - </> - ), + icon: 'trash', + label: t('views:noData.clearSearch', 'Clear filters'), onClick: () => { filtersRef.current?.reset() handleResetQuery() @@ -290,12 +286,8 @@ const PullRequestListPage: FC<PullRequestPageProps> = ({ primaryButton={ repoId ? { - label: ( - <> - <IconV2 name="plus" /> - {t('views:noData.button.createPullRequest', 'Create Pull Request')} - </> - ), + icon: 'plus', + label: t('views:noData.button.createPullRequest', 'Create Pull Request'), to: `${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/pulls/compare/` } : undefined diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 99ada0e05a..4188a19923 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -6,8 +6,8 @@ import { CopyTag, IconPropsV2, IconV2, + Layout, MoreActionsTooltip, - NoData, Separator, Skeleton, StatusBadge, @@ -27,10 +27,6 @@ export const BranchesList: FC<BranchListPageProps> = ({ isLoading, branches, defaultBranch, - isDirtyList, - setCreateBranchDialogOpen, - handleResetFiltersAndPages, - // toBranchRules, toPullRequestCompare, toPullRequest, toCode, @@ -39,97 +35,40 @@ export const BranchesList: FC<BranchListPageProps> = ({ const { t } = useTranslation() const { Link } = useRouterContext() - if (!branches?.length && !isLoading) { - return ( - <NoData - className="m-auto" - imageName={isDirtyList ? 'no-search-magnifying-glass' : 'no-data-branches'} - withBorder={isDirtyList} - title={ - isDirtyList - ? t('views:noData.noResults', 'No search results') - : t('views:noData.noBranches', 'No branches yet') - } - description={ - isDirtyList - ? [ - t( - 'views:noData.noResultsDescription', - 'No branches match your search. Try adjusting your keywords or filters.', - { - type: 'branches' - } - ) - ] - : [ - t('views:noData.createBranchDescription', "Your branches will appear here once they're created."), - t('views:noData.startBranchDescription', 'Start branching to see your work organized.') - ] - } - secondaryButton={ - isDirtyList - ? { - label: ( - <> - <IconV2 name="trash" /> - {t('views:noData.clearSearch', 'Clear Search')} - </> - ), - onClick: handleResetFiltersAndPages - } - : undefined - } - primaryButton={ - isDirtyList - ? undefined - : { - label: ( - <> - <IconV2 name="plus" /> - {t('views:repos.createBranch', 'Create Branch')} - </> - ), - onClick: () => { - setCreateBranchDialogOpen(true) - } - } - } - /> - ) - } - if (isLoading) { return <Skeleton.Table countRows={12} countColumns={6} /> } return ( - <Table.Root className={isLoading ? '[mask-image:linear-gradient(to_bottom,black_30%,transparent_100%)]' : ''}> + <Table.Root> <Table.Header> <Table.Row> - <Table.Head className="w-[25rem]"> + <Table.Head className="w-[33%]"> <Text variant="caption-strong">{t('views:repos.branch', 'Branch')}</Text> </Table.Head> - <Table.Head className="w-[11.71875rem]"> + <Table.Head className="w-[15%]"> <Text variant="caption-strong">{t('views:repos.update', 'Updated')}</Text> </Table.Head> - {branches[0]?.checks ? ( - <Table.Head className="w-[11.71875rem]"> + + {branches[0]?.checks && ( + <Table.Head className="w-[15%]"> <Text variant="caption-strong">{t('views:repos.checkStatus', 'Check status')}</Text> </Table.Head> - ) : null} - <Table.Head className="w-[11.71875rem]"> - <div className="mx-auto grid w-28 grid-flow-col grid-cols-[1fr_auto_1fr] items-center justify-center gap-x-1.5"> + )} + + <Table.Head className="w-[15%]" contentClassName="w-full"> + <Layout.Grid flow="column" columns="1fr auto 1fr" align="center" justify="center" gapX="2xs"> <Text variant="caption-strong" align="right"> {t('views:repos.behind', 'Behind')} </Text> <Separator orientation="vertical" /> <Text variant="caption-strong">{t('views:repos.ahead', 'Ahead')}</Text> - </div> + </Layout.Grid> </Table.Head> - <Table.Head className="w-[11.71875rem] whitespace-nowrap"> + <Table.Head className="w-[15%] whitespace-nowrap"> <Text variant="caption-strong">{t('views:repos.pullRequest', 'Pull Request')}</Text> </Table.Head> - <Table.Head className="w-[4.25rem]" /> + <Table.Head className="w-[7%]" /> </Table.Row> </Table.Header> @@ -139,85 +78,80 @@ export const BranchesList: FC<BranchListPageProps> = ({ return ( <Table.Row key={branch.id} className="cursor-pointer" to={toCode?.({ branchName: branch.name })}> - {/* branch name */} - <Table.Cell className="content-center"> - <div className="flex items-center"> - <CopyTag - variant="secondary" - value={branch?.name} - icon={defaultBranch === branch?.name ? 'lock' : undefined} - theme="gray" - /> - </div> + <Table.Cell> + <CopyTag + variant="secondary" + value={branch?.name} + icon={defaultBranch === branch?.name ? 'lock' : undefined} + theme="gray" + /> </Table.Cell> - {/* user avatar and timestamp */} - <Table.Cell className="content-center" disableLink> - <div className="flex items-center gap-2"> + + <Table.Cell disableLink> + <Layout.Flex align="center" gapX="xs"> <Avatar name={branch?.user?.name} src={branch?.user?.avatarUrl} size="sm" rounded /> <TimeAgoCard timestamp={branch?.timestamp} dateTimeFormatOptions={{ dateStyle: 'medium' }} textProps={{ color: 'foreground-1', truncate: true }} /> - </div> + </Layout.Flex> </Table.Cell> {/* checkstatus: show in the playground, hide the check status column if the checks are null in the gitness without data */} - {branch?.checks && ( - <Table.Cell className="content-center"> - <div className="flex items-center"> - {checkState === 'running' ? ( - <span className="mr-1.5 size-2 rounded-full bg-icons-alert" /> - ) : ( - <IconV2 - className={cn('mr-1.5', { - 'text-icons-success': checkState === 'success', - 'text-icons-danger': checkState === 'failure' - })} - name={ - cn({ - check: checkState === 'success', - xmark: checkState === 'failure' - }) as NonNullable<IconPropsV2['name']> - } - size="2xs" - /> - )} - <span className="truncate text-cn-foreground-3">{branch?.checks?.done}</span> - <span className="mx-px">/</span> - <span className="truncate text-cn-foreground-3">{branch?.checks?.total}</span> - </div> + {checkState && ( + <Table.Cell> + <Layout.Flex align="center" gapX="xs"> + <IconV2 + className={cn('shrink-0', { + 'text-cn-foreground-success': checkState === 'success', + 'text-cn-foreground-danger': checkState === 'failure', + 'text-cn-foreground-warning': checkState === 'running' + })} + name={ + cn({ + check: checkState === 'success', + xmark: checkState === 'failure', + circle: checkState === 'running' + }) as NonNullable<IconPropsV2['name']> + } + size="2xs" + /> + + <Text variant="body-single-line-strong" className="text-cn-foreground-surface-gray"> + <span>{branch?.checks?.done || 0}</span> + <span>/</span> + <span>{branch?.checks?.total || 0}</span> + </Text> + </Layout.Flex> </Table.Cell> )} {/* calculated divergence bar & default branch */} - <Table.Cell className="content-center"> - <div className="flex size-full items-center justify-start"> - {branch?.behindAhead?.default ? ( - <Tag variant="outline" size="md" value={t('views:repos.default', 'Default')} theme="gray" rounded /> - ) : ( - <DivergenceGauge behindAhead={branch?.behindAhead || {}} /> - )} - </div> + <Table.Cell> + {branch?.behindAhead?.default ? ( + <Tag className="m-auto" value={t('views:repos.default', 'Default')} rounded /> + ) : ( + <DivergenceGauge className="m-auto" behindAhead={branch?.behindAhead || {}} /> + )} </Table.Cell> - {/* PR link */} - <Table.Cell className="max-w-20 content-center" disableLink> + <Table.Cell disableLink> {branch.pullRequests && branch.pullRequests.length > 0 && branch.pullRequests[0].number && ( - <StatusBadge - variant="outline" - size="md" - theme={ - getPrState( - branch.pullRequests[0].is_draft, - branch.pullRequests[0].merged, - branch.pullRequests[0].state - ).theme - } - // className="w-[66px]" + <Link + to={toPullRequest?.({ pullRequestId: branch.pullRequests[0].number }) || ''} + onClick={e => e.stopPropagation()} + className="focus-visible:shadow-ring-focus rounded-2 inline-flex" > - <Link - to={toPullRequest?.({ pullRequestId: branch.pullRequests[0].number }) || ''} - onClick={e => e.stopPropagation()} - className="flex w-full gap-1" + {/* TODO: Merged state is not shown in the branch list, because the PR gets removed from 'branch.pullRequests' */} + <StatusBadge + variant="outline" + size="md" + theme={ + getPrState( + branch.pullRequests[0].is_draft, + branch.pullRequests[0].merged, + branch.pullRequests[0].state + ).theme + } > <IconV2 name={ @@ -230,8 +164,8 @@ export const BranchesList: FC<BranchListPageProps> = ({ size="xs" /> #{branch.pullRequests[0].number} - </Link> - </StatusBadge> + </StatusBadge> + </Link> )} </Table.Cell> <Table.Cell className="text-right" disableLink> @@ -248,10 +182,6 @@ export const BranchesList: FC<BranchListPageProps> = ({ } as ActionData ] : []), - // { - // title: t('views:repos.viewRules', 'View Rules'), - // to: toBranchRules?.() - // }, { title: t('views:repos.browse', 'Browse'), to: toCode?.({ branchName: branch.name }) || '', diff --git a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx index 03ed49d89a..e592db7132 100644 --- a/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/create-branch-dialog.tsx @@ -115,7 +115,7 @@ export function CreateBranchDialog({ <FormWrapper id="create-branch-form" {...formMethods} onSubmit={handleSubmit(handleFormSubmit)}> <FormInput.Text id="name" - label="Branch name" + label="Name" {...register('name')} maxLength={250} placeholder={t('views:forms.enterBranchName', 'Enter branch name')} diff --git a/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx b/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx index cb07730ea9..7838449da2 100644 --- a/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/divergence-gauge.tsx @@ -1,4 +1,6 @@ -import { Progress, Separator } from '@/components' +import { CSSProperties } from 'react' + +import { Layout, Progress, Separator, Text } from '@/components' import { useTranslation } from '@/context' import { cn } from '@/utils/cn' @@ -25,54 +27,42 @@ export const DivergenceGauge = ({ behindAhead, className }: GaugeProps) => { const adjustedAheadPercentage = adjustPercentage(aheadPercentage) return ( - <div className={cn('flex w-full flex-col gap-[4px]', className)}> - <div className="grid w-28 grid-flow-col grid-cols-[1fr_auto_1fr] items-center justify-start gap-x-1.5"> - <span className="truncate text-right text-2 leading-none text-cn-foreground-3"> + <Layout.Grid className={cn('', className)} gapY="3xs"> + <Layout.Grid columns="1fr auto 1fr" align="center" justify="center" gapX="2xs"> + <Text variant="body-single-line-normal" align="right"> {behindAhead.behind ?? 0} - <span className="sr-only"> - {t('views:repos.commits', 'commits')} - {t('views:repos.behind', 'behind')} - </span> - </span> + <span className="sr-only">{t('views:repos.branches.commitsBehind', 'commits behind')}</span> + </Text> <Separator orientation="vertical" /> - <span className="truncate text-2 leading-none text-cn-foreground-3"> + <Text variant="body-single-line-normal"> {behindAhead.ahead ?? 0} - <span className="sr-only"> - {t('views:repos.commits', 'commits')} - {t('views:repos.ahead', 'ahead')} - </span> - </span> - </div> + <span className="sr-only">{t('views:repos.branches.commitsAhead', 'commits ahead')}</span> + </Text> + </Layout.Grid> {/* Both behind and ahead are 0, don't show the progress bar */} {/* TODO: replace with meter component when available */} - {behindAhead?.behind === 0 && behindAhead?.ahead === 0 ? null : ( - <div className="flex w-28 items-center justify-start"> - <div className="flex w-1/2 flex-row-reverse justify-start"> - <div style={{ width: `${adjustedBehindPercentage}%` }}> - <Progress - className={`rotate-180 [&_.cn-progress-root::-moz-progress-bar]:bg-cn-background-8 [&_.cn-progress-root::-webkit-progress-value]:bg-cn-background-8 [&_.cn-progress-root]:rounded-l-none`} - value={adjustedBehindPercentage / 100} - size="sm" - hideIcon - hidePercentage - hideContainer - /> - </div> - </div> - <div className="flex w-1/2 justify-start"> - <div style={{ width: `${adjustedAheadPercentage}%` }}> - <Progress - className={`[&_.cn-progress-root::-moz-progress-bar]:bg-cn-background-13 [&_.cn-progress-root::-webkit-progress-value]:bg-cn-background-13 [&_.cn-progress-root]:rounded-l-none`} - value={adjustedAheadPercentage / 100} - size="sm" - hideIcon - hidePercentage - hideContainer - /> - </div> - </div> - </div> + {behindAhead?.behind === 0 && behindAhead?.ahead == 0 ? null : ( + <Layout.Grid columns={2} align="center" justify="center" className="mx-auto w-20"> + <Progress + className="[&_.cn-progress-root::-moz-progress-bar]:bg-cn-background-softgray [&_.cn-progress-root::-webkit-progress-value]:bg-cn-background-softgray rotate-180 justify-self-end [&_.cn-progress-root]:rounded-l-none" + value={adjustedBehindPercentage / 100} + size="sm" + hideIcon + hidePercentage + hideContainer + style={{ width: `${adjustedBehindPercentage}%` } as CSSProperties} + /> + <Progress + className="[&_.cn-progress-root::-moz-progress-bar]:bg-cn-background-solidgray [&_.cn-progress-root::-webkit-progress-value]:bg-cn-background-solidgray [&_.cn-progress-root]:rounded-l-none" + value={adjustedAheadPercentage / 100} + size="sm" + hideIcon + hidePercentage + hideContainer + style={{ width: `${adjustedAheadPercentage}%` } as CSSProperties} + /> + </Layout.Grid> )} - </div> + </Layout.Grid> ) } diff --git a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx index 70f7ae11cb..abe4f9d486 100644 --- a/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx +++ b/packages/ui/src/views/repo/repo-branch/repo-branch-list-view.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useMemo } from 'react' -import { Button, IconV2, ListActions, Pagination, SearchInput, Spacer, Text } from '@/components' +import { Button, IconV2, Layout, ListActions, NoData, Pagination, SearchInput, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' import { cn } from '@utils/cn' @@ -37,6 +37,9 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ return page !== 1 || !!searchQuery }, [page, searchQuery]) + const noBranches = !branchList?.length && !isLoading && !isDirtyList + const noSearchResults = !branchList?.length && !isLoading && isDirtyList + const getPrevPageLink = useCallback(() => { return `?page=${xPrevPage}` }, [xPrevPage]) @@ -49,15 +52,38 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ return !isLoading && !!branchList.length }, [isLoading, branchList.length]) + const openCreateBranchDialog = useCallback(() => { + setCreateBranchDialogOpen(true) + }, [setCreateBranchDialogOpen]) + + if (noBranches) { + return ( + <NoData + className="m-auto" + imageName="no-data-branches" + title={t('views:noData.noBranches', 'No branches yet')} + description={[ + t('views:noData.createBranchDescription', "Your branches will appear here once they're created."), + t('views:noData.startBranchDescription', 'Start branching to see your work organized.') + ]} + primaryButton={{ + icon: 'plus', + label: t('views:repos.branches.createBranch', 'Create Branch'), + onClick: openCreateBranchDialog + }} + /> + ) + } + return ( <SandboxLayout.Main> <SandboxLayout.Content className={cn({ 'h-full': !isLoading && !branchList.length && !searchQuery })}> - {(isLoading || !!branchList.length || isDirtyList) && ( - <> - <Text as="h1" variant="heading-section"> - {t('views:repos.branches', 'Branches')} - </Text> - <Spacer size={6} /> + <Layout.Flex direction="column" gapY="xl" className="grow"> + <Text as="h2" variant="heading-section"> + {t('views:repos.branches.title', 'Branches')} + </Text> + + <Layout.Grid gapY="md"> <ListActions.Root> <ListActions.Left> <SearchInput @@ -69,42 +95,58 @@ export const RepoBranchListView: FC<RepoBranchListViewProps> = ({ /> </ListActions.Left> <ListActions.Right> - <Button - onClick={() => { - setCreateBranchDialogOpen(true) - }} - size="md" - variant="primary" - theme="default" - > + <Button onClick={openCreateBranchDialog} size="md" variant="primary" theme="default"> <IconV2 name="plus" /> - {t('views:repos.createBranch', 'Create Branch')} + {t('views:repos.branches.createBranch', 'Create Branch')} </Button> </ListActions.Right> </ListActions.Root> - <Spacer size={4} /> - </> - )} - <BranchesList - isLoading={isLoading} - defaultBranch={defaultBranch} - branches={branchList} - setCreateBranchDialogOpen={setCreateBranchDialogOpen} - handleResetFiltersAndPages={handleResetFiltersAndPages} - onDeleteBranch={onDeleteBranch} - isDirtyList={isDirtyList} - {...routingProps} - /> - {canShowPagination && ( - <Pagination - indeterminate - hasNext={xNextPage > 0} - hasPrevious={xPrevPage > 0} - getPrevPageLink={getPrevPageLink} - getNextPageLink={getNextPageLink} - /> - )} + {!noSearchResults && ( + <BranchesList + isLoading={isLoading} + defaultBranch={defaultBranch} + branches={branchList} + setCreateBranchDialogOpen={setCreateBranchDialogOpen} + handleResetFiltersAndPages={handleResetFiltersAndPages} + onDeleteBranch={onDeleteBranch} + isDirtyList={isDirtyList} + {...routingProps} + /> + )} + </Layout.Grid> + + {canShowPagination && ( + <Pagination + indeterminate + hasNext={xNextPage > 0} + hasPrevious={xPrevPage > 0} + getPrevPageLink={getPrevPageLink} + getNextPageLink={getNextPageLink} + /> + )} + + {noSearchResults && ( + <NoData + className="grow" + imageName="no-search-magnifying-glass" + withBorder + title={t('views:noData.noResults', 'No search results')} + description={[ + t( + 'views:noData.noResultsDescription', + 'No branches match your search. Try adjusting your keywords or filters.', + { type: 'branches' } + ) + ]} + secondaryButton={{ + icon: 'trash', + label: t('views:noData.clearSearch', 'Clear filters'), + onClick: handleResetFiltersAndPages + }} + /> + )} + </Layout.Flex> </SandboxLayout.Content> </SandboxLayout.Main> ) diff --git a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx index 049f052439..071e46fa34 100644 --- a/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx +++ b/packages/ui/src/views/repo/repo-commits/repo-commits-view.tsx @@ -1,6 +1,6 @@ import { FC, useCallback } from 'react' -import { IconV2, Layout, NoData, Pagination, Skeleton, Text } from '@/components' +import { Layout, NoData, Pagination, Skeleton, Text } from '@/components' import { useTranslation } from '@/context' import { CommitsList, SandboxLayout, TypesCommit } from '@/views' @@ -91,12 +91,8 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ secondaryButton={ isDirtyList ? { - label: ( - <> - <IconV2 name="trash" /> - {t('views:noData.clearFilters', 'Clear Filters')} - </> - ), + icon: 'trash', + label: t('views:noData.clearFilters', 'Clear filters'), onClick: handleResetFiltersAndPages } : // TODO: add onClick for Creating new commit @@ -105,7 +101,8 @@ export const RepoCommitsView: FC<RepoCommitsViewProps> = ({ /** * To make the first commit, redirect to the files page so a new file can be created. */ - to: toFiles?.() || '' + to: toFiles?.() || '', + icon: 'plus' } } /> diff --git a/packages/ui/src/views/repo/repo-files/file-review-error.tsx b/packages/ui/src/views/repo/repo-files/file-review-error.tsx index 377f394a26..6173a32482 100644 --- a/packages/ui/src/views/repo/repo-files/file-review-error.tsx +++ b/packages/ui/src/views/repo/repo-files/file-review-error.tsx @@ -1,5 +1,4 @@ import { useTranslation } from '@/context' -import { IconV2 } from '@components/icon-v2' import { NoData, NoDataProps } from '@components/no-data' export const FileReviewError = ({ @@ -16,13 +15,9 @@ export const FileReviewError = ({ description={['Please try again or check your connection']} imageName="no-data-error" secondaryButton={{ - label: ( - <> - <IconV2 name="refresh" /> - {t('views:repos.fileContent.noData.tryAgain', 'Try again')} - </> - ), - props: { onClick: onButtonClick } + icon: 'refresh', + label: t('views:repos.fileContent.noData.tryAgain', 'Try again'), + onClick: onButtonClick }} /> ) diff --git a/packages/ui/src/views/repo/repo-list/repo-list.tsx b/packages/ui/src/views/repo/repo-list/repo-list.tsx index 20dcd49cf2..0eb89df1a1 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list.tsx @@ -105,12 +105,8 @@ export function RepoList({ t('views:noData.changeSearch', 'or search for a different keyword.') ]} secondaryButton={{ - label: ( - <> - <IconV2 name="trash" /> - {t('views:noData.clearFilters', 'Clear filters')} - </> - ), + icon: 'trash', + label: t('views:noData.clearFilters', 'Clear filters'), onClick: handleResetFiltersQueryAndPages }} /> @@ -123,23 +119,15 @@ export function RepoList({ t('views:noData.createOrImportRepos', 'Create new or import an existing repository.') ]} primaryButton={{ - label: ( - <> - <IconV2 name="plus" /> - {t('views:repos.createRepository', 'Create Repository')} - </> - ), + icon: 'plus', + label: t('views:repos.createRepository', 'Create Repository'), to: toCreateRepo?.() }} secondaryButton={{ - label: ( - <> - <IconV2 name="import" /> - {t('views:repos.importRepository', 'Import Repository')} - </> - ), + icon: 'import', + label: t('views:repos.importRepository', 'Import Repository'), to: toImportRepo?.(), - props: { variant: 'outline' } + variant: 'outline' }} /> ) diff --git a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx index 64e1fbcc9a..f01f7deac9 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-empty-view.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, IconV2, Layout, MarkdownViewer, NoData, Text } from '@/components' +import { Alert, Button, Layout, MarkdownViewer, NoData, Text } from '@/components' import { useTranslation } from '@/context' import { SandboxLayout } from '@/views' @@ -83,12 +83,8 @@ ${sshUrl} t('views:repos.emptyRepoPage.noData.description.1', 'README, LICENSE, and .gitignrore') ]} primaryButton={{ - label: ( - <> - <IconV2 name="plus" /> - {t('views:repos.emptyRepoPage.noData.createFile', 'Create File')} - </> - ), + icon: 'plus', + label: t('views:repos.emptyRepoPage.noData.createFile', 'Create File'), to: `${projName ? `/${projName}` : ''}/repos/${repoName}/files/new/${gitRef}/~/` }} className="py-cn-3xl" diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index b9172b7d4f..64eccdf104 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -277,7 +277,7 @@ export function RepoSummaryView({ }, { id: '1', - name: t('views:repos.branches', 'Branches'), + name: t('views:repos.branches.title', 'Branches'), count: branch_count, iconName: 'git-branch', to: props.toRepoBranches?.() ?? '#' diff --git a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx index b6cca9fac0..868649d625 100644 --- a/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx +++ b/packages/ui/src/views/repo/repo-tags/components/repo-tags-list.tsx @@ -44,7 +44,7 @@ export const RepoTagsList: FC<RepoTagsListProps> = ({ const getTableActions = (tag: CommitTagType): ActionData[] => [ { iconName: 'git-branch', - title: t('views:repos.createBranch', 'Create Branch'), + title: t('views:repos.tags.createBranch', 'Create Branch'), onClick: () => onOpenCreateBranchDialog(tag) }, { diff --git a/packages/ui/tailwind-design-system.ts b/packages/ui/tailwind-design-system.ts index 8f00a193b8..9d12a07ca5 100644 --- a/packages/ui/tailwind-design-system.ts +++ b/packages/ui/tailwind-design-system.ts @@ -77,19 +77,15 @@ export default { backdrop: 'var(--cn-comp-dialog-backdrop)', 'diff-success': 'var(--cn-comp-diff-add-content)', 'diff-danger': 'var(--cn-comp-diff-del-content)', - - // Remove solidred: 'lch(from var(--cn-set-red-solid-bg) l c h / <alpha-value>)', softgray: 'lch(from var(--cn-set-gray-soft-bg) l c h / <alpha-value>)', hover: 'var(--cn-state-hover)', selected: 'var(--cn-state-selected)', - primary: 'lch(from var(--cn-set-brand-solid-bg) l c h / <alpha-value>)', - 8: 'lch(from var(--cn-set-gray-soft-bg) l c h / <alpha-value>)', - 9: 'lch(from var(--cn-bg-3) l c h / <alpha-value>)', // avatar - remove once avatar component is completed - 11: 'lch(from var(--cn-set-gray-soft-bg) l c h / <alpha-value>)', - 12: 'lch(from var(--cn-set-gray-surface-bg-hover) l c h / <alpha-value>)', - 13: 'lch(from var(--cn-set-gray-solid-bg) l c h / <alpha-value>)' + surface: { + 'gray-hover': 'lch(from var(--cn-set-gray-surface-bg-hover) l c h / <alpha-value>)' + }, + solidgray: 'lch(from var(--cn-set-gray-solid-bg) l c h / <alpha-value>)' }, 'cn-borders': { 1: 'lch(from var(--cn-border-1) l c h / <alpha-value>)', From 2a1747ffc2a083eaa3a32e137b2742da0b10dade Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Mon, 18 Aug 2025 18:20:08 +0000 Subject: [PATCH 126/180] Chained commits selection using shift key (#10221) * de4594 improve layout; add datetime * cade3e chained commits selection --- .../changes/chained-commits-dropdown.tsx | 91 +++++++++++++++++++ .../changes/pull-request-changes-filter.tsx | 54 ++--------- 2 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 packages/ui/src/views/repo/pull-request/details/components/changes/chained-commits-dropdown.tsx diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/chained-commits-dropdown.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/chained-commits-dropdown.tsx new file mode 100644 index 0000000000..b0fc532ae8 --- /dev/null +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/chained-commits-dropdown.tsx @@ -0,0 +1,91 @@ +import { useCallback, useRef } from 'react' + +import { DropdownMenu, Text, TimeAgoCard } from '@components/index' + +import { CommitFilterItemProps } from './pull-request-changes-filter' + +export interface ChainedCommitsDropdownProps { + commitFilterOptions: CommitFilterItemProps[] + selectedCommits: CommitFilterItemProps[] + setSelectedCommits: React.Dispatch<React.SetStateAction<CommitFilterItemProps[]>> + defaultCommitFilter: CommitFilterItemProps +} + +export function ChainedCommitsDropdown(props: ChainedCommitsDropdownProps) { + const { commitFilterOptions, defaultCommitFilter, selectedCommits, setSelectedCommits } = props + + const shiftKeyDownRef = useRef(false) + + const handleCommitCheck = useCallback( + (item: CommitFilterItemProps, checked: boolean, selectRange: boolean): void => { + // If user clicked on 'All Commits', reset selection to just the default commit filter + if (item.value === defaultCommitFilter.value) { + setSelectedCommits([defaultCommitFilter]) + return + } + + // Select All commits when unselect commit + if (selectedCommits.length === 1 && selectedCommits[0].value === item.value) { + setSelectedCommits([defaultCommitFilter]) + return + } + + // Remove "All Commits" + const newSelection = selectedCommits.filter(sel => sel.value !== defaultCommitFilter.value) + + // Select single commit + if (!selectRange || newSelection.length === 0) { + newSelection.push(item) + setSelectedCommits([item]) + return + } + + // Select range + const formIdx = commitFilterOptions.findIndex(currItem => currItem.value === newSelection[0].value) + const toIdx = commitFilterOptions.findIndex(currItem => currItem.value === item.value) + const range = toIdx > formIdx ? { start: formIdx, end: toIdx } : { start: toIdx, end: formIdx } + const newRangeSelection = commitFilterOptions.slice(range.start, range.end + 1) + setSelectedCommits(newRangeSelection) + }, + [commitFilterOptions, defaultCommitFilter, selectedCommits, setSelectedCommits] + ) + + return ( + <DropdownMenu.Content className="max-auto" align="start"> + {commitFilterOptions.map((item, idx) => { + const isSelected = selectedCommits.some(sel => sel.value === item.value) + + return ( + <DropdownMenu.CheckboxItem + title={ + <div className="flex gap-4 overflow-hidden"> + <Text truncate className="w-72"> + {item.name} + </Text> + {item.datetime && ( + <TimeAgoCard + timestamp={item.datetime} + textProps={{ className: 'whitespace-nowrap' }} + cutoffDays={365} + dateTimeFormatOptions={{ dateStyle: 'medium' }} + /> + )} + </div> + } + checked={isSelected} + key={idx} + onMouseUp={e => { + shiftKeyDownRef.current = e.shiftKey + }} + onCheckedChange={checked => { + handleCommitCheck(item, checked, shiftKeyDownRef.current) + }} + /> + ) + })} + <DropdownMenu.Footer> + <Text>Shift-click to select a range</Text> + </DropdownMenu.Footer> + </DropdownMenu.Content> + ) +} diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 01e1fee715..e700781f3a 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -21,12 +21,14 @@ import { getApprovalStateVariant, processReviewDecision } from '../../pull-request-utils' +import { ChainedCommitsDropdown } from './chained-commits-dropdown' import * as FileViewGauge from './file-viewed-gauge' export interface CommitFilterItemProps { name: string count: number value: string + datetime?: string } export interface PullRequestChangesFilterProps { @@ -96,7 +98,8 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = commitsList.push({ name: commitInfo.message || '', count: 0, - value: commitInfo.sha || '' + value: commitInfo.sha || '', + datetime: commitInfo.committer?.when || '' }) }) setCommitFilterOptions(commitsList) @@ -139,46 +142,6 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = } } - /** Click handler to manage multi-selection of commits */ - const handleCommitCheck = (item: CommitFilterItemProps, checked: boolean): void => { - // If user clicked on 'All Commits', reset selection to just the default commit filter - if (item.value === defaultCommitFilter.value) { - setSelectedCommits([defaultCommitFilter]) - return - } - - setSelectedCommits((prev: CommitFilterItemProps[]) => { - // Remove the 'All' option if it exists in the selection - const withoutDefault = prev.filter(sel => sel.value !== defaultCommitFilter.value) - - if (checked) { - // Add the item to selection - return [...withoutDefault, item] - } else { - // Remove the item from selection, but ensure at least one item remains selected - const filtered = withoutDefault.filter(sel => sel.value !== item.value) - return filtered.length > 0 ? filtered : withoutDefault - } - }) - } - - function renderCommitDropdownItems(items: CommitFilterItemProps[]): JSX.Element[] { - return items.map((item, idx) => { - const isSelected = selectedCommits.some(sel => sel.value === item.value) - - return ( - <DropdownMenu.CheckboxItem - title={item.name} - checked={isSelected} - key={idx} - onCheckedChange={checked => handleCommitCheck(item, checked)} - className="flex cursor-pointer items-center" - /> - ) - }) - } - - const commitDropdownItems = renderCommitDropdownItems(commitFilterOptions) const itemsToRender = getApprovalItems(approveState, approvalItems) // const handleDiffModeChange = (value: string) => { // setDiffMode(value === 'Split' ? DiffModeEnum.Split : DiffModeEnum.Unified) @@ -216,9 +179,12 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = <IconV2 name="nav-solid-arrow-down" size="2xs" /> </Button> </DropdownMenu.Trigger> - <DropdownMenu.Content className="w-96" align="start"> - {commitDropdownItems} - </DropdownMenu.Content> + <ChainedCommitsDropdown + commitFilterOptions={commitFilterOptions} + defaultCommitFilter={defaultCommitFilter} + selectedCommits={selectedCommits} + setSelectedCommits={setSelectedCommits} + /> </DropdownMenu.Root> {/* <DropdownMenu.Root> From 07070c1a89c02dc0ea3a62c21a472fe068efed39 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Mon, 18 Aug 2025 19:40:42 +0000 Subject: [PATCH 127/180] This PR updates the delete repository warning message to utilize the repository name instead of the git reference (gitref). (#10233) * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into fix-delete-repo-message-from-gitref-to-just-name * change delete repo warning message to use repo name rather than gitref --- .../src/pages-v2/repo/repo-settings-general-container.tsx | 1 + .../ui/src/components/dialogs/delete-alert-dialog.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx b/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx index 62db29e3b1..c43eec3ca3 100644 --- a/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-settings-general-container.tsx @@ -329,6 +329,7 @@ export const RepoSettingsGeneralPageContainer = () => { isLoading={isDeletingRepo} error={apiError?.type === ErrorTypes.DELETE_REPO ? apiError : null} type="repository" + deletionItemName={repoDataStore?.name} deletionKeyword={repoDataStore?.name} /> </> diff --git a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx index a64cacec46..a9e1b0dae3 100644 --- a/packages/ui/src/components/dialogs/delete-alert-dialog.tsx +++ b/packages/ui/src/components/dialogs/delete-alert-dialog.tsx @@ -14,6 +14,7 @@ export interface DeleteAlertDialogProps { error?: { type?: string; message: string } | null withForm?: boolean message?: string + deletionItemName?: string deletionKeyword?: string violation?: boolean bypassable?: boolean @@ -29,6 +30,7 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ error, withForm = false, message, + deletionItemName, deletionKeyword = 'DELETE', violation = false, bypassable = false @@ -80,9 +82,9 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ return ( <> {parts[0]} - {identifier && ( + {(deletionItemName ?? identifier) && ( <Text as="span" variant="body-strong"> - {identifier} + {deletionItemName ?? identifier} </Text> )} {parts[1]} @@ -94,7 +96,7 @@ export const DeleteAlertDialog: FC<DeleteAlertDialogProps> = ({ 'component:deleteDialog.description', `This will permanently remove all data. This action cannot be undone.` ) - }, [type, t, message, identifier]) + }, [type, t, message, identifier, deletionItemName]) return ( <AlertDialog.Root From c060c4cca8468aa2f358c7570b4ea78ce52c0e45 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Mon, 18 Aug 2025 22:52:35 +0000 Subject: [PATCH 128/180] fix: commit-dialog docs link (#10234) * 15359d fix: commit-dialog docs link --- .../src/components/git-commit-dialog/git-commit-dialog.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx index 001e949b83..21e025064c 100644 --- a/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx +++ b/packages/ui/src/components/git-commit-dialog/git-commit-dialog.tsx @@ -173,7 +173,7 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ <> {t('component:commitDialog.form.radioGroup.directly.labelFirst', 'Commit directly to')} <Tag - className="-mt-0.5 mx-1.5 align-sub" + className="mx-1.5 -mt-0.5 align-sub" variant="secondary" theme="gray" value={currentBranch} @@ -191,8 +191,7 @@ export const GitCommitDialog: FC<GitCommitDialogProps> = ({ 'Create a new branch for this commit and start a pull request' )} caption={ - // TODO: Add correct path - <Link to="/"> + <Link to="https://developer.harness.io/docs/category/pull-requests" target="_blank"> {t('component:commitDialog.form.radioGroup.new.caption', 'Learn more about pull requests')} </Link> } From fb1ac3524f4afdae57f34fc2776c351836b9d429 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Tue, 19 Aug 2025 08:02:09 +0000 Subject: [PATCH 129/180] fix filters serialisation (#10232) * 693702 fix filters serialization --- packages/filters/src/Filters.tsx | 4 ++-- packages/ui/src/views/repo/repo-list/repo-list-page.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/filters/src/Filters.tsx b/packages/filters/src/Filters.tsx index ea8fafcf20..1b6739a5ec 100644 --- a/packages/filters/src/Filters.tsx +++ b/packages/filters/src/Filters.tsx @@ -162,7 +162,7 @@ const Filters = forwardRef(function Filters<T extends Record<string, unknown>>( } config[key] = { - defaultValue: serializedDefaultValue, + defaultValue: defaultValue, parser: initialFiltersConfig[key].parser, isSticky: isStickyFilter } @@ -302,7 +302,7 @@ const Filters = forwardRef(function Filters<T extends Record<string, unknown>>( Object.keys(updatedFiltersMap).forEach(key => { const isSticky = filtersConfig[key as FilterKeys]?.isSticky const defaultValue = filtersConfig[key as FilterKeys]?.defaultValue - const serializedDefaultValue = defaultValue + const serializedDefaultValue = filtersConfig[key as FilterKeys]?.parser?.serialize(defaultValue) ?? defaultValue const resetCurrentFilter = resetAllFilters || filters?.includes(key) let filterState = isSticky ? FilterStatus.VISIBLE : FilterStatus.HIDDEN diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index b47e5c0ef4..c5a1b6a2e4 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -111,6 +111,7 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ const { options: scopeFilterOptions, defaultValue: scopeFilterDefaultValue } = getFilterScopeOptions({ t, scope }) const filterOptions: FilterOptionConfig<keyof RepoListFilters>[] = [ { + defaultValue: false, label: t('views:connectors.filterOptions.statusOption.favorite', 'Favorites'), value: 'favorite', type: FilterFieldTypes.Checkbox, From 9205c5f4a77722f83544b6ad7aa89060636a9d70 Mon Sep 17 00:00:00 2001 From: Harish Viswanathan <harish.viswanathan@harness.io> Date: Tue, 19 Aug 2025 08:25:47 +0000 Subject: [PATCH 130/180] fix: rely on repo details for initial pr listing count (#10231) * 875d12 fix: rely on repo details for initial pr listing count --- .../pull-request/pull-request-list.tsx | 22 +++++++++++++------ .../use-get-repo-label-and-values-data.ts | 6 ++++- apps/gitness/src/utils/scope-url-utils.ts | 16 +++++++++++++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx index 7575dcfac3..422224f4df 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx @@ -44,6 +44,8 @@ export default function PullRequestListPage() { const labelBy = searchParams.get('label_by') const { scope } = useMFEContext() const { accountId = '', orgIdentifier, projectIdentifier } = scope || {} + const filtersCnt = Object.keys(filterValues).length + usePopulateLabelStore({ queryPage, query: labelsQuery, enabled: populateLabelStore, inherited: true }) const { data: { body: pullRequestData, headers } = {}, isFetching: fetchingPullReqData } = useListPullReqQuery( @@ -66,7 +68,7 @@ export default function PullRequestListPage() { ) // Make separate API calls to get open and closed PR counts for the filtered author - const { data: { headers: openHeaders } = {}, isLoading: isLoadingOpen } = useListPullReqQuery( + const { data: { headers: openHeaders } = {} } = useListPullReqQuery( { queryParams: { page: 1, @@ -82,11 +84,11 @@ export default function PullRequestListPage() { } }, { - retry: false + enabled: filtersCnt > 0 } ) - const { data: { headers: closedHeaders } = {}, isLoading: isLoadingClosed } = useListPullReqQuery( + const { data: { headers: closedHeaders } = {} } = useListPullReqQuery( { queryParams: { page: 1, @@ -102,11 +104,11 @@ export default function PullRequestListPage() { } }, { - retry: false + enabled: filtersCnt > 0 } ) - const { data: { headers: mergedHeaders } = {}, isLoading: isLoadingMerged } = useListPullReqQuery( + const { data: { headers: mergedHeaders } = {} } = useListPullReqQuery( { queryParams: { page: 1, @@ -122,7 +124,7 @@ export default function PullRequestListPage() { } }, { - retry: false + enabled: filtersCnt > 0 } ) @@ -230,6 +232,12 @@ export default function PullRequestListPage() { } }, [openHeaders, closedHeaders, mergedHeaders, setOpenClosePullRequests, setPrState]) + useEffect(() => { + if (filtersCnt !== 0) return + const { num_open_pulls = 0, num_closed_pulls = 0, num_merged_pulls = 0 } = repoData || {} + setOpenClosePullRequests(num_open_pulls, num_closed_pulls, num_merged_pulls) + }, [repoData, setOpenClosePullRequests, filtersCnt]) + useEffect(() => { setQueryPage(page) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -245,7 +253,7 @@ export default function PullRequestListPage() { <SandboxPullRequestListPage repoId={repoId} spaceId={spaceId || ''} - isLoading={fetchingPullReqData || isLoadingOpen || isLoadingClosed || isLoadingMerged} + isLoading={fetchingPullReqData} isPrincipalsLoading={fetchingPrincipalData} prCandidateBranches={prCandidateBranches} principalsSearchQuery={principalsSearchQuery} diff --git a/apps/gitness/src/pages-v2/repo/labels/hooks/use-get-repo-label-and-values-data.ts b/apps/gitness/src/pages-v2/repo/labels/hooks/use-get-repo-label-and-values-data.ts index a3e4219e0d..5c6d41b6fd 100644 --- a/apps/gitness/src/pages-v2/repo/labels/hooks/use-get-repo-label-and-values-data.ts +++ b/apps/gitness/src/pages-v2/repo/labels/hooks/use-get-repo-label-and-values-data.ts @@ -5,6 +5,8 @@ import { ILabelType, LabelValuesType, LabelValueType } from '@harnessio/ui/views import { useGetRepoRef } from '../../../../framework/hooks/useGetRepoPath' import { useGetSpaceURLParam } from '../../../../framework/hooks/useGetSpaceParam' +import { useIsMFE } from '../../../../framework/hooks/useIsMFE' +import { getSpaceRefByScope } from '../../../../utils/scope-url-utils' // eslint-disable-next-line @typescript-eslint/no-explicit-any type LabelValuesResponseResultType = { key: string; data: LabelValueType[] } | { key: string; error: any } @@ -30,6 +32,7 @@ export const useGetRepoLabelAndValuesData = ({ }: UseGetRepoLabelAndValuesDataProps) => { const space_ref = useGetSpaceURLParam() const repo_ref = useGetRepoRef() + const isMFE = useIsMFE() const [isLoadingValues, setIsLoadingValues] = useState(false) const [values, setValues] = useState<LabelValuesType>({}) @@ -68,6 +71,7 @@ export const useGetRepoLabelAndValuesData = ({ setIsLoadingValues(true) const promises = labelsData.reduce<Promise<LabelValuesResponseResultType>[]>((acc, item) => { + const space_ref_by_scope = !isMFE ? space_ref : getSpaceRefByScope(space_ref, item.scope) if (item.value_count !== 0) { acc.push( item.scope === 0 @@ -75,7 +79,7 @@ export const useGetRepoLabelAndValuesData = ({ data => ({ key: item.key, data: data.body as LabelValueType[] }), error => ({ key: item.key, error }) ) - : listSpaceLabelValues({ space_ref: `${space_ref}/+`, key: item.key, signal }).then( + : listSpaceLabelValues({ space_ref: `${space_ref_by_scope}/+`, key: item.key, signal }).then( data => ({ key: item.key, data: data.body as LabelValueType[] }), error => ({ key: item.key, error }) ) diff --git a/apps/gitness/src/utils/scope-url-utils.ts b/apps/gitness/src/utils/scope-url-utils.ts index 3e2f00f8ef..b880bac1fb 100644 --- a/apps/gitness/src/utils/scope-url-utils.ts +++ b/apps/gitness/src/utils/scope-url-utils.ts @@ -1,5 +1,5 @@ import { getScopedPath } from '@harnessio/ui/components' -import { RepositoryType, Scope, ScopeType } from '@harnessio/ui/views' +import { RepositoryType, Scope, ScopeType, ScopeValue } from '@harnessio/ui/views' export const getScopeType = ({ accountId, orgIdentifier, projectIdentifier }: Scope): ScopeType => { if (accountId && orgIdentifier && projectIdentifier) return ScopeType.Project @@ -51,6 +51,20 @@ export const getRepoUrl = ({ return prependScopeToUrl({ url: repoSubPath, scope, orgId, projectId }) } +export const getSpaceRefByScope = (spaceRef: string, scopeType: ScopeValue): string => { + const [accountId, orgId, projectId] = spaceRef.split('/') + switch (scopeType) { + case ScopeValue.Account: + return accountId + case ScopeValue.Organization: + return [accountId, orgId].filter(Boolean).join('/') + case ScopeValue.Project: + return [accountId, orgId, projectId].filter(Boolean).join('/') + default: + return spaceRef + } +} + export const getPullRequestUrl = ({ repo, scope, From cca979af715bc322290d47b08aa0167bb9d7203b Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:18:08 +0400 Subject: [PATCH 131/180] fix dialog bottom scroll (#2080) --- packages/ui/tailwind-utils-config/components/dialog.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ui/tailwind-utils-config/components/dialog.ts b/packages/ui/tailwind-utils-config/components/dialog.ts index 1dfae60cfa..ccb70dbbb9 100644 --- a/packages/ui/tailwind-utils-config/components/dialog.ts +++ b/packages/ui/tailwind-utils-config/components/dialog.ts @@ -100,8 +100,6 @@ export default { // Body Component '.cn-modal-dialog-body': { '--cn-modal-dialog-scroll-compensation': '4px', - display: 'grid', - gridTemplateColumns: 'calc(var(--cn-dialog-width) - var(--cn-dialog-px)*2)', paddingInline: 'var(--cn-modal-dialog-scroll-compensation)', paddingBottom: 'var(--cn-modal-dialog-scroll-compensation)', marginInline: 'calc(var(--cn-modal-dialog-scroll-compensation) * -1)', @@ -109,6 +107,11 @@ export default { marginTop: 'var(--cn-dialog-gap)', height: '100%', + '&.cn-scroll-area': { + display: 'flex', + flexDirection: 'column' + }, + '&-content': { display: 'flex', flexDirection: 'column', From bfc38908ca3d7069fccb521447d73f5768211033 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Tue, 19 Aug 2025 12:37:58 +0000 Subject: [PATCH 132/180] read only mode for unified view (#10241) * c50189 unified view read only mode --- .../compare/pull-request-compare-page.tsx | 2 +- .../components/pull-request-diff-viewer.tsx | 5 +++-- .../changes/pull-request-changes-filter.tsx | 17 +++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 3c4735260a..45a0bb292c 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -49,7 +49,7 @@ export type CompareFormFields = z.infer<ReturnType<typeof getPullRequestFormSche export const DiffModeOptions = [ { name: 'Split', value: 'Split' }, - { name: 'Unified', value: 'Unified' } + { name: 'Unified (Experimental - Read only mode)', value: 'Unified' } ] interface RoutingProps { toCommitDetails?: ({ sha }: { sha: string }) => string diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index 85ad058c13..e7052dc0a6 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -697,7 +697,7 @@ const PullRequestDiffViewer = ({ {/* @ts-ignore */} <ExtendedDiffView<Thread[]> ref={ref} - className="bg-tr text-cn-foreground-1 w-full" + className="bg-tr w-full text-cn-foreground-1" renderWidgetLine={renderWidgetLine} renderExtendLine={renderExtendLine} diffFile={diffFileInstance} @@ -707,7 +707,8 @@ const PullRequestDiffViewer = ({ diffViewMode={mode} registerHighlighter={highlighter} diffViewWrap={wrap} - diffViewAddWidget={addWidget} + // TODO: Remove 'mode === DiffModeEnum.Split' after the shadow dom is removed + diffViewAddWidget={addWidget && mode === DiffModeEnum.Split} diffViewTheme={isLightTheme ? 'light' : 'dark'} /> </div> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index e700781f3a..2c3098ed5c 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -3,8 +3,9 @@ import { useEffect, useMemo, useState } from 'react' import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton } from '@/components' import { useTranslation } from '@/context' import { TypesUser } from '@/types' -import { TypesCommit } from '@/views' +import { DiffModeOptions, TypesCommit } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' +import { cn } from '@utils/index' import { EnumPullReqReviewDecision, @@ -60,8 +61,8 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = submitReview, refetchReviewers, isApproving, - // diffMode, - // setDiffMode, + diffMode, + setDiffMode, pullReqCommits, defaultCommitFilter, selectedCommits, @@ -143,9 +144,9 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = } const itemsToRender = getApprovalItems(approveState, approvalItems) - // const handleDiffModeChange = (value: string) => { - // setDiffMode(value === 'Split' ? DiffModeEnum.Split : DiffModeEnum.Unified) - // } + const handleDiffModeChange = (value: string) => { + setDiffMode(value === 'Split' ? DiffModeEnum.Split : DiffModeEnum.Unified) + } return ( <Layout.Horizontal @@ -187,7 +188,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = /> </DropdownMenu.Root> - {/* <DropdownMenu.Root> + <DropdownMenu.Root> <DropdownMenu.Trigger className="group flex items-center gap-x-1.5 text-2"> <Button size="sm" variant="transparent"> {diffMode === DiffModeEnum.Split ? t('views:pullRequests.split') : t('views:pullRequests.unified')} @@ -207,7 +208,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = /> ))} </DropdownMenu.Content> - </DropdownMenu.Root> */} + </DropdownMenu.Root> </Layout.Horizontal> <Layout.Horizontal className="gap-x-7"> From adb69edf2d9d2c9f2c7aa1d9c70ea168a8b09afd Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Tue, 19 Aug 2025 16:03:05 +0300 Subject: [PATCH 133/180] Add word break to PR titles (#2083) --- .../views/repo/pull-request/components/pull-request-header.tsx | 2 +- .../repo/pull-request/components/pull-request-item-title.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx index 50075700e2..4df1321cc5 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-header.tsx @@ -65,7 +65,7 @@ export const PullRequestHeader: React.FC<PullRequestTitleProps> = ({ <> <Layout.Vertical gap="md" className={cn(className)}> <Layout.Horizontal gap="sm" align="end"> - <Text as="h1" variant="heading-section" className="[&>*]:ml-cn-xs"> + <Text as="h1" variant="heading-section" className="break-all [&>*]:ml-cn-xs"> {title} <Text as="span" variant="heading-section" color="foreground-3"> #{number} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx index 709d15b489..75366e9bf4 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-item-title.tsx @@ -43,7 +43,7 @@ export const PullRequestItemTitle: FC<PullRequestItemTitleProps> = ({ <div className="[&>*:not(:last-child)]:mr-cn-xs"> {repoId && <Tag className="align-bottom" value={repoId} icon="repository" theme="gray" />} - <Text as="span" variant="heading-base"> + <Text as="span" variant="heading-base" className="break-all"> {name} </Text> From 4b633715f2e4873eb1f5472e92e8f15f8e1409e7 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Tue, 19 Aug 2025 16:37:19 +0300 Subject: [PATCH 134/180] Fix font size for all md preview at PR pages (#2084) --- .../src/components/markdown-viewer/style.css | 24 +++++++++---------- .../common/pull-request-comment-view.tsx | 2 +- .../conversation/pull-request-comment-box.tsx | 2 +- .../pull-request-description-box.tsx | 1 + 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 00f7df4dbb..963e4118f1 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -287,22 +287,22 @@ @apply border-0; } } - } - &.comment { - @apply font-body-normal !bg-transparent; + &.pr-section { + @apply font-body-normal !bg-transparent; - pre code { - @apply bg-transparent; - } + pre code { + @apply bg-transparent; + } - a, - blockquote { - @apply font-body-normal; - } + a, + blockquote { + @apply font-body-normal; + } - strong { - @apply font-body-strong; + strong { + @apply font-body-strong; + } } } } diff --git a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx index f27e283ab2..bdafdfd2fa 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx @@ -44,7 +44,7 @@ const PRCommentView: FC<PRCommentViewProps> = ({ return ( <> <MarkdownViewer - markdownClassName="comment" + markdownClassName="pr-section" source={formattedComment || ''} suggestionBlock={{ source: diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 01c8a4ccc9..2c6f64b720 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -692,7 +692,7 @@ export const PullRequestCommentBox = ({ <div className="min-h-24 w-full"> {comment ? ( <MarkdownViewer - markdownClassName="bg-transparent w-full" + markdownClassName="pr-section bg-transparent w-full" source={replaceMentionEmailWithDisplayName(comment, principalsMentionMap)} /> ) : ( diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx index a27ce3ee00..f32d37d727 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx @@ -125,6 +125,7 @@ const PullRequestDescBox: FC<PullRequestDescBoxProps> = ({ <Text className="flex-1" color="foreground-1"> {description && ( <MarkdownViewer + markdownClassName="pr-section" source={comment} onCheckboxChange={updatedDescription => { setComment(updatedDescription) From 0f22c374b0d351a0db26ae672018d8963a2e4f8e Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:45:49 +0400 Subject: [PATCH 135/180] remove unnecessary checkmark (#2086) --- packages/ui/src/views/repo/components/file-last-change-bar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/components/file-last-change-bar.tsx b/packages/ui/src/views/repo/components/file-last-change-bar.tsx index 6e130574a1..54e41afa57 100644 --- a/packages/ui/src/views/repo/components/file-last-change-bar.tsx +++ b/packages/ui/src/views/repo/components/file-last-change-bar.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { Avatar, CommitCopyActions, IconV2, Layout, Separator, StackedList, Text, TimeAgoCard } from '@/components' +import { Avatar, CommitCopyActions, Layout, Separator, StackedList, Text, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { LatestFileTypes } from '@/views' @@ -14,7 +14,6 @@ const TopTitle: FC<LatestFileTypes> = ({ user, lastCommitMessage }) => { <Text variant="body-single-line-normal" className="line-clamp-1" truncate> {lastCommitMessage} </Text> - <IconV2 className="text-icons-success" name="check" size="xs" /> </Layout.Flex> ) } From 4ed7c9ff7361904a33ab53b954c93db3011a97bf Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Tue, 19 Aug 2025 15:43:31 +0000 Subject: [PATCH 136/180] feat: Display PR title as document title (#10239) * 8d361c add tsdoc * 3387ac add missing route page name, added tsdoc * db8b14 fix pr checks * 0fd45d code cleanup * dac20f add hook for updating page title dynamically --- .../framework/context/PageTitleContext.tsx | 21 +++++++++++ .../src/framework/hooks/usePageTitle.ts | 6 ++-- apps/gitness/src/framework/routing/types.ts | 1 + .../pull-request/pull-request-layout.tsx | 22 ++++++++++++ apps/gitness/src/routes.tsx | 36 +++++++++++-------- 5 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 apps/gitness/src/framework/context/PageTitleContext.tsx diff --git a/apps/gitness/src/framework/context/PageTitleContext.tsx b/apps/gitness/src/framework/context/PageTitleContext.tsx new file mode 100644 index 0000000000..70eb5d4735 --- /dev/null +++ b/apps/gitness/src/framework/context/PageTitleContext.tsx @@ -0,0 +1,21 @@ +import { createContext, useContext, useState } from 'react' + +/** + * Provides a context for managing the application's page title dynamically. + * This context takes precedence over titles defined in route configurations (routes.ts). + */ +export const PageTitleContext = createContext<{ + pageTitle?: string + setPageTitle: (title?: string) => void +}>({ + pageTitle: undefined, + setPageTitle: () => {} +}) + +export const PageTitleProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [pageTitle, setPageTitle] = useState<string | undefined>() + + return <PageTitleContext.Provider value={{ pageTitle, setPageTitle }}>{children}</PageTitleContext.Provider> +} + +export const usePageTitleContext = () => useContext(PageTitleContext) diff --git a/apps/gitness/src/framework/hooks/usePageTitle.ts b/apps/gitness/src/framework/hooks/usePageTitle.ts index 0a5d42e502..1192020c0f 100644 --- a/apps/gitness/src/framework/hooks/usePageTitle.ts +++ b/apps/gitness/src/framework/hooks/usePageTitle.ts @@ -3,11 +3,13 @@ import { useMatches } from 'react-router-dom' import { useTranslation } from '@harnessio/ui/context' +import { usePageTitleContext } from '../context/PageTitleContext' import { CustomHandle } from '../routing/types' const usePageTitle = () => { const { t } = useTranslation() const matches = useMatches() + const { pageTitle: dynamicTitle } = usePageTitleContext() useEffect(() => { const fullPageTitle = matches @@ -22,8 +24,8 @@ const usePageTitle = () => { }, []) .join(' | ') - document.title = fullPageTitle || t('views:app.harnessOpenSource', 'Harness Open Source') - }, [matches, t]) + document.title = dynamicTitle || fullPageTitle || t('views:app.harnessOpenSource', 'Harness Open Source') + }, [matches, t, dynamicTitle]) } export default usePageTitle diff --git a/apps/gitness/src/framework/routing/types.ts b/apps/gitness/src/framework/routing/types.ts index 890caf3431..e3e348827c 100644 --- a/apps/gitness/src/framework/routing/types.ts +++ b/apps/gitness/src/framework/routing/types.ts @@ -24,6 +24,7 @@ export enum RouteConstants { toPullRequestConversation = 'toPullRequestConversation', toPullRequestCommits = 'toPullRequestCommits', toPullRequestChanges = 'toPullRequestChanges', + toPullRequestChange = 'toPullRequestChange', toPullRequestChecks = 'toPullRequestChecks', toPipelineEdit = 'toPipelineEdit', toPipelines = 'toPipelines', diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-layout.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-layout.tsx index fb6984bf83..5243089639 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-layout.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-layout.tsx @@ -1,6 +1,8 @@ import { useEffect } from 'react' import { useParams } from 'react-router-dom' +import { capitalize } from 'lodash-es' + import { useChangeTargetBranchMutation, useGetPullReqQuery, @@ -9,7 +11,9 @@ import { import { PullRequestLayout as PullRequestLayoutView } from '@harnessio/ui/views' import { BranchSelectorContainer } from '../../components-v2/branch-selector-container' +import { usePageTitleContext } from '../../framework/context/PageTitleContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' +import useGetPullRequestTab from '../../hooks/useGetPullRequestTab' import { PathParams } from '../../RouteDefinitions' import { usePullRequestStore } from './stores/pull-request-store' @@ -19,6 +23,7 @@ const PullRequestLayout = () => { const { pullRequestId, spaceId, repoId } = useParams<PathParams>() const repoRef = useGetRepoRef() + const { setPageTitle } = usePageTitleContext() const { data: { body: pullReqData } = {}, @@ -30,6 +35,23 @@ const PullRequestLayout = () => { pullreq_number: Number(pullRequestId), queryParams: {} }) + + const pullRequestTab = useGetPullRequestTab({ spaceId, repoId, pullRequestId }) + + useEffect(() => { + if (!pullReqData && !pullRequestTab) return + + /** + * Constructs document title in the format: + * "Pull Request Title (#123) | Conversation" + */ + const { title, number } = pullReqData ?? {} + const pageTitle = [title, number ? `(#${number})` : null].filter(Boolean).join(' ') + const finalTitle = [pageTitle, capitalize(pullRequestTab || '')].filter(Boolean).join(' | ') + + setPageTitle(finalTitle) + }, [pullReqData, pullRequestTab]) + const { mutateAsync: updateTitle } = useUpdatePullReqMutation( { repo_ref: repoRef, diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 2d09b2cee1..8150b5a5df 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -16,6 +16,7 @@ import { AppShell } from './components-v2/standalone/app-shell' import { AppProvider } from './framework/context/AppContext' import { AppRouterProvider } from './framework/context/AppRouterProvider' import { ExplorerPathsProvider } from './framework/context/ExplorerPathsContext' +import { PageTitleProvider } from './framework/context/PageTitleContext' import { RbacButton } from './framework/rbac/rbac-button' import { RbacSplitButton } from './framework/rbac/rbac-split-button' import { CustomRouteObject, RouteConstants } from './framework/routing/types' @@ -358,7 +359,8 @@ export const repoRoutes: CustomRouteObject[] = [ element: <SearchPage />, handle: { breadcrumb: () => <span>{Page.Search}</span>, - routeName: RouteConstants.toRepoSearch + routeName: RouteConstants.toRepoSearch, + pageTitle: Page.Search } }, { @@ -447,7 +449,7 @@ export const repoRoutes: CustomRouteObject[] = [ ), handle: { breadcrumb: () => <span>{Page.Changes}</span>, - routeName: RouteConstants.toPullRequestChanges, + routeName: RouteConstants.toPullRequestChange, pageTitle: Page.Changes } }, @@ -783,13 +785,15 @@ export const routes: CustomRouteObject[] = [ path: '/', element: ( <AppRouterProvider> - <AppProvider> - <Sidebar.Provider className="min-h-svh"> - <ComponentProvider components={{ RbacButton, RbacSplitButton }}> - <AppShell /> - </ComponentProvider> - </Sidebar.Provider> - </AppProvider> + <PageTitleProvider> + <AppProvider> + <Sidebar.Provider className="min-h-svh"> + <ComponentProvider components={{ RbacButton, RbacSplitButton }}> + <AppShell /> + </ComponentProvider> + </Sidebar.Provider> + </AppProvider> + </PageTitleProvider> </AppRouterProvider> ), handle: { routeName: 'toHome' }, @@ -1253,12 +1257,14 @@ export const mfeRoutes = (mfeProjectId = '', mfeRouteRenderer: JSX.Element | nul path: '/', element: ( <AppRouterProvider> - <AppProvider> - <ComponentProvider components={{ RbacButton, RbacSplitButton }}> - {mfeRouteRenderer} - <AppShellMFE /> - </ComponentProvider> - </AppProvider> + <PageTitleProvider> + <AppProvider> + <ComponentProvider components={{ RbacButton, RbacSplitButton }}> + {mfeRouteRenderer} + <AppShellMFE /> + </ComponentProvider> + </AppProvider> + </PageTitleProvider> </AppRouterProvider> ), handle: { routeName: RouteConstants.toHome }, From 974180912226f553a562637d7514cb5745e3f235 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Tue, 19 Aug 2025 16:20:25 +0000 Subject: [PATCH 137/180] fix: duplicate checks (#10243) * f04c89 fix: duplicate checks --- .../conversation/sections/pull-request-checks-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-checks-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-checks-section.tsx index 55793a1aa3..cf7253f3ee 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-checks-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-checks-section.tsx @@ -61,7 +61,7 @@ const PullRequestCheckSection = ({ {checkData.map(check => { const time = timeDistance(check?.check?.created, check?.check?.updated) return ( - <Table.Row key={check.check?.id}> + <Table.Row key={check.check?.identifier}> <Table.Cell className="pl-0 w-80"> <Layout.Horizontal align="center" gap="xs"> {getStatusIcon(check?.check?.status as EnumCheckStatus)} From e71432e9dbe443d0ddbbed1844937c01df13f8a7 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Tue, 19 Aug 2025 16:54:41 +0000 Subject: [PATCH 138/180] feat: add scope filter on search page (#10235) * c4bc54 fix: minor state fix * 9f3c0c feat: track recrusive directly * 2636d3 fix: tsc * 725884 feat: only show dropdown on orgscope and acc scope * 3d4e88 feat: add scope filter --- .../views/search-page/search-page-preview.tsx | 2 ++ apps/gitness/src/pages-v2/search-page.tsx | 17 ++++++++++++----- packages/ui/src/views/search/search-page.tsx | 14 +++++++++++++- packages/ui/src/views/search/utils/utils.ts | 14 ++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 packages/ui/src/views/search/utils/utils.ts diff --git a/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx b/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx index c97fec544e..098e6ba23f 100644 --- a/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx +++ b/apps/design-system/src/subjects/views/search-page/search-page-preview.tsx @@ -26,6 +26,8 @@ export const SearchPagePreview = () => { onLanguageSelect={() => {}} onClearFilters={() => {}} isRepoScope={true} + onRecursiveToggle={() => {}} + scope={{ accountId: '1', orgIdentifier: '2', projectIdentifier: '3' }} /> ) } diff --git a/apps/gitness/src/pages-v2/search-page.tsx b/apps/gitness/src/pages-v2/search-page.tsx index a2fd292856..efb43c99a0 100644 --- a/apps/gitness/src/pages-v2/search-page.tsx +++ b/apps/gitness/src/pages-v2/search-page.tsx @@ -23,17 +23,19 @@ interface TVariables { type TError = Error | { message?: string } export default function SearchPage() { + const getApiPath = useAPIPath() + const { scope } = useMFEContext() + const { repoId } = useParams() + const [searchQuery, setSearchQuery] = useQueryState('query') const [selectedRepoId, setSelectedRepoId] = useQueryState('repo') const [selectedLanguage, setSelectedLanguage] = useQueryState('language') + const [isRecursive, setIsRecursive] = useState<boolean>(false) const [regexEnabled, setRegexEnabled] = useQueryState<boolean>('regexEnabled', parseAsBoolean) const [semanticEnabled, setSemanticEnabled] = useQueryState<boolean>('semantic', parseAsBoolean) const [searchResults, setSearchResults] = useState<SearchResultItem[]>([]) const [semanticSearchResults, setSemanticSearchResults] = useState<SemanticSearchResultItem[]>([]) const [stats, setStats] = useState<Stats>() - const getApiPath = useAPIPath() - const { scope } = useMFEContext() - const { repoId } = useParams() const scopeRef = [scope.accountId, scope.orgIdentifier, scope.projectIdentifier].filter(Boolean).join('/') const repoRef = `${scopeRef}/${repoId || selectedRepoId}` @@ -65,7 +67,7 @@ export default function SearchPage() { space_paths: repoId || selectedRepoId ? [] : [scopeRef], query: `( ${query} ) case:no${selectedLanguage ? ` lang:${selectedLanguage}` : ''}`, max_result_count: 50, - recursive: false, + recursive: isRecursive, enable_regex: regexEnabled }) }).then(res => { @@ -142,7 +144,8 @@ export default function SearchPage() { regexEnabled, selectedRepoId, selectedLanguage, - semanticEnabled + semanticEnabled, + isRecursive ]) return ( @@ -162,6 +165,7 @@ export default function SearchPage() { setSemanticEnabled={setSemanticEnabled} stats={stats} isRepoScope={!!repoId} + scope={scope} useSearchResultsStore={() => { return { results: searchResults, @@ -194,6 +198,9 @@ export default function SearchPage() { onRepoSelect={(repoName: string) => { setSelectedRepoId(repoName) }} + onRecursiveToggle={(recursive: boolean) => { + setIsRecursive(recursive) + }} onClearFilters={() => { setSelectedRepoId(null) setSelectedLanguage(null) diff --git a/packages/ui/src/views/search/search-page.tsx b/packages/ui/src/views/search/search-page.tsx index 9e9923546f..31205396ce 100644 --- a/packages/ui/src/views/search/search-page.tsx +++ b/packages/ui/src/views/search/search-page.tsx @@ -7,6 +7,7 @@ import { cn } from '@utils/cn' import { SearchResultItem, SearchResultsList } from './components/search-results-list' import { SemanticSearchResultItem, SemanticSearchResultsList } from './components/semantic-search-results-list' +import { getScopeOptions } from './utils/utils' const languageOptions = [ { label: 'JavaScript', value: 'javascript' }, @@ -40,6 +41,7 @@ export interface SearchPageViewProps { semanticEnabled: boolean setSemanticEnabled: (selected: boolean) => void isRepoScope: boolean + onRecursiveToggle: (recursive: boolean) => void semanticSearchError?: string searchError?: string useSearchResultsStore: () => { @@ -63,6 +65,7 @@ export interface SearchPageViewProps { // routing paths toRepoFileDetails: (params: { repoPath?: string; filePath: string; branch?: string }) => string toRepo: (params: { repoPath?: string }) => string + scope: { accountId: string; orgIdentifier?: string; projectIdentifier?: string } } export const SearchPageView: FC<SearchPageViewProps> = ({ @@ -81,12 +84,14 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ toRepoFileDetails, repos, selectedRepoId, + onRecursiveToggle, isReposListLoading, selectedLanguage, onLanguageSelect, onRepoSelect, onClearFilters, - toRepo + toRepo, + scope }) => { const { t } = useTranslation() const { results, semanticResults, page, setPage } = useSearchResultsStore() @@ -151,6 +156,13 @@ export const SearchPageView: FC<SearchPageViewProps> = ({ <> <Spacer size={7} /> <Layout.Horizontal gap="sm"> + {!scope.projectIdentifier ? ( + <Select + options={getScopeOptions(scope)} + onChange={value => (value === 'all' ? onRecursiveToggle(true) : onRecursiveToggle(false))} + placeholder={t('views:search.scopePlaceholder', 'Select a scope')} + /> + ) : null} {!isRepoScope && repos ? ( <Select isLoading={isReposListLoading} diff --git a/packages/ui/src/views/search/utils/utils.ts b/packages/ui/src/views/search/utils/utils.ts new file mode 100644 index 0000000000..88f57f4a93 --- /dev/null +++ b/packages/ui/src/views/search/utils/utils.ts @@ -0,0 +1,14 @@ +// Helper function to generate scope options based on available scope levels +export const getScopeOptions = (scope: { accountId: string; orgIdentifier?: string; projectIdentifier?: string }) => { + const options = [] + + if (scope.accountId && scope.orgIdentifier) { + options.push({ label: 'Organization', value: 'org' }) + options.push({ label: 'Organizations and Projects', value: 'all' }) + } else if (scope.accountId) { + options.push({ label: 'Account', value: 'account' }) + options.push({ label: 'Account, Organizations and Projects', value: 'all' }) + } + + return options +} From 34cc5641fcf3cfc6b9f30915d958abe9cc74f3e1 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Tue, 19 Aug 2025 16:55:13 +0000 Subject: [PATCH 139/180] feat: add avatar tooltip on br list (#10240) * 6c8879 chore * 9b0fa8 feat: get avatar tooltip --- .../ui/src/views/repo/repo-branch/components/branch-list.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx index 4188a19923..dc14d2f7bf 100644 --- a/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx +++ b/packages/ui/src/views/repo/repo-branch/components/branch-list.tsx @@ -2,7 +2,7 @@ import { FC } from 'react' import { ActionData, - Avatar, + AvatarWithTooltip, CopyTag, IconPropsV2, IconV2, @@ -89,7 +89,7 @@ export const BranchesList: FC<BranchListPageProps> = ({ <Table.Cell disableLink> <Layout.Flex align="center" gapX="xs"> - <Avatar name={branch?.user?.name} src={branch?.user?.avatarUrl} size="sm" rounded /> + <AvatarWithTooltip name={branch?.user?.name} src={branch?.user?.avatarUrl} size="sm" rounded /> <TimeAgoCard timestamp={branch?.timestamp} dateTimeFormatOptions={{ dateStyle: 'medium' }} From 605fa597b32e5517f81a06bc710afd17a1038fa0 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Tue, 19 Aug 2025 17:50:22 +0000 Subject: [PATCH 140/180] Closing/Ready for review - confirmation removed (#10244) * 77dd4e Fixed lint error * 7a7ba3 closing/opening a draft PR - confirmation removed --- .../conversation/pull-request-panel.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 981f2a28fd..894380b994 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -371,6 +371,14 @@ const PullRequestPanel = ({ const handleMergeTypeSelect = (value: string) => { const selectedAction = actions[parseInt(value)] const strategy = getMergeStrategyFromTitle(selectedAction.title) + const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) + const isMergeAction = mergeActionTitles.has(selectedAction.title) + setMergeButtonValue(value) + if (!isMergeAction) { + return + } + setShowActionBtn(true) + switch (strategy) { case MergeStrategy.SQUASH: setMergeMessage( @@ -396,9 +404,6 @@ const PullRequestPanel = ({ default: break } - - setShowActionBtn(true) - setMergeButtonValue(value) const showInputs = strategy === MergeStrategy.MERGE || strategy === MergeStrategy.SQUASH setShowMergeInputs(!!showInputs) } @@ -411,18 +416,9 @@ const PullRequestPanel = ({ } const handleConfirmMerge = () => { - const selectedAction = actions[parseInt(mergeButtonValue || '0')] - // Check if this is a merge action - const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) - const isMergeAction = mergeActionTitles.has(selectedAction?.title || '') - setShowMergeInputs(false) setShowActionBtn(false) - - // Only set mergeInitiated for actual merge actions - if (isMergeAction) { - setMergeInitiated(true) - } + setMergeInitiated(true) const actionIdx = actions.findIndex(action => action.id === mergeButtonValue) if (actionIdx !== -1) { @@ -611,7 +607,14 @@ const PullRequestPanel = ({ handleButtonClick={() => { const selectedAction = actions[parseInt(mergeButtonValue)] if (!selectedAction.disabled) { - handleMergeTypeSelect(mergeButtonValue) + const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) + const isMergeAction = mergeActionTitles.has(selectedAction.title) + + if (!isMergeAction) { + selectedAction.action?.() + } else { + handleMergeTypeSelect(mergeButtonValue) + } } }} size="md" @@ -624,7 +627,10 @@ const PullRequestPanel = ({ {(() => { const shouldShowButtonLayout = actions && !pullReqMetadata?.closed && (showActionBtn || isMerging || mergeInitiated) - return shouldShowButtonLayout ? ( + + if (!shouldShowButtonLayout) return null + const selectedAction = actions[parseInt(mergeButtonValue || '0')] + return ( <ButtonLayout> <Button variant="outline" @@ -640,10 +646,10 @@ const PullRequestPanel = ({ loading={isMerging || mergeInitiated} disabled={cancelInitiated || shouldDisableFastForwardMerge()} > - Confirm {actions[parseInt(mergeButtonValue || '0')]?.title || 'Merge'} + Confirm {selectedAction?.title || 'Merge'} </Button> </ButtonLayout> - ) : null + ) })()} {actions && pullReqMetadata?.closed ? ( <Button variant="primary" theme="default" size="sm" onClick={actions[0].action}> From b6be0f4e9d114696656c562b6e71fb596fe2764f Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Tue, 19 Aug 2025 18:32:29 +0000 Subject: [PATCH 141/180] fix counts for PR changes page viewed files (#10245) * 168055 fix counts for PR changes page viewed files --- .../details/pull-request-changes-page.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index 288e79a3ee..bd75f444c9 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -4,7 +4,13 @@ import { Layout, Skeleton } from '@/components' import { TypesUser } from '@/types' import { DiffModeEnum } from '@git-diff-view/react' import { cn } from '@utils/cn' -import { activityToCommentItem, HandleUploadType, SandboxLayout, TypesCommit } from '@views/index' +import { + activityToCommentItem, + FILE_VIEWED_OBSOLETE_SHA, + HandleUploadType, + SandboxLayout, + TypesCommit +} from '@views/index' import { orderBy } from 'lodash-es' import { DraggableSidebarDivider, SIDEBAR_MIN_WIDTH } from '../../components/draggable-sidebar-divider' @@ -130,6 +136,15 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ return parentActivities.map(thread => thread.map(activityToCommentItem)) }, [activities]) + const viewedFileCount = + diffs?.reduce((count, currentDiff) => { + const isInFileViewsAndNotObsolete = + currentDiff.fileViews?.has(currentDiff.filePath) && + currentDiff.fileViews?.get(currentDiff.filePath) !== FILE_VIEWED_OBSOLETE_SHA + + return count + (isInFileViewsAndNotObsolete ? 1 : 0) + }, 0) ?? 0 + const renderContent = () => { if (loadingRawDiff) { return <Skeleton.List /> @@ -224,7 +239,7 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ defaultCommitFilter={defaultCommitFilter} selectedCommits={selectedCommits} setSelectedCommits={setSelectedCommits} - viewedFiles={diffs?.[0]?.fileViews?.size || 0} + viewedFiles={viewedFileCount} pullReqStats={pullReqStats} onCommitSuggestionsBatch={onCommitSuggestionsBatch} commitSuggestionsBatchCount={commitSuggestionsBatchCount} From 771a775808f56958a835fdf015bf7738b66466dc Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Tue, 19 Aug 2025 19:40:11 +0000 Subject: [PATCH 142/180] PR statuses background color update (#10238) * 146f4c Single Enum Source * 4872ed Eliminated Multiple Classes * ef3e77 Merge remote-tracking branch 'origin/main' into rk-bugbash * cd0727 Merge remote-tracking branch 'origin/main' into rk-bugbash * aced55 Merge remote-tracking branch 'origin/main' into rk-bugbash * 25b85f Fixed Draft PR status background * ed2bff PR statuses background change * f4fbd7 Merge remote-tracking branch 'origin/main' into rk-bugbash * 13b02b Fixed since last view changed --- packages/ui/src/components/branch-tag.tsx | 20 ++- .../conversation/pull-request-panel.tsx | 127 ++++++++++++++---- .../repo/pull-request/pull-request.types.ts | 9 ++ 3 files changed, 124 insertions(+), 32 deletions(-) diff --git a/packages/ui/src/components/branch-tag.tsx b/packages/ui/src/components/branch-tag.tsx index 1fca8975d3..3862e9b9a7 100644 --- a/packages/ui/src/components/branch-tag.tsx +++ b/packages/ui/src/components/branch-tag.tsx @@ -1,21 +1,35 @@ import { CopyTag } from '@/components' import { useRouterContext } from '@/context' +import type { TagProps } from './tag' + interface BranchTagProps { branchName: string spaceId?: string repoId?: string hideBranchIcon?: boolean + theme?: TagProps['theme'] + variant?: TagProps['variant'] + size?: TagProps['size'] } -const BranchTag: React.FC<BranchTagProps> = ({ branchName, spaceId, repoId, hideBranchIcon }) => { +const BranchTag: React.FC<BranchTagProps> = ({ + branchName, + spaceId, + repoId, + hideBranchIcon, + theme = 'gray', + variant = 'secondary', + size = 'md' +}) => { const { Link } = useRouterContext() return ( <Link to={`${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/files/${branchName}`}> <CopyTag - variant="secondary" - theme="gray" + variant={variant} + theme={theme} + size={size} icon={hideBranchIcon ? undefined : 'git-branch'} value={branchName || ''} /> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 894380b994..ced26c87a7 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react' import { Accordion, Alert, + Avatar, BranchTag, Button, ButtonLayout, @@ -34,7 +35,7 @@ import { TypesPullReqCheck } from '@/views' import { cn } from '@utils/cn' -import { TypesPullReq } from '@views/repo/pull-request/pull-request.types' +import { PrState, TypesPullReq } from '@views/repo/pull-request/pull-request.types' import { DefaultReviewersDataProps, @@ -137,17 +138,48 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { const currentMergeMethod = getCurrentMergeMethod() const isFastForwardNotPossible = isRebasable && currentMergeMethod === MergeStrategy.FAST_FORWARD + const getStatusTextColor = (): 'success' | 'danger' | 'foreground-1' => { + if (isDraft || isClosed) return 'foreground-1' + + if (isOpen && (mergeable === false || ruleViolation || isFastForwardNotPossible)) { + return 'danger' + } + + if (isOpen && !unchecked) { + return 'success' + } + return 'foreground-1' + } + if (pullReqMetadata?.state === PullRequestFilterOption.MERGED) { return ( <> <div className="inline-flex w-full items-center justify-between gap-2"> - <Text className="flex items-center space-x-1" variant="body-single-line-strong" as="h2" color="foreground-1"> + <Text + className="flex items-center space-x-1 text-[var(--cn-set-purple-surface-text)]" + variant="body-single-line-strong" + as="h2" + color="inherit" + > + <Avatar name={pullReqMetadata?.merger?.display_name || ''} rounded size="sm" /> <span>{pullReqMetadata?.merger?.display_name}</span> <span>{areRulesBypassed ? `bypassed rules and ${mergeMethod}` : `${mergeMethod}`}</span> <span>into</span> - <BranchTag branchName={pullReqMetadata?.target_branch || ''} spaceId={spaceId} repoId={repoId} /> + <BranchTag + branchName={pullReqMetadata?.target_branch || ''} + spaceId={spaceId} + repoId={repoId} + theme="violet" + variant="secondary" + /> <span>from</span> - <BranchTag branchName={pullReqMetadata?.source_branch || ''} spaceId={spaceId} repoId={repoId} /> + <BranchTag + branchName={pullReqMetadata?.source_branch || ''} + spaceId={spaceId} + repoId={repoId} + theme="violet" + variant="secondary" + /> <TimeAgoCard timestamp={pullReqMetadata?.merged} /> </Text> <Layout.Horizontal> @@ -176,7 +208,7 @@ const HeaderTitle = ({ ...props }: HeaderProps) => { return ( <div className="inline-flex items-center gap-2"> - <Text variant="body-single-line-strong" as="h2" color="foreground-1"> + <Text variant="body-single-line-strong" as="h2" color={getStatusTextColor()}> {isDraft ? 'This pull request is still a work in progress' : isClosed @@ -512,37 +544,74 @@ const PullRequestPanel = ({ canBypass: !notBypassable }) + const fastForwardDisabled = shouldDisableFastForwardMerge() + const getPrState = (): PrState => { + if (pullReqMetadata?.state === PullRequestFilterOption.MERGED) { + return PrState.Merged + } else if (isClosed && !pullReqMetadata?.merged) { + return PrState.Closed + } else if (isOpen && isDraft) { + return PrState.Draft + } else if (isOpen && (!isMergeable || prPanelData.ruleViolation || fastForwardDisabled)) { + return PrState.Error + } else if (isOpen && !isDraft && !isClosed && isMergeable && !prPanelData.ruleViolation && !fastForwardDisabled) { + return PrState.Success + } else { + return PrState.Ready + } + } + + const prState = getPrState() + const headerRowBgClass = cn({ + 'bg-[var(--cn-set-green-surface-bg)]': prState === PrState.Success, + 'bg-cn-background-2': prState === PrState.Draft, + 'bg-label-background-red': prState === PrState.Error, + 'bg-cn-background-softgray': prState === PrState.Closed, + 'bg-[var(--cn-set-purple-surface-bg)]': prState === PrState.Merged + }) + + const headerTitleColorClass = cn({ + 'text-cn-foreground-success': prState === PrState.Success, + 'text-cn-foreground-danger': prState === PrState.Error + }) + return ( <> <StackedList.Root className="border-cn-borders-3 bg-cn-background-1"> <StackedList.Item - className={cn('items-center py-2 border-cn-borders-3', { - 'pr-1.5': isShowMoreTooltip - })} + className={cn( + 'items-center py-2 border-cn-borders-3', + { + 'pr-1.5': isShowMoreTooltip + }, + headerRowBgClass + )} disableHover > <StackedList.Field className={cn({ 'w-full': !pullReqMetadata?.merged })} title={ - <HeaderTitle - isDraft={isDraft} - isClosed={isClosed} - unchecked={isUnchecked} - mergeable={isMergeable} - isOpen={isOpen} - ruleViolation={prPanelData.ruleViolation} - pullReqMetadata={pullReqMetadata} - onRestoreBranch={onRestoreBranch} - onDeleteBranch={onDeleteBranch} - onRevertPR={onRevertPR} - showRestoreBranchButton={showRestoreBranchButton} - showDeleteBranchButton={showDeleteBranchButton} - headerMsg={headerMsg} - spaceId={spaceId} - repoId={repoId} - actions={actions} - mergeButtonValue={mergeButtonValue} - /> + <div className={headerTitleColorClass}> + <HeaderTitle + isDraft={isDraft} + isClosed={isClosed} + unchecked={isUnchecked} + mergeable={isMergeable} + isOpen={isOpen} + ruleViolation={prPanelData.ruleViolation} + pullReqMetadata={pullReqMetadata} + onRestoreBranch={onRestoreBranch} + onDeleteBranch={onDeleteBranch} + onRevertPR={onRevertPR} + showRestoreBranchButton={showRestoreBranchButton} + showDeleteBranchButton={showDeleteBranchButton} + headerMsg={headerMsg} + spaceId={spaceId} + repoId={repoId} + actions={actions} + mergeButtonValue={mergeButtonValue} + /> + </div> } /> @@ -690,7 +759,7 @@ const PullRequestPanel = ({ /> {showMergeInputs && ( <Layout.Vertical className="mt-2 w-full items-center pr-cn-xs pb-cn-xs"> - <Layout.Vertical className="w-full gap-1"> + <Layout.Vertical className="w-full gap-1 rounded-md border border-cn-borders-3 bg-cn-background-1 p-3"> <TextInput id="merge-title" label="Commit message" @@ -703,7 +772,7 @@ const PullRequestPanel = ({ <Textarea id="merge-message" label="Commit description" - className="w-full" + className="w-full bg-cn-background-1" value={mergeMessage} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMergeMessage(e.target.value)} optional diff --git a/packages/ui/src/views/repo/pull-request/pull-request.types.ts b/packages/ui/src/views/repo/pull-request/pull-request.types.ts index 4abb850be2..69af5954f3 100644 --- a/packages/ui/src/views/repo/pull-request/pull-request.types.ts +++ b/packages/ui/src/views/repo/pull-request/pull-request.types.ts @@ -134,6 +134,15 @@ export type EnumMergeMethod = 'fast-forward' | 'merge' | 'rebase' | 'squash' export type EnumPullReqState = 'closed' | 'merged' | 'open' +export enum PrState { + Draft = 'draft', + Ready = 'ready', + Success = 'success', + Error = 'error', + Closed = 'closed', + Merged = 'merged' +} + export declare type EnumPullReqReviewDecision = 'approved' | 'changereq' | 'pending' | 'reviewed' export enum PullReqReviewDecision { From 1f9814c61dfbaad3b25588cc24f73a2e6304798f Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Tue, 19 Aug 2025 20:03:45 +0000 Subject: [PATCH 143/180] fix: scroll file to middle from tree (#10247) * 678a01 fix: scroll file to middle from tree --- .../src/views/repo/pull-request/details/pull-request-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts index 6fda0af748..4fa667bc7f 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-utils.ts @@ -315,7 +315,7 @@ export const jumpToFile = ( // Scroll them all in order outterBlock + innerBlock + the final diff outerDOM?.scrollIntoView(false) innerDOM?.scrollIntoView(false) - diffDOM?.scrollIntoView(true) + diffDOM?.scrollIntoView({ block: 'center', inline: 'center' }) if (diffDOM && commentId) { dispatchCustomEvent<DiffViewerCustomEvent>(filePath, { From a675ca7097abc68305a9239369cec5fd38afa035 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Tue, 19 Aug 2025 20:11:37 +0000 Subject: [PATCH 144/180] fix suggestion code copy (#10242) * 904c97 fix suggestion code copy --- .../components/conversation/diff-utils.ts | 50 +++++++++++++++++++ .../conversation/pull-request-comment-box.tsx | 33 ++---------- 2 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts b/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts new file mode 100644 index 0000000000..6c506c6c0d --- /dev/null +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts @@ -0,0 +1,50 @@ +interface DiffLine { + type: 'context' | 'add' | 'remove' | 'hunk' + oldNumber: number | null + newNumber: number | null + line: string + content: string +} + +export function parseDiffToLines(lines: string[]): DiffLine[] { + const parsed: DiffLine[] = [] + let oldNum = 0 + let newNum = 0 + + for (const line of lines) { + if (line.startsWith('@@')) { + const match = /@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line) + if (!match) continue + + oldNum = parseInt(match[1], 10) + newNum = parseInt(match[2], 10) + + parsed.push({ type: 'hunk', oldNumber: oldNum, newNumber: newNum, line, content: line }) + } else if (line.startsWith('+')) { + parsed.push({ type: 'add', oldNumber: null, newNumber: newNum++, line, content: line.slice(1) }) + } else if (line.startsWith('-')) { + parsed.push({ type: 'remove', oldNumber: oldNum++, newNumber: null, line, content: line.slice(1) }) + } else if (line.startsWith(' ')) { + parsed.push({ type: 'context', oldNumber: oldNum++, newNumber: newNum++, line, content: line.slice(1) }) + } else { + // Skip diff metadata like diff --git, index, --- , +++ + } + } + + return parsed +} + +export function getDiffLinesRange( + parsed: DiffLine[], + side: 'old' | 'new', + from: number, + to: number, + ignoreHunk = true +): DiffLine[] { + return parsed.filter(line => { + if (ignoreHunk && line.type === 'hunk') return false + + const num = side === 'old' ? line.oldNumber : line.newNumber + return num !== null && num >= from && num <= to + }) +} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 2c6f64b720..55ee1842c4 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -36,6 +36,7 @@ import { cn } from '@utils/cn' import { getErrorMessage } from '@utils/utils' import { isEmpty, isUndefined } from 'lodash-es' +import { getDiffLinesRange, parseDiffToLines } from './diff-utils' import { PullRequestCommentTextarea } from './pull-request-comment-textarea' import { replaceMentionEmailWithDisplayName, replaceMentionEmailWithId } from './utils' @@ -376,36 +377,10 @@ export const PullRequestCommentBox = ({ } const diffLines = diff.split('\n') + const lines = parseDiffToLines(diffLines) + const rangeArr = getDiffLinesRange(lines, sideKey === 'newFile' ? 'new' : 'old', lineFromNumber, lineNumber) - const sideChangedLineToken = sideKey === 'newFile' ? '+' : '-' - const otherSideChangedLineToken = sideKey === 'newFile' ? '-' : '+' - - const sideDiffLines = diffLines.filter(diffLine => !diffLine.startsWith(otherSideChangedLineToken)) - - const found = sideDiffLines.reduce((previousValue, currentValue, currentIndex): string => { - if (isEmpty(previousValue) && currentValue.startsWith('@@')) { - const sectionInfoParts = currentValue.split(' ') - - const sideHeader = sectionInfoParts.find(part => part.startsWith(sideChangedLineToken)) - - const fileLineNumber = +(sideHeader?.split(',')[0].substring(1) ?? '') - - const fromOffset = lineFromNumber - fileLineNumber + 1 - const toOffset = lineNumber - fileLineNumber + 1 - - const selectedLines = sideDiffLines.slice(currentIndex + fromOffset, currentIndex + toOffset + 1) - - const cleanedLines = selectedLines.map(line => - line.startsWith(sideChangedLineToken) ? ` ${line.substring(1)}` : line - ) - - return cleanedLines.join('\n') - } - - return previousValue - }, '') - - return found + return rangeArr.map(item => item.content).join('\n') } const toolbar: ToolbarItem[] = useMemo(() => { From c15f543064e1e8158e6d4a6b039e39e649e7474d Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Tue, 19 Aug 2025 20:23:05 +0000 Subject: [PATCH 145/180] Update styles (#10246) * 5b0bbe Add focus styles and fix design system colors * a1f41c focus fix * 570452 offset * f67a0a fix * 2ae38d fix * 08c5f7 link color update * 1e83ac link colors update * 097123 lime color correction * e02682 color fix * 572b3d fix * 418282 focus-offset added * 31a1bb focus added * 6c94fe logo size updated * c4d431 color fix * e7beab Update design system changes * 3d17d3 typography fix * b35598 upd * 6f148a upd * e85191 colors update * 7a56bc colors update * a57376 upd * 445d8f ai inner shadow added * 997d40 ai hover state updated * 821f5a dark mode ai hovcer updated * beeb86 ai inner shadow added + subtle gradient for selected state * 915bee upd * e0c697 ai light mode updated * 352c1d blue shade fix * 042d59 diff light mode color correction * f716da separator color token deleted * 337c91 card gradient styles deleted since we are changed selected state to solid color. Blue shade corrected * afdeb3 brand text color style added * ec9333 success alert tok --- .../subjects/views/templates/view-only.tsx | 4 +- .../src/content/docs/components/alert.mdx | 8 + .../src/content/docs/components/button.mdx | 69 ++++++ .../content/docs/components/counter-badge.mdx | 70 ++++++ .../src/content/docs/components/logo.mdx | 3 +- .../content/docs/components/split-button.mdx | 186 ++++++++++++---- .../design-tokens/$themes.json | 49 ----- .../design-tokens/core/colors_hex.json | 58 ++--- .../design-tokens/core/colors_lch.json | 60 +++--- .../design-tokens/core/dimensions.json | 12 +- .../mode/dark/default-deuteranopia.json | 189 ++++++++-------- .../mode/dark/default-protanopia.json | 187 ++++++++-------- .../mode/dark/default-tritanopia.json | 187 ++++++++-------- .../design-tokens/mode/dark/default.json | 202 ++++++++++-------- .../mode/dark/dimmer-deuteranopia.json | 189 ++++++++-------- .../mode/dark/dimmer-protanopia.json | 187 ++++++++-------- .../mode/dark/dimmer-tritanopia.json | 187 ++++++++-------- .../design-tokens/mode/dark/dimmer.json | 187 ++++++++-------- .../mode/dark/high-contrast-deuteranopia.json | 189 ++++++++-------- .../mode/dark/high-contrast-protanopia.json | 187 ++++++++-------- .../mode/dark/high-contrast-tritanopia.json | 187 ++++++++-------- .../mode/dark/high-contrast.json | 187 ++++++++-------- .../mode/light/default-deuteranopia.json | 134 +++++------- .../mode/light/default-protanopia.json | 134 +++++------- .../mode/light/default-tritanopia.json | 134 +++++------- .../design-tokens/mode/light/default.json | 184 ++++++++-------- .../mode/light/dimmer-deuteranopia.json | 134 +++++------- .../mode/light/dimmer-protanopia.json | 134 +++++------- .../mode/light/dimmer-tritanopia.json | 134 +++++------- .../design-tokens/mode/light/dimmer.json | 134 +++++------- .../light/high-contrast-deuteranopia.json | 134 +++++------- .../mode/light/high-contrast-protanopia.json | 134 +++++------- .../mode/light/high-contrast-tritanopia.json | 134 +++++------- .../mode/light/high-contrast.json | 134 +++++------- .../ui/src/components/alert/AlertLink.tsx | 2 +- .../ui/src/components/alert/AlertRoot.tsx | 6 +- packages/ui/src/components/button.tsx | 16 +- packages/ui/src/components/card-select.tsx | 2 +- packages/ui/src/components/counter-badge.tsx | 11 +- .../ui/src/components/logo-v2/logo-v2.tsx | 1 + .../src/components/sidebar/sidebar-item.tsx | 2 +- .../components/skeletons/skeleton-logo.tsx | 1 + packages/ui/src/components/table.tsx | 4 +- .../connector-reference.tsx | 2 +- .../components/accordion.ts | 3 +- .../tailwind-utils-config/components/alert.ts | 7 +- .../components/breadcrumb.ts | 6 +- .../components/button.ts | 15 +- .../components/card-select.ts | 11 +- .../tailwind-utils-config/components/card.ts | 2 +- .../components/checkbox.ts | 2 +- .../components/icon-and-logo.ts | 2 +- .../tailwind-utils-config/components/link.ts | 8 +- .../tailwind-utils-config/components/radio.ts | 4 +- .../components/skeleton.ts | 5 +- .../components/switch.ts | 3 +- .../tailwind-utils-config/components/tabs.ts | 4 +- 57 files changed, 2258 insertions(+), 2303 deletions(-) diff --git a/apps/design-system/src/subjects/views/templates/view-only.tsx b/apps/design-system/src/subjects/views/templates/view-only.tsx index e21ecc70ca..c644a9ef0d 100644 --- a/apps/design-system/src/subjects/views/templates/view-only.tsx +++ b/apps/design-system/src/subjects/views/templates/view-only.tsx @@ -72,10 +72,10 @@ const dataMock: ViewOnlyProps[] = [ value: ( <div className="flex flex-wrap items-center gap-4"> <div className="inline-flex items-center gap-1"> - <LogoV2 size="sm" name="app-dynamics" /> delegate-1 + <LogoV2 size="xs" name="app-dynamics" /> delegate-1 </div> <div className="inline-flex items-center gap-1"> - <LogoV2 size="sm" name="katalon" /> delegate-2 + <LogoV2 size="xs" name="katalon" /> delegate-2 </div> </div> ) diff --git a/apps/portal/src/content/docs/components/alert.mdx b/apps/portal/src/content/docs/components/alert.mdx index bd8b4bf6ba..99e84aa99c 100644 --- a/apps/portal/src/content/docs/components/alert.mdx +++ b/apps/portal/src/content/docs/components/alert.mdx @@ -45,6 +45,14 @@ import { DocsPage } from "@/components/docs-page"; <Alert.Link to="/abc">Learn more</Alert.Link> </Alert.Root> +<Alert.Root theme="success"> + <Alert.Description> + Your repository has been successfully cloned and is ready for use. All + configurations have been applied correctly. + </Alert.Description> + <Alert.Link to="/abc">View repository</Alert.Link> +</Alert.Root> + <Alert.Root theme="danger" expandable> <Alert.Description> We couldn’t complete your request because it violates an Open Policy Agent diff --git a/apps/portal/src/content/docs/components/button.mdx b/apps/portal/src/content/docs/components/button.mdx index 3e6d7fc04d..f77b754594 100644 --- a/apps/portal/src/content/docs/components/button.mdx +++ b/apps/portal/src/content/docs/components/button.mdx @@ -123,6 +123,75 @@ Secondary buttons are less prominent than primary buttons. </div>`} /> +## Secondary Button with Themes + +Secondary buttons can be styled with different themes for various contexts. + +<DocsPage.ComponentExample + client:only + code={`<div className="flex flex-col items-start min-w-[600px] gap-8"> + <div className="flex gap-3 items-center"> + <Button variant="secondary" theme="default">Secondary Default</Button> + <Button variant="secondary" theme="success">Secondary Success</Button> + <Button variant="secondary" theme="danger">Secondary Danger</Button> + </div> + +<hr /> + +<h4>Secondary Button Themes with Sizes</h4> + +<div className="flex flex-col gap-4"> + <div className="flex gap-3 items-end"> + <Button variant="secondary" theme="success"> + Medium + </Button> + <Button variant="secondary" theme="success" size="sm"> + Small + </Button> + <Button variant="secondary" theme="success" size="xs"> + Extra Small + </Button> + </div> + + <div className="flex gap-3 items-end"> + <Button variant="secondary" theme="danger"> + Medium + </Button> + <Button variant="secondary" theme="danger" size="sm"> + Small + </Button> + <Button variant="secondary" theme="danger" size="xs"> + Extra Small + </Button> + </div> +</div> + +<hr /> + +<h4>Secondary Button Themes with States</h4> + +<div className="flex flex-col gap-3"> + <div className="flex gap-3"> + <Button variant="secondary" theme="success" disabled> + Success Disabled + </Button> + <Button variant="secondary" theme="success" loading> + Success Loading + </Button> + </div> + + <div className="flex gap-3"> + <Button variant="secondary" theme="danger" disabled> + Danger Disabled + </Button> + <Button variant="secondary" theme="danger" loading> + Danger Loading + </Button> + </div> +</div> +</div>`} +/> + ## Outline Button Outline buttons have a transparent background with a visible border. diff --git a/apps/portal/src/content/docs/components/counter-badge.mdx b/apps/portal/src/content/docs/components/counter-badge.mdx index 9c3a09fa29..5a756dbe53 100644 --- a/apps/portal/src/content/docs/components/counter-badge.mdx +++ b/apps/portal/src/content/docs/components/counter-badge.mdx @@ -39,6 +39,69 @@ import { CounterBadge } from '@harnessio/ui/components' <CounterBadge theme="danger">99+</CounterBadge> ``` +## Variants + +The `CounterBadge` component supports two variants: `outline` (default) and `secondary`. + +### Outline Variant + +The outline variant provides a subtle border-based appearance, ideal for secondary information display. + +<DocsPage.ComponentExample + client:only + code={`<div className="flex gap-5"> + <CounterBadge variant="outline">1</CounterBadge> + <CounterBadge variant="outline" theme="info">42</CounterBadge> + <CounterBadge variant="outline" theme="success">+2345</CounterBadge> + <CounterBadge variant="outline" theme="danger">-123</CounterBadge> + </div> +`} +/> + +### Secondary Variant + +The secondary variant uses a soft background appearance for more prominent display while maintaining subtlety. + +<DocsPage.ComponentExample + client:only + code={`<div className="flex gap-5"> + <CounterBadge variant="secondary">1</CounterBadge> + <CounterBadge variant="secondary" theme="info">42</CounterBadge> + <CounterBadge variant="secondary" theme="success">+2345</CounterBadge> + <CounterBadge variant="secondary" theme="danger">-123</CounterBadge> + </div> +`} +/> + +### Variant Comparison + +Compare both variants side by side to see the visual differences: + +<DocsPage.ComponentExample + client:only + code={`<div className="flex flex-col gap-4"> + <div className="flex gap-3"> + <span className="text-sm text-muted-foreground min-w-[80px]">Outline:</span> + <div className="flex gap-3"> + <CounterBadge variant="outline">12</CounterBadge> + <CounterBadge variant="outline" theme="info">42</CounterBadge> + <CounterBadge variant="outline" theme="success">+99</CounterBadge> + <CounterBadge variant="outline" theme="danger">-5</CounterBadge> + </div> + </div> + <div className="flex gap-3"> + <span className="text-sm text-muted-foreground min-w-[80px]">Secondary:</span> + <div className="flex gap-3"> + <CounterBadge variant="secondary">12</CounterBadge> + <CounterBadge variant="secondary" theme="info">42</CounterBadge> + <CounterBadge variant="secondary" theme="success">+99</CounterBadge> + <CounterBadge variant="secondary" theme="danger">-5</CounterBadge> + </div> + </div> + </div> +`} +/> + ## Themes The `CounterBadge` component supports four color themes that can be categorized into different groups based on their semantic meaning. @@ -79,6 +142,13 @@ The `CounterBadge` component has the following props to control its appearance: <DocsPage.PropsTable props={[ + { + name: "variant", + description: "Visual style variant of the badge", + required: false, + value: "'outline' | 'secondary'", + defaultValue: "'outline'", + }, { name: "theme", description: "Color theme of the badge", diff --git a/apps/portal/src/content/docs/components/logo.mdx b/apps/portal/src/content/docs/components/logo.mdx index 2f3c4ed5e0..1686a5ab2d 100644 --- a/apps/portal/src/content/docs/components/logo.mdx +++ b/apps/portal/src/content/docs/components/logo.mdx @@ -29,6 +29,7 @@ The `LogoV2` component can be sized using the `size` prop. client:only code={` <Layout.Flex gap="md" justify="center" align="end" className="min-w-[400px]"> + <LogoV2 name="harness" size="xs" /> <LogoV2 name="harness" size="sm" /> <LogoV2 name="harness" size="md" /> <LogoV2 name="harness" size="lg" /> @@ -64,7 +65,7 @@ The following logos are available: name: "size", description: "Size variant of the logo", required: false, - value: "'sm' | 'md' | 'lg'", + value: " 'xs' |'sm' | 'md' | 'lg'", defaultValue: "'lg'", }, { diff --git a/apps/portal/src/content/docs/components/split-button.mdx b/apps/portal/src/content/docs/components/split-button.mdx index ab39d1cab5..a22f1c9670 100644 --- a/apps/portal/src/content/docs/components/split-button.mdx +++ b/apps/portal/src/content/docs/components/split-button.mdx @@ -26,51 +26,149 @@ import { Aside } from "@astrojs/starlight/components"; {/* Variants and Themes */} <div> <h5 className="text-lg font-semibold mb-3">Variants and Themes</h5> - <div className="flex flex-wrap items-end gap-4"> - {/* Primary variant with Default theme */} - <SplitButton - id="primary-default" - variant="primary" - theme="default" - handleButtonClick={() => {}} - options={[ - { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, - { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, - ]} - handleOptionChange={() => {}} - > - Primary + Default - </SplitButton> + + {/* Primary Variant */} + <div className="mb-6"> + <h6 className="text-md font-medium mb-3 text-muted-foreground">Primary Variant</h6> + <div className="flex flex-wrap items-end gap-4"> + <SplitButton + id="primary-default" + variant="primary" + theme="default" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Primary + Default + </SplitButton> - {/* Outline variant with Success theme */} - <SplitButton - id="outline-success" - variant="outline" - theme="success" - handleButtonClick={() => {}} - options={[ - { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, - { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, - ]} - handleOptionChange={() => {}} - > - Outline + Success - </SplitButton> + <SplitButton + id="primary-success" + variant="primary" + theme="success" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Primary + Success + </SplitButton> - {/* Outline variant with Danger theme */} - <SplitButton - id="outline-danger" - variant="outline" - theme="danger" - handleButtonClick={() => {}} - options={[ - { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, - { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, - ]} - handleOptionChange={() => {}} - > - Outline + Danger - </SplitButton> + <SplitButton + id="primary-danger" + variant="primary" + theme="danger" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Primary + Danger + </SplitButton> + </div> + </div> + + {/* Secondary Variant */} + <div className="mb-6"> + <h6 className="text-md font-medium mb-3 text-muted-foreground">Secondary Variant</h6> + <div className="flex flex-wrap items-end gap-4"> + <SplitButton + id="secondary-default" + variant="secondary" + theme="default" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Secondary + Default + </SplitButton> + + <SplitButton + id="secondary-success" + variant="secondary" + theme="success" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Secondary + Success + </SplitButton> + + <SplitButton + id="secondary-danger" + variant="secondary" + theme="danger" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Secondary + Danger + </SplitButton> + </div> + </div> + + {/* Outline Variant */} + <div className="mb-6"> + <h6 className="text-md font-medium mb-3 text-muted-foreground">Outline Variant</h6> + <div className="flex flex-wrap items-end gap-4"> + <SplitButton + id="outline-default" + variant="outline" + theme="default" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Outline + Default + </SplitButton> + + <SplitButton + id="outline-success" + variant="outline" + theme="success" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Outline + Success + </SplitButton> + + <SplitButton + id="outline-danger" + variant="outline" + theme="danger" + handleButtonClick={() => {}} + options={[ + { value: 'option1', label: 'Option 1', description: 'Description for option 1' }, + { value: 'option2', label: 'Option 2', description: 'Description for option 2' }, + ]} + handleOptionChange={() => {}} + > + Outline + Danger + </SplitButton> + </div> </div> </div> @@ -297,7 +395,7 @@ return ( description: "Visual style of the button. Allowed combinations: primary+default, or outline+(default|success|danger).", required: false, - value: "'primary' | 'outline'", + value: "'primary' | 'outline' | 'secondary'", defaultValue: "'primary'", }, { diff --git a/packages/core-design-system/design-tokens/$themes.json b/packages/core-design-system/design-tokens/$themes.json index a71176cc98..9a164f8df9 100644 --- a/packages/core-design-system/design-tokens/$themes.json +++ b/packages/core-design-system/design-tokens/$themes.json @@ -1872,7 +1872,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -2109,7 +2108,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -2846,7 +2844,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -2861,7 +2858,6 @@ "set.ai.surface.text": "52ddfa5ea6aed729c40dadc69f5327434231ea59", "set.ai.surface.bg": "47707bcec328fb320335ca0cb8fc19628a812889", "set.ai.surface.bg-hover": "739c62c4ef008e2fcfcb7300cf46c96627e102d0", - "set.ai.surface.bg-selected": "e999fe279257851c15203f3cb36db3c5f0a3757d", "set.gray.solid.text": "5087347f06426c2415ddea934d3641d1120f3662", "set.gray.solid.bg": "498d5985cbe806febb13511b25ff1a0dd690ee0e", "set.gray.solid.bg-hover": "6bc718204cea97279f9c3aab5a09373ecdab477f", @@ -3083,7 +3079,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -3819,7 +3814,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -4056,7 +4050,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -4791,7 +4784,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -5028,7 +5020,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -5764,7 +5755,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -6001,7 +5991,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -6737,7 +6726,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -6974,7 +6962,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -7710,7 +7697,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -7947,7 +7933,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -8683,7 +8668,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -8920,7 +8904,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -9656,7 +9639,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -9893,7 +9875,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -10629,7 +10610,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -10866,7 +10846,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -11602,7 +11581,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -11839,7 +11817,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -12575,7 +12552,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -12812,7 +12788,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -13548,7 +13523,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -13785,7 +13759,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -14520,7 +14493,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -14757,7 +14729,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -15492,7 +15463,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -15729,7 +15699,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -16464,7 +16433,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -16701,7 +16669,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -17436,7 +17403,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -17673,7 +17639,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -18408,7 +18373,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -18645,7 +18609,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -19380,7 +19343,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -19617,7 +19579,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -20376,7 +20337,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -20613,7 +20573,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -21348,7 +21307,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -21585,7 +21543,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -22320,7 +22277,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -22557,7 +22513,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -23292,7 +23247,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -23529,7 +23483,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", @@ -24264,7 +24217,6 @@ "state.disabled.border-selected": "8bbb21952895e6137414c772a2a0b2dbcb75cfac", "set.brand.solid.text": "e54daf32b7e60abb7f4e78b7887897cdf7d62428", "set.brand.solid.bg": "b746eae610b23bc0492a1eb3509d04f29ed1fa54", - "set.brand.solid.separator": "a1484aaa6f00f4cb6a4ed1f593e3b176e2f2165c", "set.brand.solid.bg-hover": "7c2295b132267c53472f5dfb2bad5e2e8c26d387", "set.brand.solid.bg-selected": "02875959232fdcd16e8e1a9399124ff10644d9bc", "set.brand.soft.text": "519f1ce4e3b4335412eea24e63166cb7312b344e", @@ -24501,7 +24453,6 @@ "shadow-color.4": "2b2e0307d252bdc6db53a23ce2cc2e9bb4f6b914", "shadow-color.5": "5a6c707240bab54cb4403f47262abfe0756efc9a", "shadow-color.6": "928283d00759150575a8f155da90180a582be438", - "shadow-color.inner": "6cb5a98fba0b13e952859b9f023259b5c06cfd1a", "gradient.ai.gradient-stop-1": "8db516caa68afc5b8fdd6fc6e5a9bad7715c5f26", "gradient.ai.gradient-stop-2": "33930f41665251a281973a37847f357480aedca3", "gradient.ai.gradient-stop-3": "c9e14c3db8e622b10b1cd3e860115cd6f876ae49", diff --git a/packages/core-design-system/design-tokens/core/colors_hex.json b/packages/core-design-system/design-tokens/core/colors_hex.json index 5d4a9b1932..e9d9a6fc20 100644 --- a/packages/core-design-system/design-tokens/core/colors_hex.json +++ b/packages/core-design-system/design-tokens/core/colors_hex.json @@ -109,16 +109,16 @@ "$value": "#f9faff" }, "50": { - "$value": "#e5e9ff" + "$value": "#e9edff" }, "100": { - "$value": "#c7cffa" + "$value": "#c5cffa" }, "150": { - "$value": "#b6c2fb" + "$value": "#b3c2fc" }, "200": { - "$value": "#9daff8" + "$value": "#99aff8" }, "300": { "$value": "#7a9afe" @@ -133,19 +133,19 @@ "$value": "#2b71e6" }, "700": { - "$value": "#2460c5" + "$value": "#1664d2" }, "800": { - "$value": "#2751a2" + "$value": "#1952ac" }, "850": { "$value": "#2a3f75" }, "900": { - "$value": "#293356" + "$value": "#272f4f" }, "950": { - "$value": "#1d2540" + "$value": "#1d233a" }, "1000": { "$value": "#151929" @@ -214,10 +214,10 @@ "$value": "#fbb4b4" }, "200": { - "$value": "#ff9393" + "$value": "#ff9a99" }, "300": { - "$value": "#ff7e7c" + "$value": "#ff817e" }, "400": { "$value": "#f95a5a" @@ -226,7 +226,7 @@ "$value": "#e44646" }, "600": { - "$value": "#d02f2f" + "$value": "#ca3934" }, "700": { "$value": "#be292a" @@ -451,7 +451,7 @@ "$value": "#bbebc4" }, "150": { - "$value": "#55e284" + "$value": "#60ec8d" }, "200": { "$value": "#42cf6d" @@ -493,7 +493,7 @@ "$value": "#f2fdfa" }, "50": { - "$value": "#dcf9f0" + "$value": "#d7f4ec" }, "100": { "$value": "#c7eae1" @@ -502,10 +502,10 @@ "$value": "#61ddc2" }, "200": { - "$value": "#36c9ab" + "$value": "#40d0b2" }, "300": { - "$value": "#00b397" + "$value": "#14b89c" }, "400": { "$value": "#02a48b" @@ -538,46 +538,46 @@ }, "lime": { "25": { - "$value": "#f7fcf2" + "$value": "#f6fcf3" }, "50": { - "$value": "#e2eec7" + "$value": "#d9f1cc" }, "100": { - "$value": "#bce0a3" + "$value": "#b9e1a5" }, "150": { - "$value": "#8edd5a" + "$value": "#84de60" }, "200": { - "$value": "#7cc933" + "$value": "#67cc41" }, "300": { - "$value": "#6bb125" + "$value": "#56b434" }, "400": { - "$value": "#64a128" + "$value": "#52a334" }, "500": { - "$value": "#5f8f2c" + "$value": "#4f9136" }, "600": { - "$value": "#547b2d" + "$value": "#497d34" }, "700": { - "$value": "#49692b" + "$value": "#416a30" }, "800": { - "$value": "#41572a" + "$value": "#3b582e" }, "850": { - "$value": "#33481f" + "$value": "#2e4922" }, "900": { - "$value": "#2b391d" + "$value": "#273a1f" }, "950": { - "$value": "#1e2817" + "$value": "#1d2818" }, "1000": { "$value": "#131b0e" diff --git a/packages/core-design-system/design-tokens/core/colors_lch.json b/packages/core-design-system/design-tokens/core/colors_lch.json index 5b582468bb..7045025bcd 100644 --- a/packages/core-design-system/design-tokens/core/colors_lch.json +++ b/packages/core-design-system/design-tokens/core/colors_lch.json @@ -119,10 +119,10 @@ "$value": "lch(80.15% 28.7 22.17)" }, "200": { - "$value": "lch(74% 48.92 24.8)" + "$value": "lch(76.4% 48.92 24.8)" }, "300": { - "$value": "lch(68.4% 56 27.32)" + "$value": "lch(69.5% 56 27.32)" }, "400": { "$value": "lch(60.8% 70.74 29.41)" @@ -131,7 +131,7 @@ "$value": "lch(54.05% 71.9 31.04)" }, "600": { - "$value": "lch(47.29% 74.8 33.74)" + "$value": "lch(47.29% 69 33.74)" }, "700": { "$value": "lch(43% 70 33.8)" @@ -299,49 +299,49 @@ }, "lime": { "25": { - "$value": "lch(98.38% 5.17 125.66)" + "$value": "lch(98.38% 5.17 131)" }, "50": { - "$value": "lch(92.5% 19.66 117.34)" + "$value": "lch(92.5% 19.66 131)" }, "100": { - "$value": "lch(85.43% 32.93 128.12)" + "$value": "lch(85.43% 32.93 131)" }, "150": { - "$value": "lch(81% 68.86 127.38)" + "$value": "lch(81% 68.86 131)" }, "200": { - "$value": "lch(73.91% 74.97 124.29)" + "$value": "lch(73.91% 74.97 131)" }, "300": { - "$value": "lch(65.67% 70.28 123.94)" + "$value": "lch(65.67% 70.28 131)" }, "400": { - "$value": "lch(60.35% 63.02 123.93)" + "$value": "lch(60.35% 63.02 131)" }, "500": { - "$value": "lch(54.44% 53.6 123.09)" + "$value": "lch(54.44% 53.6 131)" }, "600": { - "$value": "lch(47.41% 44.32 123.68)" + "$value": "lch(47.41% 44.32 131)" }, "700": { - "$value": "lch(40.83% 36.77 124.36)" + "$value": "lch(40.83% 36.77 131)" }, "800": { - "$value": "lch(34.29% 27.73 123.53)" + "$value": "lch(34.29% 27.73 131)" }, "850": { - "$value": "lch(28.02% 26.12 124.33)" + "$value": "lch(28.02% 26.12 131)" }, "900": { - "$value": "lch(22.11% 18.67 124.14)" + "$value": "lch(22.11% 18.67 131)" }, "950": { - "$value": "lch(14.75% 12.31 127.78)" + "$value": "lch(14.75% 12.31 131)" }, "1000": { - "$value": "lch(8.63% 9.15 132.21)" + "$value": "lch(8.63% 9.15 131)" }, "$type": "color" }, @@ -356,7 +356,7 @@ "$value": "lch(89% 26 147.73)" }, "150": { - "$value": "lch(80.71% 64.76 148.08)" + "$value": "lch(84% 64.76 148.08)" }, "200": { "$value": "lch(74% 66 146.02)" @@ -398,7 +398,7 @@ "$value": "lch(98.42% 4.14 178.34)" }, "50": { - "$value": "lch(95.5% 11 176.43)" + "$value": "lch(94% 11 176.43)" }, "100": { "$value": "lch(90% 13 178)" @@ -407,10 +407,10 @@ "$value": "lch(80.61% 41.17 176.79)" }, "200": { - "$value": "lch(73% 45.21 175.62)" + "$value": "lch(75.5% 45.21 175.62)" }, "300": { - "$value": "lch(65.11% 45.18 176.28)" + "$value": "lch(67% 45.18 176.28)" }, "400": { "$value": "lch(60.06% 42.07 176.82)" @@ -494,16 +494,16 @@ "$value": "lch(98.3% 2.43 280)" }, "50": { - "$value": "lch(92.5% 11.26 280)" + "$value": "lch(93.9% 10 278)" }, "100": { - "$value": "lch(83.49% 22.44 280)" + "$value": "lch(83.49% 22.44 278)" }, "150": { - "$value": "lch(78.9% 30.56 280)" + "$value": "lch(78.9% 30.56 278)" }, "200": { - "$value": "lch(72.03% 39.64 280)" + "$value": "lch(72.03% 39.64 278)" }, "300": { "$value": "lch(64.63% 54.74 280)" @@ -518,19 +518,19 @@ "$value": "lch(48.5% 68 280)" }, "700": { - "$value": "lch(41.5% 60.5 280)" + "$value": "lch(43% 65.5 280)" }, "800": { - "$value": "lch(35% 50 280)" + "$value": "lch(35.5% 55.5 280)" }, "850": { "$value": "lch(27.23% 34.85 280)" }, "900": { - "$value": "lch(21.78% 22.8 280)" + "$value": "lch(20% 21 280)" }, "950": { - "$value": "lch(15.04% 18.61 280)" + "$value": "lch(14% 16 280)" }, "1000": { "$value": "lch(8.79% 12.01 280)" diff --git a/packages/core-design-system/design-tokens/core/dimensions.json b/packages/core-design-system/design-tokens/core/dimensions.json index 9977e125da..336e369b59 100644 --- a/packages/core-design-system/design-tokens/core/dimensions.json +++ b/packages/core-design-system/design-tokens/core/dimensions.json @@ -485,10 +485,14 @@ } }, "logoSize": { - "sm": { + "xs": { "$type": "sizing", "$value": "{size.5}" }, + "sm": { + "$type": "sizing", + "$value": "{size.6}" + }, "md": { "$type": "sizing", "$value": "{size.8}" @@ -498,5 +502,11 @@ "$value": "{size.11}", "$description": "\n" } + }, + "focus": { + "offset": { + "$type": "sizing", + "$value": "{size.px}" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json index d1278488a0..93baa7bdcd 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,12 +430,22 @@ "solid": { "text": { "$type": "color", - "$value": "{orange.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", - "$value": "{orange.300}", + "$value": "{red.300}", + "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", "$description": "Background color for prominent red surfaces." } }, @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json index c64a0f44f1..f7e8897aa2 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{blue.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{blue.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{blue.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,13 +430,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json index 14657df423..24c58e33c4 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,13 +430,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index edd2ab4484..8f5491805e 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{cyan.200}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{blue.600}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{blue.700}", @@ -259,13 +249,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.900}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.900}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -346,61 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{green.1000}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{green.200}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{green.150}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{green.150}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{green.1000}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -416,6 +428,16 @@ "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1086,6 +1108,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1131,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1360,12 +1380,12 @@ "link": { "text": { "$type": "color", - "$value": "{blue.300}", + "$value": "{cyan.200}", "$description": "Default color for text links. Creates distinct visual identification of interactive text elements." }, "text-hover": { "$type": "color", - "$value": "{blue.200}", + "$value": "{cyan.150}", "$description": "Hover color for text links. " } }, @@ -1782,18 +1802,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1807,6 +1815,19 @@ "type": "innerShadow" } } + }, + "ai": { + "inner": { + "$type": "boxShadow", + "$value": { + "x": "0", + "y": "7", + "blur": "16", + "spread": "0", + "color": "{set.ai.surface.inner-shadow}", + "type": "innerShadow" + } + } } } }, @@ -1908,45 +1929,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2097,6 +2082,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2218,16 +2216,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.1000}" - }, - "to": { - "$type": "color", - "$value": "{blue.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2691,6 +2679,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2808,5 +2814,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json index 68aa0f594e..bb4a0e71b5 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -259,13 +249,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -346,61 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -409,12 +421,22 @@ "solid": { "text": { "$type": "color", - "$value": "{orange.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", - "$value": "{orange.300}", + "$value": "{red.300}", + "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", "$description": "Background color for prominent red surfaces." } }, @@ -1086,6 +1108,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1131,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1791,18 +1811,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1925,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2078,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2221,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2684,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2826,5 +2819,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json index f693464f2c..cb04f86ace 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -259,13 +249,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -346,61 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{blue.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{blue.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{blue.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -409,13 +421,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1086,6 +1108,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1131,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1791,18 +1811,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1925,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2078,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2221,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2684,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2826,5 +2819,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json index fcaf15623a..6e7bf26d68 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -259,13 +249,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -346,61 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -409,13 +421,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1086,6 +1108,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1131,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1791,18 +1811,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1925,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2078,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2221,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2684,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2826,5 +2819,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer.json b/packages/core-design-system/design-tokens/mode/dark/dimmer.json index 2fb01508a3..3b1a73abc7 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -259,13 +249,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -346,61 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{green.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{green.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{green.200}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -409,13 +421,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1086,6 +1108,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1131,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1791,18 +1811,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1925,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2078,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2221,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2684,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2826,5 +2819,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json index 0e0e49d15e..625880f0f7 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.100}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.500}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,12 +430,22 @@ "solid": { "text": { "$type": "color", - "$value": "{orange.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", - "$value": "{orange.300}", + "$value": "{red.300}", + "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", "$description": "Background color for prominent red surfaces." } }, @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json index c5aa556917..130ae114d5 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{blue.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{blue.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{blue.100}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{blue.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{blue.500}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{blue.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,13 +430,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json index b104ab4402..e4f4297a3a 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{cyan.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{cyan.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{cyan.100}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{cyan.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{cyan.500}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{cyan.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{cyan.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,13 +430,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json index 770ca380ae..e928f46ac5 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.50}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.1000}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.150}", @@ -268,13 +258,25 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.850}", + "$value": "{purple.950}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.800}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0.35", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{blue.600}" } } }, @@ -355,61 +357,71 @@ "solid": { "text": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{green.300}", + "$value": "{mint.300}", + "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.200}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{green.200}", + "$value": "{mint.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{green.100}", + "$value": "{mint.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{green.950}", + "$value": "{mint.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{green.500}", + "$value": "{mint.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{green.900}", + "$value": "{mint.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{green.800}", + "$value": "{mint.900}", "$description": "Selected state background color for active green items." } } @@ -418,13 +430,23 @@ "solid": { "text": { "$type": "color", - "$value": "{red.950}", + "$value": "{red.1000}", "$description": "Text color for high-contrast red surfaces." }, "bg": { "$type": "color", "$value": "{red.300}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.200}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1095,6 +1117,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1140,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1820,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1934,9 @@ "$type": "color", "$value": "{pure.black}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2087,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2230,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{chrome.850}" - }, - "to": { - "$type": "color", - "$value": "{chrome.950}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2693,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{orange.800}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{purple.850}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{violet.900}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{blue.900}" + } } }, "disabled": { @@ -2835,5 +2828,13 @@ "$type": "color", "$value": "{purple.300}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json index 43ad0bc37f..7ffcd40a15 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json index d08744feb1..dfd3eac89d 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json index 81e601b205..13794b6313 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index a2ef19a33a..63cc2d3f69 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.600}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{blue.600}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{blue.700}", @@ -259,13 +249,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{orange.25}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{pink.50}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -278,17 +271,17 @@ }, "bg": { "$type": "color", - "$value": "{tuna.200}", + "$value": "{tuna.150}", "$description": "Background color for prominent gray surfaces." }, "bg-hover": { "$type": "color", - "$value": "{tuna.300}", + "$value": "{tuna.200}", "$description": "Hover state background color for prominent gray surfaces." }, "bg-selected": { "$type": "color", - "$value": "{tuna.300}", + "$value": "{tuna.200}", "$description": "Selected state background color for prominent gray surfaces." } }, @@ -353,6 +346,16 @@ "$type": "color", "$value": "{mint.600}", "$description": "Background color for prominent green surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{mint.700}", + "$description": "Background color for prominent green surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{mint.700}", + "$description": "Background color for prominent green surfaces." } }, "soft": { @@ -416,6 +419,16 @@ "$type": "color", "$value": "{red.600}", "$description": "Background color for prominent red surfaces." + }, + "bg-hover": { + "$type": "color", + "$value": "{red.700}", + "$description": "Background color for prominent red surfaces." + }, + "bg-selected": { + "$type": "color", + "$value": "{red.700}", + "$description": "Background color for prominent red surfaces." } }, "soft": { @@ -1086,6 +1099,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1122,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1166,13 +1177,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.1", + "value": "0.08", "space": "lch" } } }, "$type": "color", - "$value": "{green.200}", + "$value": "{mint.200}", "$description": "Green background highlighting newly added code lines while maintaining readability." }, "add-lineNumber": { @@ -1180,13 +1191,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.2", + "value": "0.12", "space": "lch" } } }, "$type": "color", - "$value": "{green.200}", + "$value": "{mint.200}", "$description": "Background for line numbers of added code, providing visual connection to added content." }, "add-content-highlight": { @@ -1194,13 +1205,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.25", + "value": "0.16", "space": "lch" } } }, "$type": "color", - "$value": "{green.200}", + "$value": "{mint.200}", "$description": "Stronger emphasis color for specific character changes within added lines." }, "add-widget": { @@ -1218,7 +1229,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.1", + "value": "0.08", "space": "lch" } } @@ -1232,7 +1243,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.2", + "value": "0.12", "space": "lch" } } @@ -1246,7 +1257,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.25", + "value": "0.16", "space": "lch" } } @@ -1332,7 +1343,7 @@ }, "hljs-string": { "$type": "color", - "$value": "{green.700}" + "$value": "{mint.700}" }, "hljs-title-function": { "$type": "color", @@ -1360,12 +1371,12 @@ "link": { "text": { "$type": "color", - "$value": "{blue.600}", + "$value": "{blue.700}", "$description": "Default color for text links. Creates distinct visual identification of interactive text elements." }, "text-hover": { "$type": "color", - "$value": "{blue.700}", + "$value": "{blue.800}", "$description": "Hover color for text links." } }, @@ -1791,18 +1802,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1916,9 @@ "$type": "color", "$value": "{tuna.800}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{tuna.800}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.7", - "color": "{border.brand}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "4", - "color": "{set.brand.surface.bg}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2069,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2212,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{blue.50}" - }, - "to": { - "$type": "color", - "$value": "{blue.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2675,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2826,5 +2810,13 @@ "$type": "color", "$value": "{purple.400}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json index 00b078fa90..fbfb3f50ec 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -183,20 +187,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -277,13 +267,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1104,6 +1097,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1123,12 +1120,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1809,18 +1800,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1935,45 +1914,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2124,6 +2067,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2254,16 +2210,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2727,6 +2673,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2844,5 +2808,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json index c8bdc3d8fe..8caa0d9a06 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -183,20 +187,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -277,13 +267,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1104,6 +1097,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1123,12 +1120,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1809,18 +1800,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1935,45 +1914,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2124,6 +2067,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2254,16 +2210,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2727,6 +2673,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2844,5 +2808,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json index 3105fbdf46..60b78bc311 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -183,20 +187,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -277,13 +267,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1104,6 +1097,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1123,12 +1120,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1809,18 +1800,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1935,45 +1914,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2124,6 +2067,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2254,16 +2210,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2727,6 +2673,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2844,5 +2808,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer.json b/packages/core-design-system/design-tokens/mode/light/dimmer.json index eb1c075198..9ca5d9db07 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -165,20 +169,6 @@ "$value": "{pure.black}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{pure.white}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.850}", @@ -259,13 +249,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.50}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.50}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1086,6 +1079,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1105,12 +1102,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1791,18 +1782,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1917,45 +1896,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2106,6 +2049,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2236,16 +2192,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2709,6 +2655,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2826,5 +2790,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json index f41087d36e..07248cc897 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json index 3d16da37a2..fb5d16611e 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json index 5c0a74ad1d..9e1b0da820 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast.json b/packages/core-design-system/design-tokens/mode/light/high-contrast.json index 1a3cea16ca..dbaa4e5e08 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast.json @@ -51,6 +51,10 @@ "$type": "color", "$value": "{set.yellow.soft.text}", "$description": "Primary color for text and icons in warning content. Communicates caution and potential issues requiring attention.\n\nCommon uses: Warning message text, alert text, important notifications." + }, + "brand": { + "$type": "color", + "$value": "{blue.300}" } }, "border": { @@ -174,20 +178,6 @@ "$value": "{chrome.950}", "$description": "Background color for primary brand items with strong emphasis." }, - "separator": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.25", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.50}", - "$description": "Separator color for dividing brand areas." - }, "bg-hover": { "$type": "color", "$value": "{chrome.900}", @@ -268,13 +258,16 @@ }, "bg-hover": { "$type": "color", - "$value": "{chrome.100}", + "$value": "{purple.50}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", - "$value": "{chrome.100}", - "$description": "Selected state background color for active AI items." + "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" + }, + "inner-shadow": { + "$type": "color", + "$value": "{pure.white}" } } }, @@ -1095,6 +1088,10 @@ "warning": { "$type": "color", "$value": "linear-gradient(0deg, {set.yellow.soft.bg} 42%, {gradient.alert.fade-warning} 100%)" + }, + "success": { + "$type": "color", + "$value": "linear-gradient(0deg, {set.green.soft.bg} 42%, {gradient.alert.fade-success} 100%)" } } }, @@ -1114,12 +1111,6 @@ "$description": "Inset border color for avatars. Creates subtle definition inside the avatar element." } }, - "card": { - "gradient": { - "$type": "color", - "$value": "linear-gradient(180deg, {gradient.card.from} 0%, {gradient.card.to} 100%);" - } - }, "chat": { "ai-icon": { "$type": "color", @@ -1800,18 +1791,6 @@ }, "$description": "Extra extra large shadow.\nCommon uses: This aggressive shadow is typically used sparingly in UI design, primarily for elements that need maximum visual emphasis and elevation." }, - "inner": { - "$type": "boxShadow", - "$value": { - "x": "0", - "y": "2", - "blur": "4", - "spread": "0", - "color": "{shadow-color.inner}", - "type": "innerShadow" - }, - "$description": "Inner shadow. \nCommon uses: Pressed buttons, selected states, inset form fields." - }, "comp": { "avatar": { "inner": { @@ -1926,45 +1905,9 @@ "$type": "color", "$value": "{chrome.900}", "$description": "Extremely deep shadow color. Provides maximum depth for highly elevated elements." - }, - "inner": { - "$extensions": { - "studio.tokens": { - "modify": { - "type": "alpha", - "value": "0.1", - "space": "lch" - } - } - }, - "$type": "color", - "$value": "{chrome.900}", - "$description": "Subtle inner shadow color. Creates inset effect for pressed or focused states." } }, "ring": { - "focus": { - "$type": "boxShadow", - "$value": [ - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "0.8", - "color": "{bg.1}", - "type": "dropShadow" - }, - { - "x": "0", - "y": "0", - "blur": "0", - "spread": "2", - "color": "{border.brand}", - "type": "dropShadow" - } - ], - "$description": "Focus ring effect with background offset and accent border." - }, "danger": { "$type": "boxShadow", "$value": { @@ -2115,6 +2058,19 @@ }, "$type": "color", "$value": "{set.yellow.soft.bg}" + }, + "fade-success": { + "$extensions": { + "studio.tokens": { + "modify": { + "type": "alpha", + "value": "0", + "space": "lch" + } + } + }, + "$type": "color", + "$value": "{set.green.soft.bg}" } }, "skeleton": { @@ -2245,16 +2201,6 @@ } } }, - "card": { - "from": { - "$type": "color", - "$value": "{pure.white}" - }, - "to": { - "$type": "color", - "$value": "{chrome.25}" - } - }, "dialog": { "fade": { "$extensions": { @@ -2718,6 +2664,24 @@ } } } + }, + "ai-subtle": { + "gradient-stop-1": { + "$type": "color", + "$value": "{yellow.25}" + }, + "gradient-stop-2": { + "$type": "color", + "$value": "{pink.50}" + }, + "gradient-stop-3": { + "$type": "color", + "$value": "{purple.50}" + }, + "gradient-stop-4": { + "$type": "color", + "$value": "{cyan.50}" + } } }, "disabled": { @@ -2835,5 +2799,13 @@ "$type": "color", "$value": "{purple.500}" } + }, + "focus": { + "$type": "border", + "$value": { + "color": "{border.brand}", + "width": "{borderWidth.2}", + "style": "solid" + } } } \ No newline at end of file diff --git a/packages/ui/src/components/alert/AlertLink.tsx b/packages/ui/src/components/alert/AlertLink.tsx index 41f3490831..e61d370451 100644 --- a/packages/ui/src/components/alert/AlertLink.tsx +++ b/packages/ui/src/components/alert/AlertLink.tsx @@ -25,7 +25,7 @@ export const AlertLink = forwardRef<HTMLAnchorElement, AlertLinkProps>( <div className="cn-alert-link-wrapper"> <Link ref={ref} - variant="secondary" + variant="default" suffixIcon className={cn('cn-alert-link', className)} {...externalProps} diff --git a/packages/ui/src/components/alert/AlertRoot.tsx b/packages/ui/src/components/alert/AlertRoot.tsx index 21a64d1df8..864185f555 100644 --- a/packages/ui/src/components/alert/AlertRoot.tsx +++ b/packages/ui/src/components/alert/AlertRoot.tsx @@ -12,7 +12,8 @@ const alertVariants = cva('cn-alert', { theme: { info: 'cn-alert-info', danger: 'cn-alert-danger', - warning: 'cn-alert-warning' + warning: 'cn-alert-warning', + success: 'cn-alert-success' } }, defaultVariants: { @@ -23,7 +24,8 @@ const alertVariants = cva('cn-alert', { const iconMap: Record<NonNullable<VariantProps<typeof alertVariants>['theme']>, keyof typeof IconNameMapV2> = { info: 'info-circle', danger: 'xmark-circle', - warning: 'warning-triangle' + warning: 'warning-triangle', + success: 'check-circle' } const MAX_HEIGHT = 138 diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index d5ccd3252a..f9e017bf94 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -49,12 +49,12 @@ const buttonVariants = cva('cn-button', { { variant: 'primary', theme: 'success', - class: 'cn-button-soft cn-button-success' + class: 'cn-button-solid cn-button-success' }, { variant: 'primary', theme: 'danger', - class: 'cn-button-soft cn-button-danger' + class: 'cn-button-solid cn-button-danger' }, // Secondary @@ -63,6 +63,18 @@ const buttonVariants = cva('cn-button', { theme: 'default', class: 'cn-button-muted cn-button-soft' }, + // Secondary + { + variant: 'secondary', + theme: 'success', + class: 'cn-button-success cn-button-soft' + }, + // Secondary + { + variant: 'secondary', + theme: 'danger', + class: 'cn-button-danger cn-button-soft' + }, // Default Outline { diff --git a/packages/ui/src/components/card-select.tsx b/packages/ui/src/components/card-select.tsx index d04942ce6f..1e0f048081 100644 --- a/packages/ui/src/components/card-select.tsx +++ b/packages/ui/src/components/card-select.tsx @@ -172,7 +172,7 @@ const CardSelectItem = forwardRef<HTMLLabelElement, CardSelectItemProps>( {logo && !icon && <LogoV2 name={logo} className="cn-card-select-logo" />} <div className="cn-card-select-content-container">{children}</div> </div> - {checked && <IconV2 name="check" className="cn-card-select-check" />} + {checked && <IconV2 size="md" name="check" className="cn-card-select-check" />} </div> <input type={type === 'multiple' ? 'checkbox' : 'radio'} diff --git a/packages/ui/src/components/counter-badge.tsx b/packages/ui/src/components/counter-badge.tsx index 2b57a8371c..4c7ef5905e 100644 --- a/packages/ui/src/components/counter-badge.tsx +++ b/packages/ui/src/components/counter-badge.tsx @@ -3,8 +3,12 @@ import { forwardRef } from 'react' import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' -const counterBadgeVariants = cva('cn-badge cn-badge-counter cn-badge-surface inline-flex w-fit items-center', { +const counterBadgeVariants = cva('cn-badge cn-badge-counter inline-flex w-fit items-center', { variants: { + variant: { + outline: 'cn-badge-surface', + secondary: 'cn-badge-soft' + }, theme: { default: 'cn-badge-muted', info: 'cn-badge-info', @@ -13,6 +17,7 @@ const counterBadgeVariants = cva('cn-badge cn-badge-counter cn-badge-surface inl } }, defaultVariants: { + variant: 'outline', theme: 'default' } }) @@ -21,16 +26,18 @@ type CounterBadgeProps = Omit< React.HTMLAttributes<HTMLDivElement>, 'color' | 'role' | 'aria-readonly' | 'tabIndex' | 'onClick' > & { + variant?: VariantProps<typeof counterBadgeVariants>['variant'] theme?: VariantProps<typeof counterBadgeVariants>['theme'] } const CounterBadge = forwardRef<HTMLDivElement, CounterBadgeProps>( - ({ className, theme = 'default', children, ...props }, ref) => { + ({ className, variant, theme = 'default', children, ...props }, ref) => { return ( <div ref={ref} className={cn( counterBadgeVariants({ + variant, theme }), className diff --git a/packages/ui/src/components/logo-v2/logo-v2.tsx b/packages/ui/src/components/logo-v2/logo-v2.tsx index 999742bf72..a74996d404 100644 --- a/packages/ui/src/components/logo-v2/logo-v2.tsx +++ b/packages/ui/src/components/logo-v2/logo-v2.tsx @@ -8,6 +8,7 @@ import { LogoNameMapV2 } from './logo-name-map' export const logoVariants = cva('cn-logo', { variants: { size: { + xs: 'cn-logo-xs', sm: 'cn-logo-sm', md: 'cn-logo-md', lg: 'cn-logo-lg' diff --git a/packages/ui/src/components/sidebar/sidebar-item.tsx b/packages/ui/src/components/sidebar/sidebar-item.tsx index 2c319bf602..04f064332e 100644 --- a/packages/ui/src/components/sidebar/sidebar-item.tsx +++ b/packages/ui/src/components/sidebar/sidebar-item.tsx @@ -192,7 +192,7 @@ const SidebarItemTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, Sid ))} {withLogo && props.logo && ( - <LogoV2 name={props.logo} size={withDescription ? 'md' : 'sm'} className="cn-sidebar-item-content-icon" /> + <LogoV2 name={props.logo} size={withDescription ? 'md' : 'xs'} className="cn-sidebar-item-content-icon" /> )} {withAvatar && ( diff --git a/packages/ui/src/components/skeletons/skeleton-logo.tsx b/packages/ui/src/components/skeletons/skeleton-logo.tsx index 11dabfd567..d825ee289b 100644 --- a/packages/ui/src/components/skeletons/skeleton-logo.tsx +++ b/packages/ui/src/components/skeletons/skeleton-logo.tsx @@ -9,6 +9,7 @@ import { SkeletonBase } from './components/skeleton' const skeletonLogoVariants: typeof logoVariants = cva('cn-skeleton-logo', { variants: { size: { + xs: 'cn-skeleton-logo-xs', sm: 'cn-skeleton-logo-sm', md: 'cn-skeleton-logo-md', lg: 'cn-skeleton-logo-lg' diff --git a/packages/ui/src/components/table.tsx b/packages/ui/src/components/table.tsx index 8251591d7f..58b3359ea6 100644 --- a/packages/ui/src/components/table.tsx +++ b/packages/ui/src/components/table.tsx @@ -157,8 +157,8 @@ const TableHead = forwardRef<HTMLTableCellElement, TableHeadProps>( ) => { const Title = () => ( <Text - variant="caption-strong" - color="foreground-1" + variant="caption-normal" + color="foreground-3" className={cn({ 'underline decoration-dashed': !!tooltipProps?.content }, contentClassName)} > {children} diff --git a/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx b/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx index b58a0e5ab7..5bd725a720 100644 --- a/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx +++ b/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx @@ -71,7 +71,7 @@ export const ConnectorReference: FC<ConnectorReferenceProps> = ({ className={cn('h-12 p-3', { 'bg-cn-background-hover': isSelected })} thumbnail={ connectorLogo ? ( - <LogoV2 name={connectorLogo} size="sm" /> + <LogoV2 name={connectorLogo} size="xs" /> ) : ( <IconV2 name="connectors" size="xs" className="text-cn-foreground-3" /> ) diff --git a/packages/ui/tailwind-utils-config/components/accordion.ts b/packages/ui/tailwind-utils-config/components/accordion.ts index 60716c0164..465051835d 100644 --- a/packages/ui/tailwind-utils-config/components/accordion.ts +++ b/packages/ui/tailwind-utils-config/components/accordion.ts @@ -37,8 +37,7 @@ export default { }, '&:where(:focus-visible:not([data-disabled]))': { - boxShadow: 'var(--cn-ring-focus)', - outline: 'none' + outline: 'var(--cn-focus)' }, '&:where([data-state="open"])': { diff --git a/packages/ui/tailwind-utils-config/components/alert.ts b/packages/ui/tailwind-utils-config/components/alert.ts index 72d69112e9..09e4bdc523 100644 --- a/packages/ui/tailwind-utils-config/components/alert.ts +++ b/packages/ui/tailwind-utils-config/components/alert.ts @@ -1,6 +1,6 @@ import { CSSRuleObject } from 'tailwindcss/types/config' -const themes = ['info', 'danger', 'warning'] as const +const themes = ['info', 'danger', 'warning', 'success'] as const const themeStyleMapper: Record< (typeof themes)[number], @@ -20,6 +20,11 @@ const themeStyleMapper: Record< backgroundColor: 'yellow-soft', color: 'text-warning', fadeBgColor: 'warning' + }, + success: { + backgroundColor: 'green-soft', + color: 'text-success', + fadeBgColor: 'success' } } diff --git a/packages/ui/tailwind-utils-config/components/breadcrumb.ts b/packages/ui/tailwind-utils-config/components/breadcrumb.ts index 8745fb86ff..6387112341 100644 --- a/packages/ui/tailwind-utils-config/components/breadcrumb.ts +++ b/packages/ui/tailwind-utils-config/components/breadcrumb.ts @@ -49,7 +49,11 @@ export default { }, '.cn-breadcrumb-link': { - '@apply transition-colors': '' + '@apply transition-colors': '', + + '&:focus': { + outline: 'var(--cn-focus)' + } }, '.cn-breadcrumb-separator': { diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index bfd975840c..bebc1437ef 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -20,11 +20,6 @@ function createButtonVariantStyles() { variants.forEach(variant => { themes.forEach(theme => { - // Skip solid variant for success and danger themes - if (variant === 'solid' && (theme === 'success' || theme === 'danger')) { - return - } - const style: CSSRuleObject = {} const themeStyle = themeStyleMapper[theme as keyof typeof themeStyleMapper] @@ -79,7 +74,8 @@ function createButtonVariantStyles() { * Some variants don't have separator * Hence adding border color for separator * */ - backgroundColor: `var(--cn-set-${themeStyle}-${variant}-separator, var(--cn-set-${themeStyle}-${variant}-border))` + backgroundColor: `var(--cn-set-${themeStyle}-${variant}-text)`, + opacity: 'var(--cn-opacity-20)' } } } @@ -148,12 +144,13 @@ export default { backgroundOrigin: 'border-box', backgroundClip: 'padding-box, border-box', border: 'var(--cn-badge-border) solid transparent', + boxShadow: 'var(--cn-shadow-comp-ai-inner)', '&:hover:not(:disabled, .cn-button-disabled)': { backgroundImage: `linear-gradient(to right, var(--cn-set-ai-surface-bg-hover), var(--cn-set-ai-surface-bg-hover)), var(--cn-set-ai-surface-border)` }, '&:active:not(:disabled, .cn-button-disabled), &:where(.cn-button-active)': { - backgroundImage: `linear-gradient(to right, var(--cn-set-ai-surface-bg-selected), var(--cn-set-ai-surface-bg-selected)), var(--cn-set-ai-surface-border)` + backgroundImage: 'var(--cn-set-ai-surface-bg-selected), var(--cn-set-ai-surface-border)' } }, @@ -227,8 +224,8 @@ export default { // Focus '&:where(:focus-visible)': { - boxShadow: 'var(--cn-ring-focus)', - outline: 'none', + outline: 'var(--cn-focus)', + outlineOffset: 'var(--cn-size-px)', // This is to prevent focus outline from being hidden by dropdown position: 'relative', diff --git a/packages/ui/tailwind-utils-config/components/card-select.ts b/packages/ui/tailwind-utils-config/components/card-select.ts index e8d4ec3edb..9574bafbbe 100644 --- a/packages/ui/tailwind-utils-config/components/card-select.ts +++ b/packages/ui/tailwind-utils-config/components/card-select.ts @@ -44,6 +44,11 @@ export default { borderColor: 'var(--cn-border-brand)' }, + '&:where(:not([data-disabled])):focus': { + outline: 'var(--cn-focus)', + outlineOffset: 'calc(var(--cn-size-px) * -2)' + }, + '&:where([data-disabled])': { opacity: 'var(--cn-disabled-opacity)', '@apply cursor-not-allowed': '' @@ -51,7 +56,7 @@ export default { '&:where([data-state="checked"])': { borderColor: 'var(--cn-border-brand)', - backgroundImage: 'var(--cn-comp-card-gradient)' + background: 'var(--cn-set-brand-soft-bg)' }, '.cn-card-select-content': { @@ -67,9 +72,7 @@ export default { right: 'var(--cn-spacing-4)', top: '50%', transform: 'translateY(-50%)', - width: '1rem', - height: '1rem', - color: 'var(--cn-text-1)' + color: 'var(--cn-text-brand)' }, '.cn-card-select-title': { diff --git a/packages/ui/tailwind-utils-config/components/card.ts b/packages/ui/tailwind-utils-config/components/card.ts index 4440bbb255..3bb248cb8e 100644 --- a/packages/ui/tailwind-utils-config/components/card.ts +++ b/packages/ui/tailwind-utils-config/components/card.ts @@ -76,7 +76,7 @@ export default { }, '&:where(.cn-card-selected)': { - backgroundColor: 'var(--cn-comp-card-gradient)', + backgroundColor: 'var(--cn-set-brand-soft-bg)', borderColor: 'var(--cn-border-accent)' }, '&:where(.cn-card-disabled)': { diff --git a/packages/ui/tailwind-utils-config/components/checkbox.ts b/packages/ui/tailwind-utils-config/components/checkbox.ts index 2a2c5c75ee..17dd1f8175 100644 --- a/packages/ui/tailwind-utils-config/components/checkbox.ts +++ b/packages/ui/tailwind-utils-config/components/checkbox.ts @@ -33,7 +33,7 @@ export default { backgroundColor: 'var(--cn-comp-selection-unselected-bg)', '&:where(:not([disabled])):focus': { - boxShadow: 'var(--cn-ring-focus)' + outline: 'var(--cn-focus)' }, '&:where(:not([disabled])):hover': { diff --git a/packages/ui/tailwind-utils-config/components/icon-and-logo.ts b/packages/ui/tailwind-utils-config/components/icon-and-logo.ts index bdc2e677d1..70494bc736 100644 --- a/packages/ui/tailwind-utils-config/components/icon-and-logo.ts +++ b/packages/ui/tailwind-utils-config/components/icon-and-logo.ts @@ -1,7 +1,7 @@ import { CSSRuleObject } from 'tailwindcss/types/config' const iconSizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'] as const -const logoSizes = ['sm', 'md', 'lg'] as const +const logoSizes = ['xs', 'sm', 'md', 'lg'] as const function createIconandLogoSizeStyles(entity: 'icon' | 'logo') { // change it to logoSizes diff --git a/packages/ui/tailwind-utils-config/components/link.ts b/packages/ui/tailwind-utils-config/components/link.ts index 0ba5dea97c..230f254e97 100644 --- a/packages/ui/tailwind-utils-config/components/link.ts +++ b/packages/ui/tailwind-utils-config/components/link.ts @@ -26,13 +26,17 @@ export default { '&:where([data-disabled="false"])': { '&:where(.cn-link-default)': { - '&:hover, &:focus, &:where([data-hovered="true"])': { + '&:hover, &:where([data-hovered="true"])': { color: 'var(--cn-comp-link-text-hover)' } }, - '&:hover, &:focus, &:where([data-hovered="true"])': { + '&:hover, &:where([data-hovered="true"])': { textDecorationColor: 'inherit' + }, + + '&:focus': { + outline: 'var(--cn-focus)' } }, diff --git a/packages/ui/tailwind-utils-config/components/radio.ts b/packages/ui/tailwind-utils-config/components/radio.ts index a1a6283835..928c7bc639 100644 --- a/packages/ui/tailwind-utils-config/components/radio.ts +++ b/packages/ui/tailwind-utils-config/components/radio.ts @@ -34,7 +34,7 @@ export default { backgroundColor: 'var(--cn-comp-selection-unselected-bg)', '&:where(:not([disabled])):focus': { - boxShadow: 'var(--cn-ring-focus)' + outline: 'var(--cn-focus)' }, '&:where(:not([disabled])):hover': { @@ -65,7 +65,7 @@ export default { }, '.cn-radio-error:not(:has(.cn-radio-item[data-state=checked])) .cn-radio-item': { - '&:where(:not([disabled]))' : { + '&:where(:not([disabled]))': { borderColor: 'var(--cn-border-danger)', boxShadow: `var(--cn-ring-danger)`, '&:where(:hover)': { diff --git a/packages/ui/tailwind-utils-config/components/skeleton.ts b/packages/ui/tailwind-utils-config/components/skeleton.ts index 905f3b3a3f..a12c64dc9e 100644 --- a/packages/ui/tailwind-utils-config/components/skeleton.ts +++ b/packages/ui/tailwind-utils-config/components/skeleton.ts @@ -2,7 +2,7 @@ import { CSSRuleObject } from 'tailwindcss/types/config' const sizesAvatar = ['sm', 'md', 'lg'] as const const iconSizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'] as const -const logoSizes = ['sm', 'md', 'lg'] as const +const logoSizes = ['xs', 'sm', 'md', 'lg'] as const const inputSizes = ['sm'] as const function createSkeletonAvatarVariantStyles() { @@ -18,6 +18,9 @@ function createSkeletonAvatarVariantStyles() { return styles } +/** + * TODO: Design System: Reuse styles from icons-and-logo.ts + */ function createIconandLogoSizeStyles(entity: 'icon' | 'logo') { const sizes = entity === 'icon' ? iconSizes : logoSizes diff --git a/packages/ui/tailwind-utils-config/components/switch.ts b/packages/ui/tailwind-utils-config/components/switch.ts index fd9b019953..e56ee44ab9 100644 --- a/packages/ui/tailwind-utils-config/components/switch.ts +++ b/packages/ui/tailwind-utils-config/components/switch.ts @@ -24,7 +24,8 @@ export default { borderColor: `var(--cn-comp-selection-unselected-border)`, '&:where(:not([disabled])):focus': { - boxShadow: 'var(--cn-ring-focus)' + outline: 'var(--cn-focus)', + outlineOffset: 'calc(var(--cn-size-px) * -2)' }, '&:where(:not([disabled])):hover': { diff --git a/packages/ui/tailwind-utils-config/components/tabs.ts b/packages/ui/tailwind-utils-config/components/tabs.ts index 5b1c767ece..74d54d9b34 100644 --- a/packages/ui/tailwind-utils-config/components/tabs.ts +++ b/packages/ui/tailwind-utils-config/components/tabs.ts @@ -100,7 +100,7 @@ export default { }, '&:where(:not([disabled]):hover)': { - color: 'var(--cn-text-1)' + color: 'var(--cn-text-brand)' } }, @@ -121,7 +121,7 @@ export default { }, '&:where(:not([disabled]):hover)': { - color: 'var(--cn-text-1)' + color: 'var(--cn-text-brand)' } }, From 6d200f9eff70bddfbcfa5c96e230b4bed5dc661a Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Tue, 19 Aug 2025 20:35:54 +0000 Subject: [PATCH 146/180] fix: multiple p3 bugs (#10248) * c4ded1 chore: cleanup * 430bfc fix: p3 bugs --- packages/ui/src/components/markdown-viewer/index.tsx | 2 +- .../details/components/conversation/pull-request-panel.tsx | 1 - packages/ui/src/views/repo/repo-summary/repo-summary.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/markdown-viewer/index.tsx b/packages/ui/src/components/markdown-viewer/index.tsx index 9cc84e9b08..69ec165eff 100644 --- a/packages/ui/src/components/markdown-viewer/index.tsx +++ b/packages/ui/src/components/markdown-viewer/index.tsx @@ -281,7 +281,7 @@ export function MarkdownViewer({ return <CodeSuggestionBlock code={code} suggestionBlock={suggestionBlock} /> } - return <code className={String(_className)}>{children}</code> + return <code className={`!whitespace-pre-wrap ${String(_className)}`}>{children}</code> } }} /> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index ced26c87a7..7f78e1c006 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -663,7 +663,6 @@ const PullRequestPanel = ({ })} disabled={buttonState.disabled} loading={actions[parseInt(mergeButtonValue)]?.loading} - selectedValue={mergeButtonValue} handleOptionChange={handleMergeTypeSelect} options={actions.map(action => { return { diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 64eccdf104..f6818ae13f 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -260,7 +260,7 @@ export function RepoSummaryView({ /> </StackedList.Item> </StackedList.Root> - <MarkdownViewer source={decodedReadmeContent || ''} withBorder /> + <MarkdownViewer source={decodedReadmeContent || ''} withBorder className="text-wrap" /> </SandboxLayout.Content> </SandboxLayout.Column> <SandboxLayout.Column> From 0237ffef4bc497eadf02d049d2a0daa5dcf12de1 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Wed, 20 Aug 2025 04:36:56 +0000 Subject: [PATCH 147/180] fix: p3/p4 UX issues (#10249) * c13bb4 fix: ux issues * 2f0f42 fix: p3/p4 UX issues --- packages/ui/src/components/search-files.tsx | 4 +++- packages/ui/src/components/time-ago-card.tsx | 7 ++++++- .../conversation/sections/pull-request-changes-section.tsx | 4 ++-- packages/ui/src/views/repo/repo-list/repo-list-page.tsx | 2 +- .../ui/tailwind-utils-config/components/time-ago-card.ts | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index b5bf1b6097..c4994d8f09 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -115,7 +115,9 @@ export const SearchFiles = ({ /> )) ) : ( - <DropdownMenu.NoOptions>{t('component:searchFile.noFile', 'No file found.')}</DropdownMenu.NoOptions> + <DropdownMenu.NoOptions className="!p-2"> + {t('component:searchFile.noFile', 'No file found.')} + </DropdownMenu.NoOptions> )} </DropdownMenu.Content> </DropdownMenu.Root> diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index 1e160e4101..a25e3e7190 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -190,7 +190,12 @@ export const TimeAgoCard = memo( return ( <Popover.Root open={isOpen} onOpenChange={setIsOpen}> <Popover.Trigger className={cn('cn-time-ago-card-trigger', triggerClassName)} onClick={handleClick} ref={ref}> - <Text<'time'> as="time" {...textProps} ref={textProps?.ref as Ref<HTMLTimeElement>}> + <Text<'time'> + as="time" + {...textProps} + ref={textProps?.ref as Ref<HTMLTimeElement>} + className="text-cn-foreground-accent" + > {prefix ? `${prefix} ${formattedShort}` : formattedShort} </Text> </Popover.Trigger> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx index 35f0d2f1ca..8cae7d6a05 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-changes-section.tsx @@ -78,7 +78,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ {((minApproval ?? 0) > (minReqLatestApproval ?? 0) || (!isEmpty(approvedEvaluations) && minReqLatestApproval === 0 && minApproval && minApproval > 0) || ((minApproval ?? 0) > 0 && minReqLatestApproval === undefined)) && ( - <div className="ml-7 flex items-center justify-between"> + <div className="flex items-center justify-between"> {approvedEvaluations && minApproval && minApproval <= approvedEvaluations?.length ? ( <div className="flex items-center gap-x-2"> <IconV2 size="md" name="check-circle-solid" className="text-cn-icon-success" /> @@ -110,7 +110,7 @@ const PullRequestChangesSection: FC<PullRequestChangesSectionProps> = ({ )} {(minReqLatestApproval ?? 0) > 0 && ( - <div className="ml-7 flex items-center justify-between"> + <div className="flex items-center justify-between"> {latestApprovalArr !== undefined && minReqLatestApproval !== undefined && minReqLatestApproval <= latestApprovalArr?.length ? ( diff --git a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx index c5a1b6a2e4..33ec590d46 100644 --- a/packages/ui/src/views/repo/repo-list/repo-list-page.tsx +++ b/packages/ui/src/views/repo/repo-list/repo-list-page.tsx @@ -117,7 +117,7 @@ const SandboxRepoListPage: FC<RepoListPageProps> = ({ type: FilterFieldTypes.Checkbox, sticky: true, filterFieldConfig: { - label: <IconV2 name="star-solid" size="md" className="text-cn-icon-yellow" /> + label: <IconV2 name="star-solid" size="md" className="text-cn-icon-yellow cursor-pointer" /> }, parser: booleanParser } diff --git a/packages/ui/tailwind-utils-config/components/time-ago-card.ts b/packages/ui/tailwind-utils-config/components/time-ago-card.ts index aa5bf8062d..45b17a5353 100644 --- a/packages/ui/tailwind-utils-config/components/time-ago-card.ts +++ b/packages/ui/tailwind-utils-config/components/time-ago-card.ts @@ -3,7 +3,7 @@ export default { '&-trigger': { '@apply leading-snug': '', ':where(time)': { - '@apply data-[state=open]:text-cn-foreground-1 hover:text-cn-foreground-1': '' + '@apply data-[state=open]:text-cn-foreground-1': '' }, '&:where(:focus-visible) time': { '@apply text-cn-foreground-1': '' From 07254db2e8211a4b998ca0a7d37dec39771a41ea Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:12:29 +0400 Subject: [PATCH 148/180] add props to dropdwon subcontent (#2082) --- packages/ui/src/components/dropdown-menu.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index e40fb0174d..0cf6e1e031 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -155,10 +155,14 @@ interface DropdownMenuItemProps extends Omit<DropdownBaseItemProps, 'withSubIndicator'>, Omit<ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>, 'title' | 'prefix'> { prefix?: ReactNode + subContentProps?: Omit<DropdownMenuContentProps, 'isSubContent'> } const DropdownMenuItem = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Item>, DropdownMenuItemProps>( - ({ className, children, title, description, label, shortcut, checkmark, prefix, tag, ...props }, ref) => { + ( + { className, children, title, description, label, shortcut, checkmark, prefix, tag, subContentProps, ...props }, + ref + ) => { const filteredChildren = filterChildrenByDisplayNames(children, innerComponentsDisplayNames) const withChildren = filteredChildren.length > 0 @@ -178,7 +182,9 @@ const DropdownMenuItem = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Item > <ItemContent /> </DropdownMenuSubTrigger> - <DropdownMenuContent isSubContent>{filteredChildren}</DropdownMenuContent> + <DropdownMenuContent isSubContent {...subContentProps}> + {filteredChildren} + </DropdownMenuContent> </DropdownMenuSub> ) } @@ -194,7 +200,9 @@ DropdownMenuItem.displayName = displayNames.item interface DropdownMenuCheckboxItemProps extends Omit<DropdownBaseItemProps, 'withSubIndicator'>, - Omit<ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>, 'title' | 'onSelect'> {} + Omit<ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>, 'title' | 'onSelect'> { + subContentProps?: Omit<DropdownMenuContentProps, 'isSubContent'> +} const DropdownMenuCheckboxItem = forwardRef< ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, @@ -213,6 +221,7 @@ const DropdownMenuCheckboxItem = forwardRef< tag, onCheckedChange, onClick, + subContentProps, ...props }, ref @@ -256,7 +265,9 @@ const DropdownMenuCheckboxItem = forwardRef< > <ItemContent /> </DropdownMenuSubTrigger> - <DropdownMenuContent isSubContent>{filteredChildren}</DropdownMenuContent> + <DropdownMenuContent isSubContent {...subContentProps}> + {filteredChildren} + </DropdownMenuContent> </DropdownMenuSub> ) } From 6dafbe5b5f41292d0bef8def650e45dc09edf117 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Wed, 20 Aug 2025 11:17:57 +0300 Subject: [PATCH 149/180] Fix input focus and keyboard navigation in the SearchFiles dropdown (#2079) --- packages/ui/src/components/dropdown-menu.tsx | 1 + packages/ui/src/components/search-files.tsx | 65 +++++++++++++++++++- packages/ui/src/utils/after-frames.ts | 14 +++++ packages/ui/src/utils/index.ts | 1 + 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 packages/ui/src/utils/after-frames.ts diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index 0cf6e1e031..9bd949fcbc 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -64,6 +64,7 @@ const innerComponentsDisplayNames = [ interface DropdownMenuContentProps extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> { isSubContent?: boolean scrollAreaProps?: Omit<ScrollAreaProps, 'children'> + onOpenAutoFocus?: (event: Event) => void } const DropdownMenuContent = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Content>, DropdownMenuContentProps>( diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index c4994d8f09..4b8e66b3b3 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -1,7 +1,8 @@ -import { ReactNode, useCallback, useEffect, useState } from 'react' +import { KeyboardEvent, ReactNode, useCallback, useEffect, useRef, useState } from 'react' import { DropdownMenu, SearchInput, SearchInputProps, Text } from '@/components' import { useTranslation } from '@/context' +import { afterFrames } from '@/utils' import { cn } from '@utils/cn' const markedFileClassName = 'w-full text-cn-foreground-1' @@ -58,6 +59,8 @@ export const SearchFiles = ({ const [isOpen, setIsOpen] = useState(false) const [filteredFiles, setFilteredFiles] = useState<FilteredFile[]>([]) const [currentQuery, setCurrentQuery] = useState('') + const inputRef = useRef<HTMLInputElement | null>(null) + const contentRef = useRef<HTMLDivElement | null>(null) const { t } = useTranslation() useEffect(() => { @@ -94,14 +97,70 @@ export const SearchFiles = ({ setCurrentQuery(searchQuery) }, []) + const getItems = useCallback(() => { + if (!contentRef.current) return [] + return Array.from( + contentRef.current?.querySelectorAll<HTMLElement>('[data-radix-collection-item]:not([data-disabled])') + ) + }, []) + + const focusItem = useCallback( + (isFirst = true) => { + const items = getItems() + items[isFirst ? 0 : items.length - 1]?.focus() + }, + [getItems] + ) + + const handleInputKeyDown = (e: KeyboardEvent<HTMLInputElement>) => { + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + e.preventDefault() + afterFrames(() => focusItem(e.key === 'ArrowDown')) + } + } + + const handleContentKeyDownCapture = (e: KeyboardEvent<HTMLDivElement>) => { + const items = getItems() + + if (!items.length) return + + const first = items[0] + const last = items[items.length - 1] + const activeElement = document.activeElement?.shadowRoot?.activeElement ?? document.activeElement + + if (e.key === 'ArrowUp' && activeElement === first) { + e.preventDefault() + inputRef.current?.focus() + setIsOpen(true) + return + } + + if (e.key === 'ArrowDown' && activeElement === last) { + e.preventDefault() + inputRef.current?.focus() + setIsOpen(true) + } + } + return ( <DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen} modal={false}> <div className={cn('relative', inputContainerClassName)}> <DropdownMenu.Trigger className="pointer-events-none absolute inset-0 -z-0" tabIndex={-1} /> - <SearchInput size={searchInputSize} onChange={handleInputChange} /> + <SearchInput + ref={inputRef} + size={searchInputSize} + onChange={handleInputChange} + onKeyDown={handleInputKeyDown} + /> </div> - <DropdownMenu.Content align="start" className={cn('max-h-96', 'width-popover-max-width', contentClassName)}> + <DropdownMenu.Content + ref={contentRef} + className={cn('max-h-96', 'width-popover-max-width', contentClassName)} + align="start" + onKeyDownCapture={handleContentKeyDownCapture} + onOpenAutoFocus={event => event.preventDefault()} + > {filteredFiles.length ? ( filteredFiles?.map(({ file, element }) => ( <DropdownMenu.IconItem diff --git a/packages/ui/src/utils/after-frames.ts b/packages/ui/src/utils/after-frames.ts new file mode 100644 index 0000000000..0b8be22c0b --- /dev/null +++ b/packages/ui/src/utils/after-frames.ts @@ -0,0 +1,14 @@ +export const afterFrames = (cb: () => void, frames = 2) => { + let cancelled = false + const step = () => { + requestAnimationFrame(() => { + if (cancelled) return + frames -= 1 + frames <= 0 ? cb() : step() + }) + } + step() + return () => { + cancelled = true + } +} diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index c03e7c1763..c461363713 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -5,5 +5,6 @@ export * from './typeUtils' export * from './mergeUtils' export * from './TimeUtils' export * from './getComponentDisplayName' +export * from './after-frames' export { formatDistanceToNow } from 'date-fns' From 88f815642336ca5189aff72e1d6ca262be05f0b6 Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:59:57 +0400 Subject: [PATCH 150/180] add comment truncation (#2085) --- .../components/pull-request-diff-viewer.tsx | 55 +++++++------- .../pull-request-timeline-item.tsx | 39 +++++----- .../context/pull-request-comments-context.ts | 51 +++++++++++++ .../pull-request-conversation-page.tsx | 72 ++++++++++--------- 4 files changed, 139 insertions(+), 78 deletions(-) create mode 100644 packages/ui/src/views/repo/pull-request/details/context/pull-request-comments-context.ts diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index e7052dc0a6..5eaa7600ea 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -24,6 +24,7 @@ import { OverlayScrollbars } from 'overlayscrollbars' import PRCommentView from '../details/components/common/pull-request-comment-view' import PullRequestTimelineItem from '../details/components/conversation/pull-request-timeline-item' import { replaceMentionEmailWithId, replaceMentionIdWithEmail } from '../details/components/conversation/utils' +import { ExpandedCommentsContext, useExpandedCommentsContext } from '../details/context/pull-request-comments-context' import { useDiffHighlighter } from '../hooks/useDiffHighlighter' import { quoteTransform } from '../utils' import { ExtendedDiffView } from './extended-diff-view/extended-diff-view' @@ -369,7 +370,7 @@ const PullRequestDiffViewer = ({ const commentText = newComments[commentKey] ?? '' return ( - <div className="flex w-full flex-col bg-cn-background-1 p-4"> + <div className="bg-cn-background-1 flex w-full flex-col p-4"> <PullRequestCommentBox autofocus handleUpload={handleUpload} @@ -689,31 +690,35 @@ const PullRequestDiffViewer = ({ () => !!fileName ) + const contextValue = useExpandedCommentsContext() + return ( - <div data-diff-file-path={fileName}> - {diffFileInstance && ( - <div ref={diffInstanceRef}> - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - <ExtendedDiffView<Thread[]> - ref={ref} - className="bg-tr w-full text-cn-foreground-1" - renderWidgetLine={renderWidgetLine} - renderExtendLine={renderExtendLine} - diffFile={diffFileInstance} - extendData={extend} - diffViewFontSize={fontsize} - diffViewHighlight={highlight} - diffViewMode={mode} - registerHighlighter={highlighter} - diffViewWrap={wrap} - // TODO: Remove 'mode === DiffModeEnum.Split' after the shadow dom is removed - diffViewAddWidget={addWidget && mode === DiffModeEnum.Split} - diffViewTheme={isLightTheme ? 'light' : 'dark'} - /> - </div> - )} - </div> + <ExpandedCommentsContext.Provider value={contextValue}> + <div data-diff-file-path={fileName}> + {diffFileInstance && ( + <div ref={diffInstanceRef}> + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + <ExtendedDiffView<Thread[]> + ref={ref} + className="bg-tr text-cn-foreground-1 w-full" + renderWidgetLine={renderWidgetLine} + renderExtendLine={renderExtendLine} + diffFile={diffFileInstance} + extendData={extend} + diffViewFontSize={fontsize} + diffViewHighlight={highlight} + diffViewMode={mode} + registerHighlighter={highlighter} + diffViewWrap={wrap} + // TODO: Remove 'mode === DiffModeEnum.Split' after the shadow dom is removed + diffViewAddWidget={addWidget && mode === DiffModeEnum.Split} + diffViewTheme={isLightTheme ? 'light' : 'dark'} + /> + </div> + )} + </div> + </ExpandedCommentsContext.Provider> ) } diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx index 540d8024f5..39a71d570b 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-timeline-item.tsx @@ -22,6 +22,7 @@ import { import { cn } from '@utils/cn' import { isEmpty } from 'lodash-es' +import { useExpandedComments } from '../../context/pull-request-comments-context' import { replaceEmailAsKey, replaceMentionEmailWithId } from './utils' //Utility function to calculate thread spacing based on position @@ -247,7 +248,14 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ totalThreads }) => { const [comment, setComment] = useState('') - const [isExpanded, setIsExpanded] = useState(!isResolved) + const { isExpanded: getIsExpanded, toggleExpanded } = useExpandedComments() + + const expandedKey = parentCommentId || commentId || 0 + const isExpanded = !isResolved || getIsExpanded(expandedKey) + + const handleToggleExpanded = useCallback(() => { + toggleExpanded(expandedKey) + }, [expandedKey, toggleExpanded]) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [isDeletingComment, setIsDeletingComment] = useState(false) @@ -268,12 +276,6 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ if (quoteReplyText) setComment(quoteReplyText) }, [quoteReplyText]) - useEffect(() => { - if (isResolved) { - setIsExpanded(false) - } - }, [isResolved]) - const handleOpenDeleteDialog = useCallback(() => { setIsDeleteDialogOpen(true) }, []) @@ -316,6 +318,13 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ return content } + const renderToggleButton = () => ( + <Button variant="transparent" onClick={handleToggleExpanded}> + <IconV2 name={isExpanded ? 'collapse-code' : 'expand-code'} size="xs" /> + {isExpanded ? 'Hide resolved' : 'Show resolved'} + </Button> + ) + return ( <> <div id={id}> @@ -342,12 +351,7 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ }} hideEditDelete={hideEditDelete} /> - {isResolved && !isComment && !contentHeader && ( - <Button variant="transparent" onClick={() => setIsExpanded(prev => !prev)}> - <IconV2 name={isExpanded ? 'collapse-code' : 'expand-code'} size="xs" /> - {isExpanded ? 'Hide resolved' : 'Show resolved'} - </Button> - )} + {isResolved && !isComment && !contentHeader && renderToggleButton()} </div> )} </NodeGroup.Title> @@ -357,12 +361,7 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ {!!contentHeader && ( <Layout.Horizontal align="center" justify="between" className={cn('p-2 px-4 bg-cn-background-2')}> {contentHeader} - {isResolved && ( - <Button variant="transparent" onClick={() => setIsExpanded(prev => !prev)}> - <IconV2 name={isExpanded ? 'collapse-code' : 'expand-code'} size="xs" /> - {isExpanded ? 'Hide resolved' : 'Show resolved'} - </Button> - )} + {isResolved && renderToggleButton()} </Layout.Horizontal> )} @@ -390,7 +389,7 @@ const PullRequestTimelineItem: FC<TimelineItemProps> = ({ setComment={setComment} /> ) : ( - renderContent() + <div className={cn(isExpanded ? '' : 'line-clamp-1')}>{renderContent()}</div> )} {!hideReplySection && (!isResolved || isExpanded) && ( diff --git a/packages/ui/src/views/repo/pull-request/details/context/pull-request-comments-context.ts b/packages/ui/src/views/repo/pull-request/details/context/pull-request-comments-context.ts new file mode 100644 index 0000000000..47c304433b --- /dev/null +++ b/packages/ui/src/views/repo/pull-request/details/context/pull-request-comments-context.ts @@ -0,0 +1,51 @@ +import { createContext, useCallback, useContext, useMemo, useState } from 'react' + +export interface ExpandedCommentsContextType { + expandedComments: Set<number> + toggleExpanded: (commentId: number) => void + isExpanded: (commentId: number) => boolean +} + +export const ExpandedCommentsContext = createContext<ExpandedCommentsContextType | undefined>(undefined) + +export const useExpandedComments = () => { + const context = useContext(ExpandedCommentsContext) + if (!context) { + throw new Error('useExpandedComments must be used within ExpandedCommentsProvider') + } + return context +} + +export const useExpandedCommentsContext = () => { + const [expandedComments, setExpandedComments] = useState<Set<number>>(new Set()) + + const toggleExpanded = useCallback((commentId: number) => { + setExpandedComments(prev => { + const next = new Set(prev) + if (next.has(commentId)) { + next.delete(commentId) + } else { + next.add(commentId) + } + return next + }) + }, []) + + const isExpanded = useCallback( + (commentId: number) => { + return expandedComments.has(commentId) + }, + [expandedComments] + ) + + const contextValue = useMemo( + () => ({ + expandedComments, + toggleExpanded, + isExpanded + }), + [expandedComments, toggleExpanded, isExpanded] + ) + + return contextValue +} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx index 481601e37d..763e1fa1f8 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx @@ -17,6 +17,8 @@ import { } from '@/views' import { noop } from 'lodash-es' +import { ExpandedCommentsContext, useExpandedCommentsContext } from './context/pull-request-comments-context' + export interface PullRequestConversationPageProps { rebaseErrorMessage: string | null panelProps: PullRequestPanelProps @@ -36,45 +38,49 @@ export const PullRequestConversationPage: FC<PullRequestConversationPageProps> = sideBarProps, principalProps }) => { + const contextValue = useExpandedCommentsContext() + return ( - <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px" className="mt-cn-sm"> - <SandboxLayout.Column> - <SandboxLayout.Content className="pl-0 pr-cn-xl pt-0"> - {/*TODO: update with design */} - {!!rebaseErrorMessage && ( - <Alert.Root theme="danger" className="mb-5" dismissible> - <Alert.Title>Cannot rebase branch</Alert.Title> - <Alert.Description> - <p>{rebaseErrorMessage}</p> - </Alert.Description> - </Alert.Root> - )} + <ExpandedCommentsContext.Provider value={contextValue}> + <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px" className="mt-cn-sm"> + <SandboxLayout.Column> + <SandboxLayout.Content className="pl-0 pr-cn-xl pt-0"> + {/*TODO: update with design */} + {!!rebaseErrorMessage && ( + <Alert.Root theme="danger" className="mb-5" dismissible> + <Alert.Title>Cannot rebase branch</Alert.Title> + <Alert.Description> + <p>{rebaseErrorMessage}</p> + </Alert.Description> + </Alert.Root> + )} - <PullRequestPanel {...panelProps} /> - <Spacer size={10} /> + <PullRequestPanel {...panelProps} /> + <Spacer size={10} /> - <PullRequestFilters {...filtersProps} /> - <Spacer size={6} /> + <PullRequestFilters {...filtersProps} /> + <Spacer size={6} /> - <PullRequestOverview {...overviewProps} principalProps={principalProps} /> - <Spacer size={10} /> + <PullRequestOverview {...overviewProps} principalProps={principalProps} /> + <Spacer size={10} /> - <PullRequestCommentBox {...commentBoxProps} principalProps={principalProps} /> - </SandboxLayout.Content> - </SandboxLayout.Column> + <PullRequestCommentBox {...commentBoxProps} principalProps={principalProps} /> + </SandboxLayout.Content> + </SandboxLayout.Column> - <SandboxLayout.Column> - <SandboxLayout.Content className="px-0 pt-0"> - <PullRequestSideBar - {...sideBarProps} - isReviewersLoading={principalProps?.isPrincipalsLoading} - searchQuery={principalProps?.searchPrincipalsQuery || ''} - setSearchQuery={principalProps?.setSearchPrincipalsQuery || noop} - usersList={principalProps?.principals} - /> - </SandboxLayout.Content> - </SandboxLayout.Column> - </SandboxLayout.Columns> + <SandboxLayout.Column> + <SandboxLayout.Content className="px-0 pt-0"> + <PullRequestSideBar + {...sideBarProps} + isReviewersLoading={principalProps?.isPrincipalsLoading} + searchQuery={principalProps?.searchPrincipalsQuery || ''} + setSearchQuery={principalProps?.setSearchPrincipalsQuery || noop} + usersList={principalProps?.principals} + /> + </SandboxLayout.Content> + </SandboxLayout.Column> + </SandboxLayout.Columns> + </ExpandedCommentsContext.Provider> ) } PullRequestConversationPage.displayName = 'PullRequestConversationPage-UI' From 36906683e2d73ab6f104a9feb1248eca754fea03 Mon Sep 17 00:00:00 2001 From: Ritik Kapoor <ritik.kapoor@harness.io> Date: Wed, 20 Aug 2025 12:09:32 +0000 Subject: [PATCH 151/180] fix: ai summary calls for all refs (#10254) * 2acd35 fix: ai summary calls for all refs --- apps/gitness/src/hooks/useCodePathDetails.ts | 2 ++ .../src/pages-v2/pull-request/pull-request-compare.tsx | 10 +++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/gitness/src/hooks/useCodePathDetails.ts b/apps/gitness/src/hooks/useCodePathDetails.ts index 827f84f962..4da9c4416e 100644 --- a/apps/gitness/src/hooks/useCodePathDetails.ts +++ b/apps/gitness/src/hooks/useCodePathDetails.ts @@ -5,6 +5,8 @@ import { CodeModes } from '@harnessio/ui/views' import { isRefABranch, isRefATag, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../utils/git-utils' import { removeLeadingSlash, removeTrailingSlash } from '../utils/path-utils' +// NOTE: This hook is used to get the gitRef and resourcePath from the URL TO BE USED ONLY FOR REPO FILES / SUMMARY COMPONENTS + const useCodePathDetails = () => { const params = useParams() const subCodePath = params['*'] || '' diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx index edfed84792..b9e9414617 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx @@ -42,7 +42,6 @@ import { useIsMFE } from '../../framework/hooks/useIsMFE.ts' import { useMFEContext } from '../../framework/hooks/useMFEContext' import { useQueryState } from '../../framework/hooks/useQueryState' import { useAPIPath } from '../../hooks/useAPIPath.ts' -import { useGitRef } from '../../hooks/useGitRef.ts' import { PathParams } from '../../RouteDefinitions' import { decodeGitContent, normalizeGitRef } from '../../utils/git-utils' import { getFileExtension } from '../../utils/path-utils.ts' @@ -483,7 +482,6 @@ export const CreatePullRequest = () => { } const getApiPath = useAPIPath() - const { fullGitRef: baseRef } = useGitRef() const mutation = useMutation(async ({ repoMetadata, baseRef, headRef }: AiPullRequestSummaryParams) => { return fetch(getApiPath(`/api/v1/repos/${repoMetadata.path}/+/genai/change-summary`), { @@ -500,13 +498,11 @@ export const CreatePullRequest = () => { }) const handleAiPullRequestSummary = useCallback(async () => { - if (repoMetadata && repoMetadata.path && selectedSourceBranch?.name) { - const headRef = `refs/heads/${selectedSourceBranch.name}` - + if (repoMetadata && repoMetadata.path && selectedSourceBranch?.name && selectedTargetBranch?.name) { return await mutation.mutateAsync({ repoMetadata, - baseRef, - headRef + baseRef: normalizeGitRef(selectedTargetBranch.name) || '', + headRef: normalizeGitRef(selectedSourceBranch.name) || '' }) } From ffa70975c33559b97b2a621ecc7e185aa171ff48 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Wed, 20 Aug 2025 14:58:38 +0000 Subject: [PATCH 152/180] PR Diff - File rename display (#10251) * 1fd85c Fixed comment display in renamed files * 2922d6 PR Diff - File rename display --- .../pull-request-compare.tsx | 4 +- .../pull-request/pull-request-changes.tsx | 88 ++++++++++++++++++- .../pull-request/pull-request-compare.tsx | 5 +- .../src/pages-v2/pull-request/types.ts | 1 + .../components/pull-request-accordian.tsx | 17 +++- .../changes/pull-request-changes.tsx | 27 ++++-- .../details/pull-request-changes-page.tsx | 7 +- .../details/pull-request-details-types.ts | 2 +- 8 files changed, 133 insertions(+), 18 deletions(-) diff --git a/apps/design-system/src/subjects/views/pull-request-compare/pull-request-compare.tsx b/apps/design-system/src/subjects/views/pull-request-compare/pull-request-compare.tsx index 57c71ff05b..c63271dfe7 100644 --- a/apps/design-system/src/subjects/views/pull-request-compare/pull-request-compare.tsx +++ b/apps/design-system/src/subjects/views/pull-request-compare/pull-request-compare.tsx @@ -40,7 +40,6 @@ const PullRequestCompareWrapper: FC<Partial<PullRequestComparePageProps>> = prop isDeleted: false, filePath: 'bot.txt', diffData: { - fileId: 'some-id', containerId: 'some-container-id', contentId: 'some-content-id', isCombined: false, @@ -52,7 +51,8 @@ const PullRequestCompareWrapper: FC<Partial<PullRequestComparePageProps>> = prop language: 'txt', blocks: [], oldName: 'bot.txt', - newName: 'bot.txt' + newName: 'bot.txt', + isRename: false } } ]} diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx index 6be115fca2..fb4dd4840a 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-changes.tsx @@ -337,7 +337,93 @@ export default function PullRequestChanges() { raw: diffString } }) - accumulated = [...accumulated, ...chunk] + + // Helper function to check if two files might be a rename + const isRenameCandidate = (oldBaseName: string, newBaseName: string): boolean => { + const oldBaseWithoutExt = oldBaseName.replace(/\.[^.]*$/, '') + const newBaseWithoutExt = newBaseName.replace(/\.[^.]*$/, '') + + return ( + // Exact matches + oldBaseName === newBaseName || + // Same name without extension + oldBaseWithoutExt === newBaseWithoutExt || + // New name contains old name + (oldBaseWithoutExt.length > 1 && newBaseWithoutExt.includes(oldBaseWithoutExt)) || + // Old name contains new name (reverse case) + (newBaseWithoutExt.length > 1 && oldBaseWithoutExt.includes(newBaseWithoutExt)) + ) + } + + // Detect potential renames by comparing deleted and new files + const deletedFiles = chunk.filter(d => d.isDeleted && d.oldName && d.oldName !== '/dev/null') + const newFiles = chunk.filter(d => !d.isDeleted && d.newName && d.newName !== '/dev/null') + const potentialRenames: Array<{ oldPath: string; newPath: string }> = [] + + for (const deletedFile of deletedFiles) { + for (const newFile of newFiles) { + const oldBaseName = deletedFile.oldName?.split('/').pop() || '' + const newBaseName = newFile.newName?.split('/').pop() || '' + + if (isRenameCandidate(oldBaseName, newBaseName)) { + potentialRenames.push({ + oldPath: deletedFile.oldName!, + newPath: newFile.newName! + }) + } + } + } + + // Helper function to apply rename information to diff objects + const applyRenameInfo = (diff: any, matchingRename: { oldPath: string; newPath: string }) => { + diff.isRename = true + if (diff.isDeleted) { + diff.newName = matchingRename.newPath + } else { + diff.oldName = matchingRename.oldPath + } + } + + // Helper function to check if diff is a basic rename (root-level) + const isBasicRename = (diff: any): boolean => { + return !!( + diff.oldName && + diff.newName && + diff.oldName !== diff.newName && + diff.oldName !== '/dev/null' && + diff.newName !== '/dev/null' + ) + } + + // Apply detected renames and filter the chunk + const filteredChunk = chunk.filter(diff => { + // Handle explicit renames from Diff2Html + if (diff.isRename) { + return !diff.isDeleted + } + + // Handle detected renames + const matchingRename = potentialRenames.find( + rename => + (diff.isDeleted && diff.oldName === rename.oldPath) || + (!diff.isDeleted && diff.newName === rename.newPath) + ) + + if (matchingRename) { + applyRenameInfo(diff, matchingRename) + return !diff.isDeleted + } + + // Handle basic renames (root-level files) + if (isBasicRename(diff)) { + diff.isRename = true + return !diff.isDeleted + } + + return true + }) + + accumulated = [...accumulated, ...filteredChunk] setDiffs(accumulated) currentIndex = endIndex diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx index b9e9414617..f269638d00 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-compare.tsx @@ -582,7 +582,10 @@ export const CreatePullRequest = () => { unchangedPercentage: item.unchangedPercentage, blocks: item.blocks, filePath: item.filePath, - diffData: item + diffData: { + ...item, + filePath: item.filePath + } })) || [] : [] } diff --git a/apps/gitness/src/pages-v2/pull-request/types.ts b/apps/gitness/src/pages-v2/pull-request/types.ts index 2c8d8c07be..a6d832ff63 100644 --- a/apps/gitness/src/pages-v2/pull-request/types.ts +++ b/apps/gitness/src/pages-v2/pull-request/types.ts @@ -89,6 +89,7 @@ export interface DiffFileEntry extends DiffFile { containerId: string contentId: string fileViews?: Map<string, string> + isRename?: boolean } export const changesInfoAtom = atom<{ path?: string; raw?: string; fileViews?: Map<string, string> }>({}) diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 624de82bba..30d1ae4017 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -35,6 +35,9 @@ export interface HeaderProps { isDeleted: boolean unchangedPercentage?: number isBinary?: boolean + isRename?: boolean + oldName?: string + newName?: string } interface DataProps { @@ -99,7 +102,13 @@ export const LineTitle: React.FC<LineTitleProps> = ({ currentRefForDiff }) => { const { t } = useTranslation() - const { text, addedLines, deletedLines, filePath, checksumAfter, fileViews } = header + const { text, addedLines, deletedLines, filePath, checksumAfter, fileViews, isRename, oldName, newName } = header + + // Determine the display text and link path for renamed files + const displayText = isRename ? `${oldName} → ${newName}` : text + const linkPath = isRename ? newName : filePath + const copyText = isRename ? newName || text : text + return ( <div className="flex items-center justify-between gap-x-3"> <div className="inline-flex items-center gap-x-4"> @@ -117,12 +126,12 @@ export const LineTitle: React.FC<LineTitleProps> = ({ <IconV2 name={useFullDiff ? 'collapse-code' : 'expand-code'} /> </Button> <Link - to={toRepoFileDetails?.({ path: `files/${currentRefForDiff || sourceBranch}/~/${filePath}` }) ?? ''} + to={toRepoFileDetails?.({ path: `files/${currentRefForDiff || sourceBranch}/~/${linkPath}` }) ?? ''} className="font-medium leading-tight text-cn-foreground-1" > - {text} + {displayText} </Link> - <CopyButton name={text} size="xs" color="gray" /> + <CopyButton name={copyText} size="xs" color="gray" /> </div> <div className="flex items-center gap-x-1"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx index bacb15e902..a80e1c6a96 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx @@ -97,9 +97,16 @@ function PullRequestChangesInternal({ const getFileComments = (diffItem: HeaderProps) => { return ( comments?.filter((thread: CommentItem<TypesPullReqActivity>[]) => - thread.some( - (comment: CommentItem<TypesPullReqActivity>) => comment.payload?.payload?.code_comment?.path === diffItem.text - ) + thread.some((comment: CommentItem<TypesPullReqActivity>) => { + const commentPath = comment.payload?.payload?.code_comment?.path + if (!commentPath) return false + // Handle renamed files: check both old and new paths + if (diffItem.isRename && diffItem.oldName && diffItem.newName) { + return commentPath === diffItem.oldName || commentPath === diffItem.newName + } + // For non-renamed files + return commentPath === diffItem.text + }) ) || [] ) } @@ -181,10 +188,16 @@ function PullRequestChangesInternal({ // Filter activityBlocks that are relevant for this file const fileComments = comments?.filter((thread: CommentItem<TypesPullReqActivity>[]) => - thread.some( - (comment: CommentItem<TypesPullReqActivity>) => - comment.payload?.payload?.code_comment?.path === item.text - ) + thread.some((comment: CommentItem<TypesPullReqActivity>) => { + const commentPath = comment.payload?.payload?.code_comment?.path + if (!commentPath) return false + // Handle renamed files: both old and new paths + if (item.isRename && item.oldName && item.newName) { + return commentPath === item.oldName || commentPath === item.newName + } + // For non-renamed files + return commentPath === item.text + }) ) || [] return ( diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index bd75f444c9..2bfe150c37 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -155,7 +155,7 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ principalProps={principalProps} data={ diffs?.map(item => ({ - text: item.filePath, + text: item.isRename ? `${item.oldName} → ${item.newName}` : item.filePath, addedLines: item.addedLines, deletedLines: item.deletedLines, data: item.raw, @@ -167,7 +167,10 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ diffData: item, isDeleted: !!item.isDeleted, unchangedPercentage: item.unchangedPercentage, - isBinary: item.isBinary + isBinary: item.isBinary, + isRename: !!item.isRename, + oldName: item.oldName, + newName: item.newName })) || [] } diffMode={diffMode} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts b/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts index a4e8d938c8..06702dceac 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-details-types.ts @@ -405,11 +405,11 @@ export interface CommentItem<T = unknown> { } export interface DiffFileEntry extends DiffFile { - fileId: string filePath: string containerId: string contentId: string fileViews?: Map<string, string> + isRename?: boolean } export interface DiffFileName { From 7dc76c7af459c9c8e564b34842d22413c14ce2db Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Wed, 20 Aug 2025 18:03:11 +0300 Subject: [PATCH 153/180] Fix Dropdown keyboard navigation in ShadowDOM (#2089) --- packages/ui/src/components/dropdown-menu.tsx | 79 ++++++++++++++++++- packages/ui/src/components/search-files.tsx | 8 +- .../branch-selector-dropdown.tsx | 28 ++++++- 3 files changed, 103 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index 9bd949fcbc..31b2d8909c 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -1,7 +1,15 @@ -import { ComponentPropsWithoutRef, ElementRef, forwardRef, HTMLAttributes, ReactNode } from 'react' +import { + ComponentPropsWithoutRef, + ElementRef, + forwardRef, + HTMLAttributes, + KeyboardEvent, + ReactNode, + useRef +} from 'react' import { usePortal, useTranslation } from '@/context' -import { cn, filterChildrenByDisplayNames } from '@/utils' +import { cn, filterChildrenByDisplayNames, useMergeRefs } from '@/utils' import { Avatar, AvatarProps } from '@components/avatar' import { Layout } from '@components/layout' import { ScrollArea, ScrollAreaProps } from '@components/scroll-area' @@ -68,21 +76,84 @@ interface DropdownMenuContentProps extends ComponentPropsWithoutRef<typeof Dropd } const DropdownMenuContent = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Content>, DropdownMenuContentProps>( - ({ className, children: _children, sideOffset = 4, isSubContent, scrollAreaProps, ...props }, ref) => { + ( + { + className, + children: _children, + sideOffset = 4, + isSubContent, + scrollAreaProps, + onKeyDownCapture: propOnKeyDownCapture, + ...props + }, + ref + ) => { const { portalContainer } = usePortal() + const contentRef = useRef<HTMLDivElement | null>(null) const Primitive = isSubContent ? DropdownMenuPrimitive.SubContent : DropdownMenuPrimitive.Content const header = filterChildrenByDisplayNames(_children, [displayNames.header])[0] const footer = filterChildrenByDisplayNames(_children, [displayNames.footer])[0] const children = filterChildrenByDisplayNames(_children, [displayNames.header, displayNames.footer], true) + const mergedRef = useMergeRefs<HTMLDivElement>([ + node => { + if (!node) return + + contentRef.current = node + }, + ref + ]) + + /** + * This code is executed only when inside a ShadowRoot + */ + const onKeyDownCaptureHandler = (e: KeyboardEvent<HTMLDivElement>) => { + /** + * To block further code execution in onKeyDownCaptureHandler, + * need to call e.preventDefault() inside propOnKeyDownCapture. + */ + propOnKeyDownCapture?.(e) + if (e.defaultPrevented || e.isDefaultPrevented?.()) return + + if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return + + const rootEl = contentRef.current + if (!rootEl) return + + const rootNode = rootEl.getRootNode() + + if (!(rootNode instanceof ShadowRoot)) return + + const items = Array.from( + rootEl.querySelectorAll<HTMLElement>('[data-radix-collection-item]:not([data-disabled])[role*="menuitem"]') + ) + + if (!items.length) return + + const activeEl = rootNode instanceof ShadowRoot ? rootNode.activeElement : document.activeElement + let idx = items.findIndex(el => el === activeEl || (activeEl instanceof Element && el.contains(activeEl))) + + if (idx === -1) { + idx = e.key === 'ArrowDown' ? -1 : 0 + } + + const next = + e.key === 'ArrowDown' ? (idx + 1 + items.length) % items.length : (idx - 1 + items.length) % items.length + + e.preventDefault() + e.stopPropagation() + items[next]?.focus() + } + return ( <DropdownMenuPortal container={portalContainer}> <Primitive - ref={ref} + ref={mergedRef} sideOffset={sideOffset} className={cn('cn-dropdown-menu', className)} onCloseAutoFocus={event => event.preventDefault()} + onKeyDownCapture={onKeyDownCaptureHandler} {...props} > {!!header && <div className="cn-dropdown-menu-container cn-dropdown-menu-container-header">{header}</div>} diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index 4b8e66b3b3..484f9aa4a4 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -128,18 +128,12 @@ export const SearchFiles = ({ const last = items[items.length - 1] const activeElement = document.activeElement?.shadowRoot?.activeElement ?? document.activeElement - if (e.key === 'ArrowUp' && activeElement === first) { + if ((e.key === 'ArrowUp' && activeElement === first) || (e.key === 'ArrowDown' && activeElement === last)) { e.preventDefault() inputRef.current?.focus() setIsOpen(true) return } - - if (e.key === 'ArrowDown' && activeElement === last) { - e.preventDefault() - inputRef.current?.focus() - setIsOpen(true) - } } return ( diff --git a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx index 8a9c7e3da0..e5c69fb59b 100644 --- a/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx +++ b/packages/ui/src/views/repo/components/branch-selector-v2/branch-selector-dropdown.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useState } from 'react' +import { FC, KeyboardEvent, useMemo, useRef, useState } from 'react' import { Button, DropdownMenu, Layout, Link, SearchInput, Tabs, Text } from '@/components' import { useTranslation } from '@/context' @@ -22,6 +22,8 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ }) => { const [activeTab, setActiveTab] = useState<BranchSelectorTab>(preSelectedTab) const { t } = useTranslation() + const inputRef = useRef<HTMLInputElement | null>(null) + const contentRef = useRef<HTMLDivElement | null>(null) const BRANCH_SELECTOR_LABELS = getBranchSelectorLabels(t) const filteredItems = useMemo(() => { @@ -32,6 +34,27 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ setSearchQuery(query) } + const handleContentKeyDownCapture = (e: KeyboardEvent<HTMLDivElement>) => { + const rootEl = contentRef.current + if (!rootEl) return + + const items = Array.from( + rootEl.querySelectorAll<HTMLElement>('[data-radix-collection-item]:not([data-disabled])[role*="menuitem"]') + ) + + if (!items.length) return + + const first = items[0] + const last = items[items.length - 1] + const activeElement = document.activeElement?.shadowRoot?.activeElement ?? document.activeElement + + if ((e.key === 'ArrowUp' && activeElement === first) || (e.key === 'ArrowDown' && activeElement === last)) { + e.preventDefault() + inputRef.current?.focus() + return + } + } + const viewAllUrl = activeTab === BranchSelectorTab.BRANCHES ? `${spaceId ? `/${spaceId}` : ''}/repos/${repoId}/branches` @@ -39,10 +62,12 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ return ( <DropdownMenu.Content + ref={contentRef} className="[&>.cn-dropdown-menu-container-header]:border-b-0 [&>.cn-dropdown-menu-container-header]:pb-0" style={{ width: dynamicWidth ? 'var(--radix-dropdown-menu-trigger-width)' : '358px' }} align="start" scrollAreaProps={{ className: 'max-h-[188px]' }} + onKeyDownCapture={handleContentKeyDownCapture} > <DropdownMenu.Header className="pb-0"> <Layout.Grid gapY="sm"> @@ -51,6 +76,7 @@ export const BranchSelectorDropdown: FC<BranchSelectorDropdownProps> = ({ </Text> <SearchInput + ref={inputRef} autoFocus id="search" placeholder={BRANCH_SELECTOR_LABELS[activeTab].searchPlaceholder} From 0f850012813746f3f48b9ef5e009058b9ba52df5 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Wed, 20 Aug 2025 15:34:48 +0000 Subject: [PATCH 154/180] add ability to apply list syntax at cursor for PR comment box (#10250) * a15110 fix lint * c937bf Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into pr-comment-box-apply-list-for-current-line * 45f8fe add ability to apply list syntax at cursor for PR comment box --- .../conversation/pull-request-comment-box.tsx | 159 ++++++++++++------ 1 file changed, 111 insertions(+), 48 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 55ee1842c4..db80e2b711 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -43,16 +43,26 @@ import { replaceMentionEmailWithDisplayName, replaceMentionEmailWithId } from '. interface ParsedSelection extends TextSelection { text: string textLines: string[] + textBefore: string + textAfter: string } interface ParsedComment { - originalComment: string + text: string + textLines: string[] + textLinesSelectionStartIndex: number + textLinesSelectionEndIndex: number +} + +interface CommentAction { injectedPreString: string injectedPostString: string - beforeSelection: string - beforeSelectionLine: string +} + +interface CommentMetadata { + action: CommentAction + comment: ParsedComment selection: ParsedSelection - afterSelection: string } enum BuildBehavior { @@ -297,73 +307,124 @@ export const PullRequestCommentBox = ({ }, []) useEffect(() => { + console.debug('textSelection', textSelection) if (textAreaRef.current) { textAreaRef.current.setSelectionRange(textSelection.start, textSelection.end) } }, [comment, textSelection]) const parseComment = ( - comment: string, + originalComment: string, textSelection: TextSelection, injectedPreString: string = '', injectedPostString: string = '' - ): ParsedComment => { - const isStartOfLineSelected = textSelection.start === 0 || comment.substring(0, textSelection.start).endsWith('\n') + ): CommentMetadata => { + const action: CommentAction = { + injectedPreString: injectedPreString, + injectedPostString: injectedPostString + } + + const isStartOfLineSelected = + textSelection.start === 0 || originalComment.substring(0, textSelection.start).endsWith('\n') const isEndOfLineSelected = - textSelection.end === comment.length || comment.substring(textSelection.end).startsWith('\n') + textSelection.end === originalComment.length || originalComment.substring(textSelection.end).startsWith('\n') - const injectNewline = !isStartOfLineSelected && isEndOfLineSelected && injectedPreString.includes('\n') - const injectedNewline = injectNewline ? '\n' : '' + const commentLines = originalComment.split('\n') + + const comment: ParsedComment = { + text: originalComment, + textLines: commentLines, + textLinesSelectionStartIndex: 0, + textLinesSelectionEndIndex: 0 + } + + commentLines.reduce((characterIndex, line, lineIndex): number => { + const lowerBound = lineIndex === 0 ? characterIndex : characterIndex + '\n'.length + const upperBound = lowerBound + line.length - const beforeSelection = comment.substring(0, textSelection.start) + injectedNewline - const selection = comment.substring(textSelection.start, textSelection.end) - const afterSelection = comment.substring(textSelection.end) + const selectionStartHere = textSelection.start >= lowerBound && textSelection.start <= upperBound + const selectionEndHere = textSelection.end >= lowerBound && textSelection.end <= upperBound + + if (selectionStartHere) { + comment.textLinesSelectionStartIndex = lineIndex + } + + if (selectionEndHere) { + comment.textLinesSelectionEndIndex = lineIndex + } - const selectionLines = selection.split('\n') + return upperBound + }, 0) - const beforeSelectionParts = beforeSelection.split('\n') - const previousLine = beforeSelectionParts.at(beforeSelectionParts.length - 2) ?? '' + const injectNewline = !isStartOfLineSelected && isEndOfLineSelected && injectedPreString.includes('\n') + const injectedNewline = injectNewline ? '\n' : '' + const selectionText = originalComment.substring(textSelection.start, textSelection.end) + const selectionTextLines = comment.textLines.slice( + comment.textLinesSelectionStartIndex, + comment.textLinesSelectionEndIndex + 1 + ) + const selectionTextBefore = originalComment.substring(0, textSelection.start) + injectedNewline const newTextSelectionStart = textSelection.start + injectedPreString.length + injectedNewline.length const newTextSelectionEnd = newTextSelectionStart + (textSelection.end - textSelection.start) + injectedNewline.length + const selection: ParsedSelection = { + text: selectionText, + textLines: selectionTextLines, + start: newTextSelectionStart, + end: newTextSelectionEnd, + textBefore: selectionTextBefore, + textAfter: originalComment.substring(textSelection.end) + } + return { - originalComment: comment, - injectedPreString: injectedPreString, - injectedPostString: injectedPostString, - beforeSelection: beforeSelection, - beforeSelectionLine: previousLine, - selection: { - text: selection, - textLines: selectionLines, - start: newTextSelectionStart, - end: newTextSelectionEnd - }, - afterSelection: afterSelection + action: action, + comment: comment, + selection: selection } } - const buildComment = (parsedComment: ParsedComment, buildBehavior: BuildBehavior): string => { - let injectedString = '' - + const buildComment = (commentMetadata: CommentMetadata, buildBehavior: BuildBehavior): string => { switch (buildBehavior) { - case BuildBehavior.Capture: - injectedString = `${parsedComment.injectedPreString}${parsedComment.selection.text}${parsedComment.injectedPostString}` - break - case BuildBehavior.Split: - injectedString = parsedComment.selection.textLines.reduce((prev, selectionLine, currentIndex, array) => { - return `${prev}${parsedComment.injectedPreString}${selectionLine}${parsedComment.injectedPostString}${currentIndex < array.length - 1 ? '\n' : ''}` + case BuildBehavior.Capture: { + const injectedCaptureString = `${commentMetadata.action.injectedPreString}${commentMetadata.selection.text}${commentMetadata.action.injectedPostString}` + + return `${commentMetadata.selection.textBefore}${injectedCaptureString}${commentMetadata.selection.textAfter}` + } + case BuildBehavior.Split: { + const injectedSplitString = commentMetadata.comment.textLines.reduce((prev, commentLine, commentLineIndex) => { + if (commentLineIndex > commentMetadata.comment.textLinesSelectionEndIndex) { + return `${prev}` + } + + if ( + commentLineIndex >= commentMetadata.comment.textLinesSelectionStartIndex && + commentLineIndex <= commentMetadata.comment.textLinesSelectionEndIndex + ) { + const useNewline = commentLineIndex !== commentMetadata.comment.textLinesSelectionEndIndex + + return `${prev}${commentMetadata.action.injectedPreString}${commentLine}${commentMetadata.action.injectedPostString}${useNewline ? '\n' : ''}` + } + + return `${prev}${commentLine}\n` }, '') - break - case BuildBehavior.Parse: - injectedString = isEmpty(parsedComment.selection.text) - ? `${parsedComment.injectedPreString}${parseDiff(diff, sideKey, lineNumber, lineFromNumber)}${parsedComment.injectedPostString}` - : `${parsedComment.injectedPreString}${parsedComment.selection.text}${parsedComment.injectedPostString}` - break - } - return `${parsedComment.beforeSelection}${injectedString}${parsedComment.afterSelection}` + const remainingLines = commentMetadata.comment.textLines.slice( + commentMetadata.comment.textLinesSelectionEndIndex + 1 + ) + const injectNewLine = remainingLines.length > 0 + + return `${injectedSplitString}${injectNewLine ? '\n' : ''}${remainingLines.join('\n')}` + } + case BuildBehavior.Parse: { + const injectedParseString = isEmpty(commentMetadata.selection.text) + ? `${commentMetadata.action.injectedPreString}${parseDiff(diff, sideKey, lineNumber, lineFromNumber)}${commentMetadata.action.injectedPostString}` + : `${commentMetadata.action.injectedPreString}${commentMetadata.selection.text}${commentMetadata.action.injectedPostString}` + + return `${commentMetadata.selection.textBefore}${injectedParseString}${commentMetadata.selection.textAfter}` + } + } } const parseDiff = ( @@ -546,12 +607,14 @@ export const PullRequestCommentBox = ({ break } case 'Enter': { - const parsedComment = parseComment(comment, textSelection, '') + const commentMetadata = parseComment(comment, textSelection) + const textLinesSelectionStartIndexBeforeEnter = commentMetadata.comment.textLinesSelectionStartIndex - 1 + const lineBeforeEnter = commentMetadata.comment.textLines.at(textLinesSelectionStartIndexBeforeEnter) ?? '' - if (isListString(parsedComment.beforeSelectionLine)) { + if (isListString(lineBeforeEnter)) { handleActionClick(ToolbarAction.UNORDERED_LIST, comment, textSelection) } - if (isListSelectString(parsedComment.beforeSelectionLine)) { + if (isListSelectString(lineBeforeEnter)) { handleActionClick(ToolbarAction.CHECK_LIST, comment, textSelection) } break From 5ca126c27e38625e854f430e832ddc5e16e1d579 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Wed, 20 Aug 2025 16:46:25 +0000 Subject: [PATCH 155/180] Icon swap (#10253) * d60508 prettier fix * a8e730 icon fix * 5f09ea fix * b2a788 icon swap * b2b3e0 repo-summary icon swap * 79e2b6 modified: packages/ui/src/components/file-explorer.tsx modified: packages/ui/src/components/search-files.tsx modified: packages/ui/src/views/layouts/PullRequestLayout.tsx modified: packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx modified: packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx modified: packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx --- packages/ui/src/components/file-explorer.tsx | 2 +- packages/ui/src/components/search-files.tsx | 2 +- packages/ui/src/views/layouts/PullRequestLayout.tsx | 6 +++++- .../changed-files-short-info/changed-files-short-info.tsx | 2 +- packages/ui/src/views/repo/components/summary/summary.tsx | 4 ++-- .../repo/pull-request/compare/pull-request-compare-page.tsx | 2 +- .../conversation/sections/pull-request-merge-section.tsx | 2 +- packages/ui/tailwind-utils-config/components/avatar.ts | 4 ++-- 8 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index 8dcf09b185..53e5f8129b 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -87,7 +87,7 @@ function FileItem({ children, isActive, level, link, onClick }: FileItemProps) { const { Link } = useRouterContext() const comp = ( <Item - icon="page" + icon="empty-page" isActive={isActive} className="mb-cn-4xs" style={{ diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index 484f9aa4a4..0ce0c505f8 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -164,7 +164,7 @@ export const SearchFiles = ({ setIsOpen(false) }} title={element} - icon="page" + icon="empty-page" /> )) ) : ( diff --git a/packages/ui/src/views/layouts/PullRequestLayout.tsx b/packages/ui/src/views/layouts/PullRequestLayout.tsx index 1aa127a152..2bfabae98a 100644 --- a/packages/ui/src/views/layouts/PullRequestLayout.tsx +++ b/packages/ui/src/views/layouts/PullRequestLayout.tsx @@ -63,7 +63,11 @@ export const PullRequestLayout: FC<PullRequestLayoutProps> = ({ {t('views:pullRequests.commits', 'Commits')} </Tabs.Trigger> - <Tabs.Trigger value={PullRequestTabsKeys.CHANGES} counter={pullRequest?.stats?.files_changed} icon="page"> + <Tabs.Trigger + value={PullRequestTabsKeys.CHANGES} + counter={pullRequest?.stats?.files_changed} + icon="empty-page" + > {t('views:pullRequests.changes', 'Changes')} </Tabs.Trigger> </Tabs.List> diff --git a/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx b/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx index bddfe16a93..bab0b6a5fd 100644 --- a/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx +++ b/packages/ui/src/views/repo/components/changed-files-short-info/changed-files-short-info.tsx @@ -36,7 +36,7 @@ export const ChangedFilesShortInfo = ({ diffData, diffStats, goToDiff }: Changed <DropdownMenu.IconItem key={diff.filePath} onClick={() => goToDiff(diff.filePath ?? '')} - icon="page" + icon="empty-page" title={diff.filePath} label={ <Layout.Horizontal gap="2xs" align="center"> diff --git a/packages/ui/src/views/repo/components/summary/summary.tsx b/packages/ui/src/views/repo/components/summary/summary.tsx index b774de51ec..b4cd7e1a0c 100644 --- a/packages/ui/src/views/repo/components/summary/summary.tsx +++ b/packages/ui/src/views/repo/components/summary/summary.tsx @@ -159,11 +159,11 @@ export const Summary = ({ file.status ? file.status === FileStatus.SAFE ? file.type === SummaryItemType.File - ? 'page' + ? 'empty-page' : 'folder' : 'warning-triangle-solid' : file.type === SummaryItemType.File - ? 'page' + ? 'empty-page' : 'folder' } /> diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 45a0bb292c..ebf7a5df09 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -365,7 +365,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ <Tabs.Trigger value="commits" icon="git-commit" counter={diffStats?.commits}> {t('views:pullRequests.compareChangesTabCommits', 'Commits')} </Tabs.Trigger> - <Tabs.Trigger value="changes" icon="page" counter={diffStats?.files_changed}> + <Tabs.Trigger value="changes" icon="empty-page" counter={diffStats?.files_changed}> {t('views:pullRequests.compareChangesTabChanges', 'Changes')} </Tabs.Trigger> </Tabs.List> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx index 5961e619b5..c90d7ee7c6 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/sections/pull-request-merge-section.tsx @@ -243,7 +243,7 @@ const PullRequestMergeSection = ({ <Layout.Vertical gap="xs" className="mt-1"> {conflictingFiles?.map(file => ( <Layout.Horizontal key={file} align="center" gap="xs" className="py-1.5"> - <IconV2 size="md" className="text-icons-1" name="page" /> + <IconV2 size="md" className="text-icons-1" name="empty-page" /> <Text variant="body-normal">{file}</Text> </Layout.Horizontal> ))} diff --git a/packages/ui/tailwind-utils-config/components/avatar.ts b/packages/ui/tailwind-utils-config/components/avatar.ts index 150911ede7..2d67fb9602 100644 --- a/packages/ui/tailwind-utils-config/components/avatar.ts +++ b/packages/ui/tailwind-utils-config/components/avatar.ts @@ -47,8 +47,8 @@ export default { }, '.cn-avatar-icon': { - width: '80%', - height: '80%' + width: '60%', + height: '60%' } } } From 1ffaf26b4f50faa250d44e72b89b4d5b94e66ae1 Mon Sep 17 00:00:00 2001 From: Harish Viswanathan <harish.viswanathan@harness.io> Date: Wed, 20 Aug 2025 19:07:24 +0000 Subject: [PATCH 156/180] fix: updated label filter to be a controlled component (#10259) * 5ea497 chore: Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into fix/label-dropdown * f0df67 fix: updated label filter to be a controlled component --- packages/ui/src/components/dropdown-menu.tsx | 22 +++++++++-- .../components/labels/labels-filter.tsx | 39 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index 31b2d8909c..a5e31f281b 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -228,11 +228,25 @@ interface DropdownMenuItemProps Omit<ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>, 'title' | 'prefix'> { prefix?: ReactNode subContentProps?: Omit<DropdownMenuContentProps, 'isSubContent'> + subMenuProps?: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub> } const DropdownMenuItem = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Item>, DropdownMenuItemProps>( ( - { className, children, title, description, label, shortcut, checkmark, prefix, tag, subContentProps, ...props }, + { + className, + children, + title, + description, + label, + shortcut, + checkmark, + prefix, + tag, + subContentProps, + subMenuProps, + ...props + }, ref ) => { const filteredChildren = filterChildrenByDisplayNames(children, innerComponentsDisplayNames) @@ -246,7 +260,7 @@ const DropdownMenuItem = forwardRef<ElementRef<typeof DropdownMenuPrimitive.Item if (withChildren) { return ( - <DropdownMenuSub> + <DropdownMenuSub {...subMenuProps}> <DropdownMenuSubTrigger ref={ref} className={cn('cn-dropdown-menu-item cn-dropdown-menu-item-subtrigger', className)} @@ -274,6 +288,7 @@ interface DropdownMenuCheckboxItemProps extends Omit<DropdownBaseItemProps, 'withSubIndicator'>, Omit<ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>, 'title' | 'onSelect'> { subContentProps?: Omit<DropdownMenuContentProps, 'isSubContent'> + subMenuProps?: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub> } const DropdownMenuCheckboxItem = forwardRef< @@ -294,6 +309,7 @@ const DropdownMenuCheckboxItem = forwardRef< onCheckedChange, onClick, subContentProps, + subMenuProps, ...props }, ref @@ -320,7 +336,7 @@ const DropdownMenuCheckboxItem = forwardRef< if (withChildren) { return ( - <DropdownMenuSub> + <DropdownMenuSub {...subMenuProps}> <DropdownMenuSubTrigger ref={ref} className={cn('cn-dropdown-menu-item cn-dropdown-menu-item-subtrigger', className)} diff --git a/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx b/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx index e4d6beb860..b8fa9d0442 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useRef, useState } from 'react' import { CounterBadge } from '@components/counter-badge' import { DropdownMenu } from '@components/dropdown-menu' @@ -30,6 +30,32 @@ export function LabelsFilter({ const description = !isLabelsLoading && labelOptions.length === 0 ? 'No labels found' : isLabelsLoading ? 'Loading...' : '' + const [open, setOpen] = useState<Record<number, boolean>>({}) + // Use refs to track focus state to ensure we always have the latest values + const focusStateRef = useRef<Record<number, { parent: boolean; subcontent: boolean }>>({}) + + // Common function to update focus state + const updateFocusState = (id: number, type: 'parent' | 'subcontent', isFocused: boolean) => { + focusStateRef.current = { + ...focusStateRef.current, + [id]: { + ...focusStateRef.current[id], + [type]: isFocused + } + } + + // If focus is lost, check after a short delay if we should close the dropdown + if (!isFocused) { + setTimeout(() => { + const currentFocusState = focusStateRef.current[id] || { parent: false, subcontent: false } + + if (!currentFocusState.parent && !currentFocusState.subcontent) { + setOpen(prev => ({ ...prev, [id]: false })) + } + }, 100) // Small timeout to ensure focus state is updated correctly + } + } + useEffect(() => { // Resetting the search query so that on re-open of the filter, // all options will be shown in-spite of older search query @@ -44,14 +70,25 @@ export function LabelsFilter({ inputContainerClassName="w-auto mx-1.5 mt-2 mb-2.5" onChange={value => onInputChange(value)} placeholder="Search..." + onKeyDown={e => e.stopPropagation()} /> {!isLabelsLoading && labelOptions.map(option => option.value_count > 0 ? ( <DropdownMenu.CheckboxItem key={option.id} + onBlur={() => updateFocusState(option.id, 'parent', false)} + onFocus={() => updateFocusState(option.id, 'parent', true)} title={<LabelMarker color={option.color} label={option.key} value={String(option.value_count)} />} checked={value[option.id] ? value[option.id] === true || 'indeterminate' : false} + subMenuProps={{ + open: open[option.id] as boolean, + onOpenChange: open => setOpen(prev => ({ ...prev, [option.id]: open })) + }} + subContentProps={{ + onFocus: () => updateFocusState(option.id, 'subcontent', true), + onBlur: () => updateFocusState(option.id, 'subcontent', false) + }} onCheckedChange={() => { const { [option.id]: selectedIdValue, ...rest } = value const newValue = selectedIdValue ? rest : { ...value, [option.id]: true } From 063d0bc4cb4a970f6770f1ca05ada5a4b5cc2905 Mon Sep 17 00:00:00 2001 From: Anastasiia Ramina <anastasiia.ramina@harness.io> Date: Wed, 20 Aug 2025 20:25:47 +0000 Subject: [PATCH 157/180] Colors update (#10260) * 5b52f4 a11y green color fix * 88dfdc Update button.ts * 9461a8 fix * 0ceca1 diff fix * ed511a color correction * 14cf73 color correction * c0fbae fix: apply prettier formatting to tailwind config files * 23a721 fix * 38012c green color fix * c34223 inner shadow removed * 0998ec inner shadow added * 2269c8 color fixes * d4ab09 ai hover fix * a85fb0 color fix * 6b2869 lime color updated --- .../design-tokens/core/colors_hex.json | 22 ++--- .../design-tokens/core/colors_lch.json | 22 ++--- .../design-tokens/core/dimensions.json | 6 -- .../mode/dark/default-deuteranopia.json | 2 +- .../mode/dark/default-protanopia.json | 2 +- .../mode/dark/default-tritanopia.json | 2 +- .../design-tokens/mode/dark/default.json | 34 ++++---- .../mode/dark/dimmer-deuteranopia.json | 2 +- .../mode/dark/dimmer-protanopia.json | 2 +- .../mode/dark/dimmer-tritanopia.json | 2 +- .../design-tokens/mode/dark/dimmer.json | 2 +- .../mode/dark/high-contrast-deuteranopia.json | 2 +- .../mode/dark/high-contrast-protanopia.json | 2 +- .../mode/dark/high-contrast-tritanopia.json | 2 +- .../mode/dark/high-contrast.json | 2 +- .../mode/light/default-deuteranopia.json | 2 +- .../mode/light/default-protanopia.json | 2 +- .../mode/light/default-tritanopia.json | 2 +- .../design-tokens/mode/light/default.json | 71 +++++++++------- .../mode/light/dimmer-deuteranopia.json | 2 +- .../mode/light/dimmer-protanopia.json | 2 +- .../mode/light/dimmer-tritanopia.json | 2 +- .../design-tokens/mode/light/dimmer.json | 2 +- .../light/high-contrast-deuteranopia.json | 2 +- .../mode/light/high-contrast-protanopia.json | 2 +- .../mode/light/high-contrast-tritanopia.json | 2 +- .../mode/light/high-contrast.json | 2 +- .../components/avatar.ts | 2 +- .../components/button-group.ts | 82 +++++++++---------- .../components/button.ts | 2 +- .../components/drawer.ts | 2 +- 31 files changed, 147 insertions(+), 140 deletions(-) diff --git a/packages/core-design-system/design-tokens/core/colors_hex.json b/packages/core-design-system/design-tokens/core/colors_hex.json index e9d9a6fc20..fd5182cb60 100644 --- a/packages/core-design-system/design-tokens/core/colors_hex.json +++ b/packages/core-design-system/design-tokens/core/colors_hex.json @@ -127,7 +127,7 @@ "$value": "#5d8cfe" }, "500": { - "$value": "#427ef6" + "$value": "#4d81f4" }, "600": { "$value": "#2b71e6" @@ -202,7 +202,7 @@ }, "red": { "25": { - "$value": "#fffafa" + "$value": "#fff5f5" }, "50": { "$value": "#fee3e3" @@ -211,10 +211,10 @@ "$value": "#ffd5d5" }, "150": { - "$value": "#fbb4b4" + "$value": "#ffc1c1" }, "200": { - "$value": "#ff9a99" + "$value": "#ff9696" }, "300": { "$value": "#ff817e" @@ -538,22 +538,22 @@ }, "lime": { "25": { - "$value": "#f6fcf3" + "$value": "#f0fde9" }, "50": { "$value": "#d9f1cc" }, "100": { - "$value": "#b9e1a5" + "$value": "#c3ebaf" }, "150": { - "$value": "#84de60" + "$value": "#addf96" }, "200": { - "$value": "#67cc41" + "$value": "#82d560" }, "300": { - "$value": "#56b434" + "$value": "#5fba3d" }, "400": { "$value": "#52a334" @@ -562,10 +562,10 @@ "$value": "#4f9136" }, "600": { - "$value": "#497d34" + "$value": "#438428" }, "700": { - "$value": "#416a30" + "$value": "#3d7328" }, "800": { "$value": "#3b582e" diff --git a/packages/core-design-system/design-tokens/core/colors_lch.json b/packages/core-design-system/design-tokens/core/colors_lch.json index 7045025bcd..b2df17d7b0 100644 --- a/packages/core-design-system/design-tokens/core/colors_lch.json +++ b/packages/core-design-system/design-tokens/core/colors_lch.json @@ -107,7 +107,7 @@ }, "red": { "25": { - "$value": "lch(98.66% 1.83 19.82)" + "$value": "lch(97.5% 4.4 19.82)" }, "50": { "$value": "lch(92.5% 10.21 20.43)" @@ -116,10 +116,10 @@ "$value": "lch(89% 16 21.3)" }, "150": { - "$value": "lch(80.15% 28.7 22.17)" + "$value": "lch(84% 25 22.17)" }, "200": { - "$value": "lch(76.4% 48.92 24.8)" + "$value": "lch(75% 48.92 24.8)" }, "300": { "$value": "lch(69.5% 56 27.32)" @@ -299,22 +299,22 @@ }, "lime": { "25": { - "$value": "lch(98.38% 5.17 131)" + "$value": "lch(98% 10.5 131)" }, "50": { "$value": "lch(92.5% 19.66 131)" }, "100": { - "$value": "lch(85.43% 32.93 131)" + "$value": "lch(89% 32.5 131)" }, "150": { - "$value": "lch(81% 68.86 131)" + "$value": "lch(84% 40.5 131)" }, "200": { - "$value": "lch(73.91% 74.97 131)" + "$value": "lch(78% 64 131)" }, "300": { - "$value": "lch(65.67% 70.28 131)" + "$value": "lch(68% 69 131)" }, "400": { "$value": "lch(60.35% 63.02 131)" @@ -323,10 +323,10 @@ "$value": "lch(54.44% 53.6 131)" }, "600": { - "$value": "lch(47.41% 44.32 131)" + "$value": "lch(49.5% 54 130.5)" }, "700": { - "$value": "lch(40.83% 36.77 131)" + "$value": "lch(43.5% 45.5 131)" }, "800": { "$value": "lch(34.29% 27.73 131)" @@ -512,7 +512,7 @@ "$value": "lch(59.19% 64 280)" }, "500": { - "$value": "lch(53.78% 68 280)" + "$value": "lch(55% 65 280)" }, "600": { "$value": "lch(48.5% 68 280)" diff --git a/packages/core-design-system/design-tokens/core/dimensions.json b/packages/core-design-system/design-tokens/core/dimensions.json index 336e369b59..7778787f89 100644 --- a/packages/core-design-system/design-tokens/core/dimensions.json +++ b/packages/core-design-system/design-tokens/core/dimensions.json @@ -502,11 +502,5 @@ "$value": "{size.11}", "$description": "\n" } - }, - "focus": { - "offset": { - "$type": "sizing", - "$value": "{size.px}" - } } } \ No newline at end of file diff --git a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json index 93baa7bdcd..134886b356 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json index f7e8897aa2..3a29f31802 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json index 24c58e33c4..62929c1ba3 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index 8f5491805e..511c3c7bf6 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -171,12 +171,12 @@ }, "bg-hover": { "$type": "color", - "$value": "{blue.700}", + "$value": "{blue.500}", "$description": "Hover state background color for interactive brand surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.700}", + "$value": "{blue.500}", "$description": "Selected state background color for active brand items." } }, @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { @@ -348,71 +348,71 @@ "solid": { "text": { "$type": "color", - "$value": "{mint.1000}", + "$value": "{lime.1000}", "$description": "Text color for high-contrast green surfaces." }, "bg": { "$type": "color", - "$value": "{mint.300}", + "$value": "{lime.300}", "$description": "Background color for prominent green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{mint.200}", + "$value": "{lime.200}", "$description": "Background color for prominent green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.200}", + "$value": "{lime.200}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{mint.150}", + "$value": "{lime.150}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{mint.950}", + "$value": "{lime.950}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{mint.900}", + "$value": "{lime.900}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.900}", + "$value": "{lime.900}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{mint.150}", + "$value": "{lime.150}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{mint.1000}", + "$value": "{lime.1000}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{mint.900}", + "$value": "{lime.900}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{mint.950}", + "$value": "{lime.950}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.900}", + "$value": "{lime.900}", "$description": "Selected state background color for active green items." } } @@ -1824,7 +1824,7 @@ "y": "7", "blur": "16", "spread": "0", - "color": "{set.ai.surface.inner-shadow}", + "color": "{set.ai.surface.inner}", "type": "innerShadow" } } diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json index bb4a0e71b5..ff0d5b182c 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json index cb04f86ace..19b1aa6842 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json index 6e7bf26d68..545397661f 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer.json b/packages/core-design-system/design-tokens/mode/dark/dimmer.json index 3b1a73abc7..770ffcc132 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer.json @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json index 625880f0f7..facd0ea01b 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json index 130ae114d5..dd149ff777 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json index e4f4297a3a..7aa6ddcbff 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json index e928f46ac5..e296885b64 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$extensions": { "studio.tokens": { "modify": { diff --git a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json index 7ffcd40a15..3e919510cb 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json index dfd3eac89d..21878f1a3d 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json index 13794b6313..27361a37b6 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index 63cc2d3f69..fdc55ddc95 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -234,12 +234,12 @@ "surface": { "text": { "$type": "color", - "$value": "{chrome.1000}", + "$value": "{pure.black}", "$description": "Primary text color for AI-related content with optimal readability." }, "bg": { "$type": "color", - "$value": "{pure.white}", + "$value": "{purple.50}", "$description": "Base background color for AI-related surfaces." }, "border": { @@ -249,14 +249,14 @@ }, "bg-hover": { "$type": "color", - "$value": "{purple.50}", + "$value": "{purple.100}", "$description": "Hover state background color for interactive AI surfaces." }, "bg-selected": { "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } @@ -344,66 +344,66 @@ }, "bg": { "$type": "color", - "$value": "{mint.600}", + "$value": "{lime.600}", "$description": "Background color for prominent green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{mint.700}", + "$value": "{lime.700}", "$description": "Background color for prominent green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.700}", + "$value": "{lime.700}", "$description": "Background color for prominent green surfaces." } }, "soft": { "text": { "$type": "color", - "$value": "{mint.600}", + "$value": "{lime.700}", "$description": "Text color for subtle green surfaces with good contrast." }, "bg": { "$type": "color", - "$value": "{mint.50}", + "$value": "{lime.50}", "$description": "Background color for subdued green surfaces." }, "bg-hover": { "$type": "color", - "$value": "{mint.100}", + "$value": "{lime.100}", "$description": "Background color for subdued green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.100}", + "$value": "{lime.100}", "$description": "Background color for subdued green surfaces." } }, "surface": { "text": { "$type": "color", - "$value": "{mint.800}", + "$value": "{lime.700}", "$description": "Standard text color for green surfaces with balanced readability." }, "bg": { "$type": "color", - "$value": "{mint.25}", + "$value": "{lime.25}", "$description": "Base background color for green surfaces in default states." }, "border": { "$type": "color", - "$value": "{mint.100}", + "$value": "{lime.150}", "$description": "Border color for standard green boundaries." }, "bg-hover": { "$type": "color", - "$value": "{mint.50}", + "$value": "{lime.50}", "$description": "Hover state background color for interactive green surfaces." }, "bg-selected": { "$type": "color", - "$value": "{mint.50}", + "$value": "{lime.50}", "$description": "Selected state background color for active green items." } } @@ -456,7 +456,7 @@ "surface": { "text": { "$type": "color", - "$value": "{red.800}", + "$value": "{red.700}", "$description": "Standard text color for red surfaces with balanced readability." }, "bg": { @@ -466,7 +466,7 @@ }, "border": { "$type": "color", - "$value": "{red.100}", + "$value": "{red.150}", "$description": "Border color for standard red boundaries." }, "bg-hover": { @@ -1177,13 +1177,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.08", + "value": "0.15", "space": "lch" } } }, "$type": "color", - "$value": "{mint.200}", + "$value": "{lime.200}", "$description": "Green background highlighting newly added code lines while maintaining readability." }, "add-lineNumber": { @@ -1191,13 +1191,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.12", + "value": "0.4", "space": "lch" } } }, "$type": "color", - "$value": "{mint.200}", + "$value": "{lime.200}", "$description": "Background for line numbers of added code, providing visual connection to added content." }, "add-content-highlight": { @@ -1205,13 +1205,13 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.16", + "value": "0.4", "space": "lch" } } }, "$type": "color", - "$value": "{mint.200}", + "$value": "{lime.200}", "$description": "Stronger emphasis color for specific character changes within added lines." }, "add-widget": { @@ -1229,7 +1229,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.08", + "value": "0.15", "space": "lch" } } @@ -1243,7 +1243,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.12", + "value": "0.4", "space": "lch" } } @@ -1257,7 +1257,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.16", + "value": "0.4", "space": "lch" } } @@ -1305,7 +1305,7 @@ "studio.tokens": { "modify": { "type": "alpha", - "value": "0.22", + "value": "0.4", "space": "lch" } } @@ -1815,6 +1815,19 @@ "type": "innerShadow" } } + }, + "ai": { + "inner": { + "$type": "boxShadow", + "$value": { + "x": "0", + "y": "7", + "blur": "16", + "spread": "0", + "color": "{set.ai.surface.inner}", + "type": "innerShadow" + } + } } } }, @@ -2679,7 +2692,7 @@ "ai-subtle": { "gradient-stop-1": { "$type": "color", - "$value": "{yellow.25}" + "$value": "{orange.50}" }, "gradient-stop-2": { "$type": "color", diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json index fbfb3f50ec..09fcfd03ad 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json @@ -274,7 +274,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json index 8caa0d9a06..16548b1795 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json @@ -274,7 +274,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json index 60b78bc311..3069d7c5f5 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json @@ -274,7 +274,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer.json b/packages/core-design-system/design-tokens/mode/light/dimmer.json index 9ca5d9db07..d50fcfd94c 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer.json @@ -256,7 +256,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json index 07248cc897..e9a78ce96c 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json index fb5d16611e..3a3fe438b1 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json index 9e1b0da820..59295cd643 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast.json b/packages/core-design-system/design-tokens/mode/light/high-contrast.json index dbaa4e5e08..4ed5861dd5 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast.json @@ -265,7 +265,7 @@ "$type": "color", "$value": "linear-gradient(102deg, {gradient.ai-subtle.gradient-stop-1} 8%, {gradient.ai-subtle.gradient-stop-2} 35%, {gradient.ai-subtle.gradient-stop-3} 65%, {gradient.ai-subtle.gradient-stop-4} 87%)" }, - "inner-shadow": { + "inner": { "$type": "color", "$value": "{pure.white}" } diff --git a/packages/ui/tailwind-utils-config/components/avatar.ts b/packages/ui/tailwind-utils-config/components/avatar.ts index 2d67fb9602..2a77a70735 100644 --- a/packages/ui/tailwind-utils-config/components/avatar.ts +++ b/packages/ui/tailwind-utils-config/components/avatar.ts @@ -24,7 +24,7 @@ export default { height: `var(--cn-avatar-size-lg)`, width: `var(--cn-avatar-size-lg)`, fontSize: `var(--cn-font-size-2)`, - fontWeight: `var(--cn-font-weight-default-normal-400)`, + fontWeight: `var(--cn-font-weight-default-normal-400)` }, '&:where(.cn-avatar-rounded)': { diff --git a/packages/ui/tailwind-utils-config/components/button-group.ts b/packages/ui/tailwind-utils-config/components/button-group.ts index 58bac4ffe3..d7113307ce 100644 --- a/packages/ui/tailwind-utils-config/components/button-group.ts +++ b/packages/ui/tailwind-utils-config/components/button-group.ts @@ -1,51 +1,51 @@ export default { - '.cn-button-group': { - '@apply flex': '', - - '&-horizontal': { - '& .cn-button': { - '&:not(.cn-button-group-last)': { - 'margin-right': '-1px', - }, - - '&:not(.cn-button-group-first):not(.cn-button-group-last)': { - 'border-radius': '0', - }, - - '&.cn-button-group-first:not(.cn-button-group-last)': { - 'border-top-right-radius': '0', - 'border-bottom-right-radius': '0', - }, - - '&.cn-button-group-last:not(.cn-button-group-first)': { - 'border-top-left-radius': '0', - 'border-bottom-left-radius': '0', - } - } + '.cn-button-group': { + '@apply flex': '', + + '&-horizontal': { + '& .cn-button': { + '&:not(.cn-button-group-last)': { + 'margin-right': '-1px' + }, + + '&:not(.cn-button-group-first):not(.cn-button-group-last)': { + 'border-radius': '0' + }, + + '&.cn-button-group-first:not(.cn-button-group-last)': { + 'border-top-right-radius': '0', + 'border-bottom-right-radius': '0' }, - '&-vertical': { - '@apply flex-col': '', + '&.cn-button-group-last:not(.cn-button-group-first)': { + 'border-top-left-radius': '0', + 'border-bottom-left-radius': '0' + } + } + }, - '& .cn-button': { - '&:not(.cn-button-group-last)': { - 'margin-bottom': '-1px', - }, + '&-vertical': { + '@apply flex-col': '', - '&:not(.cn-button-group-first):not(.cn-button-group-last)': { - 'border-radius': '0', - }, + '& .cn-button': { + '&:not(.cn-button-group-last)': { + 'margin-bottom': '-1px' + }, - '&.cn-button-group-first:not(.cn-button-group-last)': { - 'border-bottom-left-radius': '0', - 'border-bottom-right-radius': '0', - }, + '&:not(.cn-button-group-first):not(.cn-button-group-last)': { + 'border-radius': '0' + }, - '&.cn-button-group-last:not(.cn-button-group-first)': { - 'border-top-left-radius': '0', - 'border-top-right-radius': '0', - } - } + '&.cn-button-group-first:not(.cn-button-group-last)': { + 'border-bottom-left-radius': '0', + 'border-bottom-right-radius': '0' }, + + '&.cn-button-group-last:not(.cn-button-group-first)': { + 'border-top-left-radius': '0', + 'border-top-right-radius': '0' + } + } } + } } diff --git a/packages/ui/tailwind-utils-config/components/button.ts b/packages/ui/tailwind-utils-config/components/button.ts index bebc1437ef..bf47a1697f 100644 --- a/packages/ui/tailwind-utils-config/components/button.ts +++ b/packages/ui/tailwind-utils-config/components/button.ts @@ -143,7 +143,7 @@ export default { backgroundImage: `linear-gradient(to right, var(--cn-set-ai-surface-bg), var(--cn-set-ai-surface-bg)), var(--cn-set-ai-surface-border)`, backgroundOrigin: 'border-box', backgroundClip: 'padding-box, border-box', - border: 'var(--cn-badge-border) solid transparent', + border: 'var(--cn-btn-border) solid transparent', boxShadow: 'var(--cn-shadow-comp-ai-inner)', '&:hover:not(:disabled, .cn-button-disabled)': { diff --git a/packages/ui/tailwind-utils-config/components/drawer.ts b/packages/ui/tailwind-utils-config/components/drawer.ts index 94ef1b3170..fb3c6e1a1b 100644 --- a/packages/ui/tailwind-utils-config/components/drawer.ts +++ b/packages/ui/tailwind-utils-config/components/drawer.ts @@ -90,7 +90,7 @@ export default { '@apply flex flex-col border-b': '', '&-icon': { - flexShrink: '0', + flexShrink: '0' }, '&-icon-color': { From ac3e80fb9c347fa2e78eab7270dab8cc1c27c194 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Thu, 21 Aug 2025 03:39:47 +0000 Subject: [PATCH 158/180] add ai summary button to conversation tab edit description comment box (#10262) * 85af0f add ai summary button to conversation tab edit description comment box --- .../pull-request-conversation.tsx | 43 ++++++++++++++++++- .../conversation/pull-request-comment-box.tsx | 5 +-- .../pull-request-description-box.tsx | 5 ++- .../conversation/pull-request-overview.tsx | 4 ++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 1c99314eb5..010de30ad9 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' +import { useMutation } from '@tanstack/react-query' import copy from 'clipboard-copy' import { isEmpty } from 'lodash-es' @@ -12,6 +13,7 @@ import { OpenapiMergePullReq, OpenapiStatePullReqRequest, RebaseBranchRequestBody, + RepoRepositoryOutput, reviewerAddPullReq, reviewerDeletePullReq, statePullReq, @@ -47,8 +49,9 @@ import { useRoutes } from '../../framework/context/NavigationContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useMFEContext } from '../../framework/hooks/useMFEContext' import { useQueryState } from '../../framework/hooks/useQueryState' +import { useAPIPath } from '../../hooks/useAPIPath.ts' import { PathParams } from '../../RouteDefinitions' -import { filenameToLanguage } from '../../utils/git-utils' +import { filenameToLanguage, normalizeGitRef } from '../../utils/git-utils' import { usePrConversationLabels } from './hooks/use-pr-conversation-labels' import { usePrFilters } from './hooks/use-pr-filters' import { usePRCommonInteractions } from './hooks/usePRCommonInteractions' @@ -166,6 +169,12 @@ const onCopyClick = (commentId?: number, isNotCodeComment = false) => { copy(url.toString()) } +interface AiPullRequestSummaryParams { + repoMetadata: RepoRepositoryOutput + baseRef: string + headRef: string +} + export default function PullRequestConversationPage() { const routes = useRoutes() const { @@ -361,6 +370,36 @@ export default function PullRequestConversationPage() { [updateTitle, refetchPullReq] ) + const getApiPath = useAPIPath() + + const mutation = useMutation(async ({ repoMetadata, baseRef, headRef }: AiPullRequestSummaryParams) => { + return fetch(getApiPath(`/api/v1/repos/${repoMetadata.path}/+/genai/change-summary`), { + method: 'POST', + body: JSON.stringify({ + base_ref: baseRef, + head_ref: headRef + }) + }) + .then(res => res.json()) + .then(json => ({ + summary: json.summary + })) + }) + + const handleAiPullRequestSummary = useCallback(async () => { + if (repoMetadata && repoMetadata.path && pullReqMetadata?.source_branch && pullReqMetadata?.target_branch) { + return await mutation.mutateAsync({ + repoMetadata, + baseRef: normalizeGitRef(pullReqMetadata?.target_branch) || '', + headRef: normalizeGitRef(pullReqMetadata.source_branch) || '' + }) + } + + return Promise.resolve({ + summary: '' + }) + }, [mutation]) + const onDeleteBranch = useCallback(() => { const onSuccessDeleteCommon = () => { setShowDeleteBranchButton(false) @@ -801,6 +840,7 @@ export default function PullRequestConversationPage() { () => ({ toCommitDetails: ({ sha }: { sha: string }) => routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha }), handleUpdateDescription, + handleAiPullRequestSummary, handleDeleteComment: deleteComment, handleUpdateComment: updateComment, data: activities, @@ -827,6 +867,7 @@ export default function PullRequestConversationPage() { [ routes, handleUpdateDescription, + handleAiPullRequestSummary, deleteComment, updateComment, activities, diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index db80e2b711..7406e03790 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -307,7 +307,6 @@ export const PullRequestCommentBox = ({ }, []) useEffect(() => { - console.debug('textSelection', textSelection) if (textAreaRef.current) { textAreaRef.current.setSelectionRange(textSelection.start, textSelection.end) } @@ -676,7 +675,7 @@ export const PullRequestCommentBox = ({ resizable ref={textAreaRef} placeholder={textareaPlaceholder ?? 'Add your comment here'} - className="min-h-24 pb-8 text-cn-foreground-1" + className="min-h-32 pb-8 text-cn-foreground-1" autoFocus={!!autofocus || !!inReplyMode} principalProps={principalProps} setPrincipalsMentionMap={setPrincipalsMentionMap} @@ -727,7 +726,7 @@ export const PullRequestCommentBox = ({ </div> </Tabs.Content> <Tabs.Content className="w-full" value={TABS_KEYS.PREVIEW}> - <div className="min-h-24 w-full"> + <div className="min-h-32 w-full"> {comment ? ( <MarkdownViewer markdownClassName="pr-section bg-transparent w-full" diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx index f32d37d727..795e16b8d8 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-description-box.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useState } from 'react' import { Avatar, Button, DropdownMenu, IconV2, Layout, MarkdownViewer, Text, TimeAgoCard } from '@/components' -import { HandleUploadType, PrincipalPropsType } from '@/views' +import { HandleAiPullRequestSummaryType, HandleUploadType, PrincipalPropsType } from '@/views' import { noop } from 'lodash-es' import { PullRequestCommentBox } from './pull-request-comment-box' @@ -15,6 +15,7 @@ export interface PullRequestDescBoxProps { createdAt?: number description?: string handleUpdateDescription: (title: string, description: string) => Promise<void> + handleAiPullRequestSummary?: HandleAiPullRequestSummaryType handleUpload: HandleUploadType principalProps: PrincipalPropsType } @@ -28,6 +29,7 @@ const PullRequestDescBox: FC<PullRequestDescBoxProps> = ({ handleUpdateDescription, title, handleUpload, + handleAiPullRequestSummary, principalProps }) => { const [comment, setComment] = useState(description || '') @@ -105,6 +107,7 @@ const PullRequestDescBox: FC<PullRequestDescBoxProps> = ({ principalsMentionMap={{}} setPrincipalsMentionMap={noop} handleUpload={handleUpload} + handleAiPullRequestSummary={handleAiPullRequestSummary} onSaveComment={() => { return handleUpdateDescription(title || '', comment) .then(() => { diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx index c532f24c6d..8756bad1c4 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-overview.tsx @@ -3,6 +3,7 @@ import { FC, useMemo } from 'react' import { CommentItem, GeneralPayload, + HandleAiPullRequestSummaryType, HandleUploadType, isCodeComment, isComment, @@ -53,6 +54,7 @@ export interface PullRequestOverviewProps diffData?: { text: string; addedLines?: number; deletedLines?: number; data?: string; title: string; lang: string } toCode?: ({ sha }: { sha: string }) => string handleUpload: HandleUploadType + handleAiPullRequestSummary?: HandleAiPullRequestSummaryType principalProps: PrincipalPropsType spaceId?: string repoId?: string @@ -78,6 +80,7 @@ export const PullRequestOverview: FC<PullRequestOverviewProps> = ({ filenameToLanguage, toggleConversationStatus, handleUpdateDescription, + handleAiPullRequestSummary, toCommitDetails, toCode, principalProps @@ -180,6 +183,7 @@ export const PullRequestOverview: FC<PullRequestOverviewProps> = ({ handleUpload={handleUpload} title={pullReqMetadata?.title} handleUpdateDescription={handleUpdateDescription} + handleAiPullRequestSummary={handleAiPullRequestSummary} createdAt={pullReqMetadata?.created} isLast={!(activityBlocks?.length > 0)} author={pullReqMetadata?.author?.display_name} From 3c27cd601b238c9fb5d68cf6ac61e1528813faa7 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Thu, 21 Aug 2025 05:49:47 +0000 Subject: [PATCH 159/180] fix: sidebar width and scroll position persistence as MFE re-renders on file route change (#10255) * a305d2 fix: sidebar width and scroll position persistance as MFE re-renders on file route change --- .../src/pages-v2/repo/repo-sidebar.tsx | 4 +- packages/ui/src/components/scroll-area.tsx | 95 +++++++++++++++++++ .../components/draggable-sidebar-divider.tsx | 8 +- .../ui/src/views/repo/repo-sidebar/index.tsx | 12 ++- 4 files changed, 112 insertions(+), 7 deletions(-) diff --git a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx index c16619e76d..93bebca6c0 100644 --- a/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-sidebar.tsx @@ -24,6 +24,7 @@ import { CreateBranchDialog } from '../../components-v2/create-branch-dialog' import Explorer from '../../components-v2/FileExplorer' import { useRoutes } from '../../framework/context/NavigationContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' +import useLocalStorage from '../../framework/hooks/useLocalStorage' import { useGitRef } from '../../hooks/useGitRef' import { PathParams } from '../../RouteDefinitions' import { FILE_SEPARATOR, normalizeGitRef, REFS_BRANCH_PREFIX, REFS_TAGS_PREFIX } from '../../utils/git-utils' @@ -40,7 +41,7 @@ export const RepoSidebar = () => { const navigate = useNavigate() const [isCreateBranchDialogOpen, setCreateBranchDialogOpen] = useState(false) const [branchQueryForNewBranch, setBranchQueryForNewBranch] = useState<string>('') - const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const [sidebarWidth, setSidebarWidth] = useLocalStorage<number>('sidebarWidth', SIDEBAR_MIN_WIDTH) const containerRef = useRef<HTMLDivElement>(null) const { @@ -208,6 +209,7 @@ export const RepoSidebar = () => { navigateToNewFile={navigateToNewFile} navigateToFile={navigateToFile} filesList={filesList} + repoRef={repoRef} branchSelectorRenderer={ <BranchSelectorContainer onSelectBranchorTag={selectBranchOrTag} diff --git a/packages/ui/src/components/scroll-area.tsx b/packages/ui/src/components/scroll-area.tsx index 962789eade..f2daf34d23 100644 --- a/packages/ui/src/components/scroll-area.tsx +++ b/packages/ui/src/components/scroll-area.tsx @@ -19,9 +19,14 @@ export type ScrollAreaProps = { direction?: 'ltr' | 'rtl' className?: string classNameContent?: string + preserveScrollPosition?: boolean + storageKey?: string } & ScrollAreaIntersectionProps & HTMLAttributes<HTMLDivElement> +const MAX_AGE = 30 * 60 * 1000 // 30 minutes +const PERSIST_DEBOUNCE = 1000 // 1s debounce + const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>( ( { @@ -38,17 +43,107 @@ const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>( className, classNameContent, + preserveScrollPosition = false, + storageKey, + ...rest }, ref ) => { const viewportRef = useRef<HTMLDivElement | null>(null) + const scrollTimeoutRef = useRef<NodeJS.Timeout>() const topMarkerRef = useRef<HTMLDivElement | null>(null) const bottomMarkerRef = useRef<HTMLDivElement | null>(null) const leftMarkerRef = useRef<HTMLDivElement | null>(null) const rightMarkerRef = useRef<HTMLDivElement | null>(null) + // Save scroll position to sessionStorage + const saveScrollPosition = useCallback(() => { + if (!preserveScrollPosition || !storageKey || !viewportRef.current) return + + const scrollTop = viewportRef.current.scrollTop + + try { + sessionStorage.setItem(`scroll_${storageKey}`, JSON.stringify({ scrollTop, timestamp: Date.now() })) + } catch (error) { + // Fail silently + console.warn('Failed to save scroll position:', error) + } + }, [preserveScrollPosition, storageKey]) + + // Restore scroll position from sessionStorage + const restoreScrollPosition = useCallback(() => { + if (!preserveScrollPosition || !storageKey || !viewportRef.current) return + + try { + const stored = sessionStorage.getItem(`scroll_${storageKey}`) + if (stored) { + const { scrollTop, timestamp } = JSON.parse(stored) + + if (Date.now() - timestamp < MAX_AGE) { + viewportRef.current.scrollTop = scrollTop + } else { + sessionStorage.removeItem(`scroll_${storageKey}`) + } + } + } catch (error) { + // Fail silently + console.warn('Failed to restore scroll position:', error) + } + }, [preserveScrollPosition, storageKey]) + + // Handle scroll events with debouncing + const handleScroll = useCallback(() => { + if (!preserveScrollPosition) return + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + } + scrollTimeoutRef.current = setTimeout(() => { + saveScrollPosition() + }, PERSIST_DEBOUNCE) + }, [preserveScrollPosition, saveScrollPosition]) + + // scroll position clean up + useEffect(() => { + if (!preserveScrollPosition || !storageKey) return + try { + const keysToRemove: string[] = [] + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i) + if (key?.startsWith('scroll_') && key !== `scroll_${storageKey}`) { + keysToRemove.push(key) + } + } + keysToRemove.forEach(key => sessionStorage.removeItem(key)) + } catch (error) { + // Fail silently + } + }, [preserveScrollPosition, storageKey]) + + // Restore scroll position on mount + useEffect(() => { + if (!preserveScrollPosition || !storageKey) return + const timer = setTimeout(() => { + restoreScrollPosition() + }, 0) + return () => clearTimeout(timer) + }, [preserveScrollPosition, restoreScrollPosition, storageKey]) + + useEffect(() => { + const viewport = viewportRef.current + if (!viewport) return + + viewport.addEventListener('scroll', handleScroll, { passive: true }) + + return () => { + viewport.removeEventListener('scroll', handleScroll) + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + } + } + }, [handleScroll]) + const createObserver = useCallback( ( markerRef: RefObject<HTMLElement>, diff --git a/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx index 1a4e189239..5f4e3eebdb 100644 --- a/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx +++ b/packages/ui/src/views/repo/components/draggable-sidebar-divider.tsx @@ -4,7 +4,7 @@ import { cn } from '@utils/cn' interface DraggableSidebarDividerProps { width: number - setWidth: (width: number | ((prev: number) => number)) => void + setWidth: (width: number) => void containerRef?: RefObject<HTMLElement> minWidth?: number maxWidth?: number @@ -54,11 +54,11 @@ export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ( switch (e.key) { case 'ArrowLeft': e.preventDefault() - setWidth(prev => Math.max(minWidth, prev - step)) + setWidth(Math.max(minWidth, width - step)) break case 'ArrowRight': e.preventDefault() - setWidth(prev => Math.min(maxWidth, prev + step)) + setWidth(Math.min(maxWidth, width + step)) break case 'Home': e.preventDefault() @@ -70,7 +70,7 @@ export const DraggableSidebarDivider: React.FC<DraggableSidebarDividerProps> = ( break } }, - [setWidth, minWidth, maxWidth] + [setWidth, minWidth, maxWidth, width] ) return ( diff --git a/packages/ui/src/views/repo/repo-sidebar/index.tsx b/packages/ui/src/views/repo/repo-sidebar/index.tsx index ad9aa7172c..4078804fae 100644 --- a/packages/ui/src/views/repo/repo-sidebar/index.tsx +++ b/packages/ui/src/views/repo/repo-sidebar/index.tsx @@ -8,6 +8,7 @@ interface RepoSidebarProps { filesList?: string[] children: ReactNode branchSelectorRenderer: ReactNode + repoRef?: string } export const RepoSidebar = ({ @@ -15,7 +16,8 @@ export const RepoSidebar = ({ branchSelectorRenderer, navigateToFile, filesList, - children + children, + repoRef }: RepoSidebarProps) => { return ( <> @@ -35,7 +37,13 @@ export const RepoSidebar = ({ contentClassName="w-[800px]" /> - <ScrollArea className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5">{children}</ScrollArea> + <ScrollArea + className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5" + preserveScrollPosition={true} + storageKey={repoRef ? `fileExplorer_${repoRef}` : undefined} + > + {children} + </ScrollArea> </Layout.Flex> </div> </> From 1c33c344e4d6e2d30b9cdf76673859cbf048b8d9 Mon Sep 17 00:00:00 2001 From: Shaurya Kalia <shaurya.kalia@harness.io> Date: Thu, 21 Aug 2025 05:49:56 +0000 Subject: [PATCH 160/180] fix: height for scrollArea + file explorer folder click triggers accordion if no link provided + design for PR changed files bar (#10257) * c2ea0b fix: design for PR changed files bar * 79f76b fix: file explorer folder click triggers accordion if no link provided * fcb8f5 fix: height for scrollArea --- packages/ui/src/components/file-explorer.tsx | 26 ++++++++++--------- .../pull-request-compare-diff-list.tsx | 5 +++- .../components/pull-request-diff-sidebar.tsx | 4 +-- .../changes/pull-request-changes-filter.tsx | 20 +++++++++++--- .../details/pull-request-changes-page.tsx | 6 +++++ 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index 53e5f8129b..ecb0524ac0 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -43,24 +43,26 @@ interface FolderItemProps { function FolderItem({ children, value = '', isActive, content, link, level }: FolderItemProps) { const { Link } = useRouterContext() + const itemElement = ( + <Item + icon="folder" + isActive={isActive} + style={{ + marginLeft: `calc(-16px * ${level + 1} - 8px)`, + paddingLeft: `calc(16px * ${level + 1} + 8px)` + }} + > + {children} + </Item> + ) + return ( <Accordion.Item value={value} className="border-none"> <Accordion.Trigger className="pl-cn-2xs mb-cn-4xs p-0 [&>.cn-accordion-trigger-indicator]:mt-0 [&>.cn-accordion-trigger-indicator]:-rotate-90 [&>.cn-accordion-trigger-indicator]:self-center [&>.cn-accordion-trigger-indicator]:data-[state=open]:-rotate-0" indicatorProps={{ size: '2xs' }} > - <Link to={link || ''}> - <Item - icon="folder" - isActive={isActive} - style={{ - marginLeft: `calc(-16px * ${level + 1} - 8px)`, - paddingLeft: `calc(16px * ${level + 1} + 8px)` - }} - > - {children} - </Item> - </Link> + {link ? <Link to={link}>{itemElement}</Link> : itemElement} </Accordion.Trigger> {!!content && ( diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index 61c70ffe4b..c00e44f653 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -2,6 +2,7 @@ import { FC, RefObject, useCallback, useEffect, useMemo, useRef, useState } from import { Button, IconV2, Layout, ListActions } from '@/components' import { + ChangedFilesShortInfo, DiffModeOptions, DraggableSidebarDivider, InViewDiffRenderer, @@ -26,7 +27,7 @@ import { } from '../../utils' interface PullRequestCompareDiffListProps { - diffStats?: TypesDiffStats + diffStats: TypesDiffStats diffData: HeaderProps[] currentUser?: string sourceBranch?: string @@ -38,6 +39,7 @@ interface PullRequestCompareDiffListProps { } const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ + diffStats, diffData, currentUser, jumpToDiff, @@ -125,6 +127,7 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ > <IconV2 name={showExplorer ? 'collapse-sidebar' : 'expand-sidebar'} size="md" /> </Button> + <ChangedFilesShortInfo diffData={diffData} diffStats={diffStats} goToDiff={setJumpToDiff} /> </ListActions.Left> <ListActions.Right> <ListActions.Dropdown diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx index c54cedc83a..8f9ad6fa42 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx @@ -28,14 +28,14 @@ export const PullRequestDiffSidebar: React.FC<PullRequestDiffSidebarProps> = ({ width: `${sidebarWidth}px` }} > - <div className="flex h-full flex-col gap-3 pt-1.5"> + <div className="flex h-[calc(100vh-55px)] flex-col gap-3 pt-1.5"> <SearchFiles navigateToFile={file => { setJumpToDiff(file) }} filesList={filePaths} /> - <ScrollArea className="pb-cn-xl -mr-cn-xs pr-cn-xs flex-1"> + <ScrollArea className="pb-cn-xl -mr-cn-xs pr-cn-xs"> <PullRequestChangesExplorer paths={filePaths} setJumpToDiff={setJumpToDiff} diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 2c3098ed5c..2f61ae903a 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react' import { Button, CounterBadge, DropdownMenu, IconV2, Layout, SplitButton } from '@/components' import { useTranslation } from '@/context' import { TypesUser } from '@/types' -import { DiffModeOptions, TypesCommit } from '@/views' +import { ChangedFilesShortInfo, DiffModeOptions, TypesCommit } from '@/views' import { DiffModeEnum } from '@git-diff-view/react' import { cn } from '@utils/index' @@ -52,6 +52,12 @@ export interface PullRequestChangesFilterProps { pullReqStats?: TypesPullReqStats showExplorer: boolean setShowExplorer: (value: boolean) => void + diffData?: { + filePath: string + addedLines: number + deletedLines: number + }[] + setJumpToDiff: (fileName: string) => void } export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = ({ @@ -72,7 +78,9 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = onCommitSuggestionsBatch, pullReqStats, showExplorer, - setShowExplorer + setShowExplorer, + diffData, + setJumpToDiff }) => { const { t } = useTranslation() const [commitFilterOptions, setCommitFilterOptions] = useState([defaultCommitFilter]) @@ -169,12 +177,12 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = {selectedCommits[0].value === 'ALL' ? ( <> <span>{defaultCommitFilter.name}</span> - <span className="text-cn-foreground-3">({defaultCommitFilter.count})</span> + <CounterBadge>{defaultCommitFilter.count}</CounterBadge> </> ) : ( <> <span>Commits</span> - <span className="text-cn-foreground-3">({selectedCommits?.length})</span> + <CounterBadge>{selectedCommits?.length}</CounterBadge> </> )} <IconV2 name="nav-solid-arrow-down" size="2xs" /> @@ -209,6 +217,10 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = ))} </DropdownMenu.Content> </DropdownMenu.Root> + + <div className="mt-2.5"> + <ChangedFilesShortInfo diffData={diffData} diffStats={pullReqStats} goToDiff={setJumpToDiff} /> + </div> </Layout.Horizontal> <Layout.Horizontal className="gap-x-7"> diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index 2bfe150c37..4e309ee44b 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -248,6 +248,12 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ commitSuggestionsBatchCount={commitSuggestionsBatchCount} showExplorer={showExplorer} setShowExplorer={setShowExplorer} + diffData={diffs?.map(diff => ({ + filePath: diff.filePath, + addedLines: diff.addedLines, + deletedLines: diff.deletedLines + }))} + setJumpToDiff={setJumpToDiff} /> {renderContent()} </SandboxLayout.Content> From 4c4ad248fd9499561a5b74a077b420527ab4a633 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Thu, 21 Aug 2025 13:20:31 +0300 Subject: [PATCH 161/180] Change Delete filter Dropdown to Button (#2091) --- .../components/filters/filter-box-wrapper.tsx | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/components/filters/filter-box-wrapper.tsx b/packages/ui/src/components/filters/filter-box-wrapper.tsx index 06b685c30b..c3a93172bc 100644 --- a/packages/ui/src/components/filters/filter-box-wrapper.tsx +++ b/packages/ui/src/components/filters/filter-box-wrapper.tsx @@ -67,23 +67,15 @@ const FilterBoxWrapper = ({ <DropdownMenu.Header> <Layout.Flex align="center" justify="between"> <Text as="span">{filterLabel}</Text> - - <DropdownMenu.Root> - <DropdownMenu.Trigger className="group flex h-[18px] items-center px-1"> - <IconV2 - className="text-icons-1 transition-colors duration-200 group-hover:text-cn-foreground-1" - name="more-horizontal" - size="2xs" - /> - </DropdownMenu.Trigger> - <DropdownMenu.Content align="start"> - <DropdownMenu.IconItem - icon="trash" - onSelect={handleRemoveFilter} - title={t('component:filter.delete', 'Delete filter')} - /> - </DropdownMenu.Content> - </DropdownMenu.Root> + <Button + iconOnly + variant="outline" + size="2xs" + onClick={handleRemoveFilter} + aria-label={t('component:filter.delete', 'Delete filter')} + > + <IconV2 name="trash" skipSize /> + </Button> </Layout.Flex> </DropdownMenu.Header> From 5308020c1ed825ad5e6ce4afda1b7cb9ca2f22d6 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Thu, 21 Aug 2025 12:00:42 +0000 Subject: [PATCH 162/180] limit multiline selection to one hunk (#10256) * 6370ee remove not used code * 718536 improve * a4c22a scope multiline selection to one hunk * 4c47d6 use blocks for getting lines for suggestion --- .../extended-diff-view-types.ts | 1 + .../extended-diff-view/extended-diff-view.tsx | 6 +- .../components/pull-request-accordian.tsx | 1 + .../components/pull-request-diff-viewer.tsx | 17 ++- .../components/conversation/diff-utils.ts | 100 ++++++++++-------- .../conversation/pull-request-comment-box.tsx | 21 ++-- 6 files changed, 90 insertions(+), 56 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-types.ts b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-types.ts index 05ec6ad518..7630f4fe01 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-types.ts +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-types.ts @@ -7,6 +7,7 @@ export type LinesRange = { } export interface ExtendedDiffViewProps<T> extends Omit<DiffViewProps<T>, 'extendData' | 'renderWidgetLine'> { + scopeMultilineSelectionToOneHunk?: (lineRange: LinesRange) => LinesRange extendData?: { oldFile?: Record< string, diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx index 9b2e4765bd..eac08384e9 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view.tsx @@ -25,7 +25,7 @@ export const ExtendedDiffView = forwardRef( getDiffFileInstance: () => DiffFile }> ) => { - const { extendData, renderWidgetLine, diffViewAddWidget } = props + const { extendData, renderWidgetLine, diffViewAddWidget, scopeMultilineSelectionToOneHunk } = props const enableMultiSelect = !!diffViewAddWidget @@ -81,7 +81,9 @@ export const ExtendedDiffView = forwardRef( if (!line) return if (line !== null && selectedRangeRef.current) { - selectedRangeRef.current = { ...selectedRangeRef.current, end: line } + selectedRangeRef.current = scopeMultilineSelectionToOneHunk + ? scopeMultilineSelectionToOneHunk({ ...selectedRangeRef.current, end: line }) + : { ...selectedRangeRef.current, end: line } updateSelection(containerRef.current, selectedRangeRef.current, preselectedLinesRef.current) } } diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 30d1ae4017..9fd15ec37b 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -387,6 +387,7 @@ export const PullRequestAccordion: React.FC<{ <PullRequestDiffViewer principalProps={principalProps} handleUpload={handleUpload} + blocks={header.diffData.blocks} data={rawDiffData} fontsize={fontsize} highlight={highlight} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx index 5eaa7600ea..e198be3e17 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-viewer.tsx @@ -22,13 +22,14 @@ import { debounce, get } from 'lodash-es' import { OverlayScrollbars } from 'overlayscrollbars' import PRCommentView from '../details/components/common/pull-request-comment-view' +import { scopeLinesRangeToOneBlock } from '../details/components/conversation/diff-utils' import PullRequestTimelineItem from '../details/components/conversation/pull-request-timeline-item' import { replaceMentionEmailWithId, replaceMentionIdWithEmail } from '../details/components/conversation/utils' import { ExpandedCommentsContext, useExpandedCommentsContext } from '../details/context/pull-request-comments-context' import { useDiffHighlighter } from '../hooks/useDiffHighlighter' import { quoteTransform } from '../utils' import { ExtendedDiffView } from './extended-diff-view/extended-diff-view' -import { ExtendedDiffViewProps } from './extended-diff-view/extended-diff-view-types' +import { ExtendedDiffViewProps, LinesRange } from './extended-diff-view/extended-diff-view-types' interface Thread { parent: CommentItem<TypesPullReqActivity> @@ -81,6 +82,7 @@ interface PullRequestDiffviewerProps { const PullRequestDiffViewer = ({ data, + blocks, highlight, fontsize, mode, @@ -370,7 +372,7 @@ const PullRequestDiffViewer = ({ const commentText = newComments[commentKey] ?? '' return ( - <div className="bg-cn-background-1 flex w-full flex-col p-4"> + <div className="flex w-full flex-col bg-cn-background-1 p-4"> <PullRequestCommentBox autofocus handleUpload={handleUpload} @@ -401,6 +403,7 @@ const PullRequestDiffViewer = ({ lineNumber={lineNumber} lineFromNumber={lineFromNumber} sideKey={sideKey} + blocks={blocks} diff={data} lang={lang} comment={commentText} @@ -532,6 +535,7 @@ const PullRequestDiffViewer = ({ onCancelClick={() => { toggleEditMode(componentId, '') }} + blocks={blocks} diff={data} lang={lang} comment={replaceMentionIdWithEmail( @@ -621,6 +625,7 @@ const PullRequestDiffViewer = ({ onCancelClick={() => { toggleEditMode(replyComponentId, '') }} + blocks={blocks} diff={data} lang={lang} comment={replaceMentionIdWithEmail( @@ -692,6 +697,11 @@ const PullRequestDiffViewer = ({ const contextValue = useExpandedCommentsContext() + const scopeMultilineSelectionToOneHunk = useCallback( + (linesRange: LinesRange) => (blocks ? scopeLinesRangeToOneBlock(blocks, linesRange) : linesRange), + [blocks] + ) + return ( <ExpandedCommentsContext.Provider value={contextValue}> <div data-diff-file-path={fileName}> @@ -701,7 +711,7 @@ const PullRequestDiffViewer = ({ {/* @ts-ignore */} <ExtendedDiffView<Thread[]> ref={ref} - className="bg-tr text-cn-foreground-1 w-full" + className="bg-tr w-full text-cn-foreground-1" renderWidgetLine={renderWidgetLine} renderExtendLine={renderExtendLine} diffFile={diffFileInstance} @@ -714,6 +724,7 @@ const PullRequestDiffViewer = ({ // TODO: Remove 'mode === DiffModeEnum.Split' after the shadow dom is removed diffViewAddWidget={addWidget && mode === DiffModeEnum.Split} diffViewTheme={isLightTheme ? 'light' : 'dark'} + scopeMultilineSelectionToOneHunk={scopeMultilineSelectionToOneHunk} /> </div> )} diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts b/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts index 6c506c6c0d..b418241752 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/diff-utils.ts @@ -1,50 +1,64 @@ -interface DiffLine { - type: 'context' | 'add' | 'remove' | 'hunk' - oldNumber: number | null - newNumber: number | null - line: string - content: string +import { LinesRange } from '@views/repo/pull-request/components/extended-diff-view/extended-diff-view-types' + +import { DiffBlock, DiffLine } from '../../pull-request-details-types' + +export function getLinesFromBlocks( + blocks: DiffBlock[], + side: 'old' | 'new', + fromLine: number, + toLine: number +): (DiffLine & { cleanContent: string })[] { + const rangeLines: (DiffLine & { cleanContent: string })[] = [] + + blocks.forEach(block => { + block.lines.forEach(line => { + const num = side === 'old' ? line.oldNumber : line.newNumber + if (typeof num !== 'undefined' && num >= fromLine && num <= toLine) { + rangeLines.push({ ...line, cleanContent: line.content.slice(1) }) + } + }) + }) + + return rangeLines } -export function parseDiffToLines(lines: string[]): DiffLine[] { - const parsed: DiffLine[] = [] - let oldNum = 0 - let newNum = 0 - - for (const line of lines) { - if (line.startsWith('@@')) { - const match = /@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line) - if (!match) continue - - oldNum = parseInt(match[1], 10) - newNum = parseInt(match[2], 10) - - parsed.push({ type: 'hunk', oldNumber: oldNum, newNumber: newNum, line, content: line }) - } else if (line.startsWith('+')) { - parsed.push({ type: 'add', oldNumber: null, newNumber: newNum++, line, content: line.slice(1) }) - } else if (line.startsWith('-')) { - parsed.push({ type: 'remove', oldNumber: oldNum++, newNumber: null, line, content: line.slice(1) }) - } else if (line.startsWith(' ')) { - parsed.push({ type: 'context', oldNumber: oldNum++, newNumber: newNum++, line, content: line.slice(1) }) - } else { - // Skip diff metadata like diff --git, index, --- , +++ +function getBlockContainsLine(blocks: DiffBlock[], side: 'old' | 'new', lineNumber: number) { + return blocks.find(block => { + return !!block.lines.find(line => { + const num = side === 'old' ? line.oldNumber : line.newNumber + if (typeof num !== 'undefined' && num === lineNumber) { + return true + } + }) + }) +} + +export function scopeLinesRangeToOneBlock(blocks: DiffBlock[], linesRange: LinesRange) { + if (linesRange.start === linesRange.end) return linesRange + + const block = getBlockContainsLine(blocks, linesRange.side, linesRange.start) + + if (!block) return linesRange + + const minMax = { min: Number.MAX_SAFE_INTEGER, max: 0 } + + block.lines.forEach(line => { + const num = linesRange.side === 'old' ? line.oldNumber : line.newNumber + if (num) { + minMax.min = Math.min(minMax.min, num) + minMax.max = Math.max(minMax.max, num) } + }) + + const newLinesRange = { ...linesRange } + + if (linesRange.start < linesRange.end) { + newLinesRange.end = Math.min(linesRange.end, minMax.max) } - return parsed -} + if (linesRange.start > linesRange.end) { + newLinesRange.end = Math.max(linesRange.end, minMax.min) + } -export function getDiffLinesRange( - parsed: DiffLine[], - side: 'old' | 'new', - from: number, - to: number, - ignoreHunk = true -): DiffLine[] { - return parsed.filter(line => { - if (ignoreHunk && line.type === 'hunk') return false - - const num = side === 'old' ? line.oldNumber : line.newNumber - return num !== null && num >= from && num <= to - }) + return newLinesRange } diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 7406e03790..4bed5b1345 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -23,6 +23,7 @@ import { Text } from '@/components' import { + DiffBlock, HandleAiPullRequestSummaryType, handleFileDrop, handlePaste, @@ -36,7 +37,7 @@ import { cn } from '@utils/cn' import { getErrorMessage } from '@utils/utils' import { isEmpty, isUndefined } from 'lodash-es' -import { getDiffLinesRange, parseDiffToLines } from './diff-utils' +import { getLinesFromBlocks } from './diff-utils' import { PullRequestCommentTextarea } from './pull-request-comment-textarea' import { replaceMentionEmailWithDisplayName, replaceMentionEmailWithId } from './utils' @@ -88,6 +89,7 @@ export interface PullRequestCommentBoxProps { className?: string comment: string lang?: string + blocks?: DiffBlock[] diff?: string lineNumber?: number lineFromNumber?: number @@ -129,6 +131,7 @@ export const PullRequestCommentBox = ({ inReplyMode = false, autofocus = false, onCancelClick, + blocks, diff = '', lang = '', sideKey, @@ -418,7 +421,7 @@ export const PullRequestCommentBox = ({ } case BuildBehavior.Parse: { const injectedParseString = isEmpty(commentMetadata.selection.text) - ? `${commentMetadata.action.injectedPreString}${parseDiff(diff, sideKey, lineNumber, lineFromNumber)}${commentMetadata.action.injectedPostString}` + ? `${commentMetadata.action.injectedPreString}${parseDiff(blocks, sideKey, lineNumber, lineFromNumber)}${commentMetadata.action.injectedPostString}` : `${commentMetadata.action.injectedPreString}${commentMetadata.selection.text}${commentMetadata.action.injectedPostString}` return `${commentMetadata.selection.textBefore}${injectedParseString}${commentMetadata.selection.textAfter}` @@ -427,7 +430,7 @@ export const PullRequestCommentBox = ({ } const parseDiff = ( - diff: string = '', + blocks: DiffBlock[] = [], sideKey?: 'oldFile' | 'newFile', lineNumber?: number, lineFromNumber?: number @@ -436,11 +439,13 @@ export const PullRequestCommentBox = ({ return '' } - const diffLines = diff.split('\n') - const lines = parseDiffToLines(diffLines) - const rangeArr = getDiffLinesRange(lines, sideKey === 'newFile' ? 'new' : 'old', lineFromNumber, lineNumber) - - return rangeArr.map(item => item.content).join('\n') + const linesFromBlocks = getLinesFromBlocks( + blocks, + sideKey === 'newFile' ? 'new' : 'old', + lineFromNumber, + lineNumber + ) + return linesFromBlocks.map(item => item.cleanContent).join('\n') } const toolbar: ToolbarItem[] = useMemo(() => { From a5c8aed3f219c863756846fefc298913333b5e77 Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Thu, 21 Aug 2025 15:44:01 +0300 Subject: [PATCH 163/180] =?UTF-8?q?PR=20panel=20(Draft=20view):=20show=20?= =?UTF-8?q?=E2=80=9CClose=20PR=E2=80=9D=20in=20MoreActionsTooltip=20(#2092?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pull-request-conversation.tsx | 16 +- .../conversation/pull-request-panel.tsx | 179 +++++++++--------- 2 files changed, 88 insertions(+), 107 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx index 010de30ad9..3869a8ad38 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-conversation.tsx @@ -84,9 +84,7 @@ const getMockPullRequestActions = ( id: '0', title: 'Open for review', description: 'Open this pull request for review.', - action: () => { - handlePrState('open') - } + action: () => handlePrState('open') } ] : pullReqMetadata?.is_draft @@ -95,17 +93,7 @@ const getMockPullRequestActions = ( id: '0', title: 'Ready for review', description: 'Open this pull request for review.', - action: () => { - handlePrState('open') - } - }, - { - id: '1', - title: 'Close pull request', - description: 'Close this pull request. You can still re-open the request after closing.', - action: () => { - handlePrState('closed') - } + action: () => handlePrState('open') } ] : [ diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx index 7f78e1c006..730153b7cb 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-panel.tsx @@ -297,8 +297,7 @@ const getDataFromPullReqMetadata = (pullReqMetadata?: TypesPullReq) => { isOpen, isDraft, isUnchecked: pullReqMetadata?.merge_check_status === MergeCheckStatus.UNCHECKED && !isClosed, - isRebasable: pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged, - isShowMoreTooltip: isOpen && !isDraft + isRebasable: pullReqMetadata?.merge_target_sha !== pullReqMetadata?.merge_base_sha && !pullReqMetadata?.merged } } @@ -479,7 +478,7 @@ const PullRequestPanel = ({ setAccordionValues(data) }, []) - const { isMergeable, isClosed, isOpen, isDraft, isUnchecked, isRebasable, isShowMoreTooltip } = + const { isMergeable, isClosed, isOpen, isDraft, isUnchecked, isRebasable } = getDataFromPullReqMetadata(pullReqMetadata) useEffect(() => { @@ -575,17 +574,23 @@ const PullRequestPanel = ({ 'text-cn-foreground-danger': prState === PrState.Error }) + const shouldShowConfirmation = actions && !pullReqMetadata?.closed && (showActionBtn || isMerging || mergeInitiated) + + const shouldShowSplitButton = + actions?.length > 1 && + !pullReqMetadata?.closed && + !showActionBtn && + !isMerging && + !pullReqMetadata?.merged && + !mergeInitiated + + const shouldShowMoreActions = !shouldShowConfirmation && isOpen + return ( <> <StackedList.Root className="border-cn-borders-3 bg-cn-background-1"> <StackedList.Item - className={cn( - 'items-center py-2 border-cn-borders-3', - { - 'pr-1.5': isShowMoreTooltip - }, - headerRowBgClass - )} + className={cn('items-center py-2 border-cn-borders-3', { 'pr-1.5': shouldShowMoreActions }, headerRowBgClass)} disableHover > <StackedList.Field @@ -628,6 +633,7 @@ const PullRequestPanel = ({ <CounterBadge theme="info">{commitSuggestionsBatchCount}</CounterBadge> </Button> )} + {!notBypassable && isMergeable && !isDraft && prPanelData.ruleViolation && ( <Checkbox className="flex-1" @@ -643,99 +649,86 @@ const PullRequestPanel = ({ truncateLabel={false} /> )} - {(() => { - // Only show SplitButton if we're not in any merge-related state - const shouldShowSplitButton = - actions && - !pullReqMetadata?.closed && - !showActionBtn && - !isMerging && - !pullReqMetadata?.merged && - !mergeInitiated - return shouldShowSplitButton ? ( - <SplitButton - // because of the complex SplitButtonProps type, we need to cast the theme and variant to const - {...(buttonState.variant === 'primary' - ? { theme: 'default' as const, variant: 'primary' as const } - : { - theme: (buttonState.theme || 'default') as 'success' | 'danger' | 'default', - variant: 'outline' as const - })} - disabled={buttonState.disabled} - loading={actions[parseInt(mergeButtonValue)]?.loading} - handleOptionChange={handleMergeTypeSelect} - options={actions.map(action => { - return { - value: action.id, - label: action.title, - description: action.description, - disabled: action.disabled - } - })} - handleButtonClick={() => { - const selectedAction = actions[parseInt(mergeButtonValue)] - if (!selectedAction.disabled) { - const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) - const isMergeAction = mergeActionTitles.has(selectedAction.title) - - if (!isMergeAction) { - selectedAction.action?.() - } else { - handleMergeTypeSelect(mergeButtonValue) - } + + {/*Only show SplitButton if we're not in any merge-related state*/} + {shouldShowSplitButton && ( + <SplitButton + theme={buttonState.variant === 'primary' ? 'default' : (buttonState.theme ?? 'default')} + variant={buttonState.variant} + disabled={buttonState.disabled} + loading={actions[parseInt(mergeButtonValue)]?.loading} + handleOptionChange={handleMergeTypeSelect} + options={actions.map(action => { + return { + value: action.id, + label: action.title, + description: action.description, + disabled: action.disabled + } + })} + handleButtonClick={() => { + const selectedAction = actions[parseInt(mergeButtonValue)] + if (!selectedAction.disabled) { + const mergeActionTitles = new Set(Object.values(MERGE_METHOD_TITLES)) + const isMergeAction = mergeActionTitles.has(selectedAction.title) + + if (!isMergeAction) { + selectedAction.action?.() + } else { + handleMergeTypeSelect(mergeButtonValue) } - }} - size="md" - > - {actions[parseInt(mergeButtonValue)].title} - </SplitButton> - ) : null - })()} + } + }} + size="md" + > + {actions[parseInt(mergeButtonValue)].title} + </SplitButton> + )} + {/* When in merge input mode or merging, show Cancel/Confirm buttons */} - {(() => { - const shouldShowButtonLayout = - actions && !pullReqMetadata?.closed && (showActionBtn || isMerging || mergeInitiated) - - if (!shouldShowButtonLayout) return null - const selectedAction = actions[parseInt(mergeButtonValue || '0')] - return ( - <ButtonLayout> - <Button - variant="outline" - onClick={handleCancelMerge} - loading={cancelInitiated} - disabled={isMerging || mergeInitiated} - > - Cancel - </Button> - <Button - theme="success" - onClick={handleConfirmMerge} - loading={isMerging || mergeInitiated} - disabled={cancelInitiated || shouldDisableFastForwardMerge()} - > - Confirm {selectedAction?.title || 'Merge'} - </Button> - </ButtonLayout> - ) - })()} - {actions && pullReqMetadata?.closed ? ( - <Button variant="primary" theme="default" size="sm" onClick={actions[0].action}> + {shouldShowConfirmation && ( + <ButtonLayout> + <Button + variant="outline" + onClick={handleCancelMerge} + loading={cancelInitiated} + disabled={isMerging || mergeInitiated} + > + Cancel + </Button> + <Button + theme="success" + onClick={handleConfirmMerge} + loading={isMerging || mergeInitiated} + disabled={cancelInitiated || shouldDisableFastForwardMerge()} + > + Confirm {actions[parseInt(mergeButtonValue || '0')]?.title || 'Merge'} + </Button> + </ButtonLayout> + )} + + {actions?.length === 1 && ( + <Button variant="primary" theme="default" onClick={actions[0].action}> {actions[0].title} </Button> - ) : null} - {isShowMoreTooltip && ( + )} + + {shouldShowMoreActions && ( <MoreActionsTooltip className="!ml-2" iconName="more-horizontal" sideOffset={4} alignOffset={0} actions={[ - { - title: 'Mark as draft', - onClick: () => handlePrState('draft'), - iconName: 'page-edit' - }, + ...(!isDraft + ? [ + { + title: 'Mark as draft', + onClick: () => handlePrState('draft'), + iconName: 'page-edit' as const + } + ] + : []), { title: 'Close pull request', onClick: () => handlePrState('closed'), From 46d07c1bebcb340f2da33da87fd17d238c0dbe2c Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:25:11 +0400 Subject: [PATCH 164/180] add files tree design review (#2088) * add files tree design review * fixes * add lines * fix lines position * fix focused state, fix gaps --- .../commit-details-diff-view-wrapper.tsx | 17 ++- .../repo/repo-commit-details-diff.tsx | 18 ++- packages/ui/src/components/file-explorer.tsx | 111 +++++++++++------- packages/ui/src/components/search-files.tsx | 4 +- packages/ui/src/components/tabs.tsx | 2 +- packages/ui/src/context/router-context.tsx | 2 +- .../src/views/layouts/PullRequestLayout.tsx | 4 +- .../commits/pull-request-commits.tsx | 5 +- .../pull-request-compare-diff-list.tsx | 8 +- .../compare/pull-request-compare-page.tsx | 2 +- .../extended-diff-view-style.css | 2 +- .../components/pull-request-accordian.tsx | 2 +- .../components/pull-request-diff-sidebar.tsx | 22 ++-- .../changes/pull-request-changes-explorer.tsx | 79 ++++++++----- .../changes/pull-request-changes-filter.tsx | 8 +- .../changes/pull-request-changes.tsx | 2 +- .../details/pull-request-changes-page.tsx | 6 +- .../pull-request-conversation-page.tsx | 4 +- .../components/commit-diff.tsx | 6 +- .../components/commit-sidebar.tsx | 17 ++- .../repo-commit-details-view.tsx | 99 ++++++++-------- .../views/repo/repo-files/repo-files-view.tsx | 4 +- .../ui/src/views/repo/repo-sidebar/index.tsx | 11 +- .../views/repo/repo-summary/repo-summary.tsx | 2 - 24 files changed, 251 insertions(+), 186 deletions(-) diff --git a/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx b/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx index a0eae357ea..40721e02ad 100644 --- a/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx +++ b/apps/design-system/src/pages/view-preview/commit-details-diff-view-wrapper.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback } from 'react' +import { FC, useCallback, useRef, useState } from 'react' import { commitDetailsStore } from '@subjects/views/commit-details/commit-details-store' import { repoFilesStore } from '@subjects/views/repo-files/components/repo-files-store' @@ -6,18 +6,27 @@ import { renderEntries } from '@utils/fileViewUtils' import { noop } from '@utils/viewUtils' import { FileExplorer, Layout } from '@harnessio/ui/components' -import { CommitDiff, CommitSidebar, ICommitDetailsStore } from '@harnessio/ui/views' +import { + CommitDiff, + CommitSidebar, + DraggableSidebarDivider, + ICommitDetailsStore, + SIDEBAR_MIN_WIDTH +} from '@harnessio/ui/views' export const CommitDetailsDiffViewWrapper: FC = () => { const useCommitDetailsStore = useCallback((): ICommitDetailsStore => commitDetailsStore, []) + const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const containerRef = useRef<HTMLDivElement>(null) return ( - <Layout.Flex gapX="xl"> - <CommitSidebar navigateToFile={() => {}} filesList={repoFilesStore.filesList}> + <Layout.Flex gapX="lg" ref={containerRef}> + <CommitSidebar navigateToFile={() => {}} filesList={repoFilesStore.filesList} sidebarWidth={sidebarWidth}> <FileExplorer.Root onValueChange={noop} value={[]}> {renderEntries(repoFilesStore.filesTreeData, '')} </FileExplorer.Root> </CommitSidebar> + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> <CommitDiff useCommitDetailsStore={useCommitDetailsStore} /> </Layout.Flex> ) diff --git a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx index c85a274b67..9eb022763d 100644 --- a/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-commit-details-diff.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useRef, useState } from 'react' import { useParams } from 'react-router-dom' import * as Diff2Html from 'diff2html' @@ -11,7 +11,7 @@ import { useListPathsQuery } from '@harnessio/code-service-client' import { Layout } from '@harnessio/ui/components' -import { CommitDiff, CommitSidebar } from '@harnessio/ui/views' +import { CommitDiff, CommitSidebar, DraggableSidebarDivider, SIDEBAR_MIN_WIDTH } from '@harnessio/ui/views' import Explorer from '../../components-v2/FileExplorer' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' @@ -33,6 +33,8 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool const isMfe = useIsMFE() const { fullGitRef } = useCodePathDetails() const { setDiffs, setDiffStats, setCommitSHA } = useCommitDetailsStore() + const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_MIN_WIDTH) + const containerRef = useRef<HTMLDivElement>(null) const defaultCommitRange = compact(commitSHA?.split(/~1\.\.\.|\.\.\./g)) const diffApiPath = `${defaultCommitRange[0]}~1...${defaultCommitRange[defaultCommitRange.length - 1]}` @@ -107,11 +109,15 @@ export const CommitDiffContainer = ({ showSidebar = true }: { showSidebar?: bool const filesList = filesData?.body?.files || [] return ( - <Layout.Flex gapX="xl"> + <Layout.Flex gapX="lg" ref={containerRef}> {showSidebar && ( - <CommitSidebar navigateToFile={() => {}} filesList={filesList}> - {!!repoDetails?.body?.content?.entries?.length && <Explorer repoDetails={repoDetails?.body} />} - </CommitSidebar> + <> + <CommitSidebar navigateToFile={() => {}} filesList={filesList} sidebarWidth={sidebarWidth}> + {!!repoDetails?.body?.content?.entries?.length && <Explorer repoDetails={repoDetails?.body} />} + </CommitSidebar> + + <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> + </> )} <CommitDiff diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index ecb0524ac0..887ba9e11f 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -1,36 +1,70 @@ -import { ReactNode } from 'react' +import { ForwardedRef, forwardRef, ReactNode } from 'react' -import { Accordion, GridProps, IconPropsV2, IconV2, Layout, Text } from '@/components' -import { useRouterContext } from '@/context' +import { Accordion, GridProps, IconPropsV2, IconV2, Layout, Text, Tooltip, TooltipProps } from '@/components' +import { LinkProps, useRouterContext } from '@/context' import { cn } from '@utils/cn' -interface ItemProps extends GridProps { +interface BaseItemProps { icon: NonNullable<IconPropsV2['name']> isActive?: boolean } -const Item = ({ className, children, icon, isActive, ...props }: ItemProps) => { - return ( - <Layout.Grid - align="center" - gap="2xs" - flow="column" - justify="start" - className={cn( - 'py-cn-2xs pr-1.5 rounded text-cn-foreground-2 hover:text-cn-foreground-1 hover:bg-cn-background-hover ', - { 'bg-cn-background-selected text-cn-foreground-1': isActive }, - className - )} - {...props} - > - <IconV2 className="text-inherit" name={icon} size="md" /> - <Text className="text-inherit" truncate> - {children} - </Text> - </Layout.Grid> - ) +interface DefaultItemProps extends BaseItemProps, GridProps { + link?: never } +interface LinkItemProps extends BaseItemProps, Omit<LinkProps, 'to'> { + link?: LinkProps['to'] +} + +type ItemProps = DefaultItemProps | LinkItemProps + +const Item = forwardRef<HTMLDivElement, ItemProps>( + ({ className, children, icon, isActive, link, ...props }: ItemProps, ref) => { + const { Link } = useRouterContext() + + const commonClassnames = cn( + 'w-[fill-available] py-cn-2xs pr-1.5 rounded text-cn-foreground-2 hover:text-cn-foreground-1 hover:bg-cn-background-hover focus-visible:text-cn-foreground-1 focus-visible:bg-cn-background-hover focus-visible:outline-none', + { + 'bg-cn-background-selected text-cn-foreground-1': isActive, + 'grid items-center justify-start gap-cn-2xs grid-flow-col': !!link + }, + className + ) + + return link ? ( + <Link + ref={ref as ForwardedRef<HTMLAnchorElement>} + to={link} + className={commonClassnames} + {...(props as Omit<LinkItemProps, 'to'>)} + > + <IconV2 className="text-inherit" name={icon} size="md" /> + <Text className="text-inherit" truncate> + {children} + </Text> + </Link> + ) : ( + <Layout.Grid + ref={ref} + align="center" + gap="2xs" + flow="column" + justify="start" + className={commonClassnames} + as="button" + {...(props as DefaultItemProps)} + > + <IconV2 className="text-inherit" name={icon} size="md" /> + <Text className="text-inherit" truncate> + {children} + </Text> + </Layout.Grid> + ) + } +) +Item.displayName = 'FileExplorerItem' + interface FolderItemProps { children: ReactNode level: number @@ -41,33 +75,30 @@ interface FolderItemProps { } function FolderItem({ children, value = '', isActive, content, link, level }: FolderItemProps) { - const { Link } = useRouterContext() - const itemElement = ( <Item icon="folder" isActive={isActive} - style={{ - marginLeft: `calc(-16px * ${level + 1} - 8px)`, - paddingLeft: `calc(16px * ${level + 1} + 8px)` - }} + style={{ marginLeft: `calc(-16px * ${level + 1} - 8px)`, paddingLeft: `calc(16px * ${level + 1} + 8px)` }} + link={link} > {children} </Item> ) return ( - <Accordion.Item value={value} className="border-none"> + <Accordion.Item value={value} className="border-none "> <Accordion.Trigger - className="pl-cn-2xs mb-cn-4xs p-0 [&>.cn-accordion-trigger-indicator]:mt-0 [&>.cn-accordion-trigger-indicator]:-rotate-90 [&>.cn-accordion-trigger-indicator]:self-center [&>.cn-accordion-trigger-indicator]:data-[state=open]:-rotate-0" + className=" bg-cn-background-1 pl-cn-2xs mb-cn-4xs relative z-[1] p-0 [&>.cn-accordion-trigger-indicator]:mt-0 [&>.cn-accordion-trigger-indicator]:-rotate-90 [&>.cn-accordion-trigger-indicator]:self-center [&>.cn-accordion-trigger-indicator]:data-[state=open]:-rotate-0" indicatorProps={{ size: '2xs' }} + asChild > - {link ? <Link to={link}>{itemElement}</Link> : itemElement} + {itemElement} </Accordion.Trigger> {!!content && ( <Accordion.Content - containerClassName="overflow-visible data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" + containerClassName="overflow-visible data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 relative after:absolute after:left-3 after:top-0 after:block after:h-full after:w-px after:bg-cn-borders-3 after:-translate-x-1/2" className="pl-cn-md pb-0" > {content} @@ -83,26 +114,24 @@ interface FileItemProps { isActive?: boolean link?: string onClick?: () => void + tooltip?: TooltipProps['content'] } -function FileItem({ children, isActive, level, link, onClick }: FileItemProps) { - const { Link } = useRouterContext() +function FileItem({ children, isActive, level, link, onClick, tooltip }: FileItemProps) { const comp = ( <Item icon="empty-page" isActive={isActive} className="mb-cn-4xs" - style={{ - marginLeft: `calc(-16px * ${level})`, - paddingLeft: level ? `calc(16px * ${level} + 8px)` : '40px' - }} + style={{ marginLeft: `calc(-16px * ${level})`, paddingLeft: level ? `calc(16px * ${level} + 8px)` : '24px' }} onClick={onClick} + link={link} > {children} </Item> ) - return link ? <Link to={link}>{comp}</Link> : comp + return tooltip ? <Tooltip content={tooltip}>{comp}</Tooltip> : comp } interface RootProps { diff --git a/packages/ui/src/components/search-files.tsx b/packages/ui/src/components/search-files.tsx index 0ce0c505f8..8580173b06 100644 --- a/packages/ui/src/components/search-files.tsx +++ b/packages/ui/src/components/search-files.tsx @@ -52,7 +52,7 @@ interface SearchFilesProps { export const SearchFiles = ({ navigateToFile, filesList, - searchInputSize = 'sm', + searchInputSize = 'md', inputContainerClassName, contentClassName }: SearchFilesProps) => { @@ -150,7 +150,7 @@ export const SearchFiles = ({ <DropdownMenu.Content ref={contentRef} - className={cn('max-h-96', 'width-popover-max-width', contentClassName)} + className={cn('w-[800px]', contentClassName)} align="start" onKeyDownCapture={handleContentKeyDownCapture} onOpenAutoFocus={event => event.preventDefault()} diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 65678c622f..9f8f53da6c 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -188,7 +188,7 @@ const TabsTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, TabsTrigge const { type, activeTabValue, onValueChange } = useContext(TabsContext) const { NavLink } = useRouterContext() - const iconSize = variant === 'ghost' || variant === 'outlined' ? 'sm' : 'xs' + const iconSize = variant === 'ghost' || variant === 'outlined' ? 'xs' : 'sm' const logoSize: LogoPropsV2['size'] = variant === 'ghost' || variant === 'outlined' ? 'md' : 'sm' const TabTriggerContent = () => ( diff --git a/packages/ui/src/context/router-context.tsx b/packages/ui/src/context/router-context.tsx index 5d7707f76b..d19fe2f10e 100644 --- a/packages/ui/src/context/router-context.tsx +++ b/packages/ui/src/context/router-context.tsx @@ -125,4 +125,4 @@ export const RouterContextProvider = ({ ) } -export { NavLinkProps } +export { NavLinkProps, LinkProps } diff --git a/packages/ui/src/views/layouts/PullRequestLayout.tsx b/packages/ui/src/views/layouts/PullRequestLayout.tsx index 2bfabae98a..184c0a5d90 100644 --- a/packages/ui/src/views/layouts/PullRequestLayout.tsx +++ b/packages/ui/src/views/layouts/PullRequestLayout.tsx @@ -36,7 +36,7 @@ export const PullRequestLayout: FC<PullRequestLayoutProps> = ({ return ( <SandboxLayout.Main fullWidth> - <SandboxLayout.Content className="px-cn-2xl"> + <SandboxLayout.Content> {pullRequest && ( <PullRequestHeader className="mb-cn-3xl" @@ -50,7 +50,7 @@ export const PullRequestLayout: FC<PullRequestLayoutProps> = ({ )} <Tabs.NavRoot> - <Tabs.List className="-mx-6 mb-cn-sm px-6" variant="overlined"> + <Tabs.List className="-mx-8 px-8" variant="overlined"> <Tabs.Trigger value={PullRequestTabsKeys.CONVERSATION} icon="chat-bubble-empty" diff --git a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx index 908cdabe81..d23b1e505c 100644 --- a/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx +++ b/packages/ui/src/views/repo/pull-request/commits/pull-request-commits.tsx @@ -29,13 +29,14 @@ const PullRequestCommitsView: FC<RepoPullRequestCommitsViewProps> = ({ }, [xNextPage]) if (isFetchingCommits) { - return <Skeleton.List /> + return <Skeleton.List className="mt-cn-xl" /> } return ( <> {!commitsList?.length && ( <NoData + className="mt-cn-xl" imageName="no-data-folder" title={t('views:pullRequests.noCommitsYet')} description={[t('views:pullRequests.noCommitDataDescription')]} @@ -46,7 +47,7 @@ const PullRequestCommitsView: FC<RepoPullRequestCommitsViewProps> = ({ <CommitsList toCode={toCode} toCommitDetails={toCommitDetails} - className="mt-cn-sm" + className="mt-cn-xl" data={commitsList.map((item: TypesCommit) => ({ sha: item.sha, parent_shas: item.parent_shas, diff --git a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx index c00e44f653..c72b2a0082 100644 --- a/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/components/pull-request-compare-diff-list.tsx @@ -97,7 +97,7 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ return ( <Layout.Flex className="flex-1" ref={containerRef}> {showExplorer && ( - <> + <Layout.Flex className="-mb-7"> <PullRequestDiffSidebar sidebarWidth={sidebarWidth} filePaths={diffData?.map(diff => diff.filePath) || []} @@ -114,10 +114,10 @@ const PullRequestCompareDiffList: FC<PullRequestCompareDiffListProps> = ({ } /> <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> - </> + </Layout.Flex> )} - <Layout.Flex className={cn('p-0', showExplorer ? 'pl-cn-xl' : '')} direction="column"> - <ListActions.Root className="layer-high bg-cn-background-1 sticky top-[55px] py-2"> + <Layout.Flex className={cn('p-0', showExplorer ? 'pl-cn-lg' : '')} direction="column"> + <ListActions.Root className="layer-high bg-cn-background-1 pt-cn-lg sticky top-[var(--cn-breadcrumbs-height)] gap-x-5 pb-2"> <ListActions.Left> <Button size="md" diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index ebf7a5df09..3b12b3eee9 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -448,7 +448,7 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ /> )} </Tabs.Content> - <Tabs.Content className="pt-5" value="changes"> + <Tabs.Content value="changes"> {/* Content for Changes */} {(diffData ?? []).length > 0 ? ( <PullRequestCompareDiffList diff --git a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css index 8828d8399b..0611b0c18d 100644 --- a/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css +++ b/packages/ui/src/views/repo/pull-request/components/extended-diff-view/extended-diff-view-style.css @@ -38,7 +38,7 @@ } .diff-add-widget-wrapper { - z-index: 200 !important; + z-index: 10 !important; position: absolute !important; } diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx index 9fd15ec37b..40b97af500 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-accordian.tsx @@ -337,7 +337,7 @@ export const PullRequestAccordion: React.FC<{ <Accordion.Item value={header?.text ?? ''} className="rounded-3 border-none"> <Accordion.Trigger className="rounded-t-3 bg-cn-background-2 px-4 py-2 [&>.cn-accordion-trigger-indicator]:m-0 [&>.cn-accordion-trigger-indicator]:self-center" - headerClassName="z-[18] sticky top-[107px] border-cn-borders-2 border rounded-t-3" + headerClassName="z-[18] sticky top-[119px] border-cn-borders-2 border rounded-t-3" > <LineTitle header={header} diff --git a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx index 8f9ad6fa42..e4b6f90976 100644 --- a/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx +++ b/packages/ui/src/views/repo/pull-request/components/pull-request-diff-sidebar.tsx @@ -1,4 +1,4 @@ -import { ScrollArea, SearchFiles } from '@/components' +import { Layout, ScrollArea, SearchFiles } from '@/components' import { SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_WIDTH } from '../../components/draggable-sidebar-divider' import { @@ -23,19 +23,17 @@ export const PullRequestDiffSidebar: React.FC<PullRequestDiffSidebarProps> = ({ }) => { return ( <div - className={`h-screen sticky top-[55px] shrink-0 min-w-[${SIDEBAR_MIN_WIDTH}px] max-w-[${SIDEBAR_MAX_WIDTH}px] pr-cn-xs overflow-hidden`} + className="nested-sidebar-height pr-cn-lg sticky top-[var(--cn-breadcrumbs-height)] -ml-8 overflow-hidden" style={{ - width: `${sidebarWidth}px` + width: `${sidebarWidth}px`, + minWidth: `${SIDEBAR_MIN_WIDTH}px`, + maxWidth: `${SIDEBAR_MAX_WIDTH}px` }} > - <div className="flex h-[calc(100vh-55px)] flex-col gap-3 pt-1.5"> - <SearchFiles - navigateToFile={file => { - setJumpToDiff(file) - }} - filesList={filePaths} - /> - <ScrollArea className="pb-cn-xl -mr-cn-xs pr-cn-xs"> + <Layout.Flex direction="column" className="pt-cn-xl max-h-full pl-8" gapY="sm"> + <SearchFiles navigateToFile={setJumpToDiff} filesList={filePaths} /> + + <ScrollArea className="pr-cn-lg -mr-cn-lg grid-cols-[100%] pb-7"> <PullRequestChangesExplorer paths={filePaths} setJumpToDiff={setJumpToDiff} @@ -43,7 +41,7 @@ export const PullRequestDiffSidebar: React.FC<PullRequestDiffSidebarProps> = ({ diffsData={diffsData} /> </ScrollArea> - </div> + </Layout.Flex> </div> ) } diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx index 082bc467f0..12ca15d588 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-explorer.tsx @@ -1,6 +1,6 @@ import { memo, useMemo, useRef, useState } from 'react' -import { FileExplorer, StatusBadge, Text, Tooltip } from '@/components' +import { FileExplorer, Layout, StatusBadge } from '@/components' type TreeNode = FolderNode | FileNode @@ -8,12 +8,14 @@ interface FileNode { type: 'file' name: string path: string + level: number } interface FolderNode { type: 'folder' name: string path: string + level: number children: TreeNode[] } @@ -26,6 +28,29 @@ export interface ExplorerDiffData { unchangedPercentage: number } +/** + * Assigns correct nesting levels to all nodes based on their position in the tree + * @param nodes - Tree nodes without level property + * @param currentLevel - Current nesting level (starts at 0) + * @returns Tree nodes with level property added + */ +function assignLevels(nodes: Omit<TreeNode, 'level'>[], currentLevel: number = 0): TreeNode[] { + return nodes.map(node => { + if (node.type === 'folder') { + return { + ...node, + level: currentLevel, + children: assignLevels((node as FolderNode).children, currentLevel + 1) + } as FolderNode + } else { + return { + ...node, + level: currentLevel + } as FileNode + } + }) +} + /** * Builds a hierarchical file tree from flat file paths for UI rendering. * STEPS: @@ -77,12 +102,14 @@ export function buildFileTree(rawPaths: string[]): FolderNode[] { type: 'folder' as const, name: node.name, path: node.path, - children: fileTreeToNodes(node.children) + children: fileTreeToNodes(node.children), + level: 0 } : { type: 'file' as const, name: node.name, - path: node.path + path: node.path, + level: 0 } ) } @@ -111,7 +138,8 @@ export function buildFileTree(rawPaths: string[]): FolderNode[] { type: 'folder' as const, name: mergedName, path: mergedPath, - children: kids + children: kids, + level: 0 } } else { return node @@ -120,7 +148,10 @@ export function buildFileTree(rawPaths: string[]): FolderNode[] { } const rawTree = fileTreeToNodes(root) - return flatten(rawTree) as FolderNode[] + const flattenedTree = flatten(rawTree) + const treeWithLevels = assignLevels(flattenedTree) + + return treeWithLevels as FolderNode[] } /** @@ -252,10 +283,7 @@ function renderTree( activePath?: string, diffsData?: ExplorerDiffData[] ): React.ReactNode[] { - return nodes.map(node => { - // Calculate level based on path depth - const level = (node.path ?? '').split('/').length - 1 - + return nodes.map(({ level, ...node }) => { if (node.type === 'folder') { const isActive = activePath?.startsWith(node.path) return ( @@ -279,25 +307,22 @@ function renderTree( isActive={isActive} level={level} onClick={() => setJumpToDiff(node.path)} + tooltip={ + <Layout.Flex gapX="3xs" align="center"> + {addedLines > 0 && ( + <StatusBadge variant="secondary" size="sm" theme="success"> + +{addedLines} + </StatusBadge> + )} + {deletedLines > 0 && ( + <StatusBadge variant="secondary" size="sm" theme="danger"> + -{deletedLines} + </StatusBadge> + )} + </Layout.Flex> + } > - <Tooltip - content={ - <> - {addedLines > 0 && ( - <StatusBadge variant="outline" size="sm" theme="success"> - +{addedLines} - </StatusBadge> - )} - {deletedLines > 0 && ( - <StatusBadge variant="outline" size="sm" theme="danger"> - -{deletedLines} - </StatusBadge> - )} - </> - } - > - <Text title="">{node.name}</Text> - </Tooltip> + {node.name} </FileExplorer.FileItem> ) } diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx index 2f61ae903a..6c2bd93417 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes-filter.tsx @@ -160,9 +160,9 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = <Layout.Horizontal align="center" justify="between" - className="layer-high sticky top-[55px] gap-x-5 bg-cn-background-1 py-2" + className="layer-high bg-cn-background-1 pt-cn-lg sticky top-[var(--cn-breadcrumbs-height)] gap-x-5 pb-2" > - <Layout.Horizontal className="grow gap-x-5"> + <Layout.Horizontal className="grow gap-x-5" align="center"> <Button size="md" title={showExplorer ? 'Collapse Sidebar' : 'Expand Sidebar'} @@ -218,9 +218,7 @@ export const PullRequestChangesFilter: React.FC<PullRequestChangesFilterProps> = </DropdownMenu.Content> </DropdownMenu.Root> - <div className="mt-2.5"> - <ChangedFilesShortInfo diffData={diffData} diffStats={pullReqStats} goToDiff={setJumpToDiff} /> - </div> + <ChangedFilesShortInfo diffData={diffData} diffStats={pullReqStats} goToDiff={setJumpToDiff} /> </Layout.Horizontal> <Layout.Horizontal className="gap-x-7"> diff --git a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx index a80e1c6a96..5167d48e0e 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/changes/pull-request-changes.tsx @@ -201,7 +201,7 @@ function PullRequestChangesInternal({ ) || [] return ( - <div className={`${blockIndex === 0 ? 'pt-2' : 'pt-4'}`} key={item.filePath}> + <div className="pt-2" key={item.filePath}> <InViewDiffRenderer key={item.filePath} blockName={innerBlockName(item.filePath)} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx index 4e309ee44b..bceca814e9 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-changes-page.tsx @@ -207,7 +207,7 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ return ( <Layout.Flex className="flex-1" ref={containerRef}> {showExplorer && ( - <> + <Layout.Flex className="-mb-7"> <PullRequestDiffSidebar sidebarWidth={sidebarWidth} filePaths={diffs?.map(diff => diff.filePath) || []} @@ -224,10 +224,10 @@ const PullRequestChangesPage: FC<RepoPullRequestChangesPageProps> = ({ } /> <DraggableSidebarDivider width={sidebarWidth} setWidth={setSidebarWidth} containerRef={containerRef} /> - </> + </Layout.Flex> )} <SandboxLayout.Main> - <SandboxLayout.Content className={cn('flex flex-col p-0', showExplorer ? 'pl-cn-xl' : '')}> + <SandboxLayout.Content className={cn('flex flex-col p-0', showExplorer ? 'pl-cn-lg' : '')}> <PullRequestChangesFilter active={''} isApproving={isApproving} diff --git a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx index 763e1fa1f8..7dbc0a5da8 100644 --- a/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx +++ b/packages/ui/src/views/repo/pull-request/details/pull-request-conversation-page.tsx @@ -42,9 +42,9 @@ export const PullRequestConversationPage: FC<PullRequestConversationPageProps> = return ( <ExpandedCommentsContext.Provider value={contextValue}> - <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px" className="mt-cn-sm"> + <SandboxLayout.Columns columnWidths="minmax(calc(100% - 334px), 1fr) 334px" className="mt-cn-xl"> <SandboxLayout.Column> - <SandboxLayout.Content className="pl-0 pr-cn-xl pt-0"> + <SandboxLayout.Content className="pr-cn-xl pl-0 pt-0"> {/*TODO: update with design */} {!!rebaseErrorMessage && ( <Alert.Root theme="danger" className="mb-5" dismissible> diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx index 08c705ee9e..79123f0776 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-diff.tsx @@ -12,11 +12,7 @@ export const CommitDiff: React.FC<CommitDiffsViewProps> = ({ useCommitDetailsSto const { diffs, diffStats, commitSHA } = useCommitDetailsStore() return ( - <Layout.Flex - direction="column" - className="pb-cn-xl min-h-[calc(100vh-var(--cn-page-nav-full-height))] w-full" - gapY="sm" - > + <Layout.Flex direction="column" className="pb-cn-lg pt-cn-xl w-full" gapY="sm"> {/* TODO: add goToDiff handler */} <ChangedFilesShortInfo diffData={diffs} diffStats={diffStats} goToDiff={() => {}} /> diff --git a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx index de87a33b18..8bbac37487 100644 --- a/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/components/commit-sidebar.tsx @@ -1,20 +1,29 @@ import { ReactNode } from 'react' import { Layout, ScrollArea, SearchFiles } from '@/components' +import { SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_WIDTH } from '@views/repo/components' interface CommitsSidebarProps { navigateToFile: (file: string) => void filesList?: string[] children: ReactNode + sidebarWidth: number } -export const CommitSidebar = ({ navigateToFile, filesList, children }: CommitsSidebarProps) => { +export const CommitSidebar = ({ navigateToFile, filesList, children, sidebarWidth }: CommitsSidebarProps) => { return ( - <div className="nested-sidebar-height pt-cn-md -mt-cn-md sticky top-[var(--cn-breadcrumbs-height)]"> + <div + className="nested-sidebar-height pt-cn-xl sticky top-[var(--cn-breadcrumbs-height)]" + style={{ + width: `${sidebarWidth}px`, + minWidth: `${SIDEBAR_MIN_WIDTH}px`, + maxWidth: `${SIDEBAR_MAX_WIDTH}px` + }} + > <Layout.Flex direction="column" className="max-h-full overflow-hidden" gapY="sm"> - <SearchFiles navigateToFile={navigateToFile} filesList={filesList} contentClassName="width-popover-max-width" /> + <SearchFiles navigateToFile={navigateToFile} filesList={filesList} /> - <ScrollArea className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5" classNameContent="w-[248px]"> + <ScrollArea className="pb-cn-xl pr-cn-lg -mr-5 grid-cols-[100%]" classNameContent="w-[248px]"> {children} </ScrollArea> </Layout.Flex> diff --git a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx index a2c9b826f7..56c02a96da 100644 --- a/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx +++ b/packages/ui/src/views/repo/repo-commit-details/repo-commit-details-view.tsx @@ -13,7 +13,6 @@ interface RoutingProps { } export interface RepoCommitDetailsViewProps extends RoutingProps { useCommitDetailsStore: () => ICommitDetailsStore - showSidebar?: boolean loadingCommitDetails?: boolean } @@ -40,59 +39,61 @@ export const RepoCommitDetailsView: FC<RepoCommitDetailsViewProps> = ({ return ( <SandboxLayout.Main fullWidth> - <SandboxLayout.Content className="gap-y-cn-md pb-0"> - <Text variant="heading-section" as="h2"> - {t('views:commits.commitDetailsTitle', 'Commit')}  - <Text variant="heading-section" color="foreground-3" as="span"> - {commitData?.sha?.substring(0, 7)} + <SandboxLayout.Content className="gap-0 pb-0"> + <Layout.Grid gapY="md"> + <Text variant="heading-section" as="h2"> + {t('views:commits.commitDetailsTitle', 'Commit')}  + <Text variant="heading-section" color="foreground-3" as="span"> + {commitData?.sha?.substring(0, 7)} + </Text> </Text> - </Text> - <div className="border-cn-borders-3 rounded-3 overflow-hidden border"> - <Layout.Grid - flow="column" - justify="between" - align="center" - className="border-cn-borders-3 bg-cn-background-2 px-cn-md py-cn-sm border-b" - gapX="md" - > - <CommitTitleWithPRLink - toPullRequest={toPullRequest} - commitMessage={commitData?.title} - title={commitData?.title} - textProps={{ variant: 'body-code' }} - /> + <div className="border-cn-borders-3 rounded-3 overflow-hidden border"> + <Layout.Grid + flow="column" + justify="between" + align="center" + className="border-cn-borders-3 bg-cn-background-2 px-cn-md py-cn-sm border-b" + gapX="md" + > + <CommitTitleWithPRLink + toPullRequest={toPullRequest} + commitMessage={commitData?.title} + title={commitData?.title} + textProps={{ variant: 'body-code' }} + /> - <Button variant="outline" asChild> - <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> - <IconV2 name="folder" /> - {t('views:commits.browseFiles', 'Browse Files')} - </Link> - </Button> - </Layout.Grid> + <Button variant="outline" asChild> + <Link to={toCode?.({ sha: commitData?.sha || '' }) || ''}> + <IconV2 name="folder" /> + {t('views:commits.browseFiles', 'Browse Files')} + </Link> + </Button> + </Layout.Grid> - <Layout.Flex align="center" justify="between" className="px-cn-md py-cn-sm"> - {commitData?.author?.identity?.name && commitData?.author?.when && ( - <Layout.Flex align="center" gapX="2xs"> - <Avatar name={commitData.author.identity.name} rounded /> - <Text variant="body-single-line-strong" color="foreground-1"> - {commitData.author.identity.name} - </Text> - <Text variant="body-single-line-normal"> - {t('views:commits.commitDetailsAuthored', 'authored')}{' '} - <TimeAgoCard - timestamp={new Date(commitData.author.when).getTime()} - cutoffDays={3} - beforeCutoffPrefix="" - afterCutoffPrefix="on" - /> - </Text> - </Layout.Flex> - )} + <Layout.Flex align="center" justify="between" className="px-cn-md py-cn-sm"> + {commitData?.author?.identity?.name && commitData?.author?.when && ( + <Layout.Flex align="center" gapX="2xs"> + <Avatar name={commitData.author.identity.name} rounded /> + <Text variant="body-single-line-strong" color="foreground-1"> + {commitData.author.identity.name} + </Text> + <Text variant="body-single-line-normal"> + {t('views:commits.commitDetailsAuthored', 'authored')}{' '} + <TimeAgoCard + timestamp={new Date(commitData.author.when).getTime()} + cutoffDays={3} + beforeCutoffPrefix="" + afterCutoffPrefix="on" + /> + </Text> + </Layout.Flex> + )} - <CommitCopyActions toCommitDetails={toCommitDetails} sha={commitData?.sha || ''} /> - </Layout.Flex> - </div> + <CommitCopyActions toCommitDetails={toCommitDetails} sha={commitData?.sha || ''} /> + </Layout.Flex> + </div> + </Layout.Grid> <Outlet /> </SandboxLayout.Content> diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 4242493a8a..329be32795 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -153,8 +153,8 @@ export const RepoFiles: FC<RepoFilesProps> = ({ ]) return ( - <SandboxLayout.Main className="repo-files-height bg-transparent"> - <SandboxLayout.Content className="pl-cn-xl gap-y-cn-md flex h-full flex-col"> + <SandboxLayout.Main className="repo-files-height pt-cn-xl bg-transparent"> + <SandboxLayout.Content className="pl-cn-lg gap-y-cn-md flex h-full flex-col"> {isView && !isRepoEmpty && ( <PathActionBar codeMode={codeMode} diff --git a/packages/ui/src/views/repo/repo-sidebar/index.tsx b/packages/ui/src/views/repo/repo-sidebar/index.tsx index 4078804fae..87a84584ca 100644 --- a/packages/ui/src/views/repo/repo-sidebar/index.tsx +++ b/packages/ui/src/views/repo/repo-sidebar/index.tsx @@ -22,7 +22,7 @@ export const RepoSidebar = ({ return ( <> <div className="repo-files-height sticky top-[var(--cn-page-nav-full-height)]"> - <Layout.Flex direction="column" className="max-h-full overflow-hidden px-5 pt-7" gapY="sm"> + <Layout.Flex direction="column" className="pr-cn-lg pl-cn-2xl pt-cn-xl max-h-full overflow-hidden" gapY="sm"> <Layout.Grid columns="1fr auto" flow="column" align="center" gapX="xs"> {branchSelectorRenderer} <Button iconOnly variant="outline" aria-label="Create file" onClick={navigateToNewFile}> @@ -30,15 +30,10 @@ export const RepoSidebar = ({ </Button> </Layout.Grid> - <SearchFiles - navigateToFile={navigateToFile} - filesList={filesList} - searchInputSize="md" - contentClassName="w-[800px]" - /> + <SearchFiles navigateToFile={navigateToFile} filesList={filesList} /> <ScrollArea - className="pb-cn-xl -mr-5 grid-cols-[100%] pr-5" + className="pb-cn-xl pr-cn-lg -mr-5 grid-cols-[100%]" preserveScrollPosition={true} storageKey={repoRef ? `fileExplorer_${repoRef}` : undefined} > diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index f6818ae13f..9664ddbae8 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -192,9 +192,7 @@ export function RepoSummaryView({ <SearchFiles navigateToFile={navigateToFile} filesList={filesList} - searchInputSize="md" inputContainerClassName="max-w-80 min-w-40 w-full" - contentClassName="w-[800px]" /> </ButtonLayout> </ListActions.Left> From 2c0b80b0550e572bcb1aa87348963892f83bcf3d Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Thu, 21 Aug 2025 15:22:18 +0000 Subject: [PATCH 165/180] fix: revert timeago card color, add hover, labels delete (#10261) * 26747b fix: error handling * 3cb3da fix: prettier * 29e062 feat: delete parent scope labels * b60832 feat: trigger time ago on hover * 561ef2 fix: revert timeago card color --- .../labels/project-labels-list-container.tsx | 18 +++++++--- .../repo/labels/labels-list-container.tsx | 33 +++++++++++++++---- packages/ui/src/components/time-ago-card.tsx | 31 +++++++++-------- .../components/time-ago-card.ts | 2 +- 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx index 9a624f20c3..896a341876 100644 --- a/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/project/labels/project-labels-list-container.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useDeleteSpaceLabelMutation } from '@harnessio/code-service-client' -import { DeleteAlertDialog } from '@harnessio/ui/components' +import { DeleteAlertDialog, MessageTheme } from '@harnessio/ui/components' import { ILabelType, LabelsListPage, SandboxLayout } from '@harnessio/ui/views' import { useGetSpaceURLParam } from '../../../framework/hooks/useGetSpaceParam' @@ -11,6 +11,7 @@ import { useQueryState } from '../../../framework/hooks/useQueryState' import usePaginationQueryStateWithStore from '../../../hooks/use-pagination-query-state-with-store' import { PathParams } from '../../../RouteDefinitions.ts' import { getScopedRuleUrl } from '../../../utils/rule-url-utils.ts' +import { getSpaceRefByScope } from '../../../utils/scope-url-utils.ts' import { useLabelsStore } from '../stores/labels-store' import { useFillLabelStoreWithProjectLabelValuesData } from './hooks/use-fill-label-store-with-project-label-values-data.ts' @@ -26,7 +27,7 @@ export const ProjectLabelsList = () => { routeUtils } = useMFEContext() - const { page, setPage, deleteLabel: deleteStoreLabel } = useLabelsStore() + const { page, setPage, deleteLabel: deleteStoreLabel, labels: storeLabels } = useLabelsStore() const { queryPage } = usePaginationQueryStateWithStore({ page, setPage }) const [query, setQuery] = useQueryState('query') @@ -35,11 +36,17 @@ export const ProjectLabelsList = () => { useFillLabelStoreWithProjectLabelValuesData({ queryPage, query }) const handleOpenDeleteDialog = (identifier: string) => { + resetDeleteMutation() setOpenAlertDeleteDialog(true) setIdentifier(identifier) } - const { mutate: deleteSpaceLabel, isLoading: isDeletingSpaceLabel } = useDeleteSpaceLabelMutation( + const { + mutate: deleteSpaceLabel, + isLoading: isDeletingSpaceLabel, + error, + reset: resetDeleteMutation + } = useDeleteSpaceLabelMutation( { space_ref: `${space_ref}/+` }, { onSuccess: (_data, variables) => { @@ -54,7 +61,9 @@ export const ProjectLabelsList = () => { } const handleDeleteLabel = (identifier: string) => { - deleteSpaceLabel({ key: identifier }) + const label = storeLabels.find(label => label.key === identifier) + + deleteSpaceLabel({ space_ref: `${getSpaceRefByScope(space_ref ?? '', label?.scope ?? 0)}/+`, key: identifier }) } return ( @@ -88,6 +97,7 @@ export const ProjectLabelsList = () => { type="label" deleteFn={handleDeleteLabel} isLoading={isDeletingSpaceLabel} + error={error ? { type: MessageTheme.ERROR, message: error?.message ?? 'Unable to delete label' } : undefined} /> </SandboxLayout.Content> ) diff --git a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx index 1f65fcdf0e..21ae93ab6c 100644 --- a/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx +++ b/apps/gitness/src/pages-v2/repo/labels/labels-list-container.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useDeleteRepoLabelMutation, useDeleteSpaceLabelMutation } from '@harnessio/code-service-client' -import { DeleteAlertDialog } from '@harnessio/ui/components' +import { DeleteAlertDialog, MessageTheme } from '@harnessio/ui/components' import { ILabelType, LabelsListPage } from '@harnessio/ui/views' import { useRoutes } from '../../../framework/context/NavigationContext' @@ -11,6 +11,7 @@ import { useQueryState } from '../../../framework/hooks/useQueryState' import usePaginationQueryStateWithStore from '../../../hooks/use-pagination-query-state-with-store' import { PathParams } from '../../../RouteDefinitions' import { getScopedRuleUrl } from '../../../utils/rule-url-utils.ts' +import { getSpaceRefByScope } from '../../../utils/scope-url-utils.ts' import { useLabelsStore } from '../../project/stores/labels-store' import { usePopulateLabelStore } from './hooks/use-populate-label-store.ts' @@ -34,11 +35,18 @@ export const RepoLabelsList = () => { const { space_ref, repo_ref } = usePopulateLabelStore({ queryPage, query }) const handleOpenDeleteDialog = (identifier: string) => { + resetDeleteRepoMutation() + resetDeleteSpaceMutation() setOpenAlertDeleteDialog(true) setIdentifier(identifier) } - const { mutate: deleteRepoLabel, isLoading: isDeletingRepoLabel } = useDeleteRepoLabelMutation( + const { + mutate: deleteRepoLabel, + isLoading: isDeletingRepoLabel, + error: repoError, + reset: resetDeleteRepoMutation + } = useDeleteRepoLabelMutation( { repo_ref: repo_ref ?? '' }, { onSuccess: (_data, variables) => { @@ -48,7 +56,12 @@ export const RepoLabelsList = () => { } ) - const { mutate: deleteSpaceLabel, isLoading: isDeletingSpaceLabel } = useDeleteSpaceLabelMutation( + const { + mutate: deleteSpaceLabel, + isLoading: isDeletingSpaceLabel, + error: spaceError, + reset: resetDeleteSpaceMutation + } = useDeleteSpaceLabelMutation( { space_ref: space_ref ?? '' }, { onSuccess: (_data, variables) => { @@ -65,9 +78,9 @@ export const RepoLabelsList = () => { const handleDeleteLabel = (key: string) => { const label = storeLabels.find(label => label.key === key) - if (!label) return - - label?.scope === 0 ? deleteRepoLabel({ key }) : deleteSpaceLabel({ key }) + label?.scope === 0 + ? deleteRepoLabel({ key }) + : deleteSpaceLabel({ space_ref: `${getSpaceRefByScope(space_ref ?? '', label?.scope ?? 0)}/+`, key }) } return ( @@ -102,6 +115,14 @@ export const RepoLabelsList = () => { type="label" deleteFn={handleDeleteLabel} isLoading={isDeletingRepoLabel || isDeletingSpaceLabel} + error={ + repoError || spaceError + ? { + type: MessageTheme.ERROR, + message: repoError?.message ?? spaceError?.message ?? 'Unable to delete label' + } + : undefined + } /> </> ) diff --git a/packages/ui/src/components/time-ago-card.tsx b/packages/ui/src/components/time-ago-card.tsx index a25e3e7190..64f568d15a 100644 --- a/packages/ui/src/components/time-ago-card.tsx +++ b/packages/ui/src/components/time-ago-card.tsx @@ -1,4 +1,4 @@ -import { FC, forwardRef, Fragment, memo, MouseEvent, Ref, useState } from 'react' +import { FC, forwardRef, Fragment, memo, MouseEvent, Ref, useCallback, useState } from 'react' import { Popover, StatusBadge, Text, TextProps } from '@/components' import { cn } from '@utils/cn' @@ -167,6 +167,14 @@ export const TimeAgoCard = memo( dateTimeFormatOptions ) + const handleMouseEnter = useCallback(() => { + setIsOpen(true) + }, []) + + const handleMouseLeave = useCallback(() => { + setIsOpen(false) + }, []) + if (timestamp === null || timestamp === undefined) { return ( <Text as="span" {...textProps} ref={ref}> @@ -175,27 +183,22 @@ export const TimeAgoCard = memo( ) } - const handleClick = (event: MouseEvent) => { - event.preventDefault() - event.stopPropagation() - setIsOpen(prev => !prev) - } - const handleClickContent = (event: MouseEvent) => { event.stopPropagation() } + const hasPrefix = beforeCutoffPrefix || afterCutoffPrefix const prefix = hasPrefix ? (isBeyondCutoff ? afterCutoffPrefix : beforeCutoffPrefix) : undefined return ( <Popover.Root open={isOpen} onOpenChange={setIsOpen}> - <Popover.Trigger className={cn('cn-time-ago-card-trigger', triggerClassName)} onClick={handleClick} ref={ref}> - <Text<'time'> - as="time" - {...textProps} - ref={textProps?.ref as Ref<HTMLTimeElement>} - className="text-cn-foreground-accent" - > + <Popover.Trigger + className={cn('cn-time-ago-card-trigger', triggerClassName)} + ref={ref} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + > + <Text<'time'> as="time" {...textProps} ref={textProps?.ref as Ref<HTMLTimeElement>}> {prefix ? `${prefix} ${formattedShort}` : formattedShort} </Text> </Popover.Trigger> diff --git a/packages/ui/tailwind-utils-config/components/time-ago-card.ts b/packages/ui/tailwind-utils-config/components/time-ago-card.ts index 45b17a5353..aa5bf8062d 100644 --- a/packages/ui/tailwind-utils-config/components/time-ago-card.ts +++ b/packages/ui/tailwind-utils-config/components/time-ago-card.ts @@ -3,7 +3,7 @@ export default { '&-trigger': { '@apply leading-snug': '', ':where(time)': { - '@apply data-[state=open]:text-cn-foreground-1': '' + '@apply data-[state=open]:text-cn-foreground-1 hover:text-cn-foreground-1': '' }, '&:where(:focus-visible) time': { '@apply text-cn-foreground-1': '' From 1728ec15efb74241cbfa9f3cd282e45f7a89e97e Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Thu, 21 Aug 2025 21:57:30 +0000 Subject: [PATCH 166/180] PR Conversation - File link should be clickable (#10265) * f7d5a3 PR Conversation - File link should be clickable --- .../conversation/pull-request-comment-box.tsx | 12 +++--------- .../conversation/regular-and-code-comment.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 4bed5b1345..92560ea87f 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -771,15 +771,9 @@ export const PullRequestCommentBox = ({ </Button> )} - {isEditMode ? ( - <Button loading={parentIsLoading || isLoading} onClick={handleSaveComment}> - {buttonTitle || 'Save'} - </Button> - ) : ( - <Button loading={parentIsLoading || isLoading} onClick={handleSaveComment}> - {buttonTitle || 'Comment'} - </Button> - )} + <Button loading={parentIsLoading || isLoading} onClick={handleSaveComment}> + {buttonTitle || 'Comment'} + </Button> </Layout.Flex> ) : null} </Layout.Flex> diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx index 95a00f4db3..299dabef5c 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx @@ -1,6 +1,6 @@ import { FC, memo, useCallback, useState } from 'react' -import { Avatar, CopyButton, IconV2, Layout, Separator, Tag, Text, TextInput, TimeAgoCard } from '@/components' +import { Avatar, CopyButton, IconV2, Layout, Link, Separator, Tag, Text, TextInput, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { activitiesToDiffCommentItems, @@ -335,7 +335,12 @@ const PullRequestRegularAndCodeCommentInternal: FC<PullRequestRegularAndCodeComm isNotCodeComment: true, contentHeader: ( <Layout.Horizontal gap="sm" align="center"> - <Text variant="body-single-line-normal">{payload?.code_comment?.path}</Text> + <Link + to={`../changes?path=${encodeURIComponent(payload?.code_comment?.path || '')}&commentId=${payload?.id}`} + className="font-medium leading-tight text-cn-foreground-1" + > + {payload?.code_comment?.path} + </Link> <CopyButton name={payload?.code_comment?.path || ''} size="xs" color="gray" /> </Layout.Horizontal> ), From e1e510d1b3ce4bdba1bf0defbcb2c7b79627b199 Mon Sep 17 00:00:00 2001 From: Jacob Bassett <jacob.bassett@harness.io> Date: Fri, 22 Aug 2025 00:19:24 +0000 Subject: [PATCH 167/180] moves the cursor to the end of a quoted reply in pr comment box (#10266) * d2c486 moves the cursor to the end of a quoted reply in pr comment box --- .../conversation/pull-request-comment-box.tsx | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx index 92560ea87f..c182dbe288 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/pull-request-comment-box.tsx @@ -158,6 +158,8 @@ export const PullRequestCommentBox = ({ const fileInputRef = useRef<HTMLInputElement | null>(null) const textAreaRef = useRef<HTMLTextAreaElement | null>(null) const [isDragging, setIsDragging] = useState(false) + const [dirty, setDirty] = useState<boolean>(false) + const [initialComment, setInitialComment] = useState<string | undefined>(undefined) const [textSelection, setTextSelection] = useState({ start: 0, end: 0 }) const [showAiLoader, setShowAiLoader] = useState(false) const dropZoneRef = useRef<HTMLDivElement>(null) @@ -309,6 +311,18 @@ export const PullRequestCommentBox = ({ } }, []) + useEffect(() => { + if (!dirty) { + setInitialComment(comment) + } + }, [comment]) + + useEffect(() => { + if (!isUndefined(initialComment)) { + setTextSelection({ start: initialComment.length, end: initialComment.length }) + } + }, [initialComment]) + useEffect(() => { if (textAreaRef.current) { textAreaRef.current.setSelectionRange(textSelection.start, textSelection.end) @@ -333,7 +347,7 @@ export const PullRequestCommentBox = ({ const commentLines = originalComment.split('\n') - const comment: ParsedComment = { + const parsedComment: ParsedComment = { text: originalComment, textLines: commentLines, textLinesSelectionStartIndex: 0, @@ -348,11 +362,11 @@ export const PullRequestCommentBox = ({ const selectionEndHere = textSelection.end >= lowerBound && textSelection.end <= upperBound if (selectionStartHere) { - comment.textLinesSelectionStartIndex = lineIndex + parsedComment.textLinesSelectionStartIndex = lineIndex } if (selectionEndHere) { - comment.textLinesSelectionEndIndex = lineIndex + parsedComment.textLinesSelectionEndIndex = lineIndex } return upperBound @@ -362,9 +376,9 @@ export const PullRequestCommentBox = ({ const injectedNewline = injectNewline ? '\n' : '' const selectionText = originalComment.substring(textSelection.start, textSelection.end) - const selectionTextLines = comment.textLines.slice( - comment.textLinesSelectionStartIndex, - comment.textLinesSelectionEndIndex + 1 + const selectionTextLines = parsedComment.textLines.slice( + parsedComment.textLinesSelectionStartIndex, + parsedComment.textLinesSelectionEndIndex + 1 ) const selectionTextBefore = originalComment.substring(0, textSelection.start) + injectedNewline const newTextSelectionStart = textSelection.start + injectedPreString.length + injectedNewline.length @@ -382,7 +396,7 @@ export const PullRequestCommentBox = ({ return { action: action, - comment: comment, + comment: parsedComment, selection: selection } } @@ -565,6 +579,7 @@ export const PullRequestCommentBox = ({ replaceHistory?: CommentHistory[], replaceFuture?: CommentHistory[] ): void => { + setDirty(true) setTextSelection(textSelection) setComment(comment) From 7bee28d59891481b415a0b35b32bd12d96fa5802 Mon Sep 17 00:00:00 2001 From: Radhakrishna Dodla <c_radhakrishna.dodla@harness.io> Date: Fri, 22 Aug 2025 03:12:25 +0000 Subject: [PATCH 168/180] README in Summary - Edit icon should be available (#10268) * 4ed5a0 Merge remote-tracking branch 'origin/main' into rk-conversation-file-link * 8a30be README - Edit Icon * f7d5a3 PR Conversation - File link should be clickable --- .../src/views/repo/repo-summary/repo-summary.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx index 9664ddbae8..f7677ed6ae 100644 --- a/packages/ui/src/views/repo/repo-summary/repo-summary.tsx +++ b/packages/ui/src/views/repo/repo-summary/repo-summary.tsx @@ -246,14 +246,12 @@ export function RepoSummaryView({ <StackedList.Field right title={ - <Button variant="outline" iconOnly asChild> - <Link - to={`${toRepoFiles?.()}/${doesReadmeExistInFiles(files) ? 'edit' : 'new'}/${gitRef || selectedBranchOrTag?.name}/~/${README_PATH}`} - aria-label={t('views:repos.editReadme', 'Edit README.md')} - > - <IconV2 name="edit-pencil" className="text-icons-3" /> - </Link> - </Button> + <Link + to={`${toRepoFiles?.()}/${doesReadmeExistInFiles(files) ? 'edit' : 'new'}/${gitRef || selectedBranchOrTag?.name}/~/${README_PATH}`} + aria-label={t('views:repos.editReadme', 'Edit README.md')} + > + <IconV2 name="edit-pencil" className="text-icons-3" size="sm" /> + </Link> } /> </StackedList.Item> From a8ab7f58bf337e9ca757db0cf2bb56b4ebed7e2e Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 22 Aug 2025 04:48:47 +0000 Subject: [PATCH 169/180] fix: minor bugs across app (#10269) * f2567a fix: minor bugs --- apps/gitness/src/routes.tsx | 8 ++++++-- .../pull-request/compare/pull-request-compare-page.tsx | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index 8150b5a5df..e4ded1ab6b 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -156,7 +156,7 @@ const rulesRoute = { } }, { - path: ':ruleId', + path: ':ruleId/edit', element: <ProjectRulesContainer />, handle: { breadcrumb: ({ ruleId }: { ruleId: string }) => <span>{ruleId}</span>, @@ -523,6 +523,10 @@ export const repoRoutes: CustomRouteObject[] = [ } ] }, + { + path: 'webhooks', + element: <Navigate to="../settings/webhooks" replace /> + }, { path: 'settings', element: <RepoSettingsLayout />, @@ -575,7 +579,7 @@ export const repoRoutes: CustomRouteObject[] = [ } }, { - path: ':identifier', + path: ':identifier/edit', element: <RepoRulesContainer />, handle: { breadcrumb: ({ identifier }: { identifier: string }) => <span>{identifier}</span>, diff --git a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx index 3b12b3eee9..cc23dc54de 100644 --- a/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx +++ b/packages/ui/src/views/repo/pull-request/compare/pull-request-compare-page.tsx @@ -340,8 +340,6 @@ export const PullRequestComparePage: FC<PullRequestComparePageProps> = ({ <span className="text-cn-foreground-2">{`#${prBranchCombinationExists.number}`}</span> </div> </Layout.Horizontal> - - <Text>{prBranchCombinationExists.description}</Text> </div> </div> <Button From 1f31428784aee8255255151eb4f24243a511c8d8 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 22 Aug 2025 08:52:13 +0000 Subject: [PATCH 170/180] fix: full app reload on commits page (#10271) * 084f58 fix: style * 8e674f fix: routing on commit list * 77a27a fix: full app reload on committs page --- .../ui/src/components/commit-copy-actions.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/commit-copy-actions.tsx b/packages/ui/src/components/commit-copy-actions.tsx index 18e3d2f7ce..7c30aaf7fa 100644 --- a/packages/ui/src/components/commit-copy-actions.tsx +++ b/packages/ui/src/components/commit-copy-actions.tsx @@ -1,7 +1,6 @@ import { KeyboardEvent } from 'react' -import { ButtonGroup, ButtonGroupButtonProps, ButtonProps, Text, useCopyButton } from '@/components' -import { useRouterContext } from '@/context' +import { ButtonGroup, ButtonGroupButtonProps, ButtonProps, Link, Text, useCopyButton } from '@/components' interface CommitCopyActionsProps { sha: string @@ -11,11 +10,10 @@ interface CommitCopyActionsProps { export const CommitCopyActions = ({ sha, toCommitDetails, size = 'xs' }: CommitCopyActionsProps) => { const { copyButtonProps, CopyIcon } = useCopyButton({ copyData: sha, iconSize: '2xs', color: 'surfaceGray' }) - const { navigate } = useRouterContext() const handleNavigation = (ev: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent) => { ev.stopPropagation() - navigate(toCommitDetails?.({ sha: sha || '' }) || '') + toCommitDetails?.({ sha: sha || '' }) } const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => { @@ -28,11 +26,12 @@ export const CommitCopyActions = ({ sha, toCommitDetails, size = 'xs' }: CommitC buttonsProps={[ { children: ( - <Text className="font-mono" color="inherit"> - {sha.substring(0, 6)} - </Text> + <Link to={toCommitDetails?.({ sha: sha || '' }) || ''} variant="secondary" className="hover:no-underline"> + <Text className="font-mono" color="inherit"> + {sha.substring(0, 6)} + </Text> + </Link> ), - onClick: handleNavigation, onKeyDown: handleKeyDown, className: 'font-mono' }, From 6d4eb02558fffe4e57ea4c2e010b1c479ea32003 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 22 Aug 2025 09:27:50 +0000 Subject: [PATCH 171/180] feat: SSE Pub/Sub Implementation (#10267) * 0ad18d fix: prettier * 3f910b fix: review comments * 02aad7 remove: log * 0381db chore: cleanup * 70e47c fix: sse provider removed * 6fb5d8 chore: cleanup * fa396d feat: add all events enum * c0ea26 fix: single event stream on route change * b55ff6 fix: import repo toast * 58ebc9 feat: regster event listeners for each event type * e76d4c feat: clean up SSE listeners * d53ca3 fix: import event fixes, add pull-req event * f6234f chore: trying to get POC working * 3c63ab feat: add basic event manager --- .../src/components-v2/mfe/app-shell.tsx | 9 ++- .../components-v2/standalone/app-shell.tsx | 8 +- .../src/framework/event/EventManager.ts | 51 ++++++++++++ .../framework/hooks/useRepoImportEvent.tsx | 59 -------------- .../hooks/useRepoImportWithPubSub.tsx | 76 ++++++++++++++++++ .../src/framework/hooks/useSpaceSSE.tsx | 79 ------------------- .../framework/hooks/useSpaceSSEWithPubSub.tsx | 74 +++++++++++++++++ .../src/pages-v2/repo/repo-import-page.tsx | 2 + apps/gitness/src/pages-v2/repo/repo-list.tsx | 1 - apps/gitness/src/types.ts | 48 ++++++++++- 10 files changed, 262 insertions(+), 145 deletions(-) create mode 100644 apps/gitness/src/framework/event/EventManager.ts delete mode 100644 apps/gitness/src/framework/hooks/useRepoImportEvent.tsx create mode 100644 apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx delete mode 100644 apps/gitness/src/framework/hooks/useSpaceSSE.tsx create mode 100644 apps/gitness/src/framework/hooks/useSpaceSSEWithPubSub.tsx diff --git a/apps/gitness/src/components-v2/mfe/app-shell.tsx b/apps/gitness/src/components-v2/mfe/app-shell.tsx index 67843553a8..08827b4968 100644 --- a/apps/gitness/src/components-v2/mfe/app-shell.tsx +++ b/apps/gitness/src/components-v2/mfe/app-shell.tsx @@ -4,13 +4,18 @@ import { Outlet } from 'react-router-dom' import { Toaster } from '@harnessio/ui/components' import { MainContentLayout } from '@harnessio/ui/views' -import { useRepoImportEvents } from '../../framework/hooks/useRepoImportEvent' +import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam' +import useSpaceSSEWithPubSub from '../../framework/hooks/useSpaceSSEWithPubSub' import { Breadcrumbs } from '../breadcrumbs/breadcrumbs' import { useGetBreadcrumbs } from '../breadcrumbs/useGetBreadcrumbs' export const AppShellMFE = memo(() => { - useRepoImportEvents() const { breadcrumbs } = useGetBreadcrumbs() + const spaceURL = useGetSpaceURLParam() + + useSpaceSSEWithPubSub({ + space: spaceURL ?? '' + }) return ( <> diff --git a/apps/gitness/src/components-v2/standalone/app-shell.tsx b/apps/gitness/src/components-v2/standalone/app-shell.tsx index 3ca7941151..0bc90bfe2f 100644 --- a/apps/gitness/src/components-v2/standalone/app-shell.tsx +++ b/apps/gitness/src/components-v2/standalone/app-shell.tsx @@ -8,9 +8,10 @@ import { MainContentLayout } from '@harnessio/ui/views' import { getNavbarMenuData } from '../../data/navbar-menu-data' import { getPinnedMenuItemsData } from '../../data/pinned-items' import { useRoutes } from '../../framework/context/NavigationContext' +import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam' import { useLocationChange } from '../../framework/hooks/useLocationChange' -import { useRepoImportEvents } from '../../framework/hooks/useRepoImportEvent' import { useSelectedSpaceId } from '../../framework/hooks/useSelectedSpaceId' +import useSpaceSSEWithPubSub from '../../framework/hooks/useSpaceSSEWithPubSub' import { PathParams } from '../../RouteDefinitions' import { Breadcrumbs } from '../breadcrumbs/breadcrumbs' import { useGetBreadcrumbs } from '../breadcrumbs/useGetBreadcrumbs' @@ -28,6 +29,7 @@ export const AppShell: FC = () => { const { isMobile } = useSidebar() const routes = useRoutes() const { spaceId } = useParams<PathParams>() + const spaceURL = useGetSpaceURLParam() ?? '' const { pinnedMenu, setRecent, setNavLinks } = useNav() const { t } = useTranslation() const selectedSpaceId = useSelectedSpaceId(spaceId) @@ -64,7 +66,9 @@ export const AppShell: FC = () => { } }, [spaceIdPathParam]) - useRepoImportEvents() + useSpaceSSEWithPubSub({ + space: spaceURL + }) return ( <> diff --git a/apps/gitness/src/framework/event/EventManager.ts b/apps/gitness/src/framework/event/EventManager.ts new file mode 100644 index 0000000000..04c2f2da24 --- /dev/null +++ b/apps/gitness/src/framework/event/EventManager.ts @@ -0,0 +1,51 @@ +type Callback = (data: any) => void + +class EventManager { + private listeners: Record<string, Callback[]> = {} + + /** + * Check if a callback is already subscribed to an event type + */ + isSubscribed(eventType: string, callback: Callback): boolean { + if (!this.listeners[eventType]) { + return false + } + return this.listeners[eventType].includes(callback) + } + + /** + * Subscribe a callback to an event type if not already subscribed + */ + subscribe(eventType: string, callback: Callback) { + if (!this.listeners[eventType]) { + this.listeners[eventType] = [] + } + + // Only add the callback if it's not already subscribed + if (!this.isSubscribed(eventType, callback)) { + this.listeners[eventType].push(callback) + } + + return () => { + this.listeners[eventType] = this.listeners[eventType].filter(cb => cb !== callback) + } + } + + publish(eventType: string, data: any) { + if (this.listeners[eventType]) { + for (const callback of this.listeners[eventType]) { + callback(data) + } + } + } + + clear(eventType?: string) { + if (eventType) { + delete this.listeners[eventType] + } else { + this.listeners = {} + } + } +} + +export const eventManager = new EventManager() diff --git a/apps/gitness/src/framework/hooks/useRepoImportEvent.tsx b/apps/gitness/src/framework/hooks/useRepoImportEvent.tsx deleted file mode 100644 index 475d69a905..0000000000 --- a/apps/gitness/src/framework/hooks/useRepoImportEvent.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useCallback, useMemo } from 'react' -import { useParams } from 'react-router-dom' - -import { RepoRepositoryOutput } from '@harnessio/code-service-client' -import { Link, useToast } from '@harnessio/ui/components' - -import { useRepoStore } from '../../pages-v2/repo/stores/repo-list-store' -import { transformRepoList } from '../../pages-v2/repo/transform-utils/repo-list-transform' -import { PathParams } from '../../RouteDefinitions' -import { SSEEvent } from '../../types' -import { useRoutes } from '../context/NavigationContext' -import { useGetSpaceURLParam } from './useGetSpaceParam' -import useSpaceSSE from './useSpaceSSE' - -export const useRepoImportEvents = () => { - const { update } = useToast() - const routes = useRoutes() - const { importToastId, importRepoIdentifier, setImportRepoIdentifier, setImportToastId, updateRepository } = - useRepoStore() - const spaceURL = useGetSpaceURLParam() ?? '' - const { spaceId } = useParams<PathParams>() - - const onEvent = useCallback( - (eventData: RepoRepositoryOutput) => { - update({ - id: importToastId ?? '', - title: 'Successfully imported', - description: ( - <Link to={routes.toRepoSummary({ spaceId, repoId: importRepoIdentifier ?? '' })}>{importRepoIdentifier}</Link> - ), - action: null, - duration: 5000, - variant: 'success' - }) - const transformedRepo = transformRepoList([eventData]) - updateRepository(transformedRepo[0]) - setImportRepoIdentifier(null) - setImportToastId(null) - }, - [importToastId] - ) - const onError = useCallback(() => { - update({ - id: importToastId ?? '', - title: 'Import failed', - variant: 'failed', - action: null - }) - }, [importToastId]) - const events = useMemo(() => [SSEEvent.REPO_IMPORTED], []) - - useSpaceSSE({ - space: spaceURL, - events, - onEvent, - onError, - shouldRun: true - }) -} diff --git a/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx b/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx new file mode 100644 index 0000000000..0fe786448f --- /dev/null +++ b/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx @@ -0,0 +1,76 @@ +import { useCallback, useEffect } from 'react' +import { useParams } from 'react-router-dom' + +import { RepoRepositoryOutput } from '@harnessio/code-service-client' +import { Link, useToast } from '@harnessio/ui/components' + +import { useRepoStore } from '../../pages-v2/repo/stores/repo-list-store' +import { transformRepoList } from '../../pages-v2/repo/transform-utils/repo-list-transform' +import { PathParams } from '../../RouteDefinitions' +import { SSEEvent } from '../../types' +import { useRoutes } from '../context/NavigationContext' +import { eventManager } from '../event/EventManager' + +export const useRepoImportWithPubSub = () => { + const { update, toast } = useToast() + const routes = useRoutes() + const { spaceId } = useParams<PathParams>() + + const handleImportEvent = useCallback( + (eventData: RepoRepositoryOutput) => { + const { importToastId, importRepoIdentifier, setImportRepoIdentifier, setImportToastId, updateRepository } = + useRepoStore.getState() + + if (importToastId && importRepoIdentifier && importRepoIdentifier === eventData.identifier) { + try { + update({ + id: importToastId, + open: false + }) + } catch (error) { + console.error('No toast to dismiss:', error) + } + + toast({ + title: 'Successfully imported', + description: ( + <Link to={routes.toRepoSummary({ spaceId, repoId: importRepoIdentifier })}>{importRepoIdentifier}</Link> + ), + action: null, + duration: 5000, + variant: 'success' + }) + + const transformedRepo = transformRepoList([eventData]) + updateRepository(transformedRepo[0]) + + setImportRepoIdentifier(null) + setImportToastId(null) + } + }, + [routes, spaceId, toast, update] + ) + + const handleError = useCallback(() => { + const { importToastId } = useRepoStore.getState() + + if (importToastId) { + update({ + id: importToastId, + title: 'Import failed', + variant: 'failed', + action: null + }) + } + }, [update]) + + useEffect(() => { + eventManager.subscribe(SSEEvent.REPO_IMPORTED, handleImportEvent) + eventManager.subscribe('error', handleError) + + // We're not unsubscribing because we want these handlers to persist across navigation + // This aligns with the provider-based architecture for SSE connections where the + // EventManager singleton handles pub-sub event distribution independently of component lifecycle + return undefined + }, [handleImportEvent, handleError]) +} diff --git a/apps/gitness/src/framework/hooks/useSpaceSSE.tsx b/apps/gitness/src/framework/hooks/useSpaceSSE.tsx deleted file mode 100644 index f1b0901969..0000000000 --- a/apps/gitness/src/framework/hooks/useSpaceSSE.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useRef, useState } from 'react' - -import { EventSourcePolyfill } from 'event-source-polyfill' -import { isEqual } from 'lodash-es' - -import { useAPIPath } from '../../hooks/useAPIPath' - -type UseSpaceSSEProps = { - space: string - events: string[] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onEvent: (data: any, type: string) => void - onError?: (event: Event) => void - shouldRun?: boolean -} - -const useSpaceSSE = ({ space, events: _events, onEvent, onError, shouldRun = true }: UseSpaceSSEProps) => { - // const { standalone, routingId, hooks } = useAppContext() - const apiPath = useAPIPath() - - const [events, setEvents] = useState(_events) - const eventSourceRef = useRef<EventSource | null>(null) - useEffect(() => { - if (!isEqual(events, _events)) { - setEvents(_events) - } - }, [_events, setEvents, events]) - - useEffect(() => { - // Conditionally establish the event stream - don't want to open on a finished execution - if (shouldRun && events.length > 0) { - if (!eventSourceRef.current) { - const pathAndQuery = apiPath(`/api/v1/spaces/${space}/+/events`) - - const options: { heartbeatTimeout: number; headers?: { Authorization?: string } } = { - heartbeatTimeout: 999999999 - } - - eventSourceRef.current = new EventSourcePolyfill(pathAndQuery, options) - const handleMessage = (event: MessageEvent) => { - const data = JSON.parse(event.data) - onEvent(data, event.type) - } - - const handleError = (event: Event) => { - if (onError) onError(event) - eventSourceRef?.current?.close() - } - - // always register error - eventSourceRef?.current?.addEventListener('error', handleError) - - // register requested events - for (const i in events) { - const eventType = events[i] - eventSourceRef?.current?.addEventListener(eventType, handleMessage) - } - - return () => { - eventSourceRef.current?.removeEventListener('error', handleError) - for (const i in events) { - const eventType = events[i] - eventSourceRef.current?.removeEventListener(eventType, handleMessage) - } - eventSourceRef.current?.close() - eventSourceRef.current = null - } - } - } else { - // If shouldRun is false, close and cleanup any existing stream - if (eventSourceRef.current) { - eventSourceRef.current.close() - eventSourceRef.current = null - } - } - }, [space, events, shouldRun, onEvent, onError]) -} - -export default useSpaceSSE diff --git a/apps/gitness/src/framework/hooks/useSpaceSSEWithPubSub.tsx b/apps/gitness/src/framework/hooks/useSpaceSSEWithPubSub.tsx new file mode 100644 index 0000000000..3a8cef197c --- /dev/null +++ b/apps/gitness/src/framework/hooks/useSpaceSSEWithPubSub.tsx @@ -0,0 +1,74 @@ +import { useEffect, useRef } from 'react' + +import { EventSourcePolyfill } from 'event-source-polyfill' + +import { useAPIPath } from '../../hooks/useAPIPath' +import { SSEEvent } from '../../types' +import { eventManager } from '../event/EventManager' + +type UseSpaceSSEProps = { + space: string +} + +/** + * Hook that establishes a single SSE connection and publishes all events to the EventManager + * regardless of the events array + */ +const useSpaceSSEWithPubSub = ({ space }: UseSpaceSSEProps) => { + const apiPath = useAPIPath() + const eventSourceRef = useRef<EventSource | null>(null) + + // Get all event types from the SSEEvent enum + const allEventTypes = Object.values(SSEEvent) + + useEffect(() => { + // Conditionally establish the event stream + if (!eventSourceRef.current) { + const pathAndQuery = apiPath(`/api/v1/spaces/${space}/+/events`) + + const options: { heartbeatTimeout: number; headers?: { Authorization?: string } } = { + heartbeatTimeout: 999999999 + } + + eventSourceRef.current = new EventSourcePolyfill(pathAndQuery, options) + + // Handle messages by publishing to the EventManager + const handleMessage = (event: MessageEvent) => { + try { + const data = JSON.parse(event.data) + eventManager.publish(event.type, data) + } catch (error) { + console.error('Error parsing event data:', error) + } + } + + // Handle errors + const handleError = (event: Event) => { + eventManager.publish('error', event) + eventSourceRef?.current?.close() + } + + // Register error handler + eventSourceRef?.current?.addEventListener('error', handleError) + + // Register listeners for all event types from the SSEEvent enum + for (const eventType of allEventTypes) { + eventSourceRef?.current?.addEventListener(eventType, handleMessage) + } + + return () => { + eventSourceRef.current?.removeEventListener('error', handleError) + + // Remove all event type listeners + for (const eventType of allEventTypes) { + eventSourceRef.current?.removeEventListener(eventType, handleMessage) + } + + eventSourceRef.current?.close() + eventSourceRef.current = null + } + } + }, [space]) +} + +export default useSpaceSSEWithPubSub diff --git a/apps/gitness/src/pages-v2/repo/repo-import-page.tsx b/apps/gitness/src/pages-v2/repo/repo-import-page.tsx index 988efaa815..b7abadfd7f 100644 --- a/apps/gitness/src/pages-v2/repo/repo-import-page.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-import-page.tsx @@ -5,6 +5,7 @@ import { ImportRepoFormFields, RepoImportPage as RepoImportPageView } from '@har import { useRoutes } from '../../framework/context/NavigationContext' import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam' +import { useRepoImportWithPubSub } from '../../framework/hooks/useRepoImportWithPubSub' import { getRepoProviderConfig, PROVIDER_TYPE_MAP } from './constants/import-providers-map' import { useRepoStore } from './stores/repo-list-store' @@ -14,6 +15,7 @@ export const ImportRepo = () => { const navigate = useNavigate() const { mutate: importRepoMutation, error, isLoading } = useImportRepositoryMutation({}) const { setImportRepoIdentifier } = useRepoStore() + useRepoImportWithPubSub() const onSubmit = async (data: ImportRepoFormFields) => { setImportRepoIdentifier(data.identifier) diff --git a/apps/gitness/src/pages-v2/repo/repo-list.tsx b/apps/gitness/src/pages-v2/repo/repo-list.tsx index 19cf34a033..5a64e72913 100644 --- a/apps/gitness/src/pages-v2/repo/repo-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-list.tsx @@ -117,7 +117,6 @@ export default function ReposListPage() { </Toast.Action> ) }) - setImportToastId(id) } }, [importRepoIdentifier, setImportRepoIdentifier]) diff --git a/apps/gitness/src/types.ts b/apps/gitness/src/types.ts index 46c8f3003d..13778790fc 100644 --- a/apps/gitness/src/types.ts +++ b/apps/gitness/src/types.ts @@ -2,12 +2,56 @@ * Ensure this should come from @harnessio/code-service-client instead */ export enum SSEEvent { + // Execution events EXECUTION_UPDATED = 'execution_updated', + EXECUTION_RUNNING = 'execution_running', EXECUTION_COMPLETED = 'execution_completed', EXECUTION_CANCELED = 'execution_canceled', - EXECUTION_RUNNING = 'execution_running', + + // Repository import/export events + REPO_IMPORTED = 'repository_import_completed', + REPO_EXPORT_COMPLETED = 'repository_export_completed', + + // Pull request events PULLREQ_UPDATED = 'pullreq_updated', - REPO_IMPORTED = 'repository_import_completed' + PULLREQ_REVIEWER_ADDED = 'pullreq_reviewer_added', + PULLREQ_REVIEWER_REMOVED = 'pullreq_reviewer_removed', + PULLREQ_COMMENT_CREATED = 'pullreq_comment_created', + PULLREQ_COMMENT_EDITED = 'pullreq_comment_edited', + PULLREQ_COMMENT_UPDATED = 'pullreq_comment_updated', + PULLREQ_COMMENT_STATUS_RESOLVED = 'pullreq_comment_status_resolved', + PULLREQ_COMMENT_STATUS_REACTIVATED = 'pullreq_comment_status_reactivated', + PULLREQ_OPENED = 'pullreq_opened', + PULLREQ_CLOSED = 'pullreq_closed', + PULLREQ_MARKED_AS_DRAFT = 'pullreq_marked_as_draft', + PULLREQ_READY_FOR_REVIEW = 'pullreq_ready_for_review', + + // Branch events + BRANCH_MERGABLE_UPDATED = 'branch_mergable_updated', + BRANCH_CREATED = 'branch_created', + BRANCH_UPDATED = 'branch_updated', + BRANCH_DELETED = 'branch_deleted', + + // Tag events + TAG_CREATED = 'tag_created', + TAG_UPDATED = 'tag_updated', + TAG_DELETED = 'tag_deleted', + + // Status events + STATUS_CHECK_REPORT_UPDATED = 'status_check_report_updated', + + // Log events + LOG_LINE_APPENDED = 'log_line_appended', + + // Rule events + RULE_CREATED = 'rule_created', + RULE_UPDATED = 'rule_updated', + RULE_DELETED = 'rule_deleted', + + // Webhook events + WEBHOOK_CREATED = 'webhook_created', + WEBHOOK_UPDATED = 'webhook_updated', + WEBHOOK_DELETED = 'webhook_deleted' } export interface CreateFormType { From 14f80a59928fae5d1e99ea06ae42549909ddb80e Mon Sep 17 00:00:00 2001 From: Alex <zemka4@gmail.com> Date: Fri, 22 Aug 2025 14:26:44 +0300 Subject: [PATCH 172/180] Fix scrolling in a Drawer inside Shadow DOM (#2094) --- .../src/components/drawer/DrawerContent.tsx | 56 ++++++++++++------- .../src/components/drawer/DrawerOverlay.tsx | 8 +-- .../ui/src/components/drawer/DrawerRoot.tsx | 2 +- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/components/drawer/DrawerContent.tsx b/packages/ui/src/components/drawer/DrawerContent.tsx index ad7675e327..472f83e2c0 100644 --- a/packages/ui/src/components/drawer/DrawerContent.tsx +++ b/packages/ui/src/components/drawer/DrawerContent.tsx @@ -46,29 +46,45 @@ export const DrawerContent = forwardRef<ElementRef<typeof DrawerPrimitive.Conten ref ) => { const { portalContainer } = usePortal() - const { direction } = useDrawerContext() + const { direction, modal } = useDrawerContext() + + const withCustomOverlay = forceWithOverlay && modal === false + + const Content = ( + <DrawerPrimitive.Content + ref={ref} + className={cn( + drawerContentVariants({ size, direction: direction as DrawerContentVariantsDirection }), + className + )} + {...props} + > + {!hideClose && ( + <DrawerPrimitive.Close asChild> + <Button className="cn-drawer-close-button" variant="transparent" iconOnly> + <IconV2 className="cn-drawer-close-button-icon" name="xmark" skipSize /> + </Button> + </DrawerPrimitive.Close> + )} + {children} + </DrawerPrimitive.Content> + ) return ( <DrawerPrimitive.Portal container={portalContainer}> - <DrawerOverlay className={overlayClassName} forceWithOverlay={forceWithOverlay} /> - - <DrawerPrimitive.Content - ref={ref} - className={cn( - drawerContentVariants({ size, direction: direction as DrawerContentVariantsDirection }), - className - )} - {...props} - > - {!hideClose && ( - <DrawerPrimitive.Close asChild> - <Button className="cn-drawer-close-button" variant="transparent" iconOnly> - <IconV2 className="cn-drawer-close-button-icon" name="xmark" skipSize /> - </Button> - </DrawerPrimitive.Close> - )} - {children} - </DrawerPrimitive.Content> + {/* !!! */} + {/* For the scroll to work when using the Drawer with modal === true in Shadow DOM, the Overlay needs to wrap the Content */} + {/* Here’s the issue for the scroll bug in Shadow DOM, works same for Vaul - https://github.com/radix-ui/primitives/issues/3353 */} + {modal ? ( + <DrawerOverlay className={overlayClassName} withCustomOverlay={withCustomOverlay}> + {Content} + </DrawerOverlay> + ) : ( + <> + <DrawerOverlay className={overlayClassName} withCustomOverlay={withCustomOverlay} /> + {Content} + </> + )} </DrawerPrimitive.Portal> ) } diff --git a/packages/ui/src/components/drawer/DrawerOverlay.tsx b/packages/ui/src/components/drawer/DrawerOverlay.tsx index 869737ba06..d7fe396aa8 100644 --- a/packages/ui/src/components/drawer/DrawerOverlay.tsx +++ b/packages/ui/src/components/drawer/DrawerOverlay.tsx @@ -7,11 +7,9 @@ import { useDrawerContext } from './drawer-context' export const DrawerOverlay = forwardRef< ElementRef<typeof DrawerPrimitive.Overlay>, - ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> & { forceWithOverlay?: boolean } ->(({ className, forceWithOverlay, ...props }, ref) => { - const { nested, isParentOpen, modal } = useDrawerContext() - - const withCustomOverlay = forceWithOverlay && modal === false + ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> & { withCustomOverlay?: boolean } +>(({ className, withCustomOverlay = false, ...props }, ref) => { + const { nested, isParentOpen } = useDrawerContext() if (withCustomOverlay) { return ( diff --git a/packages/ui/src/components/drawer/DrawerRoot.tsx b/packages/ui/src/components/drawer/DrawerRoot.tsx index 81362c8bf8..449f36f2fb 100644 --- a/packages/ui/src/components/drawer/DrawerRoot.tsx +++ b/packages/ui/src/components/drawer/DrawerRoot.tsx @@ -69,7 +69,7 @@ export const DrawerRoot = ({ return ( <DrawerContext.Provider - value={{ direction, nested, isParentOpen: isParentOpen || open || isTriggerOpen, modal: props.modal }} + value={{ direction, nested, isParentOpen: isParentOpen || open || isTriggerOpen, modal: props?.modal ?? true }} > <RootComponent handleOnly {...rootProps} container={portalContainer as HTMLElement} data-root="drawer"> {nested && FakeTriggers} From fc5cf52268bbecf57bc68ab4ea25909d0f4ab303 Mon Sep 17 00:00:00 2001 From: Srdjan Arsic <srdjan.arsic@harness.io> Date: Fri, 22 Aug 2025 12:57:07 +0000 Subject: [PATCH 173/180] add clone to input factory (#10272) * 2acbf0 add clone to input factory --- packages/forms/package.json | 2 +- packages/forms/src/core/factory/InputFactory.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/forms/package.json b/packages/forms/package.json index 34b7b7b99f..968ee1d5ef 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json @@ -1,6 +1,6 @@ { "name": "@harnessio/forms", - "version": "0.0.11", + "version": "0.0.12", "description": "Harness Forms Library", "scripts": { "playground": "vite dev --config vite.config.playground.ts", diff --git a/packages/forms/src/core/factory/InputFactory.ts b/packages/forms/src/core/factory/InputFactory.ts index 524e43849e..e72201c698 100644 --- a/packages/forms/src/core/factory/InputFactory.ts +++ b/packages/forms/src/core/factory/InputFactory.ts @@ -50,4 +50,21 @@ export class InputFactory { listRegisteredComponents(): string[] { return Array.from(this.componentBank.keys()) } + + /** + * Clone factory with all registered input components + * + * @returns new input factory + */ + clone() { + const cloneFactory = new InputFactory({ + allowOverride: this.allowOverride + }) + + this.listRegisteredComponents().forEach(inputType => { + cloneFactory.registerComponent(this.componentBank.get(inputType) as InputComponent<unknown>) + }) + + return cloneFactory + } } From 57e4236a31a66054a063317a29ac72755a32318e Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:27:42 +0400 Subject: [PATCH 174/180] fix suggested changes ui (#2093) * fix suggested changes ui * fixes --- packages/ui/locales/en/views.json | 7 ++ packages/ui/locales/fr/views.json | 7 ++ .../markdown-viewer/CodeSuggestionBlock.tsx | 21 ++-- .../src/components/markdown-viewer/index.tsx | 84 +++++++++------ .../src/components/markdown-viewer/style.css | 4 +- .../common/pull-request-comment-view.tsx | 101 +++++++++--------- 6 files changed, 130 insertions(+), 94 deletions(-) diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index ccb489a991..6e85834fcd 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -637,6 +637,13 @@ "changereq": "Changes Requested", "split": "Split", "unified": "Unified", + "comments": { + "suggestionApplied": "Suggestion applied", + "codeSuggestion": "Code suggestion", + "commitSuggestion": "Commit suggestion", + "removeSuggestion": "Remove suggestion from batch", + "addSuggestion": "Add suggestion to batch" + }, "deleted": "Deleted", "searchUsers": "Search users", "compareChangesFormDescriptionLabel": "Description", diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index e27503df78..b5499e1cee 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -627,6 +627,13 @@ "changereq": "Changement Demandé", "split": "Diviser", "unified": "Unifié", + "comments": { + "suggestionApplied": "Suggestion applied", + "codeSuggestion": "Code suggestion", + "commitSuggestion": "Commit suggestion", + "removeSuggestion": "Remove suggestion from batch", + "addSuggestion": "Add suggestion to batch" + }, "deleted": "Supprimé", "searchUsers": "Rechercher des utilisateurs", "compareChangesFormDescriptionLabel": "Description", diff --git a/packages/ui/src/components/markdown-viewer/CodeSuggestionBlock.tsx b/packages/ui/src/components/markdown-viewer/CodeSuggestionBlock.tsx index d08ccdf371..54ec60cad2 100644 --- a/packages/ui/src/components/markdown-viewer/CodeSuggestionBlock.tsx +++ b/packages/ui/src/components/markdown-viewer/CodeSuggestionBlock.tsx @@ -5,9 +5,6 @@ import hljs from 'highlight.js' export interface SuggestionBlock { source: string lang?: string - commentId?: number - appliedCheckSum?: string - appliedCommitSha?: string } export interface Suggestion { @@ -35,13 +32,15 @@ export function CodeSuggestionBlock({ code, suggestionBlock }: CodeSuggestionBlo }, [code, language]) return ( - <div className="pt-1"> - <pre className="!bg-cn-background-diff-danger"> - <code className={`${language} code-highlight`} dangerouslySetInnerHTML={{ __html: highlightedHtmlOld }}></code> - </pre> - <pre className="!bg-cn-background-diff-success"> - <code className={`${language} code-highlight`} dangerouslySetInnerHTML={{ __html: highlightedHtmlNew }}></code> - </pre> - </div> + <> + <code + className={`${language} code-highlight !bg-cn-background-diff-danger !pl-cn-md`} + dangerouslySetInnerHTML={{ __html: highlightedHtmlOld }} + /> + <code + className={`${language} code-highlight !bg-cn-background-diff-success !pl-cn-md`} + dangerouslySetInnerHTML={{ __html: highlightedHtmlNew }} + /> + </> ) } diff --git a/packages/ui/src/components/markdown-viewer/index.tsx b/packages/ui/src/components/markdown-viewer/index.tsx index 69ec165eff..f1f108637e 100644 --- a/packages/ui/src/components/markdown-viewer/index.tsx +++ b/packages/ui/src/components/markdown-viewer/index.tsx @@ -1,6 +1,6 @@ -import { CSSProperties, useCallback, useEffect, useMemo, useRef } from 'react' +import { CSSProperties, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react' -import { CopyButton } from '@/components' +import { CopyButton, Text } from '@/components' import MarkdownPreview from '@uiw/react-markdown-preview' import rehypeExternalLinks from 'rehype-external-links' import { getCodeString, RehypeRewriteOptions } from 'rehype-rewrite' @@ -28,10 +28,11 @@ type MarkdownViewerProps = { className?: string suggestionBlock?: SuggestionBlock suggestionCheckSum?: string - isSuggestion?: boolean markdownClassName?: string showLineNumbers?: boolean onCheckboxChange?: (source: string) => void + suggestionTitle?: string + suggestionFooter?: ReactNode } export function MarkdownViewer({ @@ -40,11 +41,11 @@ export function MarkdownViewer({ withBorder = false, className, suggestionBlock, - suggestionCheckSum, - isSuggestion, markdownClassName, showLineNumbers = false, - onCheckboxChange + onCheckboxChange, + suggestionTitle, + suggestionFooter }: MarkdownViewerProps) { const { navigate } = useRouterContext() const refRootHref = useMemo(() => document.getElementById('repository-ref-root')?.getAttribute('href'), []) @@ -56,6 +57,8 @@ export function MarkdownViewer({ // Reset checkbox counter at the start of each render checkboxCounter.current = 0 + const filteredSource = useMemo(() => source.split('\n').filter(line => line !== '' && line !== '```'), [source]) + const styles: CSSProperties = maxHeight ? { maxHeight } : {} const rewriteRelativeLinks = useCallback( @@ -168,6 +171,18 @@ export function MarkdownViewer({ [onCheckboxChange, source] ) + const getIsSuggestion = useCallback( + (code: string) => { + const trimmedCode = code.trim() + const codeLines = trimmedCode.split('\n') + const codeIndex = filteredSource.findIndex(line => line.includes(codeLines[0] || '')) + const isSuggestion = codeIndex !== -1 && filteredSource[codeIndex - 1]?.includes('suggestion') + + return { isSuggestion, codeLines } + }, + [filteredSource] + ) + useEffect(() => { const container = ref.current @@ -183,23 +198,9 @@ export function MarkdownViewer({ return ( <div className={cn({ 'rounded-b-md border-x border-b py-6 px-16': withBorder }, className)}> <div ref={ref} style={styles}> - {isSuggestion && ( - <div className="border-cn-borders-2 bg-cn-background-2 rounded-t-md border-x border-t px-4 py-3"> - <span className="text-2 text-cn-foreground-1"> - {suggestionBlock?.appliedCheckSum && suggestionBlock?.appliedCheckSum === suggestionCheckSum - ? 'Suggestion applied' - : 'Suggested change'} - </span> - </div> - )} - <MarkdownPreview source={source} - className={cn( - 'prose prose-invert', - { '[&>div>pre]:rounded-t-none [&>div>pre]:mb-2': isSuggestion }, - markdownClassName - )} + className={cn('prose prose-invert', markdownClassName)} rehypeRewrite={rehypeRewrite} remarkPlugins={[remarkBreaks]} rehypePlugins={[ @@ -235,14 +236,33 @@ export function MarkdownViewer({ codeContent = code } - const trimmedCode = codeContent.trim() - const codeLines = trimmedCode.split('\n') + const { isSuggestion, codeLines } = getIsSuggestion(codeContent) + const filteredLines = codeLines.length > 0 && codeLines[codeLines.length - 1] === '' ? codeLines.slice(0, -1) : codeLines const hasLineNumbers = showLineNumbers && filteredLines.length > 1 + if (isSuggestion) { + return ( + <div className="rounded-2 overflow-hidden border"> + <div className="bg-cn-background-2 px-cn-md py-cn-sm border-b"> + <Text variant="body-strong" color="foreground-1" className="!m-0"> + {suggestionTitle} + </Text> + </div> + <pre>{children}</pre> + <div className="p-cn-md">{suggestionFooter}</div> + </div> + ) + } + return ( - <div className="mb-cn-md relative"> + <div + className={cn( + 'min-h-[52px] mb-cn-md rounded-2 pl-cn-md pr-cn-sm py-cn-sm relative overflow-hidden border', + { '!pt-[15px]': codeLines.length === 1 } + )} + > <CopyButton className="absolute right-3 top-3 z-10" buttonVariant="outline" @@ -250,7 +270,7 @@ export function MarkdownViewer({ iconSize="xs" size="xs" /> - <pre className={cn('min-h-[52px]', { '!pt-[15px]': codeLines.length === 1 })}> + <pre> {hasLineNumbers ? ( <div className="relative flex w-full bg-transparent"> <div className="bg-cn-background-2 flex-none select-none text-right"> @@ -272,16 +292,20 @@ export function MarkdownViewer({ code: ({ children = [], className: _className, ...props }) => { const code = props.node && props.node.children ? getCodeString(props.node.children) : children - if ( + const isPossibleSuggestion = typeof code === 'string' && - isSuggestion && typeof _className === 'string' && 'language-suggestion' === _className.split(' ')[0].toLocaleLowerCase() - ) { - return <CodeSuggestionBlock code={code} suggestionBlock={suggestionBlock} /> + + if (isPossibleSuggestion) { + const { isSuggestion } = getIsSuggestion(code) + + if (isSuggestion) { + return <CodeSuggestionBlock code={code} suggestionBlock={suggestionBlock} /> + } } - return <code className={`!whitespace-pre-wrap ${String(_className)}`}>{children}</code> + return <code className={`mr-cn-3xl !whitespace-pre-wrap ${String(_className)}`}>{children}</code> } }} /> diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 963e4118f1..5c9e8b7458 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -246,10 +246,10 @@ } pre { - @apply font-body-code bg-cn-background-1 border-cn-borders-3 text-cn-foreground-1 mb-0 rounded border py-3 pl-4 pr-3; + @apply font-body-code bg-cn-background-1 border-cn-borders-3 text-cn-foreground-1 mb-0 rounded-none border-0 p-0; code { - @apply bg-cn-background-1 !text-cn-foreground-1 mr-cn-3xl p-0; + @apply bg-cn-background-1 !text-cn-foreground-1 rounded-none p-0; } } diff --git a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx index bdafdfd2fa..227c410f83 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/common/pull-request-comment-view.tsx @@ -1,8 +1,7 @@ -// SuggestionCommentContent.tsx - import { FC } from 'react' -import { Button, CounterBadge, Layout, MarkdownViewer } from '@/components' +import { Button, ButtonLayout, CounterBadge, MarkdownViewer } from '@/components' +import { useTranslation } from '@/context' import { CommitSuggestion } from '@views/repo/pull-request/pull-request.types' import { CommentItem, TypesPullReqActivity } from '../../pull-request-details-types' @@ -27,14 +26,15 @@ const PRCommentView: FC<PRCommentViewProps> = ({ removeSuggestionFromBatch, parentItem }) => { + const { t } = useTranslation() const pathSegments = commentItem?.payload?.code_comment?.path?.split('/') || [] const fileLang = filenameToLanguage?.(pathSegments.pop() || '') || '' const appliedCheckSum = commentItem?.payload?.metadata?.suggestions?.applied_check_sum ?? '' const checkSums = commentItem?.payload?.metadata?.suggestions?.check_sums ?? [] - const isSuggestion = !!checkSums?.length const isApplied = appliedCheckSum === checkSums?.[0] const isInBatch = suggestionsBatch?.some(suggestion => suggestion.comment_id === commentItem.id) + const suggestionCheckSum = checkSums?.[0] || '' const formattedComment = replaceMentionIdWithDisplayName( commentItem?.payload?.text || '', @@ -42,59 +42,58 @@ const PRCommentView: FC<PRCommentViewProps> = ({ ) return ( - <> - <MarkdownViewer - markdownClassName="pr-section" - source={formattedComment || ''} - suggestionBlock={{ - source: - commentItem.codeBlockContent ?? - (parentItem && parentItem.codeBlockContent ? parentItem.codeBlockContent : ''), - lang: fileLang, - commentId: commentItem.id, - appliedCheckSum: appliedCheckSum, - appliedCommitSha: commentItem.appliedCommitSha || '' - }} - suggestionCheckSum={checkSums?.[0] || ''} - isSuggestion={isSuggestion} - /> - - {/* Only show the suggestion buttons if the suggestion is not yet applied */} - {isSuggestion && !isApplied && ( - <Layout.Horizontal align="center" justify="end" gap="sm" className="pt-4"> - <Button - className="gap-x-2" - variant="outline" - onClick={() => { - onCommitSuggestion?.({ - check_sum: checkSums?.[0] || '', - comment_id: commentItem.id - }) - }} - > - Commit suggestion - {!!suggestionsBatch?.length && <CounterBadge theme="info">{suggestionsBatch.length}</CounterBadge>} - </Button> - {isInBatch ? ( - <Button variant="outline" theme="danger" onClick={() => removeSuggestionFromBatch?.(commentItem.id)}> - Remove suggestion from batch - </Button> - ) : ( + <MarkdownViewer + markdownClassName="pr-section" + source={formattedComment || ''} + suggestionBlock={{ + source: + commentItem.codeBlockContent ?? + (parentItem && parentItem.codeBlockContent ? parentItem.codeBlockContent : ''), + lang: fileLang + }} + suggestionCheckSum={suggestionCheckSum} + suggestionTitle={ + appliedCheckSum && appliedCheckSum === suggestionCheckSum + ? t('views:pullRequests.comments.suggestionApplied', 'Suggestion applied') + : t('views:pullRequests.comments.codeSuggestion', 'Code suggestion') + } + suggestionFooter={ + !isApplied && ( + <ButtonLayout className="flex-wrap"> <Button + className="gap-x-2" variant="outline" - onClick={() => - addSuggestionToBatch?.({ - check_sum: checkSums?.[0] || '', + onClick={() => { + onCommitSuggestion?.({ + check_sum: suggestionCheckSum, comment_id: commentItem.id }) - } + }} > - Add suggestion to batch + {t('views:pullRequests.comments.commitSuggestion', 'Commit suggestion')} + {!!suggestionsBatch?.length && <CounterBadge theme="info">{suggestionsBatch.length}</CounterBadge>} </Button> - )} - </Layout.Horizontal> - )} - </> + {isInBatch ? ( + <Button variant="outline" theme="danger" onClick={() => removeSuggestionFromBatch?.(commentItem.id)}> + {t('views:pullRequests.comments.removeSuggestion', 'Remove suggestion from batch')} + </Button> + ) : ( + <Button + variant="outline" + onClick={() => + addSuggestionToBatch?.({ + check_sum: suggestionCheckSum, + comment_id: commentItem.id + }) + } + > + {t('views:pullRequests.comments.addSuggestion', 'Add suggestion to batch')} + </Button> + )} + </ButtonLayout> + ) + } + /> ) } From a6a6366fadefa0e0c92c9e1c75f1c4c006f62b16 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Fri, 22 Aug 2025 14:40:25 +0000 Subject: [PATCH 175/180] fix: Cleanup AppMFE corresponding to optimizations done in parent NG UI app (#10270) * c3a3f0 fix nav imports * 2a0592 cleanup - remove console log * 97be65 Move "MFERouteRenderer" out of AppMFE * 8fdb18 rename and adding temp console logs --- apps/gitness/src/AppMFE.tsx | 75 +++---------------- apps/gitness/src/MFERouteRenderer.tsx | 52 +++++++++++++ .../framework/context/AppRouterProvider.tsx | 29 ++++--- .../src/framework/context/MFEContext.tsx | 10 ++- apps/gitness/src/routes.tsx | 3 +- 5 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 apps/gitness/src/MFERouteRenderer.tsx diff --git a/apps/gitness/src/AppMFE.tsx b/apps/gitness/src/AppMFE.tsx index 7521c7f34e..d316df366e 100644 --- a/apps/gitness/src/AppMFE.tsx +++ b/apps/gitness/src/AppMFE.tsx @@ -1,6 +1,6 @@ -import { useEffect, useMemo, useRef } from 'react' +import { useEffect, useRef } from 'react' import { I18nextProvider } from 'react-i18next' -import { createBrowserRouter, matchPath, RouterProvider, useLocation, useNavigate } from 'react-router-dom' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' import { QueryClientProvider } from '@tanstack/react-query' @@ -10,70 +10,18 @@ import { PortalProvider, TranslationProvider } from '@harnessio/ui/context' import ShadowRootWrapper from './components-v2/shadow-root-wrapper' import { ExitConfirmProvider } from './framework/context/ExitConfirmContext' -import { IMFEContext, MFEContext } from './framework/context/MFEContext' +import { MFEContext, MFEContextProps } from './framework/context/MFEContext' import { NavigationProvider } from './framework/context/NavigationContext' import { ThemeProvider, useThemeStore } from './framework/context/ThemeContext' import { queryClient } from './framework/queryClient' -import { extractRedirectRouteObjects } from './framework/routing/utils' import { useLoadMFEStyles } from './hooks/useLoadMFEStyles' import i18n from './i18n/i18n' import { useTranslationStore } from './i18n/stores/i18n-store' -import { mfeRoutes, repoRoutes } from './routes' -import { decodeURIComponentIfValid } from './utils/path-utils' +import { getMFERoutes } from './routes' -export interface MFERouteRendererProps { - renderUrl: string - parentLocationPath: string - onRouteChange: (updatedLocationPathname: string) => void -} - -const filteredRedirectRoutes = extractRedirectRouteObjects(repoRoutes) -const isRouteNotMatchingRedirectRoutes = (pathToValidate: string) => { - return filteredRedirectRoutes.every(route => !matchPath(`/${route.path}` as string, pathToValidate)) -} - -function MFERouteRenderer({ renderUrl, parentLocationPath, onRouteChange }: MFERouteRendererProps) { - const navigate = useNavigate() - const location = useLocation() - const parentPath = parentLocationPath.replace(renderUrl, '') - const isNotRedirectPath = isRouteNotMatchingRedirectRoutes(location.pathname) - - /** - * renderUrl ==> base URL of parent application - * parentPath ==> path name of parent application after base URL - * location.pathname ==> path name of MFE - * isNotRedirectPath ==> check if the current path is not a redirect path - */ - const canNavigate = useMemo( - () => - renderUrl && - decodeURIComponentIfValid(parentPath) !== decodeURIComponentIfValid(location.pathname) && - isNotRedirectPath, - [isNotRedirectPath, location.pathname, parentPath, renderUrl] - ) - - // Handle location change detected from parent route - useEffect(() => { - if (canNavigate) { - navigate(decodeURIComponentIfValid(parentPath), { replace: true }) - } - }, [parentPath]) - - // Notify parent about route change - useEffect(() => { - if (canNavigate) { - onRouteChange?.(decodeURIComponentIfValid(`${renderUrl}${location.pathname}${location.search}`)) - } - }, [location.pathname]) - - return null -} - -interface AppMFEProps extends IMFEContext { +interface AppMFEProps extends MFEContextProps { on401?: () => void useMFEThemeContext: () => { theme: string; setTheme: (newTheme: string) => void } - parentLocationPath: string - onRouteChange: (updatedLocationPathname: string) => void } function decode<T = unknown>(arg: string): T { @@ -138,12 +86,9 @@ export default function AppMFE({ // Router Configuration const basename = `/ng${renderUrl}` - const routesToRender = mfeRoutes( - scope.projectIdentifier, - <MFERouteRenderer renderUrl={renderUrl} onRouteChange={onRouteChange} parentLocationPath={parentLocationPath} /> - ) + const mfeRoutes = getMFERoutes(scope.projectIdentifier) - const router = createBrowserRouter(routesToRender, { basename }) + const router = createBrowserRouter(mfeRoutes, { basename }) const { t } = useTranslationStore() return ( @@ -166,7 +111,9 @@ export default function AppMFE({ routes, routeUtils, hooks, - setMFETheme + setMFETheme, + parentLocationPath, + onRouteChange }} > <I18nextProvider i18n={i18n}> @@ -176,7 +123,7 @@ export default function AppMFE({ <Toast.Provider> <TooltipProvider> <ExitConfirmProvider> - <NavigationProvider routes={routesToRender}> + <NavigationProvider routes={mfeRoutes}> <RouterProvider router={router} /> </NavigationProvider> </ExitConfirmProvider> diff --git a/apps/gitness/src/MFERouteRenderer.tsx b/apps/gitness/src/MFERouteRenderer.tsx new file mode 100644 index 0000000000..2700fb4e26 --- /dev/null +++ b/apps/gitness/src/MFERouteRenderer.tsx @@ -0,0 +1,52 @@ +import { useEffect, useMemo } from 'react' +import { matchPath, useLocation, useNavigate } from 'react-router-dom' + +import { useMFEContext } from './framework/hooks/useMFEContext' +import { extractRedirectRouteObjects } from './framework/routing/utils' +import { repoRoutes } from './routes' +import { decodeURIComponentIfValid } from './utils/path-utils' + +export const MFERouteRenderer: React.FC = () => { + const navigate = useNavigate() + const location = useLocation() + const { renderUrl, parentLocationPath, onRouteChange } = useMFEContext() + const parentPath = parentLocationPath.replace(renderUrl, '') + + const filteredRedirectRoutes = extractRedirectRouteObjects(repoRoutes) + + const isRouteNotMatchingRedirectRoutes = (pathToValidate: string) => { + return filteredRedirectRoutes.every(route => !matchPath(`/${route.path}` as string, pathToValidate)) + } + + const isNotRedirectPath = isRouteNotMatchingRedirectRoutes(location.pathname) + + /** + * renderUrl ==> base URL of parent application + * parentPath ==> path name of parent application after base URL + * location.pathname ==> path name of MFE + * isNotRedirectPath ==> check if the current path is not a redirect path + */ + const canNavigate = useMemo( + () => + renderUrl && + decodeURIComponentIfValid(parentPath) !== decodeURIComponentIfValid(location.pathname) && + isNotRedirectPath, + [isNotRedirectPath, location.pathname, parentPath, renderUrl] + ) + + // Handle location change detected from parent route + useEffect(() => { + if (canNavigate) { + navigate(decodeURIComponentIfValid(parentPath), { replace: true }) + } + }, [parentPath]) + + // Notify parent about route change + useEffect(() => { + if (canNavigate) { + onRouteChange?.(decodeURIComponentIfValid(`${renderUrl}${location.pathname}${location.search}`)) + } + }, [location.pathname]) + + return null +} diff --git a/apps/gitness/src/framework/context/AppRouterProvider.tsx b/apps/gitness/src/framework/context/AppRouterProvider.tsx index d04a6a1e08..530c2add6e 100644 --- a/apps/gitness/src/framework/context/AppRouterProvider.tsx +++ b/apps/gitness/src/framework/context/AppRouterProvider.tsx @@ -12,6 +12,8 @@ import { import { RouterContextProvider } from '@harnessio/ui/context' +import { MFERouteRenderer } from '../../MFERouteRenderer' + interface AppRouterProviderProps { children: ReactNode } @@ -21,18 +23,21 @@ const AppRouterProvider: FC<AppRouterProviderProps> = ({ children }) => { const location = useLocation() return ( - <RouterContextProvider - Link={Link} - NavLink={NavLink} - Outlet={Outlet} - location={location} - navigate={navigate} - useSearchParams={useSearchParams} - useMatches={useMatches} - useParams={useParams} - > - {children} - </RouterContextProvider> + <> + <MFERouteRenderer /> + <RouterContextProvider + Link={Link} + NavLink={NavLink} + Outlet={Outlet} + location={location} + navigate={navigate} + useSearchParams={useSearchParams} + useMatches={useMatches} + useParams={useParams} + > + {children} + </RouterContextProvider> + </> ) } diff --git a/apps/gitness/src/framework/context/MFEContext.tsx b/apps/gitness/src/framework/context/MFEContext.tsx index 75be433218..3bec9cbea6 100644 --- a/apps/gitness/src/framework/context/MFEContext.tsx +++ b/apps/gitness/src/framework/context/MFEContext.tsx @@ -65,7 +65,7 @@ export interface Hooks { export type Unknown = any -export interface IMFEContext { +export interface MFEContextProps { /** * Scope will be later referred from "Scope" from @harness/microfrontends * */ @@ -101,9 +101,11 @@ export interface IMFEContext { }> hooks: Hooks setMFETheme: (newTheme: string) => void + parentLocationPath: string + onRouteChange: (updatedLocationPathname: string) => void } -export const MFEContext = createContext<IMFEContext>({ +export const MFEContext = createContext<MFEContextProps>({ scope: { accountId: '' }, parentContextObj: { appStoreContext: createContext({ @@ -118,5 +120,7 @@ export const MFEContext = createContext<IMFEContext>({ routes: {}, routeUtils: {}, hooks: {}, - setMFETheme: noop + setMFETheme: noop, + parentLocationPath: '', + onRouteChange: noop }) diff --git a/apps/gitness/src/routes.tsx b/apps/gitness/src/routes.tsx index e4ded1ab6b..e2f105a9f0 100644 --- a/apps/gitness/src/routes.tsx +++ b/apps/gitness/src/routes.tsx @@ -1256,7 +1256,7 @@ export const routes: CustomRouteObject[] = [ } ] -export const mfeRoutes = (mfeProjectId = '', mfeRouteRenderer: JSX.Element | null = null): CustomRouteObject[] => [ +export const getMFERoutes = (mfeProjectId?: string): CustomRouteObject[] => [ { path: '/', element: ( @@ -1264,7 +1264,6 @@ export const mfeRoutes = (mfeProjectId = '', mfeRouteRenderer: JSX.Element | nul <PageTitleProvider> <AppProvider> <ComponentProvider components={{ RbacButton, RbacSplitButton }}> - {mfeRouteRenderer} <AppShellMFE /> </ComponentProvider> </AppProvider> From 57b4687eb3ae07902dcb449ca61499ec9a4933db Mon Sep 17 00:00:00 2001 From: Drew <34187607+ankormoreankor@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:35:39 +0400 Subject: [PATCH 176/180] fix repo files content header (#2096) --- packages/ui/src/views/repo/repo-files/repo-files-view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx index 329be32795..85323f1cf6 100644 --- a/packages/ui/src/views/repo/repo-files/repo-files-view.tsx +++ b/packages/ui/src/views/repo/repo-files/repo-files-view.tsx @@ -153,8 +153,8 @@ export const RepoFiles: FC<RepoFilesProps> = ({ ]) return ( - <SandboxLayout.Main className="repo-files-height pt-cn-xl bg-transparent"> - <SandboxLayout.Content className="pl-cn-lg gap-y-cn-md flex h-full flex-col"> + <SandboxLayout.Main className="repo-files-height bg-transparent"> + <SandboxLayout.Content className="pl-cn-lg gap-y-cn-md pt-cn-xl flex h-full flex-col"> {isView && !isRepoEmpty && ( <PathActionBar codeMode={codeMode} From ca936329a941b4fd79d9ce4eb40e8039abae72f5 Mon Sep 17 00:00:00 2001 From: Abhinav Rastogi <abhinav.rastogi@harness.io> Date: Fri, 22 Aug 2025 18:54:27 +0000 Subject: [PATCH 177/180] fix: remove eager true (#10276) * 96b65c fix: remove eager true --- apps/gitness/webpack.config.cjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/gitness/webpack.config.cjs b/apps/gitness/webpack.config.cjs index e11fee7492..84bae8a840 100644 --- a/apps/gitness/webpack.config.cjs +++ b/apps/gitness/webpack.config.cjs @@ -48,12 +48,10 @@ module.exports = { react: { singleton: true, requiredVersion: false, - eager: true }, 'react-dom': { singleton: true, requiredVersion: false, - eager: true } } }), From 2d85c86bcedf3a7d436518e3bf9202363a609bc5 Mon Sep 17 00:00:00 2001 From: Sanskar Sehgal <c_sanskar.sehgal@harness.io> Date: Fri, 22 Aug 2025 21:06:20 +0000 Subject: [PATCH 178/180] fix: routing on commits pull req page (#10278) * 5792c1 fix: routing on commits pr --- .../src/pages-v2/pull-request/pull-request-commits.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx index a5b51747fc..6e127f3b6a 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-commits.tsx @@ -46,9 +46,7 @@ export function PullRequestCommitPage() { return ( <PullRequestCommitsView toCode={({ sha }: { sha: string }) => `${routes.toRepoFiles({ spaceId, repoId })}/${sha}`} - toCommitDetails={({ sha }: { sha: string }) => - routes.toPullRequestChanges({ spaceId, repoId, pullRequestId, commitSHA: sha }) - } + toCommitDetails={({ sha }: { sha: string }) => routes.toRepoCommitDetails({ spaceId, repoId, commitSHA: sha })} usePullRequestCommitsStore={usePullRequestCommitsStore} /> ) From f94f7d4629171fe110a68cf3e5b895bcc12c8cd3 Mon Sep 17 00:00:00 2001 From: Harish Viswanathan <harish.viswanathan@harness.io> Date: Fri, 22 Aug 2025 21:15:50 +0000 Subject: [PATCH 179/180] fix: updated history state with replace wherever applicable (#10273) * c717d9 chore: Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/canary into fix/browser-navigation * f1a2eb fix: updated history state with replace wherever applicable --- apps/gitness/src/framework/hooks/useQueryState.ts | 6 ++++-- .../project/pull-request/pull-request-list.tsx | 10 +++------- .../src/pages-v2/pull-request/pull-request-list.tsx | 10 +++------- apps/gitness/src/pages-v2/repo/repo-summary.tsx | 2 +- packages/filters/src/Filters.tsx | 7 +++++-- packages/ui/src/context/router-context.tsx | 5 ++++- .../pull-request/components/labels/labels-filter.tsx | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/gitness/src/framework/hooks/useQueryState.ts b/apps/gitness/src/framework/hooks/useQueryState.ts index d6e24b5b91..28f314777a 100644 --- a/apps/gitness/src/framework/hooks/useQueryState.ts +++ b/apps/gitness/src/framework/hooks/useQueryState.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react' -import { useSearchParams } from 'react-router-dom' + +import { useRouterContext } from '@harnessio/ui/context' interface Parser<T> { parse: (value: string | null) => T @@ -10,6 +11,7 @@ const useQueryState = <T = string>( key: string, parser: Parser<T> = parseAsString as unknown as Parser<T> // Default parser is for strings ): [T, (value: T | null) => void] => { + const { useSearchParams } = useRouterContext() const [searchParams, setSearchParams] = useSearchParams() // Parse the current value or fallback to the default value @@ -25,7 +27,7 @@ const useQueryState = <T = string>( } else { newParams.set(key, String(newValue)) } - setSearchParams(newParams) + setSearchParams(newParams, { replace: true }) }, [key, setSearchParams] ) diff --git a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx index 4aff31d7e8..4f148cc322 100644 --- a/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/project/pull-request/pull-request-list.tsx @@ -21,7 +21,8 @@ import { import { useRoutes } from '../../../framework/context/NavigationContext' import { useIsMFE } from '../../../framework/hooks/useIsMFE' import { useMFEContext } from '../../../framework/hooks/useMFEContext' -import { parseAsInteger, useQueryState } from '../../../framework/hooks/useQueryState' +import { useQueryState } from '../../../framework/hooks/useQueryState' +import usePaginationQueryStateWithStore from '../../../hooks/use-pagination-query-state-with-store' import { useAPIPath } from '../../../hooks/useAPIPath' import { PathParams } from '../../../RouteDefinitions' import { getPullRequestUrl, getScopeType } from '../../../utils/scope-url-utils' @@ -38,7 +39,7 @@ export default function PullRequestListPage() { /* Query and Pagination */ const [query, setQuery] = useQueryState('query') - const [queryPage, setQueryPage] = useQueryState('page', parseAsInteger.withDefault(1)) + const { queryPage } = usePaginationQueryStateWithStore({ page, setPage }) const [filterValues, setFilterValues] = useState<ListSpacePullReqQueryQueryParams>({ include_subspaces: false }) const [principalsSearchQuery, setPrincipalsSearchQuery] = useState<string>() const [populateLabelStore, setPopulateLabelStore] = useState(false) @@ -173,11 +174,6 @@ export default function PullRequestListPage() { } }, [pullRequestData, setPullRequests]) - useEffect(() => { - setQueryPage(page) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, queryPage, setPage]) - useEffect(() => { if (labelBy) { setPopulateLabelStore(true) diff --git a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx index 422224f4df..63b7684f45 100644 --- a/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx +++ b/apps/gitness/src/pages-v2/pull-request/pull-request-list.tsx @@ -17,7 +17,8 @@ import { import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' import { useMFEContext } from '../../framework/hooks/useMFEContext' -import { parseAsInteger, useQueryState } from '../../framework/hooks/useQueryState' +import { useQueryState } from '../../framework/hooks/useQueryState' +import usePaginationQueryStateWithStore from '../../hooks/use-pagination-query-state-with-store' import { useGitRef } from '../../hooks/useGitRef' import { PathParams } from '../../RouteDefinitions' import { PageResponseHeader } from '../../types' @@ -35,7 +36,7 @@ export default function PullRequestListPage() { /* Query and Pagination */ const [query, setQuery] = useQueryState('query') - const [queryPage, setQueryPage] = useQueryState('page', parseAsInteger.withDefault(1)) + const { queryPage } = usePaginationQueryStateWithStore({ page, setPage }) const [filterValues, setFilterValues] = useState<ListPullReqQueryQueryParams>({}) const [principalsSearchQuery, setPrincipalsSearchQuery] = useState<string>() const [populateLabelStore, setPopulateLabelStore] = useState(false) @@ -238,11 +239,6 @@ export default function PullRequestListPage() { setOpenClosePullRequests(num_open_pulls, num_closed_pulls, num_merged_pulls) }, [repoData, setOpenClosePullRequests, filtersCnt]) - useEffect(() => { - setQueryPage(page) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, queryPage, setPage]) - useEffect(() => { if (labelBy) { setPopulateLabelStore(true) diff --git a/apps/gitness/src/pages-v2/repo/repo-summary.tsx b/apps/gitness/src/pages-v2/repo/repo-summary.tsx index 460f542e97..b06072fa2b 100644 --- a/apps/gitness/src/pages-v2/repo/repo-summary.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-summary.tsx @@ -84,7 +84,7 @@ export default function RepoSummaryPage() { // Navigate to default branch if no branch is selected useEffect(() => { if (!fullGitRefWoDefault && prefixedDefaultBranch && fullGitRef) { - navigate(routes.toRepoSummary({ spaceId, repoId, '*': fullGitRef })) + navigate(routes.toRepoSummary({ spaceId, repoId, '*': fullGitRef }), { replace: true }) } }, [fullGitRefWoDefault, prefixedDefaultBranch, fullGitRef]) diff --git a/packages/filters/src/Filters.tsx b/packages/filters/src/Filters.tsx index 1b6739a5ec..273ad9cdb2 100644 --- a/packages/filters/src/Filters.tsx +++ b/packages/filters/src/Filters.tsx @@ -59,7 +59,7 @@ const Filters = forwardRef(function Filters<T extends Record<string, unknown>>( } }) const mergedParams = mergeURLSearchParams(paramsOtherthanFilters, params) - routerUpdateURL(mergedParams) + routerUpdateURL(mergedParams, true) } const setFiltersMapTrigger = (filtersMap: Record<FilterKeys, FilterType>) => { @@ -201,7 +201,10 @@ const Filters = forwardRef(function Filters<T extends Record<string, unknown>>( const query = createQueryString(newFiltersOrder, map) debug('Updating URL with query: %s', query) - updateURL(new URLSearchParams(query)) + + if (query) { + updateURL(new URLSearchParams(query)) + } // remove setVisibleFilters initialFiltersRef.current = map diff --git a/packages/ui/src/context/router-context.tsx b/packages/ui/src/context/router-context.tsx index d19fe2f10e..f53e5c2c80 100644 --- a/packages/ui/src/context/router-context.tsx +++ b/packages/ui/src/context/router-context.tsx @@ -6,9 +6,12 @@ import type { NavLinkProps, OutletProps, Params, + SetURLSearchParams, UIMatch } from 'react-router-dom' +import { noop } from 'lodash-es' + import { RouterContextProvider as FiltersRouterContextProvider } from '@harnessio/filters' const resolveTo = (to: LinkProps['to']) => (typeof to === 'string' ? to : to.pathname || '/') @@ -54,7 +57,7 @@ const navigateFnDefault: NavigateFunction = to => { } const useSearchParamsDefault = () => { - const setSearchParams = (_params: URLSearchParams | ((currentParams: URLSearchParams) => URLSearchParams)): void => {} + const setSearchParams: SetURLSearchParams = noop return [new URLSearchParams(), setSearchParams] as const } diff --git a/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx b/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx index b8fa9d0442..7b0ab4536e 100644 --- a/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx +++ b/packages/ui/src/views/repo/pull-request/components/labels/labels-filter.tsx @@ -147,8 +147,8 @@ export function getParserConfig() { return result }, - serialize: (value: LabelsValue): string => { - const parts = Object.entries(value) + serialize: (value?: LabelsValue): string => { + const parts = Object.entries(value ?? {}) .map(([key, val]) => { if (!val) return '' return `${key}:${val}` From c7fdc54d5c7e29246e813d72118295204a7ec8c0 Mon Sep 17 00:00:00 2001 From: Vardan Bansal <vardan.bansal@harness.io> Date: Fri, 22 Aug 2025 21:16:59 +0000 Subject: [PATCH 180/180] fix: Show "Edit" and "Delete" comment options on PR conversation page (#10277) * 3b965d remove current user check --- .../details/components/conversation/regular-and-code-comment.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx index 299dabef5c..fbeffd1ca5 100644 --- a/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx +++ b/packages/ui/src/views/repo/pull-request/details/components/conversation/regular-and-code-comment.tsx @@ -113,7 +113,6 @@ const BaseComp: FC<BaseCompProps> = ({ currentUser={currentUser?.display_name} toggleConversationStatus={toggleConversationStatus} parentCommentId={payload?.id} - hideEditDelete={payload?.author?.uid !== currentUser?.uid} payload={payload} header={[ {