mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
Consolidate buttons and related components (#6787)
SegmentedControl, Chip, ChipFlow. Bring in the portal dark mode theme and other small fixes to issues I found during testing
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
declare module "*.css" {}
|
||||
@@ -14,7 +14,7 @@ void React;
|
||||
|
||||
import { TierProvider, type Tier } from "@portal/contexts/TierContext";
|
||||
import { LinkProvider, type LinkState } from "@portal/contexts/LinkContext";
|
||||
import { ThemeProvider } from "@portal/contexts/ThemeContext";
|
||||
import { ThemeProvider, useTheme } from "@portal/contexts/ThemeContext";
|
||||
import { UIProvider } from "@portal/contexts/UIContext";
|
||||
import { SuiProvider } from "@portal/theme/SuiProvider";
|
||||
import { handlers } from "@portal/mocks/handlers";
|
||||
@@ -78,13 +78,21 @@ function TierKey({
|
||||
);
|
||||
}
|
||||
|
||||
/** Keeps useTheme() and the data-theme attribute in sync. */
|
||||
function ThemeWatcher() {
|
||||
/**
|
||||
* Makes the Storybook toolbar the SINGLE source of truth for the theme.
|
||||
*/
|
||||
function ThemeBridge({
|
||||
theme,
|
||||
children,
|
||||
}: {
|
||||
theme: "light" | "dark";
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { setTheme } = useTheme();
|
||||
useEffect(() => {
|
||||
// The addon-themes decorator already sets data-theme on <html>.
|
||||
// We just read it on mount so ThemeProvider picks it up.
|
||||
}, []);
|
||||
return null;
|
||||
setTheme(theme);
|
||||
}, [theme, setTheme]);
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const withProviders: Decorator = (Story, context) => {
|
||||
@@ -101,20 +109,21 @@ const withProviders: Decorator = (Story, context) => {
|
||||
return (
|
||||
<MemoryRouter initialEntries={["/"]}>
|
||||
<ThemeProvider>
|
||||
<SuiProvider colorScheme={colorScheme}>
|
||||
{/* LinkProvider must wrap TierProvider: TierContext derives its tier
|
||||
from useLink() (matches App.tsx's nesting). */}
|
||||
<LinkProvider key={linkState} initialState={linkState}>
|
||||
<TierKey tier={tier}>
|
||||
<UIProvider>
|
||||
<ThemeWatcher />
|
||||
<Suspense fallback={null}>
|
||||
<Story />
|
||||
</Suspense>
|
||||
</UIProvider>
|
||||
</TierKey>
|
||||
</LinkProvider>
|
||||
</SuiProvider>
|
||||
<ThemeBridge theme={colorScheme}>
|
||||
<SuiProvider colorScheme={colorScheme}>
|
||||
{/* LinkProvider must wrap TierProvider: TierContext derives its tier
|
||||
from useLink() (matches App.tsx's nesting). */}
|
||||
<LinkProvider key={linkState} initialState={linkState}>
|
||||
<TierKey tier={tier}>
|
||||
<UIProvider>
|
||||
<Suspense fallback={null}>
|
||||
<Story />
|
||||
</Suspense>
|
||||
</UIProvider>
|
||||
</TierKey>
|
||||
</LinkProvider>
|
||||
</SuiProvider>
|
||||
</ThemeBridge>
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"jsx": "react-jsx",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@app/*": [
|
||||
"../editor/src/desktop/*",
|
||||
"../editor/src/proprietary/*",
|
||||
"../editor/src/core/*"
|
||||
],
|
||||
"@core/*": ["../editor/src/core/*"],
|
||||
"@proprietary/*": ["../editor/src/proprietary/*"],
|
||||
"@portal/*": ["../editor/src/portal/*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
}
|
||||
@@ -7,6 +7,7 @@ black = "Black"
|
||||
blue = "Blue"
|
||||
cancel = "Cancel"
|
||||
chooseFile = "Choose File"
|
||||
clear = "Clear"
|
||||
close = "Close"
|
||||
comingSoon = "Coming soon"
|
||||
confirm = "Confirm"
|
||||
@@ -87,6 +88,7 @@ processingCompleteMultiple = "{{count}} files are ready."
|
||||
property = "Property"
|
||||
quickPosition = "Quick Position"
|
||||
red = "Red"
|
||||
remove = "Remove"
|
||||
reset = "Reset"
|
||||
review = "Review"
|
||||
save = "Save"
|
||||
@@ -180,6 +182,7 @@ addMoreFiles = "Add more files..."
|
||||
attachments = "Select Attachments"
|
||||
info = "Select files to attach to your PDF. These files will be embedded and accessible through the PDF's attachment panel."
|
||||
placeholder = "Choose files..."
|
||||
removeFile = "Remove file"
|
||||
selectedFiles = "Selected Files"
|
||||
submit = "Add Attachments"
|
||||
|
||||
@@ -1628,6 +1631,9 @@ title = "Do you want to help make Stirling PDF better?"
|
||||
tags = "annotate,highlight,draw,markup,comment,notes,review,redline,feedback,markup tools,sticky notes,shapes,arrows,text box,freehand"
|
||||
|
||||
[annotation]
|
||||
alignCenter = "Align center"
|
||||
alignLeft = "Align left"
|
||||
alignRight = "Align right"
|
||||
annotationStyle = "Annotation style"
|
||||
backgroundColor = "Background color"
|
||||
borderOff = "Border: Off"
|
||||
@@ -2531,6 +2537,7 @@ title = "Rule of thumb"
|
||||
|
||||
[certSign.source]
|
||||
device = "This device"
|
||||
noOtherSources = "No other certificate sources are available."
|
||||
server = "Server"
|
||||
stepTitle = "Certificate source"
|
||||
upload = "Upload"
|
||||
@@ -3595,7 +3602,9 @@ makeCopy = "Make a copy"
|
||||
mobileShort = "Mobile"
|
||||
mobileUpload = "Mobile Upload"
|
||||
mobileUploadNotAvailable = "Mobile upload not enabled"
|
||||
moreOptions = "More options"
|
||||
myFiles = "My Files"
|
||||
nextFile = "Next file"
|
||||
noFiles = "No files available"
|
||||
noFilesFound = "No files found matching your search"
|
||||
noRecentFiles = "No recent files found"
|
||||
@@ -3605,6 +3614,7 @@ openInFileEditor = "Open in File Editor"
|
||||
openInPageEditor = "Open in Page Editor"
|
||||
owner = "Owner"
|
||||
ownerUnknown = "Unknown"
|
||||
previousFile = "Previous file"
|
||||
recent = "Recent"
|
||||
removeBoth = "Remove from both"
|
||||
removeFilePrompt = "This file is saved on this device and on your server. Where would you like to remove it from?"
|
||||
@@ -4614,10 +4624,10 @@ welcomeTitle = "You've been invited!"
|
||||
[landing]
|
||||
addFiles = "Add Files"
|
||||
heroSubtitle = "Drop in or add an existing PDF to get started."
|
||||
heroTitle = "Stirling PDF"
|
||||
mobileUpload = "Upload from Mobile"
|
||||
openFromComputer = "Open from computer"
|
||||
uploadFromComputer = "Upload from computer"
|
||||
workbenchEmptyStateHero = "Drop a PDF anywhere"
|
||||
|
||||
[language]
|
||||
direction = "ltr"
|
||||
@@ -4930,6 +4940,7 @@ title = "Output"
|
||||
[onboarding]
|
||||
activeFiles = "The <strong>Active Files</strong> view shows all of the PDFs you have loaded into the tool, and allows you to select which ones to process."
|
||||
allTools = "This is the <strong>Tools</strong> panel, where you can browse and select from all available PDF tools."
|
||||
close = "Close"
|
||||
cropSettings = "Now that we've selected the file we want crop, we can configure the Crop tool to choose the area that we want to crop the PDF to."
|
||||
fileCheckbox = "Clicking one of the files selects it for processing. You can select multiple files for batch operations."
|
||||
fileReplacement = "The modified file will replace the original file in the Workbench automatically, allowing you to easily run it through more tools."
|
||||
@@ -4955,6 +4966,7 @@ skipTheTour = "Skip the tour"
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
body = "Stirling works best as a desktop app. You can use it offline, access documents faster, and make edits locally on your computer."
|
||||
selectOs = "Select operating system"
|
||||
title = "Download"
|
||||
titleWithOs = "Download for {{osLabel}}"
|
||||
|
||||
@@ -5566,7 +5578,9 @@ viewLabel = "PDF Editor"
|
||||
|
||||
[pdfTextEditor.actions]
|
||||
applyChanges = "Apply Changes"
|
||||
clearText = "Clear text"
|
||||
downloadCopy = "Download Copy"
|
||||
moreOptions = "More options"
|
||||
reset = "Reset Changes"
|
||||
|
||||
[pdfTextEditor.badges]
|
||||
@@ -9670,7 +9684,9 @@ memberRemoved = "Member removed successfully"
|
||||
namePlaceholder = "Enter team name"
|
||||
personal = "Personal"
|
||||
removeError = "Failed to remove member"
|
||||
renameCancel = "Cancel rename"
|
||||
renameError = "Failed to rename team"
|
||||
renameSubmit = "Save team name"
|
||||
renameSuccess = "Team renamed successfully"
|
||||
|
||||
[team.invitationBanner]
|
||||
@@ -9686,6 +9702,7 @@ sendButton = "Send Invitation"
|
||||
title = "Invite Team Member"
|
||||
|
||||
[team.members]
|
||||
actions = "Member actions"
|
||||
emailColumn = "Email"
|
||||
empty = "No team members yet"
|
||||
nameColumn = "Name"
|
||||
@@ -10041,14 +10058,23 @@ zoomOut = "Zoom Out"
|
||||
[viewer.attachments]
|
||||
addAttachment = "Add attachment"
|
||||
close = "Close attachments"
|
||||
closeSidebar = "Close attachments sidebar"
|
||||
download = "Download attachment"
|
||||
empty = "No attachments in this document"
|
||||
loading = "Loading attachments..."
|
||||
noDocument = "Open a PDF to view its attachments."
|
||||
noMatch = "No attachments match your search"
|
||||
noSupport = "Attachment support is unavailable for this viewer."
|
||||
retry = "Retry"
|
||||
searchPlaceholder = "Search attachments"
|
||||
title = "Attachments"
|
||||
|
||||
[viewer.bookmarks]
|
||||
bookmarkTitle = "Bookmark title"
|
||||
closeSidebar = "Close bookmarks sidebar"
|
||||
collapseAll = "Collapse all bookmarks"
|
||||
expandAll = "Expand all bookmarks"
|
||||
|
||||
[viewer.comments]
|
||||
addComment = "Add comment"
|
||||
addCommentPlaceholder = "Add comment..."
|
||||
@@ -10059,6 +10085,7 @@ clearAll = "Clear all comments"
|
||||
clearAllDescription = "This removes comments and replies from the sidebar while keeping any attached annotations in the document."
|
||||
clearAllTitle = "Clear all comments?"
|
||||
close = "Close comments"
|
||||
closeSidebar = "Close comments sidebar"
|
||||
deleteAnnotationAndComment = "Delete annotation & comment"
|
||||
deleteDescription = "This annotation has a comment attached. You can remove just the comment from the sidebar while keeping the annotation, or delete everything."
|
||||
deleteTitle = "Remove annotation from comments?"
|
||||
@@ -10090,6 +10117,11 @@ title = "Form Fields"
|
||||
unsavedBadge = "Unsaved"
|
||||
unsavedDesc = "You have unsaved changes"
|
||||
|
||||
[viewer.layers]
|
||||
closeSidebar = "Close layers sidebar"
|
||||
hideAll = "Hide all layers"
|
||||
showAll = "Show all layers"
|
||||
|
||||
[viewer.link]
|
||||
delete = "Delete link"
|
||||
|
||||
@@ -10120,6 +10152,9 @@ resultsOf = "of {{total}}"
|
||||
[viewer.signature]
|
||||
delete = "Delete signature"
|
||||
|
||||
[viewer.thumbnails]
|
||||
closeSidebar = "Close thumbnails sidebar"
|
||||
|
||||
[viewPdf]
|
||||
tags = "view,read,annotate,text,image,highlight,edit"
|
||||
title = "View/Edit PDF"
|
||||
@@ -10546,6 +10581,7 @@ loading = "Loading people..."
|
||||
locked = "locked"
|
||||
lockedBadge = "Locked"
|
||||
loginRequired = "Enable login mode first"
|
||||
memberActions = "Member actions"
|
||||
noMembersFound = "No members found"
|
||||
role = "Role"
|
||||
searchMembers = "Search members..."
|
||||
@@ -10555,6 +10591,7 @@ unlockAccount = "Unlock Account"
|
||||
unlockUserError = "Failed to unlock user account"
|
||||
unlockUserSuccess = "User account unlocked successfully"
|
||||
user = "User"
|
||||
userInfo = "User info"
|
||||
|
||||
[workspace.people.actions]
|
||||
upgrade = "Upgrade"
|
||||
@@ -10700,6 +10737,7 @@ removeMemberError = "Failed to remove user from team"
|
||||
removeMemberSuccess = "User removed from team"
|
||||
renameTeamLabel = "Rename Team"
|
||||
system = "System"
|
||||
teamActions = "Team actions"
|
||||
teamName = "Team Name"
|
||||
teamNotFound = "Team not found"
|
||||
title = "Teams"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
import { Button, Group, ActionIcon } from "@mantine/core";
|
||||
import { Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import { TFunction } from "i18next";
|
||||
import {
|
||||
@@ -31,22 +33,6 @@ export function renderButtons({
|
||||
(btn) => btn.group === "right",
|
||||
);
|
||||
|
||||
const buttonStyles = (variant: ButtonDefinition["variant"]) =>
|
||||
variant === "primary"
|
||||
? {
|
||||
root: {
|
||||
background: "var(--onboarding-primary-button-bg)",
|
||||
color: "var(--onboarding-primary-button-text)",
|
||||
},
|
||||
}
|
||||
: {
|
||||
root: {
|
||||
background: "var(--onboarding-secondary-button-bg)",
|
||||
border: "1px solid var(--onboarding-secondary-button-border)",
|
||||
color: "var(--onboarding-secondary-button-text)",
|
||||
},
|
||||
};
|
||||
|
||||
const resolveButtonLabel = (button: ButtonDefinition) => {
|
||||
// Translate the label (it's a translation key)
|
||||
const label = button.label ?? "";
|
||||
@@ -65,20 +51,15 @@ export function renderButtons({
|
||||
<ActionIcon
|
||||
key={button.key}
|
||||
onClick={() => onAction(button.action)}
|
||||
radius="md"
|
||||
size={40}
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
background: "var(--onboarding-secondary-button-bg)",
|
||||
border: "1px solid var(--onboarding-secondary-button-border)",
|
||||
color: "var(--onboarding-secondary-button-text)",
|
||||
},
|
||||
}}
|
||||
aria-label={t("onboarding.buttons.back", "Back")}
|
||||
>
|
||||
{button.icon === "chevron-left" && (
|
||||
{button.icon === "chevron-left" ? (
|
||||
<ChevronLeftIcon fontSize="small" />
|
||||
)}
|
||||
) : null}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
@@ -91,7 +72,8 @@ export function renderButtons({
|
||||
key={button.key}
|
||||
onClick={() => onAction(button.action)}
|
||||
disabled={disabled}
|
||||
styles={buttonStyles(variant)}
|
||||
variant={variant === "primary" ? "primary" : "secondary"}
|
||||
accent="neutral"
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Badge, Button, TextInput } from "@mantine/core";
|
||||
import { Badge, TextInput } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SlideConfig } from "@app/types/types";
|
||||
import { createLightSlideBackground } from "@app/components/onboarding/slides/unifiedBackgroundConfig";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { Modal, Stack, Button } from "@mantine/core";
|
||||
import { Modal, Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CelebrationIcon from "@mui/icons-material/CelebrationOutlined";
|
||||
import AnimatedSlideBackground from "@app/components/onboarding/slides/AnimatedSlideBackground";
|
||||
@@ -169,7 +170,7 @@ export function FreeLimitReachedModal({ onClose }: FreeLimitReachedModalProps) {
|
||||
>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="free-limit-modal-button"
|
||||
style={{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { Modal, Stack, Button } from "@mantine/core";
|
||||
import { Modal, Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TrendingUpIcon from "@mui/icons-material/TrendingUpOutlined";
|
||||
import AnimatedSlideBackground from "@app/components/onboarding/slides/AnimatedSlideBackground";
|
||||
@@ -167,7 +168,7 @@ export function SpendCapReachedModal({ onClose }: SpendCapReachedModalProps) {
|
||||
>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="spend-cap-modal-button"
|
||||
style={{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Group, Text } from "@mantine/core";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { InfoBanner } from "@app/components/shared/InfoBanner";
|
||||
@@ -75,9 +76,9 @@ export function TeamInvitationBanner() {
|
||||
const actionButtons = (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Button
|
||||
variant="white"
|
||||
color="gray"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
onClick={handleAccept}
|
||||
loading={processing}
|
||||
leftSection={
|
||||
@@ -88,17 +89,12 @@ export function TeamInvitationBanner() {
|
||||
style={{ color: "var(--mantine-color-dark-9)" }}
|
||||
/>
|
||||
}
|
||||
styles={{
|
||||
label: {
|
||||
color: "var(--mantine-color-dark-9)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("team.invitationBanner.acceptButton", "Accept")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={handleReject}
|
||||
loading={processing}
|
||||
style={{ color: "rgba(255, 255, 255, 0.7)" }}
|
||||
|
||||
@@ -14,14 +14,13 @@
|
||||
--payg-divider: var(--border-subtle);
|
||||
}
|
||||
|
||||
/* Dark mode: the modal content bg is #2a2f36 and so is --bg-surface, so plain
|
||||
cards vanish. Lift cards a shade above the modal and strengthen borders. */
|
||||
/* Dark mode: modal content bg is #131729 (--bg-surface), so lift cards above it */
|
||||
[data-mantine-color-scheme="dark"] .payg {
|
||||
--payg-card-bg: #313842;
|
||||
--payg-card-border: #3d444e;
|
||||
--payg-inset-bg: #272c33;
|
||||
--payg-accent-soft: rgba(10, 139, 255, 0.16);
|
||||
--payg-divider: #3d444e;
|
||||
--payg-card-bg: #1c2340;
|
||||
--payg-card-border: #2d3560;
|
||||
--payg-inset-bg: #0d1020;
|
||||
--payg-accent-soft: rgba(79, 142, 245, 0.16);
|
||||
--payg-divider: #2d3560;
|
||||
}
|
||||
|
||||
/* ── Header ─────────────────────────────────────────────────────────── */
|
||||
@@ -258,16 +257,16 @@
|
||||
|
||||
/* Dark mode: the stock default button is a flat slab that disappears into the
|
||||
card. Lift it with a soft top-down gradient + brighter border, and warm the
|
||||
hover so the buttons feel tactile against --payg-card-bg (#313842). Driven
|
||||
hover so the buttons feel tactile against --payg-card-bg (#1c2340). Driven
|
||||
through Mantine's --button-* vars. The same rule covers disabled buttons
|
||||
(Cancel/Update cap before the form is dirty) so they read identically to the
|
||||
always-enabled ones — Mantine only fades them via opacity. */
|
||||
[data-mantine-color-scheme="dark"]
|
||||
.payg
|
||||
.mantine-Button-root[data-variant="default"] {
|
||||
--button-bg: linear-gradient(180deg, #3d4651 0%, #353d48 100%);
|
||||
--button-hover: linear-gradient(180deg, #475160 0%, #3d4654 100%);
|
||||
--button-bd: 1px solid #4b5563;
|
||||
--button-bg: linear-gradient(180deg, #232a50 0%, #1c2340 100%);
|
||||
--button-hover: linear-gradient(180deg, #2d3560 0%, #232a50 100%);
|
||||
--button-bd: 1px solid #2d3560;
|
||||
--button-color: var(--mantine-color-white);
|
||||
box-shadow:
|
||||
0 1px 0 rgba(255, 255, 255, 0.04) inset,
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* V1 — so it shows a real empty state, not fabricated rows
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Button, Group, Stack, Text } from "@mantine/core";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useRenderCount } from "@app/hooks/useRenderCount";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import LockIcon from "@mui/icons-material/LockOutlined";
|
||||
@@ -110,16 +111,16 @@ export function DocHelp() {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className="payg-help">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="payg-help__toggle"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
leftSection={<HelpOutlineIcon sx={{ fontSize: 15 }} />}
|
||||
>
|
||||
<HelpOutlineIcon sx={{ fontSize: 15 }} />
|
||||
{t("payg.docHelp.toggle", "What counts as a PDF?")}
|
||||
<ExpandMoreIcon className="payg-help__chevron" sx={{ fontSize: 16 }} />
|
||||
</button>
|
||||
</Button>
|
||||
{open && (
|
||||
<div className="payg-help__panel">
|
||||
<ul>
|
||||
@@ -446,16 +447,16 @@ function CapReachedHelp() {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className="payg-help">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="payg-help__toggle"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
leftSection={<HelpOutlineIcon sx={{ fontSize: 15 }} />}
|
||||
>
|
||||
<HelpOutlineIcon sx={{ fontSize: 15 }} />
|
||||
{t("payg.gates.title", "What happens when the cap is reached")}
|
||||
<ExpandMoreIcon className="payg-help__chevron" sx={{ fontSize: 16 }} />
|
||||
</button>
|
||||
</Button>
|
||||
{open && (
|
||||
<div className="payg-help__panel">
|
||||
<div className="payg-gates">
|
||||
@@ -656,7 +657,7 @@ function StripePortalLink({
|
||||
onClick={handleClick}
|
||||
loading={loading}
|
||||
rightSection={<OpenInNewIcon sx={{ fontSize: 16 }} />}
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
>
|
||||
{t("payg.stripe.open", "Open billing portal")}
|
||||
</Button>
|
||||
|
||||
@@ -217,25 +217,6 @@
|
||||
border-top: 1px solid var(--payg-divider);
|
||||
padding-top: 16px;
|
||||
}
|
||||
.paygf-cta__button {
|
||||
padding: 13px 22px;
|
||||
border: none;
|
||||
border-radius: 11px;
|
||||
background: linear-gradient(135deg, var(--payg-accent) 0%, #6c5ce7 100%);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 120ms ease,
|
||||
box-shadow 120ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.paygf-cta__button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 22px -6px rgba(10, 139, 255, 0.55);
|
||||
}
|
||||
.paygf-cta__reassurance {
|
||||
margin: 0;
|
||||
font-size: 0.78rem;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import BoltIcon from "@mui/icons-material/BoltRounded";
|
||||
import AllInclusiveIcon from "@mui/icons-material/AllInclusiveRounded";
|
||||
import CheckIcon from "@mui/icons-material/CheckRounded";
|
||||
@@ -171,14 +172,14 @@ function ProcessorCard({ snap, isLeader, onTurnOn }: ProcessorCardProps) {
|
||||
<FreeMeterPanel snap={snap} />
|
||||
{isLeader ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="paygf-cta__button paygf-proc__cta"
|
||||
<Button
|
||||
fullWidth
|
||||
className="paygf-proc__cta"
|
||||
onClick={onTurnOn}
|
||||
data-testid="turn-on-processor"
|
||||
>
|
||||
{t("payg.free.cta.button", "Turn on Processor →")}
|
||||
</button>
|
||||
</Button>
|
||||
<span className="paygf-cta__reassurance paygf-proc__reassure">
|
||||
{t(
|
||||
"payg.free.cta.reassurance",
|
||||
|
||||
+4
-4
@@ -22,10 +22,10 @@
|
||||
gap: 14px;
|
||||
}
|
||||
[data-mantine-color-scheme="dark"] .scc {
|
||||
--scc-accent-text: #66b8ff;
|
||||
--scc-accent-soft: rgba(10, 139, 255, 0.16);
|
||||
--scc-chip-bg: #272d35;
|
||||
--scc-chip-border: #3d444e;
|
||||
--scc-accent-text: #7ab4ff;
|
||||
--scc-accent-soft: rgba(79, 142, 245, 0.16);
|
||||
--scc-chip-bg: #1c2340;
|
||||
--scc-chip-border: #2d3560;
|
||||
}
|
||||
|
||||
/* ── Inline row: presets · custom · no-cap · (save) ──────────────────── */
|
||||
|
||||
+3
-7
@@ -64,6 +64,7 @@
|
||||
*/
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useAuth } from "@app/auth/UseSession";
|
||||
import {
|
||||
createCheckoutSession,
|
||||
@@ -267,17 +268,12 @@ const StripeCheckoutPanel: React.FC<StripeCheckoutPanelProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="upm-btn"
|
||||
data-variant="primary"
|
||||
onClick={onComplete}
|
||||
>
|
||||
<Button onClick={onComplete}>
|
||||
{t(
|
||||
"payg.checkout.mock.continue",
|
||||
"Continue with mock subscription",
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Group,
|
||||
Text,
|
||||
@@ -8,9 +7,10 @@ import {
|
||||
Alert,
|
||||
Table,
|
||||
Badge,
|
||||
ActionIcon,
|
||||
Menu,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSaaSTeam } from "@app/contexts/SaaSTeamContext";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
@@ -233,19 +233,18 @@ const TeamSection: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
color="blue"
|
||||
onClick={handleRenameSubmit}
|
||||
loading={renamingTeam}
|
||||
disabled={!newTeamName.trim()}
|
||||
aria-label={t("team.renameSubmit", "Save team name")}
|
||||
>
|
||||
<LocalIcon icon="check" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
variant="tertiary"
|
||||
onClick={handleCancelRename}
|
||||
disabled={renamingTeam}
|
||||
aria-label={t("team.renameCancel", "Cancel rename")}
|
||||
>
|
||||
<LocalIcon icon="close" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
@@ -257,7 +256,7 @@ const TeamSection: React.FC = () => {
|
||||
</Text>
|
||||
{isTeamLeader && !isPersonalTeam && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={handleStartRename}
|
||||
aria-label={t("team.editName", "Edit team name")}
|
||||
@@ -285,9 +284,9 @@ const TeamSection: React.FC = () => {
|
||||
</div>
|
||||
{!isPersonalTeam && !isTeamLeader && !isEditingName && (
|
||||
<Button
|
||||
color="red"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
accent="danger"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleLeaveTeam}
|
||||
leftSection={
|
||||
<LocalIcon icon="logout" width="1rem" height="1rem" />
|
||||
@@ -449,7 +448,13 @@ const TeamSection: React.FC = () => {
|
||||
zIndex={Z_INDEX_OVER_CONFIG_MODAL}
|
||||
>
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="subtle">
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t(
|
||||
"team.members.actions",
|
||||
"Member actions",
|
||||
)}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="more-vert"
|
||||
width="1rem"
|
||||
@@ -504,8 +509,8 @@ const TeamSection: React.FC = () => {
|
||||
{isTeamLeader && !isPersonalTeam && (
|
||||
<Table.Td>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
variant="tertiary"
|
||||
accent="danger"
|
||||
onClick={() =>
|
||||
handleCancelInvitation(
|
||||
invitation.invitationId,
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
--upm-muted: var(--text-muted);
|
||||
}
|
||||
[data-mantine-color-scheme="dark"] .upm {
|
||||
--upm-card-bg: #313842;
|
||||
--upm-border: #3d444e;
|
||||
--upm-divider: #3d444e;
|
||||
--upm-accent-soft: rgba(10, 139, 255, 0.14);
|
||||
--upm-card-bg: #1c2340;
|
||||
--upm-border: #2d3560;
|
||||
--upm-divider: #2d3560;
|
||||
--upm-accent-soft: rgba(79, 142, 245, 0.14);
|
||||
}
|
||||
|
||||
/* Backdrop locks the page and centres the modal. z-index matches
|
||||
@@ -101,26 +101,6 @@
|
||||
color: var(--upm-text);
|
||||
margin: 0;
|
||||
}
|
||||
.upm-header__close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--upm-muted);
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition:
|
||||
background 120ms ease,
|
||||
color 120ms ease;
|
||||
}
|
||||
.upm-header__close:hover {
|
||||
background: var(--upm-divider);
|
||||
color: var(--upm-text);
|
||||
}
|
||||
|
||||
/* Left cluster: optional back arrow + title. The back arrow only renders on the
|
||||
checkout step (cap/confirm have no parent step to return to) — it replaces the
|
||||
old footer "← Back" button. */
|
||||
@@ -130,28 +110,6 @@
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.upm-header__back {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--upm-muted);
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-left: -6px;
|
||||
transition:
|
||||
background 120ms ease,
|
||||
color 120ms ease;
|
||||
}
|
||||
.upm-header__back:hover {
|
||||
background: var(--upm-divider);
|
||||
color: var(--upm-text);
|
||||
}
|
||||
|
||||
.upm-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -357,41 +315,6 @@
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.upm-btn {
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
border: 1.5px solid transparent;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: all 120ms ease;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.upm-btn[data-variant="ghost"] {
|
||||
background: transparent;
|
||||
border-color: var(--upm-divider);
|
||||
color: var(--upm-text);
|
||||
}
|
||||
.upm-btn[data-variant="ghost"]:hover {
|
||||
border-color: var(--upm-accent);
|
||||
color: var(--upm-accent);
|
||||
}
|
||||
.upm-btn[data-variant="primary"] {
|
||||
background: linear-gradient(135deg, var(--upm-accent), var(--upm-accent-2));
|
||||
color: white;
|
||||
}
|
||||
.upm-btn[data-variant="primary"]:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 18px -6px rgba(10, 139, 255, 0.5);
|
||||
}
|
||||
.upm-btn[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* ── Step 3: confirmation ─────────────────────────────────────────────── */
|
||||
|
||||
.upm-confirm {
|
||||
|
||||
+18
-30
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
import React, { Suspense, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import CloseIcon from "@mui/icons-material/CloseRounded";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import ShieldIcon from "@mui/icons-material/ShieldOutlined";
|
||||
@@ -152,14 +154,14 @@ export default function UpgradeModal({
|
||||
<header className="upm-header">
|
||||
<div className="upm-header__left">
|
||||
{step === "checkout" && (
|
||||
<button
|
||||
type="button"
|
||||
className="upm-header__back"
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("payg.upgrade.backAria", "Back")}
|
||||
onClick={goBackToCap}
|
||||
style={{ marginLeft: -6 }}
|
||||
>
|
||||
<ArrowBackIcon fontSize="small" />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
)}
|
||||
<h2 className="upm-header__title">
|
||||
{step === "confirm"
|
||||
@@ -170,14 +172,13 @@ export default function UpgradeModal({
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="upm-header__close"
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
aria-label={t("payg.upgrade.closeAria", "Close")}
|
||||
onClick={closeAndReset}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
</header>
|
||||
|
||||
{/* Step indicator. Hidden on the confirmation panel since the
|
||||
@@ -198,13 +199,13 @@ export default function UpgradeModal({
|
||||
"{{symbol}}{{amount}} / month",
|
||||
{ symbol: sym, amount: effectiveCap },
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="upm-step__edit"
|
||||
onClick={goBackToCap}
|
||||
>
|
||||
{t("payg.upgrade.checkout.edit", "Edit")}
|
||||
</button>
|
||||
</Button>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
@@ -264,36 +265,23 @@ export default function UpgradeModal({
|
||||
<div className="upm-footer__actions">
|
||||
{step === "cap" && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="upm-btn"
|
||||
data-variant="ghost"
|
||||
onClick={closeAndReset}
|
||||
>
|
||||
<Button variant="secondary" onClick={closeAndReset}>
|
||||
{t("payg.upgrade.button.cancel", "Cancel")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="upm-btn"
|
||||
data-variant="primary"
|
||||
onClick={goToCheckout}
|
||||
>
|
||||
</Button>
|
||||
<Button onClick={goToCheckout}>
|
||||
{t("payg.upgrade.button.continue", "Continue →")}
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{step === "confirm" && (
|
||||
<button
|
||||
type="button"
|
||||
className="upm-btn"
|
||||
data-variant="primary"
|
||||
<Button
|
||||
onClick={() => {
|
||||
setStep("cap");
|
||||
onComplete({ capUsd: effectiveCap });
|
||||
}}
|
||||
>
|
||||
{t("payg.upgrade.button.finish", "Finish")}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.5 3.3125C16.5712 3.3125 14.6613 3.6924 12.8793 4.43052C11.0974 5.16864 9.47823 6.25051 8.11437 7.61437C5.35993 10.3688 3.8125 14.1046 3.8125 18C3.8125 24.4919 8.02781 29.9997 13.8588 31.9531C14.5931 32.0706 14.8281 31.6153 14.8281 31.2187V28.7366C10.7597 29.6178 9.89312 26.7684 9.89312 26.7684C9.2175 25.0647 8.26281 24.6094 8.26281 24.6094C6.92625 23.6987 8.36562 23.7281 8.36562 23.7281C9.83437 23.8309 10.6128 25.2409 10.6128 25.2409C11.8906 27.4734 14.0497 26.8125 14.8869 26.46C15.0191 25.5053 15.4009 24.8591 15.8122 24.4919C12.5516 24.1247 9.12937 22.8616 9.12937 17.2656C9.12937 15.6353 9.6875 14.3281 10.6422 13.2853C10.4953 12.9181 9.98125 11.3906 10.7891 9.40781C10.7891 9.40781 12.0228 9.01125 14.8281 10.9059C15.9884 10.5828 17.2516 10.4212 18.5 10.4212C19.7484 10.4212 21.0116 10.5828 22.1719 10.9059C24.9772 9.01125 26.2109 9.40781 26.2109 9.40781C27.0188 11.3906 26.5047 12.9181 26.3578 13.2853C27.3125 14.3281 27.8706 15.6353 27.8706 17.2656C27.8706 22.8762 24.4338 24.11 21.1584 24.4772C21.6872 24.9325 22.1719 25.8284 22.1719 27.1944V31.2187C22.1719 31.6153 22.4069 32.0853 23.1559 31.9531C28.9869 29.985 33.1875 24.4919 33.1875 18C33.1875 16.0712 32.8076 14.1613 32.0695 12.3793C31.3314 10.5974 30.2495 8.97823 28.8856 7.61437C27.5218 6.25051 25.9026 5.16864 24.1207 4.43052C22.3387 3.6924 20.4288 3.3125 18.5 3.3125Z" fill="black"/>
|
||||
<path d="M18.5 3.3125C16.5712 3.3125 14.6613 3.6924 12.8793 4.43052C11.0974 5.16864 9.47823 6.25051 8.11437 7.61437C5.35993 10.3688 3.8125 14.1046 3.8125 18C3.8125 24.4919 8.02781 29.9997 13.8588 31.9531C14.5931 32.0706 14.8281 31.6153 14.8281 31.2187V28.7366C10.7597 29.6178 9.89312 26.7684 9.89312 26.7684C9.2175 25.0647 8.26281 24.6094 8.26281 24.6094C6.92625 23.6987 8.36562 23.7281 8.36562 23.7281C9.83437 23.8309 10.6128 25.2409 10.6128 25.2409C11.8906 27.4734 14.0497 26.8125 14.8869 26.46C15.0191 25.5053 15.4009 24.8591 15.8122 24.4919C12.5516 24.1247 9.12937 22.8616 9.12937 17.2656C9.12937 15.6353 9.6875 14.3281 10.6422 13.2853C10.4953 12.9181 9.98125 11.3906 10.7891 9.40781C10.7891 9.40781 12.0228 9.01125 14.8281 10.9059C15.9884 10.5828 17.2516 10.4212 18.5 10.4212C19.7484 10.4212 21.0116 10.5828 22.1719 10.9059C24.9772 9.01125 26.2109 9.40781 26.2109 9.40781C27.0188 11.3906 26.5047 12.9181 26.3578 13.2853C27.3125 14.3281 27.8706 15.6353 27.8706 17.2656C27.8706 22.8762 24.4338 24.11 21.1584 24.4772C21.6872 24.9325 22.1719 25.8284 22.1719 27.1944V31.2187C22.1719 31.6153 22.4069 32.0853 23.1559 31.9531C28.9869 29.985 33.1875 24.4919 33.1875 18C33.1875 16.0712 32.8076 14.1613 32.0695 12.3793C31.3314 10.5974 30.2495 8.97823 28.8856 7.61437C27.5218 6.25051 25.9026 5.16864 24.1207 4.43052C22.3387 3.6924 20.4288 3.3125 18.5 3.3125Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Card, Group, Text, Button, Progress } from "@mantine/core";
|
||||
import { Card, Group, Text, Progress } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import StorageIcon from "@mui/icons-material/Storage";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
@@ -58,21 +59,16 @@ const StorageStatsCard: React.FC<StorageStatsCardProps> = ({
|
||||
<Group gap="xs">
|
||||
{filesCount > 0 && (
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
onClick={onClearAll}
|
||||
leftSection={<DeleteIcon style={{ fontSize: 16 }} />}
|
||||
>
|
||||
{t("fileManager.clearAll", "Clear All")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
size="xs"
|
||||
onClick={onReloadFiles}
|
||||
>
|
||||
<Button variant="secondary" size="sm" onClick={onReloadFiles}>
|
||||
Reload Files
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
Popover,
|
||||
Stack,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ColorizeIcon from "@mui/icons-material/Colorize";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
|
||||
// safari and firefox do not support the eye dropper API, only edge, chrome and opera do.
|
||||
// the button is hidden in the UI if the API is not supported.
|
||||
@@ -66,24 +66,12 @@ export function ColorControl({
|
||||
<Popover.Target>
|
||||
<Tooltip label={label}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={label}
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
size="md"
|
||||
onClick={() => setOpened(!opened)}
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
backgroundColor: "var(--bg-raised)",
|
||||
border: "1px solid var(--border-default)",
|
||||
color: "var(--text-secondary)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--hover-bg)",
|
||||
borderColor: "var(--border-strong)",
|
||||
color: "var(--text-primary)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ColorSwatch color={localColor} size={18} />
|
||||
</ActionIcon>
|
||||
@@ -117,11 +105,14 @@ export function ColorControl({
|
||||
label={t("color.eyeDropper.tooltip", "Pick colour from screen")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t(
|
||||
"color.eyeDropper.tooltip",
|
||||
"Pick colour from screen",
|
||||
)}
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
onClick={handleEyeDropper}
|
||||
style={{ color: "var(--text-primary)" }}
|
||||
>
|
||||
<ColorizeIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -4,13 +4,12 @@ import {
|
||||
Stack,
|
||||
ColorPicker as MantineColorPicker,
|
||||
Group,
|
||||
Button,
|
||||
ColorSwatch,
|
||||
Slider,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@app/ui/Button";
|
||||
interface ColorPickerProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Paper, Button, Modal, Stack, Text, Group } from "@mantine/core";
|
||||
import { Paper, Modal, Stack, Text, Group } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ColorSwatchButton } from "@app/components/annotation/shared/ColorPicker";
|
||||
import PenSizeSelector from "@app/components/tools/sign/PenSizeSelector";
|
||||
import SignaturePad from "signature_pad";
|
||||
@@ -331,7 +332,7 @@ export const DrawingCanvas: React.FC<DrawingCanvasProps> = ({
|
||||
</PrivateContent>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Button variant="subtle" color="red" onClick={clear}>
|
||||
<Button variant="tertiary" accent="danger" onClick={clear}>
|
||||
{t("sign.canvas.clear", "Clear canvas")}
|
||||
</Button>
|
||||
<Button onClick={closeModal}>{t("common.done", "Done")}</Button>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
import { Group, Button, ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { Group, Tooltip } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LocalIcon } from "@app/components/shared/LocalIcon";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
|
||||
interface DrawingControlsProps {
|
||||
onUndo?: () => void;
|
||||
@@ -37,12 +39,11 @@ export const DrawingControls: React.FC<DrawingControlsProps> = ({
|
||||
{onUndo && (
|
||||
<Tooltip label={t("sign.undo", "Undo")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
aria-label={t("sign.undo", "Undo")}
|
||||
onClick={onUndo}
|
||||
disabled={undoDisabled}
|
||||
color={undoDisabled ? "gray" : "blue"}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="undo"
|
||||
@@ -56,12 +57,11 @@ export const DrawingControls: React.FC<DrawingControlsProps> = ({
|
||||
{onRedo && (
|
||||
<Tooltip label={t("sign.redo", "Redo")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
aria-label={t("sign.redo", "Redo")}
|
||||
onClick={onRedo}
|
||||
disabled={redoDisabled}
|
||||
color={redoDisabled ? "gray" : "blue"}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="redo"
|
||||
@@ -78,11 +78,9 @@ export const DrawingControls: React.FC<DrawingControlsProps> = ({
|
||||
{/* Place Signature Button */}
|
||||
{showPlaceButton && onPlaceSignature && (
|
||||
<Button
|
||||
variant="filled"
|
||||
color="blue"
|
||||
onClick={onPlaceSignature}
|
||||
disabled={disabled || !hasSignatureData}
|
||||
ml="auto"
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
{placeButtonText}
|
||||
</Button>
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
Popover,
|
||||
Stack,
|
||||
Slider,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Tooltip, Popover, Stack, Slider, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import OpacityIcon from "@mui/icons-material/Opacity";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
|
||||
interface OpacityControlProps {
|
||||
value: number; // 0-100
|
||||
@@ -29,24 +23,12 @@ export function OpacityControl({
|
||||
<Popover.Target>
|
||||
<Tooltip label={t("annotation.opacity", "Opacity")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("annotation.opacity", "Opacity")}
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
size="md"
|
||||
onClick={() => setOpened(!opened)}
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
backgroundColor: "var(--bg-raised)",
|
||||
border: "1px solid var(--border-default)",
|
||||
color: "var(--text-secondary)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--hover-bg)",
|
||||
borderColor: "var(--border-strong)",
|
||||
color: "var(--text-primary)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<OpacityIcon style={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
Popover,
|
||||
Stack,
|
||||
Slider,
|
||||
Text,
|
||||
Group,
|
||||
Button,
|
||||
} from "@mantine/core";
|
||||
import { Tooltip, Popover, Stack, Slider, Text, Group } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useState } from "react";
|
||||
import type { TrackedAnnotation } from "@embedpdf/plugin-annotation";
|
||||
import type { PdfAnnotationObject } from "@embedpdf/models";
|
||||
@@ -106,21 +99,24 @@ export function PropertiesPopover({
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant={currentAlign === "left" ? "filled" : "default"}
|
||||
aria-label={t("annotation.alignLeft", "Align left")}
|
||||
variant={currentAlign === "left" ? "primary" : "secondary"}
|
||||
onClick={() => onUpdate({ textAlign: 0 })}
|
||||
size="md"
|
||||
>
|
||||
<FormatAlignLeftIcon style={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={currentAlign === "center" ? "filled" : "default"}
|
||||
aria-label={t("annotation.alignCenter", "Align center")}
|
||||
variant={currentAlign === "center" ? "primary" : "secondary"}
|
||||
onClick={() => onUpdate({ textAlign: 1 })}
|
||||
size="md"
|
||||
>
|
||||
<FormatAlignCenterIcon style={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={currentAlign === "right" ? "filled" : "default"}
|
||||
aria-label={t("annotation.alignRight", "Align right")}
|
||||
variant={currentAlign === "right" ? "primary" : "secondary"}
|
||||
onClick={() => onUpdate({ textAlign: 2 })}
|
||||
size="md"
|
||||
>
|
||||
@@ -176,8 +172,8 @@ export function PropertiesPopover({
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
variant={!borderVisible ? "filled" : "light"}
|
||||
size="sm"
|
||||
variant={!borderVisible ? "primary" : "secondary"}
|
||||
onClick={() => {
|
||||
const newValue = borderVisible ? 0 : 1;
|
||||
onUpdate({
|
||||
@@ -201,24 +197,12 @@ export function PropertiesPopover({
|
||||
<Popover.Target>
|
||||
<Tooltip label={t("annotation.properties", "Properties")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("annotation.properties", "Properties")}
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
size="md"
|
||||
onClick={() => setOpened(!opened)}
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
backgroundColor: "var(--bg-raised)",
|
||||
border: "1px solid var(--border-default)",
|
||||
color: "var(--text-secondary)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--hover-bg)",
|
||||
borderColor: "var(--border-strong)",
|
||||
color: "var(--text-primary)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TuneIcon style={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
useCombobox,
|
||||
Group,
|
||||
Box,
|
||||
SegmentedControl,
|
||||
} from "@mantine/core";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ColorPicker } from "@app/components/annotation/shared/ColorPicker";
|
||||
|
||||
@@ -253,14 +253,14 @@ export const TextInputWithFont: React.FC<TextInputWithFontProps> = ({
|
||||
|
||||
{/* Text Alignment */}
|
||||
{onTextAlignChange && (
|
||||
<SegmentedControl
|
||||
<SegmentedControl<"left" | "center" | "right">
|
||||
value={textAlign}
|
||||
onChange={(value: string) => {
|
||||
onTextAlignChange(value as "left" | "center" | "right");
|
||||
onChange={(value) => {
|
||||
onTextAlignChange(value);
|
||||
onAnyChange?.();
|
||||
}}
|
||||
disabled={disabled}
|
||||
data={[
|
||||
loading={disabled}
|
||||
options={[
|
||||
{ label: t("textAlign.left", "Left"), value: "left" },
|
||||
{ label: t("textAlign.center", "Center"), value: "center" },
|
||||
{ label: t("textAlign.right", "Right"), value: "right" },
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
Popover,
|
||||
Stack,
|
||||
Slider,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Tooltip, Popover, Stack, Slider, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import LineWeightIcon from "@mui/icons-material/LineWeight";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
|
||||
interface WidthControlProps {
|
||||
value: number;
|
||||
@@ -33,24 +27,12 @@ export function WidthControl({
|
||||
<Popover.Target>
|
||||
<Tooltip label={t("annotation.width", "Width")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("annotation.width", "Width")}
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
size="md"
|
||||
onClick={() => setOpened(!opened)}
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
backgroundColor: "var(--bg-raised)",
|
||||
border: "1px solid var(--border-default)",
|
||||
color: "var(--text-secondary)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--hover-bg)",
|
||||
borderColor: "var(--border-strong)",
|
||||
color: "var(--text-primary)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LineWeightIcon style={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Button, Group } from "@mantine/core";
|
||||
import { Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
@@ -106,6 +107,7 @@ const AddFileCard = ({
|
||||
>
|
||||
{!isUploadHover && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
style={{
|
||||
backgroundColor: "var(--landing-button-bg)",
|
||||
color: "var(--landing-button-color)",
|
||||
@@ -133,6 +135,7 @@ const AddFileCard = ({
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label={t("addFileCard.upload", "Upload")}
|
||||
title={terminology.uploadFromComputer}
|
||||
style={{
|
||||
|
||||
@@ -291,15 +291,15 @@
|
||||
DARK MODE OVERRIDES
|
||||
========================= */
|
||||
:global([data-mantine-color-scheme="dark"]) .card {
|
||||
outline-color: #3a4047; /* deselected stroke */
|
||||
outline-color: #1c2340; /* deselected stroke */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .card[data-selected="true"] {
|
||||
outline-color: #4b525a; /* selected stroke (subtle grey) */
|
||||
outline-color: #2d3560; /* selected stroke (subtle navy) */
|
||||
}
|
||||
|
||||
:global([data-mantine-color-scheme="dark"]) .headerResting {
|
||||
background: #1f2329; /* requested default unselected color */
|
||||
background: #0d1020; /* requested default unselected color */
|
||||
color: var(--tool-header-text); /* #D0D6DC */
|
||||
border-bottom-color: var(--tool-header-border); /* #3A4047 */
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import React, { useState, useCallback, useRef, useMemo } from "react";
|
||||
import {
|
||||
Text,
|
||||
Modal,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { Text, Modal, Group, Stack, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import { alert } from "@app/components/toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -545,9 +539,12 @@ const FileEditorThumbnail = ({
|
||||
)}
|
||||
>
|
||||
<ActionIcon
|
||||
size="xs"
|
||||
variant="filled"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
accent="warning"
|
||||
aria-label={t(
|
||||
"encryptedPdfUnlock.unlockPrompt",
|
||||
"Unlock PDF to continue",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEncryptedUnlockPrompt(file.id);
|
||||
@@ -606,17 +603,13 @@ const FileEditorThumbnail = ({
|
||||
<PrivateContent>{file.name}</PrivateContent>
|
||||
</Text>
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="light" onClick={handleCancelClose}>
|
||||
<Button variant="secondary" onClick={handleCancelClose}>
|
||||
{t("confirmCloseCancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="red"
|
||||
onClick={handleConfirmClose}
|
||||
>
|
||||
<Button accent="danger" onClick={handleConfirmClose}>
|
||||
{t("confirmCloseDiscard", "Discard changes and close")}
|
||||
</Button>
|
||||
<Button variant="filled" onClick={handleSaveAndClose}>
|
||||
<Button onClick={handleSaveAndClose}>
|
||||
{t("confirmCloseSave", "Save and close")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -633,14 +626,10 @@ const FileEditorThumbnail = ({
|
||||
<PrivateContent>{file.name}</PrivateContent>
|
||||
</Text>
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="light" onClick={handleCancelClose}>
|
||||
<Button variant="secondary" onClick={handleCancelClose}>
|
||||
{t("confirmCloseCancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="red"
|
||||
onClick={handleConfirmClose}
|
||||
>
|
||||
<Button accent="danger" onClick={handleConfirmClose}>
|
||||
{t("confirmCloseConfirm", "Close File")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
import { Stack, Box, Text, Button, ActionIcon, Center } from "@mantine/core";
|
||||
import { Stack, Box, Text, Center } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
@@ -128,18 +130,20 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
{hasMultipleFiles && (
|
||||
<Box style={{ display: "flex", gap: "0.25rem" }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={onPrevious}
|
||||
disabled={isAnimating}
|
||||
aria-label={t("fileManager.previousFile", "Previous file")}
|
||||
>
|
||||
<ChevronLeftIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={onNext}
|
||||
disabled={isAnimating}
|
||||
aria-label={t("fileManager.nextFile", "Next file")}
|
||||
>
|
||||
<ChevronRightIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
@@ -150,16 +154,10 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
{/* Action Button */}
|
||||
<Button
|
||||
size="sm"
|
||||
accent="neutral"
|
||||
onClick={onOpenFiles}
|
||||
disabled={!hasSelection && !canCloseAll}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor:
|
||||
hasSelection || canCloseAll
|
||||
? "var(--btn-open-file)"
|
||||
: "var(--mantine-color-gray-4)",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{canCloseAll
|
||||
? t("fileManager.closeAllFiles", "Close all files")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Group, Text, Stack } from "@mantine/core";
|
||||
import { Group, Text, Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
@@ -77,6 +78,7 @@ const EmptyFilesState: React.FC = () => {
|
||||
onMouseLeave={() => setIsUploadHover(false)}
|
||||
>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label={t("emptyFilesState.upload", "Upload")}
|
||||
style={{
|
||||
backgroundColor: "var(--bg-file-manager)",
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
Group,
|
||||
Text,
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
SegmentedControl,
|
||||
} from "@mantine/core";
|
||||
import { Group, Text, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
@@ -131,26 +127,29 @@ const FileActions: React.FC = () => {
|
||||
}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={handleSelectAll}
|
||||
disabled={filteredFiles.length === 0}
|
||||
radius="sm"
|
||||
aria-label={
|
||||
allFilesSelected
|
||||
? t("fileManager.deselectAll", "Deselect All")
|
||||
: t("fileManager.selectAll", "Select All")
|
||||
}
|
||||
>
|
||||
<SelectAllIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{showStorageFilter && (
|
||||
<SegmentedControl
|
||||
size="xs"
|
||||
size="sm"
|
||||
value={storageFilter}
|
||||
onChange={(value) =>
|
||||
onStorageFilterChange(
|
||||
value as "all" | "local" | "sharedWithMe" | "sharedByMe",
|
||||
)
|
||||
}
|
||||
data={storageFilterOptions}
|
||||
options={storageFilterOptions}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
@@ -177,12 +176,11 @@ const FileActions: React.FC = () => {
|
||||
{uploadEnabled && (
|
||||
<Tooltip label={t("fileManager.uploadSelected", "Upload Selected")}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={() => setShowBulkUploadModal(true)}
|
||||
disabled={!canBulkUpload}
|
||||
radius="sm"
|
||||
aria-label={t("fileManager.uploadSelected", "Upload Selected")}
|
||||
>
|
||||
<CloudUploadIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
@@ -191,12 +189,11 @@ const FileActions: React.FC = () => {
|
||||
{shareLinksEnabled && (
|
||||
<Tooltip label={t("fileManager.shareSelected", "Share Selected")}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={() => setShowBulkShareModal(true)}
|
||||
disabled={!canBulkShare}
|
||||
radius="sm"
|
||||
aria-label={t("fileManager.shareSelected", "Share Selected")}
|
||||
>
|
||||
<LinkIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
@@ -204,12 +201,12 @@ const FileActions: React.FC = () => {
|
||||
)}
|
||||
<Tooltip label={t("fileManager.deleteSelected", "Delete Selected")}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
accent="danger"
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={!hasSelection}
|
||||
radius="sm"
|
||||
aria-label={t("fileManager.deleteSelected", "Delete Selected")}
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
@@ -217,12 +214,11 @@ const FileActions: React.FC = () => {
|
||||
|
||||
<Tooltip label={terminology.downloadSelected}>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
color="dimmed"
|
||||
onClick={handleDownloadSelected}
|
||||
disabled={!hasSelection || !hasDownloadAccess}
|
||||
radius="sm"
|
||||
aria-label={terminology.downloadSelected}
|
||||
>
|
||||
<DownloadIcon style={{ fontSize: "1rem" }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Stack, Button, Box } from "@mantine/core";
|
||||
import { Stack, Box } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIndexedDBThumbnail } from "@app/hooks/useIndexedDBThumbnail";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
@@ -108,16 +109,10 @@ const FileDetails: React.FC<FileDetailsProps> = ({ compact = false }) => {
|
||||
|
||||
<Button
|
||||
size="md"
|
||||
accent="neutral"
|
||||
onClick={onOpenFiles}
|
||||
disabled={!hasSelection && !canCloseAll}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor:
|
||||
hasSelection || canCloseAll
|
||||
? "var(--btn-open-file)"
|
||||
: "var(--mantine-color-gray-4)",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{canCloseAll
|
||||
? t("fileManager.closeAllFiles", "Close all files")
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
Group,
|
||||
Divider,
|
||||
ScrollArea,
|
||||
Button,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { detectFileExtension, getFileSize } from "@app/utils/fileUtils";
|
||||
import { StirlingFileStub } from "@app/types/fileContext";
|
||||
@@ -208,7 +208,7 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
<Divider />
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
onClick={() => onMakeCopy(currentFile)}
|
||||
fullWidth
|
||||
>
|
||||
@@ -260,7 +260,7 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
</Group>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
onClick={() => setShowShareManageModal(true)}
|
||||
fullWidth
|
||||
>
|
||||
|
||||
@@ -3,12 +3,12 @@ import {
|
||||
Group,
|
||||
Box,
|
||||
Text,
|
||||
ActionIcon,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Menu,
|
||||
Badge,
|
||||
} from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
@@ -323,10 +323,11 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="md"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("fileManager.moreOptions", "More options")}
|
||||
style={{
|
||||
opacity: shouldShowHovered ? 1 : 0,
|
||||
transform: shouldShowHovered ? "scale(1)" : "scale(0.8)",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { Stack, Text, Button, Group } from "@mantine/core";
|
||||
import { Stack, Text, Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
import PhonelinkIcon from "@mui/icons-material/Phonelink";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -65,86 +66,45 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
// Determine visibility of Mobile QR Scanner button
|
||||
const shouldHideMobileQR =
|
||||
!isMobileUploadEnabled && config?.hideDisabledToolsMobileQRScanner;
|
||||
|
||||
const buttonProps = {
|
||||
variant: (source: string) =>
|
||||
activeSource === source ? "filled" : "subtle",
|
||||
getColor: (source: string) =>
|
||||
activeSource === source ? "var(--mantine-color-gray-2)" : undefined,
|
||||
getStyles: (source: string) => ({
|
||||
root: {
|
||||
backgroundColor: activeSource === source ? undefined : "transparent",
|
||||
color:
|
||||
activeSource === source
|
||||
? "var(--mantine-color-gray-9)"
|
||||
: "var(--mantine-color-gray-6)",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
activeSource === source ? undefined : "var(--mantine-color-gray-0)",
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Shared Button has no `xs`; map the old horizontal `xs` to `sm`.
|
||||
const buttonSize = "sm" as const;
|
||||
const buttonJustify = horizontal ? "center" : "start";
|
||||
const buttons = (
|
||||
<>
|
||||
<Button
|
||||
variant={activeSource === "recent" ? "primary" : "tertiary"}
|
||||
accent="neutral"
|
||||
leftSection={<HistoryIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
justify={buttonJustify}
|
||||
onClick={() => onSourceChange("recent")}
|
||||
fullWidth={!horizontal}
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
color={buttonProps.getColor("recent")}
|
||||
styles={buttonProps.getStyles("recent")}
|
||||
size={buttonSize}
|
||||
>
|
||||
{horizontal
|
||||
? t("fileManager.recent", "Recent")
|
||||
: t("fileManager.recent", "Recent")}
|
||||
{t("fileManager.recent", "Recent")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="var(--mantine-color-gray-6)"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
leftSection={<UploadIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
justify={buttonJustify}
|
||||
onClick={onLocalFileClick}
|
||||
fullWidth={!horizontal}
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--mantine-color-gray-0)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
size={buttonSize}
|
||||
>
|
||||
{horizontal ? terminology.upload : terminology.uploadFiles}
|
||||
</Button>
|
||||
|
||||
{!shouldHideGoogleDrive && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="var(--mantine-color-gray-6)"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
leftSection={<GoogleDriveIcon colored={isGoogleDriveEnabled} />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
justify={buttonJustify}
|
||||
onClick={handleGoogleDriveClick}
|
||||
fullWidth={!horizontal}
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
size={buttonSize}
|
||||
disabled={!isGoogleDriveEnabled}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: isGoogleDriveEnabled
|
||||
? "var(--mantine-color-gray-0)"
|
||||
: "transparent",
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
!isGoogleDriveEnabled
|
||||
? t(
|
||||
@@ -162,25 +122,14 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
|
||||
{!shouldHideMobileQR && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="var(--mantine-color-gray-6)"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
leftSection={<PhonelinkIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
justify={buttonJustify}
|
||||
onClick={handleMobileUploadClick}
|
||||
fullWidth={!horizontal}
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
size={buttonSize}
|
||||
disabled={!isMobileUploadEnabled}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: isMobileUploadEnabled
|
||||
? "var(--mantine-color-gray-0)"
|
||||
: "transparent",
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
!isMobileUploadEnabled
|
||||
? t(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Alert, Button, Group, Modal, Radio, Stack, Text } from "@mantine/core";
|
||||
import { Alert, Group, Modal, Radio, Stack, Text } from "@mantine/core";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined";
|
||||
|
||||
import { Button } from "@app/ui/Button";
|
||||
|
||||
import type { StirlingFileStub } from "@app/types/fileContext";
|
||||
import type { DeleteScope } from "@app/services/serverStorageDelete";
|
||||
|
||||
@@ -164,11 +166,11 @@ export function DeleteFilesDialog({
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose} disabled={submitting}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={submitting}>
|
||||
{t("filesPage.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
accent="danger"
|
||||
loading={submitting}
|
||||
onClick={() => runConfirm(showChoice ? scope : fixedScope)}
|
||||
>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Alert, Checkbox, Group, Modal, Stack, Text } from "@mantine/core";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined";
|
||||
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { FolderRecord } from "@app/types/folder";
|
||||
|
||||
interface DeleteFolderDialogProps {
|
||||
@@ -95,11 +88,11 @@ export function DeleteFolderDialog({
|
||||
</Alert>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose} disabled={submitting}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={submitting}>
|
||||
{t("filesPage.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
accent="danger"
|
||||
loading={submitting}
|
||||
onClick={async () => {
|
||||
setSubmitting(true);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon, Badge, Button, Tooltip } from "@mantine/core";
|
||||
import { Badge, Tooltip } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import DriveFileMoveIcon from "@mui/icons-material/DriveFileMove";
|
||||
@@ -143,7 +145,12 @@ export function FileDetailsPanel({
|
||||
label={t("filesPage.closeDetails", "Close details")}
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon variant="subtle" size="sm" onClick={onClose}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
aria-label={t("filesPage.closeDetails", "Close details")}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
@@ -181,25 +188,27 @@ export function FileDetailsPanel({
|
||||
<span className="files-page-details-ext-tag">{ext}</span>
|
||||
)}
|
||||
{(single.versionNumber ?? 1) > 1 && (
|
||||
<Badge size="sm" variant="filled" color="blue">
|
||||
<Badge size="sm" color="blue">
|
||||
v{single.versionNumber}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="files-page-details-collapse-toggle"
|
||||
onClick={() => setFieldsOpen((o) => !o)}
|
||||
aria-expanded={fieldsOpen}
|
||||
rightSection={
|
||||
<KeyboardArrowDownIcon
|
||||
className={`files-page-details-collapse-chevron${
|
||||
fieldsOpen ? " is-open" : ""
|
||||
}`}
|
||||
fontSize="small"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>{t("filesPage.fileInfo", "File info")}</span>
|
||||
<KeyboardArrowDownIcon
|
||||
className={`files-page-details-collapse-chevron${
|
||||
fieldsOpen ? " is-open" : ""
|
||||
}`}
|
||||
fontSize="small"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
{fieldsOpen && (
|
||||
<div className="files-page-details-fieldlist">
|
||||
<DetailField
|
||||
@@ -256,7 +265,7 @@ export function FileDetailsPanel({
|
||||
(compactVersions && onOpenVersionHistory ? (
|
||||
<Button
|
||||
leftSection={<HistoryIcon fontSize="small" />}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
onClick={onOpenVersionHistory}
|
||||
>
|
||||
{t(
|
||||
@@ -291,7 +300,6 @@ export function FileDetailsPanel({
|
||||
<div className="files-page-details-actions">
|
||||
<Button
|
||||
leftSection={<OpenInNewIcon fontSize="small" />}
|
||||
variant="filled"
|
||||
onClick={() => onAddToWorkspace(selectedFileIds)}
|
||||
>
|
||||
{files.length === 1
|
||||
@@ -302,7 +310,7 @@ export function FileDetailsPanel({
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<DownloadIcon fontSize="small" />}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
onClick={handleDownload}
|
||||
loading={downloading}
|
||||
>
|
||||
@@ -329,14 +337,12 @@ export function FileDetailsPanel({
|
||||
>
|
||||
<Button
|
||||
leftSection={<LinkIcon fontSize="small" />}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
disabled={!sharingEnabled}
|
||||
onClick={() => setShareModalOpen(true)}
|
||||
styles={{
|
||||
root: {
|
||||
// Keep tooltip hoverable while button is disabled.
|
||||
pointerEvents: sharingEnabled ? undefined : "auto",
|
||||
},
|
||||
style={{
|
||||
// Keep tooltip hoverable while button is disabled.
|
||||
pointerEvents: sharingEnabled ? undefined : "auto",
|
||||
}}
|
||||
>
|
||||
{t("filesPage.shareManage", "Manage sharing")}
|
||||
@@ -345,7 +351,7 @@ export function FileDetailsPanel({
|
||||
)}
|
||||
<Button
|
||||
leftSection={<DriveFileMoveIcon fontSize="small" />}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
onClick={() => onMove(selectedFileIds)}
|
||||
>
|
||||
{t("filesPage.moveTo", "Move to…")}
|
||||
@@ -363,16 +369,12 @@ export function FileDetailsPanel({
|
||||
>
|
||||
<Button
|
||||
leftSection={<CloudUploadIcon fontSize="small" />}
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
disabled={Boolean(saveToServerDisabledReason)}
|
||||
onClick={() => onSaveToServer(localOnlyFiles)}
|
||||
styles={{
|
||||
root: {
|
||||
// Keep tooltip hoverable while button is disabled.
|
||||
pointerEvents: saveToServerDisabledReason
|
||||
? "auto"
|
||||
: undefined,
|
||||
},
|
||||
style={{
|
||||
// Keep tooltip hoverable while button is disabled.
|
||||
pointerEvents: saveToServerDisabledReason ? "auto" : undefined,
|
||||
}}
|
||||
>
|
||||
{t("filesPage.saveToServer", "Save to server")}
|
||||
@@ -381,8 +383,7 @@ export function FileDetailsPanel({
|
||||
)}
|
||||
<Button
|
||||
leftSection={<DeleteIcon fontSize="small" />}
|
||||
color="red"
|
||||
variant="light"
|
||||
accent="danger"
|
||||
onClick={() => onRemove(selectedFileIds)}
|
||||
>
|
||||
{t("filesPage.remove", "Delete")}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon, Button, Checkbox, Menu, Tooltip } from "@mantine/core";
|
||||
import { Checkbox, Menu, Tooltip } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import ShieldOutlinedIcon from "@mui/icons-material/ShieldOutlined";
|
||||
import FolderIcon from "@mui/icons-material/Folder";
|
||||
@@ -297,10 +299,10 @@ function EmptyState({
|
||||
<span style={{ display: "inline-flex" }}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
leftSection={<CreateNewFolderIcon fontSize="small" />}
|
||||
disabled
|
||||
styles={{ root: { pointerEvents: "auto" } }}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
>
|
||||
{t("filesPage.empty.newFolderCta", "Create folder")}
|
||||
</Button>
|
||||
@@ -309,7 +311,7 @@ function EmptyState({
|
||||
) : (
|
||||
<Button
|
||||
size="md"
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
leftSection={<CreateNewFolderIcon fontSize="small" />}
|
||||
onClick={onCreateFolder}
|
||||
>
|
||||
@@ -528,8 +530,6 @@ function FolderCard({
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
ref={kebabRef}
|
||||
variant="filled"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("filesPage.folderMenu", "Folder actions")}
|
||||
@@ -771,8 +771,6 @@ function FileCard({
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
ref={kebabRef}
|
||||
variant="filled"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("filesPage.fileMenu", "File actions")}
|
||||
@@ -1151,7 +1149,7 @@ function FolderRow({
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
ref={kebabRef}
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("filesPage.folderMenu", "Folder actions")}
|
||||
@@ -1370,7 +1368,7 @@ function FileRow({
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
ref={kebabRef}
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t("filesPage.fileMenu", "File actions")}
|
||||
|
||||
@@ -7,19 +7,12 @@ import React, {
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Drawer,
|
||||
Group,
|
||||
MultiSelect,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { Drawer, Group, MultiSelect, Select, Tooltip } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
||||
import QrCode2Icon from "@mui/icons-material/QrCode2";
|
||||
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
||||
@@ -910,8 +903,8 @@ export default function FileManagerView() {
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
loading={refreshing}
|
||||
disabled={refreshing || Boolean(signInRequiredReason)}
|
||||
aria-busy={refreshing}
|
||||
@@ -930,11 +923,11 @@ export default function FileManagerView() {
|
||||
>
|
||||
<span style={{ display: "inline-flex" }}>
|
||||
<Button
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
leftSection={<CreateNewFolderIcon fontSize="small" />}
|
||||
disabled
|
||||
styles={{ root: { pointerEvents: "auto" } }}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
>
|
||||
{t("filesPage.newFolder", "New folder")}
|
||||
</Button>
|
||||
@@ -942,7 +935,7 @@ export default function FileManagerView() {
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
leftSection={<CreateNewFolderIcon fontSize="small" />}
|
||||
onClick={() => openNewFolderDialog()}
|
||||
@@ -966,9 +959,8 @@ export default function FileManagerView() {
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
variant="default"
|
||||
radius="md"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => setMobileUploadModalOpen(true)}
|
||||
aria-label={t(
|
||||
"filesPage.uploadFromMobile",
|
||||
@@ -1012,11 +1004,11 @@ export default function FileManagerView() {
|
||||
<span>{folders.error}</span>
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
aria-label={t("filesPage.dismissError", "Dismiss")}
|
||||
onClick={() => folders.setError(null)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
×
|
||||
</ActionIcon>
|
||||
</div>
|
||||
)}
|
||||
@@ -1147,8 +1139,8 @@ export default function FileManagerView() {
|
||||
w={280}
|
||||
>
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (allSelected) {
|
||||
setSelectedFileIds(new Set());
|
||||
@@ -1210,19 +1202,17 @@ export default function FileManagerView() {
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
leftSection={<CloudUploadIcon fontSize="small" />}
|
||||
disabled={Boolean(saveToServerDisabledReason)}
|
||||
onClick={() =>
|
||||
setSaveToServerTarget(localOnlySelectedStubs)
|
||||
}
|
||||
styles={{
|
||||
root: {
|
||||
// Keep the tooltip hoverable while disabled.
|
||||
pointerEvents: saveToServerDisabledReason
|
||||
? "auto"
|
||||
: undefined,
|
||||
},
|
||||
style={{
|
||||
// Keep the tooltip hoverable while disabled.
|
||||
pointerEvents: saveToServerDisabledReason
|
||||
? "auto"
|
||||
: undefined,
|
||||
}}
|
||||
aria-label={t(
|
||||
"filesPage.saveToServer",
|
||||
@@ -1242,7 +1232,7 @@ export default function FileManagerView() {
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
leftSection={
|
||||
<InfoOutlinedIcon fontSize="small" />
|
||||
}
|
||||
@@ -1259,7 +1249,7 @@ export default function FileManagerView() {
|
||||
<Tooltip label={moveLabel} withinPortal>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
leftSection={<DriveFileMoveIcon fontSize="small" />}
|
||||
onClick={() => promptMoveFiles(selectedFiles)}
|
||||
aria-label={moveLabel}
|
||||
@@ -1270,8 +1260,8 @@ export default function FileManagerView() {
|
||||
<Tooltip label={removeLabel} withinPortal>
|
||||
<Button
|
||||
size="sm"
|
||||
color="red"
|
||||
variant="light"
|
||||
accent="danger"
|
||||
variant="secondary"
|
||||
leftSection={<DeleteIcon fontSize="small" />}
|
||||
onClick={() => handleRemoveFiles(selectedFiles)}
|
||||
aria-label={removeLabel}
|
||||
@@ -1284,7 +1274,7 @@ export default function FileManagerView() {
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="md"
|
||||
onClick={() => clearSelection()}
|
||||
aria-label={t(
|
||||
@@ -1292,7 +1282,7 @@ export default function FileManagerView() {
|
||||
"Clear selection",
|
||||
)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
×
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
@@ -1388,7 +1378,7 @@ export default function FileManagerView() {
|
||||
/>
|
||||
<span className="files-page-toolbar-divider" aria-hidden="true" />
|
||||
<SegmentedControl
|
||||
size="xs"
|
||||
size="sm"
|
||||
value={viewMode}
|
||||
onChange={(v) => {
|
||||
// Mantine only emits values declared in `data[].value`, but
|
||||
@@ -1401,7 +1391,7 @@ export default function FileManagerView() {
|
||||
setViewMode(v as (typeof FILES_PAGE_VIEW_MODES)[number]);
|
||||
}}
|
||||
aria-label={t("filesPage.viewMode.label", "View mode")}
|
||||
data={[
|
||||
options={[
|
||||
{
|
||||
value: "grid",
|
||||
label: (
|
||||
@@ -1686,12 +1676,12 @@ const SearchField = React.forwardRef<
|
||||
/>
|
||||
{value && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={() => onChange("")}
|
||||
aria-label={t("filesPage.clearSearch", "Clear search")}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
×
|
||||
</ActionIcon>
|
||||
)}
|
||||
</div>
|
||||
@@ -1712,8 +1702,8 @@ function Breadcrumbs() {
|
||||
const isLast = idx === trail.length - 1;
|
||||
return (
|
||||
<React.Fragment key={entry.id ?? "root"}>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className={`files-page-breadcrumb${isLast ? " is-current" : ""}`}
|
||||
onClick={() => folders.setCurrentFolderId(entry.id)}
|
||||
onDragOver={(e) => {
|
||||
@@ -1772,7 +1762,7 @@ function Breadcrumbs() {
|
||||
}}
|
||||
>
|
||||
{entry.name}
|
||||
</button>
|
||||
</Button>
|
||||
{!isLast && (
|
||||
<KeyboardArrowRightIcon
|
||||
className="files-page-breadcrumb-sep"
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
FOLDER_ICONS,
|
||||
FolderIconOption,
|
||||
} from "@app/components/filesPage/folderIcons";
|
||||
import { Button } from "@app/ui/Button";
|
||||
|
||||
interface FolderAppearancePickerProps {
|
||||
folder: FolderRecord;
|
||||
@@ -50,9 +51,9 @@ export function FolderAppearancePicker({
|
||||
}}
|
||||
>
|
||||
{FOLDER_COLOR_PALETTE.map((c) => (
|
||||
<button
|
||||
<Button
|
||||
key={c}
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={disabled}
|
||||
aria-label={t(
|
||||
"filesPage.appearance.useColour",
|
||||
@@ -146,8 +147,8 @@ function IconButton({
|
||||
}) {
|
||||
return (
|
||||
<Tooltip label={icon.label} withinPortal>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
disabled={disabled}
|
||||
aria-label={icon.label}
|
||||
onClick={(e) => {
|
||||
@@ -172,7 +173,7 @@ function IconButton({
|
||||
}}
|
||||
>
|
||||
{icon.glyph || "-"}
|
||||
</button>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Alert, Button, Group, Modal, Stack, TextInput } from "@mantine/core";
|
||||
import { Alert, Group, Modal, Stack, TextInput } from "@mantine/core";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined";
|
||||
|
||||
import { Button } from "@app/ui/Button";
|
||||
|
||||
interface FolderNameDialogProps {
|
||||
opened: boolean;
|
||||
title: string;
|
||||
@@ -94,7 +96,7 @@ export function FolderNameDialog({
|
||||
</Alert>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose}>
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{t("filesPage.folderName.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon, Menu } from "@mantine/core";
|
||||
import { Menu } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import HomeIcon from "@mui/icons-material/Home";
|
||||
@@ -409,8 +410,8 @@ function TreeNodeRow({
|
||||
>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
className="files-page-tree-kebab"
|
||||
aria-label={t(
|
||||
"filesPage.treeMenu.actions",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Button,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
@@ -15,9 +13,10 @@ import HomeIcon from "@mui/icons-material/Home";
|
||||
import FolderIcon from "@mui/icons-material/Folder";
|
||||
import FolderOpenIcon from "@mui/icons-material/FolderOpen";
|
||||
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined";
|
||||
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { FolderId, FolderRecord, ROOT_FOLDER_ID } from "@app/types/folder";
|
||||
|
||||
interface MoveToFolderDialogProps {
|
||||
@@ -228,8 +227,7 @@ export function MoveToFolderDialog({
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={handleCancel}
|
||||
disabled={creating}
|
||||
@@ -238,20 +236,20 @@ export function MoveToFolderDialog({
|
||||
"Discard",
|
||||
)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
×
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
) : (
|
||||
<Button
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
leftSection={<CreateNewFolderIcon fontSize="small" />}
|
||||
onClick={() => {
|
||||
setCreatingFolder(true);
|
||||
setNewFolderName("");
|
||||
}}
|
||||
styles={{ root: { alignSelf: "flex-start" } }}
|
||||
style={{ alignSelf: "flex-start" }}
|
||||
data-testid="move-dialog-create-folder-toggle"
|
||||
>
|
||||
{t(
|
||||
@@ -272,7 +270,7 @@ export function MoveToFolderDialog({
|
||||
</Alert>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose} disabled={submitting}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={submitting}>
|
||||
{t("filesPage.moveDialog.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
@@ -325,35 +323,35 @@ function FolderPick({
|
||||
onPick,
|
||||
}: FolderPickProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
justify="start"
|
||||
fullWidth
|
||||
onClick={onPick}
|
||||
disabled={disabled}
|
||||
leftSection={
|
||||
isRoot ? (
|
||||
<HomeIcon fontSize="small" />
|
||||
) : isActive ? (
|
||||
<FolderOpenIcon fontSize="small" style={{ color }} />
|
||||
) : (
|
||||
<FolderIcon fontSize="small" style={{ color }} />
|
||||
)
|
||||
}
|
||||
style={{
|
||||
all: "unset",
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
opacity: disabled ? 0.45 : 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
padding: `0.4rem 0.75rem 0.4rem ${0.75 + depth * 0.85}rem`,
|
||||
width: "100%",
|
||||
background: isActive ? "var(--hover-bg)" : "transparent",
|
||||
borderBottom: "1px solid var(--border-subtle)",
|
||||
boxSizing: "border-box",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{isRoot ? (
|
||||
<HomeIcon fontSize="small" />
|
||||
) : isActive ? (
|
||||
<FolderOpenIcon fontSize="small" style={{ color }} />
|
||||
) : (
|
||||
<FolderIcon fontSize="small" style={{ color }} />
|
||||
)}
|
||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon, Badge, Menu } from "@mantine/core";
|
||||
import { Badge, Menu } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
@@ -144,8 +146,8 @@ export function VersionTimeline({
|
||||
<span className="files-page-details-version-timeline-rail-line" />
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="files-page-details-version-timeline-ellipsis-btn"
|
||||
onClick={() => setShowAllCollapsed(true)}
|
||||
>
|
||||
@@ -154,7 +156,7 @@ export function VersionTimeline({
|
||||
"Show {{count}} earlier versions",
|
||||
{ count: row.hidden },
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -181,24 +183,31 @@ export function VersionTimeline({
|
||||
)}
|
||||
</div>
|
||||
<div className="files-page-details-version-timeline-body">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="files-page-details-version-timeline-summary"
|
||||
onClick={() => toggleExpand(v.id)}
|
||||
aria-expanded={isExpanded}
|
||||
leftSection={
|
||||
<Badge
|
||||
size="xs"
|
||||
variant={isActive ? "filled" : "outline"}
|
||||
color="blue"
|
||||
>
|
||||
v{v.versionNumber ?? 1}
|
||||
</Badge>
|
||||
}
|
||||
rightSection={
|
||||
<KeyboardArrowDownIcon
|
||||
className={`files-page-details-version-timeline-chevron${
|
||||
isExpanded ? " is-expanded" : ""
|
||||
}`}
|
||||
fontSize="small"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
size="xs"
|
||||
variant={isActive ? "filled" : "outline"}
|
||||
color="blue"
|
||||
>
|
||||
v{v.versionNumber ?? 1}
|
||||
</Badge>
|
||||
{delta ? (
|
||||
<span className="files-page-details-version-timeline-delta">
|
||||
<span className="files-page-details-version-timeline-delta-plus">
|
||||
+
|
||||
</span>
|
||||
<ToolLabel toolId={delta.toolId} />
|
||||
</span>
|
||||
) : (
|
||||
@@ -206,14 +215,7 @@ export function VersionTimeline({
|
||||
{t("filesPage.versionOrigin", "Original upload")}
|
||||
</span>
|
||||
)}
|
||||
<span className="files-page-details-version-timeline-spacer" />
|
||||
<KeyboardArrowDownIcon
|
||||
className={`files-page-details-version-timeline-chevron${
|
||||
isExpanded ? " is-expanded" : ""
|
||||
}`}
|
||||
fontSize="small"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
<div className="files-page-details-version-timeline-meta-line">
|
||||
<span>{formatFileSize(v.size)}</span>
|
||||
{v.lastModified ? (
|
||||
@@ -230,7 +232,7 @@ export function VersionTimeline({
|
||||
<Menu position="bottom-end" withinPortal shadow="md">
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
aria-label={t(
|
||||
"filesPage.versionActions",
|
||||
@@ -303,13 +305,13 @@ export function VersionTimeline({
|
||||
})}
|
||||
</ol>
|
||||
{collapsible && showAllCollapsed && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="files-page-details-version-timeline-collapse-btn"
|
||||
onClick={() => setShowAllCollapsed(false)}
|
||||
>
|
||||
{t("filesPage.versionCollapse", "Collapse middle versions")}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
+12
-29
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
import { Button, Group, ActionIcon } from "@mantine/core";
|
||||
import { Group } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -33,22 +35,6 @@ export function SlideButtons({
|
||||
(btn) => btn.group === "right",
|
||||
);
|
||||
|
||||
const buttonStyles = (variant: ButtonDefinition["variant"]) =>
|
||||
variant === "primary"
|
||||
? {
|
||||
root: {
|
||||
background: "var(--onboarding-primary-button-bg)",
|
||||
color: "var(--onboarding-primary-button-text)",
|
||||
},
|
||||
}
|
||||
: {
|
||||
root: {
|
||||
background: "var(--onboarding-secondary-button-bg)",
|
||||
border: "1px solid var(--onboarding-secondary-button-border)",
|
||||
color: "var(--onboarding-secondary-button-text)",
|
||||
},
|
||||
};
|
||||
|
||||
const resolveButtonLabel = (button: ButtonDefinition) => {
|
||||
// Special case: override "See Plans" with "Upgrade now" when over limit
|
||||
if (
|
||||
@@ -77,20 +63,14 @@ export function SlideButtons({
|
||||
<ActionIcon
|
||||
key={button.key}
|
||||
onClick={() => onAction(button.action)}
|
||||
radius="md"
|
||||
size={40}
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
background: "var(--onboarding-secondary-button-bg)",
|
||||
border: "1px solid var(--onboarding-secondary-button-border)",
|
||||
color: "var(--onboarding-secondary-button-text)",
|
||||
},
|
||||
}}
|
||||
aria-label={t("onboarding.buttons.back", "Back")}
|
||||
>
|
||||
{button.icon === "chevron-left" && (
|
||||
{button.icon === "chevron-left" ? (
|
||||
<ChevronLeftIcon fontSize="small" />
|
||||
)}
|
||||
) : null}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
@@ -103,7 +83,10 @@ export function SlideButtons({
|
||||
key={button.key}
|
||||
onClick={() => onAction(button.action)}
|
||||
disabled={disabled}
|
||||
styles={buttonStyles(variant)}
|
||||
variant={variant === "primary" ? "primary" : "secondary"}
|
||||
accent={
|
||||
button.accent ?? (variant === "primary" ? "default" : "neutral")
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Modal, Stack, ActionIcon } from "@mantine/core";
|
||||
import { Modal, Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import DiamondOutlinedIcon from "@mui/icons-material/DiamondOutlined";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
import type {
|
||||
SlideDefinition,
|
||||
@@ -45,6 +46,7 @@ export default function OnboardingModalSlide({
|
||||
onAction,
|
||||
allowDismiss = true,
|
||||
}: OnboardingModalSlideProps) {
|
||||
const { t } = useTranslation();
|
||||
const renderHero = () => {
|
||||
if (slideDefinition.hero.type === "dual-icon") {
|
||||
return (
|
||||
@@ -139,8 +141,9 @@ export default function OnboardingModalSlide({
|
||||
{allowDismiss && (
|
||||
<ActionIcon
|
||||
onClick={onSkip}
|
||||
radius="md"
|
||||
size={36}
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
aria-label={t("common.close", "Close")}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 16,
|
||||
@@ -150,15 +153,12 @@ export default function OnboardingModalSlide({
|
||||
backdropFilter: "blur(4px)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
styles={{
|
||||
root: {
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.3)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
<LocalIcon
|
||||
icon="close-rounded"
|
||||
width="1.25rem"
|
||||
height="1.25rem"
|
||||
/>
|
||||
</ActionIcon>
|
||||
)}
|
||||
<div className={styles.heroLogo} key={`logo-${slideContent.key}`}>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import React from "react";
|
||||
import { TourProvider, useTour, type StepType } from "@reactour/tour";
|
||||
import { CloseButton, ActionIcon } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
@@ -137,7 +137,7 @@ export default function OnboardingTour({
|
||||
setIsOpen,
|
||||
})
|
||||
}
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
aria-label={
|
||||
isLast
|
||||
@@ -151,11 +151,15 @@ export default function OnboardingTour({
|
||||
}}
|
||||
components={{
|
||||
Close: ({ onClick }) => (
|
||||
<CloseButton
|
||||
<ActionIcon
|
||||
onClick={onClick}
|
||||
variant="tertiary"
|
||||
size="md"
|
||||
aria-label={t("onboarding.close", "Close")}
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}
|
||||
/>
|
||||
>
|
||||
×
|
||||
</ActionIcon>
|
||||
),
|
||||
Content: ({ content }: { content: string }) => (
|
||||
<div
|
||||
|
||||
@@ -8,6 +8,7 @@ import TourOverviewSlide from "@app/components/onboarding/slides/TourOverviewSli
|
||||
import AnalyticsChoiceSlide from "@app/components/onboarding/slides/AnalyticsChoiceSlide";
|
||||
import MFASetupSlide from "@app/components/onboarding/slides/MFASetupSlide";
|
||||
import { SlideConfig, LicenseNotice } from "@app/types/types";
|
||||
import type { ButtonAccent } from "@app/ui/Button";
|
||||
|
||||
export type SlideId =
|
||||
| "first-login"
|
||||
@@ -83,6 +84,8 @@ export interface ButtonDefinition {
|
||||
label?: string;
|
||||
icon?: "chevron-left";
|
||||
variant?: "primary" | "secondary" | "default";
|
||||
/** Accent for the shared Button; defaults to neutral. */
|
||||
accent?: ButtonAccent;
|
||||
group: "left" | "right";
|
||||
action: ButtonAction;
|
||||
disabledWhen?: (state: FlowState) => boolean;
|
||||
@@ -238,6 +241,7 @@ export const SLIDE_DEFINITIONS: Record<SlideId, SlideDefinition> = {
|
||||
type: "button",
|
||||
label: "onboarding.serverLicense.seePlans",
|
||||
variant: "primary",
|
||||
accent: "premium",
|
||||
group: "right",
|
||||
action: "see-plans",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Button } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import i18n from "@app/i18n";
|
||||
import { SlideConfig } from "@app/types/types";
|
||||
@@ -36,7 +36,7 @@ export default function AnalyticsChoiceSlide({
|
||||
<br />
|
||||
<div style={{ textAlign: "right", marginTop: 0 }}>
|
||||
<Button
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Menu, ActionIcon } from "@mantine/core";
|
||||
import { Menu } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
|
||||
export interface OSOption {
|
||||
@@ -67,8 +68,12 @@ export const DesktopInstallTitle: React.FC<DesktopInstallTitleProps> = ({
|
||||
<Menu position="bottom" offset={5} zIndex={10000}>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
aria-label={t(
|
||||
"onboarding.desktopInstall.selectOs",
|
||||
"Select operating system",
|
||||
)}
|
||||
style={{
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { Stack, PasswordInput, Button, Alert, Text } from "@mantine/core";
|
||||
import { Stack, PasswordInput, Alert, Text } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SlideConfig } from "@app/types/types";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
@@ -199,8 +200,8 @@ function FirstLoginForm({
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={
|
||||
!newPassword ||
|
||||
@@ -209,7 +210,7 @@ function FirstLoginForm({
|
||||
confirmPassword.length < 8
|
||||
}
|
||||
size="md"
|
||||
mt="xs"
|
||||
style={{ marginTop: "var(--mantine-spacing-xs)" }}
|
||||
>
|
||||
{t("firstLogin.changePassword", "Change Password")}
|
||||
</Button>
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Loader,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SlideConfig } from "@app/types/types";
|
||||
@@ -210,8 +210,8 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
|
||||
|
||||
<Group justify="space-between" wrap="wrap">
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
variant="light"
|
||||
onClick={fetchMfaSetup}
|
||||
disabled={mfaLoading || submitting || setupComplete}
|
||||
>
|
||||
@@ -226,7 +226,7 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
|
||||
>
|
||||
Enable MFA
|
||||
</Button>
|
||||
<Button type="button" variant="light" onClick={onLogout}>
|
||||
<Button variant="secondary" type="button" onClick={onLogout}>
|
||||
Logout
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -5,7 +5,9 @@ import React, {
|
||||
useMemo,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { ActionIcon, CheckboxIndicator } from "@mantine/core";
|
||||
import { CheckboxIndicator } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutlined";
|
||||
@@ -259,7 +261,7 @@ const FileThumbnail = ({
|
||||
{/* Kebab menu */}
|
||||
<ActionIcon
|
||||
aria-label={t("moreOptions", "More options")}
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
className={styles.kebab}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -277,8 +279,18 @@ const FileThumbnail = ({
|
||||
style={{ width: actionsWidth }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
<Button
|
||||
variant="tertiary"
|
||||
justify="start"
|
||||
fullWidth
|
||||
className={styles.actionRow}
|
||||
leftSection={
|
||||
isPinned ? (
|
||||
<PushPinIcon fontSize="small" />
|
||||
) : (
|
||||
<PushPinOutlinedIcon fontSize="small" />
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
if (actualFile) {
|
||||
if (isPinned) {
|
||||
@@ -292,38 +304,36 @@ const FileThumbnail = ({
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
{isPinned ? (
|
||||
<PushPinIcon fontSize="small" />
|
||||
) : (
|
||||
<PushPinOutlinedIcon fontSize="small" />
|
||||
)}
|
||||
<span>{isPinned ? t("unpin", "Unpin") : t("pin", "Pin")}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
{isPinned ? t("unpin", "Unpin") : t("pin", "Pin")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
justify="start"
|
||||
fullWidth
|
||||
className={styles.actionRow}
|
||||
leftSection={<DownloadOutlinedIcon fontSize="small" />}
|
||||
onClick={() => {
|
||||
downloadSelectedFile();
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
<DownloadOutlinedIcon fontSize="small" />
|
||||
<span>{terminology.download}</span>
|
||||
</button>
|
||||
|
||||
{terminology.download}
|
||||
</Button>
|
||||
<div className={styles.actionsDivider} />
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="tertiary"
|
||||
justify="start"
|
||||
fullWidth
|
||||
className={`${styles.actionRow} ${styles.actionDanger}`}
|
||||
leftSection={<DeleteOutlineIcon fontSize="small" />}
|
||||
onClick={() => {
|
||||
onDeleteFile(file.id);
|
||||
onSetStatus(`Deleted ${file.name}`);
|
||||
setShowActions(false);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlineIcon fontSize="small" />
|
||||
<span>{t("delete", "Delete")}</span>
|
||||
</button>
|
||||
{t("delete", "Delete")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Tooltip, ActionIcon } from "@mantine/core";
|
||||
import { Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import UndoIcon from "@mui/icons-material/Undo";
|
||||
import RedoIcon from "@mui/icons-material/Redo";
|
||||
import ContentCutIcon from "@mui/icons-material/ContentCut";
|
||||
@@ -135,28 +136,22 @@ const PageEditorControls = ({
|
||||
{/* Undo/Redo */}
|
||||
<Tooltip label={t("pageEditor.toolbar.undo", "Undo")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={onUndo}
|
||||
disabled={!canUndo}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color: canUndo ? "var(--text-secondary)" : "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t("pageEditor.toolbar.undo", "Undo")}
|
||||
>
|
||||
<UndoIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("pageEditor.toolbar.redo", "Redo")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={onRedo}
|
||||
disabled={!canRedo}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color: canRedo ? "var(--text-secondary)" : "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t("pageEditor.toolbar.redo", "Redo")}
|
||||
>
|
||||
<RedoIcon />
|
||||
</ActionIcon>
|
||||
@@ -176,17 +171,14 @@ const PageEditorControls = ({
|
||||
label={t("pageEditor.toolbar.rotateLeft", "Rotate Selected Left")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={() => onRotate("left")}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color:
|
||||
selectedPageIds.length > 0
|
||||
? "var(--text-secondary)"
|
||||
: "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t(
|
||||
"pageEditor.toolbar.rotateLeft",
|
||||
"Rotate Selected Left",
|
||||
)}
|
||||
>
|
||||
<RotateLeftIcon />
|
||||
</ActionIcon>
|
||||
@@ -195,68 +187,47 @@ const PageEditorControls = ({
|
||||
label={t("pageEditor.toolbar.rotateRight", "Rotate Selected Right")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={() => onRotate("right")}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color:
|
||||
selectedPageIds.length > 0
|
||||
? "var(--text-secondary)"
|
||||
: "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t(
|
||||
"pageEditor.toolbar.rotateRight",
|
||||
"Rotate Selected Right",
|
||||
)}
|
||||
>
|
||||
<RotateRightIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("pageEditor.toolbar.delete", "Delete Selected")}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={onDelete}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color:
|
||||
selectedPageIds.length > 0
|
||||
? "var(--text-secondary)"
|
||||
: "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t("pageEditor.toolbar.delete", "Delete Selected")}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={getSplitTooltip()}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={onSplit}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color:
|
||||
selectedPageIds.length > 0
|
||||
? "var(--text-secondary)"
|
||||
: "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={getSplitTooltip()}
|
||||
>
|
||||
<ContentCutIcon />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={getPageBreakTooltip()}>
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
onClick={onPageBreak}
|
||||
disabled={selectedPageIds.length === 0}
|
||||
variant="subtle"
|
||||
style={{
|
||||
color:
|
||||
selectedPageIds.length > 0
|
||||
? "var(--text-secondary)"
|
||||
: "var(--text-muted)",
|
||||
}}
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={getPageBreakTooltip()}
|
||||
>
|
||||
<InsertPageBreakIcon />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ActionIcon, Popover } from "@mantine/core";
|
||||
import { Popover } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { Tooltip } from "@app/components/shared/Tooltip";
|
||||
import BulkSelectionPanel from "@app/components/pageEditor/BulkSelectionPanel";
|
||||
@@ -37,8 +38,7 @@ export default function PageSelectByNumberButton({
|
||||
<Popover.Target>
|
||||
<div style={{ display: "inline-flex" }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
variant="tertiary"
|
||||
disabled={disabled || totalPages === 0}
|
||||
aria-label={label}
|
||||
>
|
||||
|
||||
+7
-6
@@ -1,4 +1,5 @@
|
||||
import { Button, Text, Group, Divider } from "@mantine/core";
|
||||
import { Text, Group, Divider } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classes from "@app/components/pageEditor/bulkSelectionPanel/BulkSelectionPanel.module.css";
|
||||
import { LogicalOperator } from "@app/utils/bulkselection/selectionBuilders";
|
||||
@@ -22,7 +23,7 @@ const OperatorsSection = ({
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className={classes.operatorChip}
|
||||
onClick={() => onInsertOperator("and")}
|
||||
disabled={!csvInput.trim()}
|
||||
@@ -37,7 +38,7 @@ const OperatorsSection = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className={classes.operatorChip}
|
||||
onClick={() => onInsertOperator("or")}
|
||||
disabled={!csvInput.trim()}
|
||||
@@ -52,7 +53,7 @@ const OperatorsSection = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className={classes.operatorChip}
|
||||
onClick={() => onInsertOperator("not")}
|
||||
disabled={!csvInput.trim()}
|
||||
@@ -70,7 +71,7 @@ const OperatorsSection = ({
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className={classes.operatorChip}
|
||||
onClick={() => onInsertOperator("even")}
|
||||
title={t(
|
||||
@@ -84,7 +85,7 @@ const OperatorsSection = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className={classes.operatorChip}
|
||||
onClick={() => onInsertOperator("odd")}
|
||||
title={t(
|
||||
|
||||
+9
-13
@@ -1,4 +1,5 @@
|
||||
import { TextInput, Button, Text, Flex, Switch } from "@mantine/core";
|
||||
import { TextInput, Text, Flex, Switch } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { Tooltip } from "@app/components/shared/Tooltip";
|
||||
@@ -77,20 +78,15 @@ const PageSelectionInput = ({
|
||||
placeholder="1,3,5-10"
|
||||
rightSection={
|
||||
csvInput && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
onClick={onClear}
|
||||
style={{
|
||||
color: "var(--text-muted)",
|
||||
minWidth: "auto",
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
padding: 0,
|
||||
}}
|
||||
aria-label={t("clear", "Clear")}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
×
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
onKeyDown={(e) => e.key === "Enter" && onUpdatePagesFromCSV()}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Text, NumberInput, Group } from "@mantine/core";
|
||||
import { Text, NumberInput, Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import classes from "@app/components/pageEditor/bulkSelectionPanel/BulkSelectionPanel.module.css";
|
||||
|
||||
interface SelectPagesProps {
|
||||
|
||||
@@ -5,7 +5,8 @@ import React, {
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { Badge, Modal, Text, ActionIcon, Tooltip, Group } from "@mantine/core";
|
||||
import { Badge, Modal, Text, Tooltip, Group } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
@@ -365,7 +366,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
/>
|
||||
<ActionIcon
|
||||
ref={closeButtonRef}
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
onClick={handleClose}
|
||||
aria-label={t("settings.close", "Close")}
|
||||
data-autofocus
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Alert,
|
||||
TextInput,
|
||||
Paper,
|
||||
Select,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import ContentCopyRoundedIcon from "@mui/icons-material/ContentCopyRounded";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -243,8 +243,8 @@ const BulkShareModal: React.FC<BulkShareModalProps> = ({
|
||||
label={t("storageShare.linkLabel", "Share link")}
|
||||
rightSection={
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
leftSection={
|
||||
<ContentCopyRoundedIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
@@ -297,7 +297,7 @@ const BulkShareModal: React.FC<BulkShareModalProps> = ({
|
||||
</Paper>
|
||||
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={onClose} disabled={isWorking}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={isWorking}>
|
||||
{t("cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Modal, Stack, Text, Button, Group, Alert } from "@mantine/core";
|
||||
import { Modal, Stack, Text, Group, Alert } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -151,7 +152,7 @@ const BulkUploadToServerModal: React.FC<BulkUploadToServerModalProps> = ({
|
||||
)}
|
||||
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={onClose} disabled={isUploading}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={isUploading}>
|
||||
{t("cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -8,6 +8,14 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider>{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// The shared SegmentedControl renders each option as a radio <input> (inside a
|
||||
// <label>) whose `value` attribute matches the option value. Select by value
|
||||
// since it is stable regardless of how the label is wrapped (e.g. FitText).
|
||||
const getRadioByValue = (container: HTMLElement, value: string) =>
|
||||
container.querySelector<HTMLInputElement>(
|
||||
`input[type="radio"][value="${value}"]`,
|
||||
);
|
||||
|
||||
describe("ButtonSelector", () => {
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
@@ -15,7 +23,7 @@ describe("ButtonSelector", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should render all options as buttons", () => {
|
||||
test("should render all options as segments", () => {
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
@@ -37,13 +45,13 @@ describe("ButtonSelector", () => {
|
||||
expect(screen.getByText("Option 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should highlight selected button with filled variant", () => {
|
||||
test("should mark selected option as checked", () => {
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
@@ -54,22 +62,22 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
const selectedButton = screen.getByRole("button", { name: "Option 1" });
|
||||
const unselectedButton = screen.getByRole("button", { name: "Option 2" });
|
||||
const selectedRadio = getRadioByValue(container, "option1");
|
||||
const unselectedRadio = getRadioByValue(container, "option2");
|
||||
|
||||
// Check data-variant attribute for filled/outline
|
||||
expect(selectedButton).toHaveAttribute("data-variant", "filled");
|
||||
expect(unselectedButton).toHaveAttribute("data-variant", "outline");
|
||||
// Selected option is marked via the radio's checked state.
|
||||
expect(selectedRadio).toBeChecked();
|
||||
expect(unselectedRadio).not.toBeChecked();
|
||||
expect(screen.getByText("Selection Label")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should call onChange when button is clicked", () => {
|
||||
test("should call onChange when an option is clicked", () => {
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
@@ -79,7 +87,7 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Option 2" }));
|
||||
fireEvent.click(getRadioByValue(container, "option2")!);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith("option2");
|
||||
});
|
||||
@@ -90,7 +98,7 @@ describe("ButtonSelector", () => {
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value={undefined}
|
||||
@@ -100,17 +108,17 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
// Both buttons should be outlined when no value is selected
|
||||
const button1 = screen.getByRole("button", { name: "Option 1" });
|
||||
const button2 = screen.getByRole("button", { name: "Option 2" });
|
||||
// No option should be checked when no value is selected
|
||||
const radio1 = getRadioByValue(container, "option1");
|
||||
const radio2 = getRadioByValue(container, "option2");
|
||||
|
||||
expect(button1).toHaveAttribute("data-variant", "outline");
|
||||
expect(button2).toHaveAttribute("data-variant", "outline");
|
||||
expect(radio1).not.toBeChecked();
|
||||
expect(radio2).not.toBeChecked();
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
description: "disable buttons when disabled prop is true",
|
||||
description: "disable options when disabled prop is true",
|
||||
options: [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
@@ -128,7 +136,7 @@ describe("ButtonSelector", () => {
|
||||
expectedStates: [false, true],
|
||||
},
|
||||
])("should $description", ({ options, globalDisabled, expectedStates }) => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
@@ -140,18 +148,18 @@ describe("ButtonSelector", () => {
|
||||
);
|
||||
|
||||
options.forEach((option, index) => {
|
||||
const button = screen.getByRole("button", { name: option.label });
|
||||
expect(button).toHaveProperty("disabled", expectedStates[index]);
|
||||
const radio = getRadioByValue(container, String(option.value));
|
||||
expect(radio).toHaveProperty("disabled", expectedStates[index]);
|
||||
});
|
||||
});
|
||||
|
||||
test("should not call onChange when disabled button is clicked", () => {
|
||||
test("should not allow selecting a disabled option", () => {
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2", disabled: true },
|
||||
];
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<TestWrapper>
|
||||
<ButtonSelector
|
||||
value="option1"
|
||||
@@ -161,12 +169,16 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Option 2" }));
|
||||
|
||||
// The disabled option's radio is disabled, so a real user cannot select it
|
||||
// and onChange will not fire from genuine interaction. (jsdom does not
|
||||
// replicate the browser's disabled-click blocking, so assert the disabled
|
||||
// state — that is what prevents selection for real users.)
|
||||
const disabledRadio = getRadioByValue(container, "option2");
|
||||
expect(disabledRadio).toBeDisabled();
|
||||
expect(mockOnChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should not apply fullWidth styling when fullWidth is false", () => {
|
||||
test("should render options when fullWidth is false", () => {
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
@@ -184,8 +196,7 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole("button", { name: "Option 1" });
|
||||
expect(button).not.toHaveStyle({ flex: "1" });
|
||||
expect(screen.getByText("Option 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Layout Label")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -205,14 +216,14 @@ describe("ButtonSelector", () => {
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
// Should render buttons
|
||||
// Should render the options
|
||||
expect(screen.getByText("Option 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Option 2")).toBeInTheDocument();
|
||||
|
||||
// Stack should only contain the Group (buttons), no Text element for label
|
||||
// Stack should only contain the SegmentedControl, no label Text element
|
||||
const stackElement = container.querySelector(
|
||||
'[class*="mantine-Stack-root"]',
|
||||
);
|
||||
expect(stackElement?.children).toHaveLength(1); // Only the Group, no label Text
|
||||
expect(stackElement?.children).toHaveLength(1); // Only the SegmentedControl, no label Text
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, Group, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { Stack, Text, Tooltip } from "@mantine/core";
|
||||
import FitText from "@app/components/shared/FitText";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
|
||||
export interface ButtonOption<T> {
|
||||
value: T;
|
||||
@@ -15,7 +16,6 @@ interface ButtonSelectorProps<T> {
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
buttonClassName?: string;
|
||||
textClassName?: string;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,43 @@ const ButtonSelector = <T extends string | number>({
|
||||
label = undefined,
|
||||
disabled = false,
|
||||
fullWidth = true,
|
||||
buttonClassName,
|
||||
textClassName,
|
||||
}: ButtonSelectorProps<T>) => {
|
||||
const selectedValue = value === undefined ? "" : String(value);
|
||||
|
||||
const segmentedOptions = options.map((option) => {
|
||||
const isDisabled = disabled || option.disabled;
|
||||
const fitText = (
|
||||
<FitText
|
||||
text={option.label}
|
||||
lines={1}
|
||||
minimumFontScale={0.5}
|
||||
fontSize={10}
|
||||
className={textClassName}
|
||||
/>
|
||||
);
|
||||
|
||||
return {
|
||||
value: String(option.value),
|
||||
disabled: isDisabled,
|
||||
label:
|
||||
option.tooltip && isDisabled ? (
|
||||
<Tooltip label={option.tooltip} position="top" withArrow>
|
||||
<span>{fitText}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
fitText
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const handleChange = (next: string) => {
|
||||
const matched = options.find((option) => String(option.value) === next);
|
||||
if (matched) {
|
||||
onChange(matched.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="var(--mantine-spacing-sm)">
|
||||
{/* Label (if it exists) */}
|
||||
@@ -44,69 +78,12 @@ const ButtonSelector = <T extends string | number>({
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<Group gap="4px">
|
||||
{options.map((option) => {
|
||||
const isDisabled = disabled || option.disabled;
|
||||
const button = (
|
||||
<Button
|
||||
variant={value === option.value ? "filled" : "outline"}
|
||||
color={
|
||||
value === option.value
|
||||
? "var(--color-primary-500)"
|
||||
: "var(--text-muted)"
|
||||
}
|
||||
onClick={() => onChange(option.value)}
|
||||
disabled={isDisabled}
|
||||
className={buttonClassName}
|
||||
style={{
|
||||
flex: fullWidth ? 1 : undefined,
|
||||
height: "auto",
|
||||
minHeight: "2.5rem",
|
||||
fontSize: "var(--mantine-font-size-sm)",
|
||||
lineHeight: "1.4",
|
||||
paddingTop: "0.5rem",
|
||||
paddingBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<FitText
|
||||
text={option.label}
|
||||
lines={1}
|
||||
minimumFontScale={0.5}
|
||||
fontSize={10}
|
||||
className={textClassName}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Wrap with tooltip if provided (useful for disabled state explanations)
|
||||
if (option.tooltip && isDisabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
key={option.value}
|
||||
label={option.tooltip}
|
||||
position="top"
|
||||
withArrow
|
||||
>
|
||||
<span
|
||||
style={{ flex: fullWidth ? 1 : undefined, display: "flex" }}
|
||||
>
|
||||
{button}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
key={option.value}
|
||||
style={{ flex: fullWidth ? 1 : undefined, display: "flex" }}
|
||||
>
|
||||
{button}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
<SegmentedControl
|
||||
options={segmentedOptions}
|
||||
value={selectedValue}
|
||||
onChange={handleChange}
|
||||
fullWidth={fullWidth}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Stack } from "@mantine/core";
|
||||
import React from "react";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
|
||||
export interface ButtonToggleOption {
|
||||
value: string;
|
||||
@@ -13,70 +12,49 @@ export interface ButtonToggleProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
orientation?: "vertical" | "horizontal";
|
||||
size?: "xs" | "sm" | "md" | "lg";
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export const ButtonToggle: React.FC<ButtonToggleProps> = ({
|
||||
export const ButtonToggle = ({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
orientation = "vertical",
|
||||
size = "md",
|
||||
fullWidth = true,
|
||||
}) => {
|
||||
const isVertical = orientation === "vertical";
|
||||
}: ButtonToggleProps) => {
|
||||
const segmentedSize = size === "xs" || size === "sm" ? "sm" : "md";
|
||||
|
||||
const buttonStyle: React.CSSProperties = {
|
||||
justifyContent: "flex-start",
|
||||
height: isVertical ? "auto" : undefined,
|
||||
minHeight: isVertical ? "50px" : undefined,
|
||||
padding: isVertical ? "12px 16px" : undefined,
|
||||
textAlign: "left",
|
||||
};
|
||||
|
||||
const renderButton = (option: ButtonToggleOption) => {
|
||||
const isSelected = value === option.value;
|
||||
const isDisabled = disabled || option.disabled;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={option.value}
|
||||
variant={isSelected ? "filled" : "outline"}
|
||||
onClick={() => !isDisabled && onChange(option.value)}
|
||||
disabled={isDisabled}
|
||||
size={size}
|
||||
fullWidth={fullWidth}
|
||||
style={buttonStyle}
|
||||
>
|
||||
<div style={{ width: "100%" }}>
|
||||
<div style={{ fontWeight: 600 }}>{option.label}</div>
|
||||
{option.description && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.85em",
|
||||
opacity: 0.8,
|
||||
marginTop: "4px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{option.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
if (isVertical) {
|
||||
return <Stack gap="xs">{options.map(renderButton)}</Stack>;
|
||||
}
|
||||
const segmentedOptions = options.map((option) => ({
|
||||
value: option.value,
|
||||
disabled: disabled || option.disabled,
|
||||
label: (
|
||||
<div>
|
||||
<div style={{ fontWeight: 600 }}>{option.label}</div>
|
||||
{option.description && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.85em",
|
||||
opacity: 0.8,
|
||||
marginTop: 4,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{option.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
||||
{options.map(renderButton)}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
options={segmentedOptions}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
size={segmentedSize}
|
||||
fullWidth={fullWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { Button, Group } from "@mantine/core";
|
||||
import { Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileState } from "@app/contexts/FileContext";
|
||||
import { useFileActions } from "@app/contexts/file/fileHooks";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Z_INDEX_TOAST } from "@app/styles/zIndex";
|
||||
|
||||
interface DismissAllErrorsButtonProps {
|
||||
@@ -32,10 +32,9 @@ const DismissAllErrorsButton: React.FC<DismissAllErrorsButtonProps> = ({
|
||||
return (
|
||||
<Group className={className}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
variant="secondary"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
leftSection={<CloseIcon fontSize="small" />}
|
||||
onClick={handleDismissAllErrors}
|
||||
style={{
|
||||
position: "absolute",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Z_INDEX_AUTOMATE_DROPDOWN } from "@app/styles/zIndex";
|
||||
export interface DropdownItem {
|
||||
value: string;
|
||||
name: string;
|
||||
leftIcon?: ReactNode;
|
||||
leftSection?: ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -232,9 +232,9 @@ const DropdownListWithFooter: React.FC<DropdownListWithFooterProps> = ({
|
||||
}}
|
||||
>
|
||||
<Group gap="sm" style={{ flex: 1 }}>
|
||||
{item.leftIcon && (
|
||||
{item.leftSection && (
|
||||
<Box style={{ display: "flex", alignItems: "center" }}>
|
||||
{item.leftIcon}
|
||||
{item.leftSection}
|
||||
</Box>
|
||||
)}
|
||||
<Text size="sm">{item.name}</Text>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
PasswordInput,
|
||||
Group,
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { PasswordInput, Group, Tooltip, TextInput } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
|
||||
interface EditableSecretFieldProps {
|
||||
@@ -95,7 +90,7 @@ export default function EditableSecretField({
|
||||
<TextInput value="••••••••" disabled style={{ flex: 1 }} readOnly />
|
||||
<Tooltip label={t("editSecret")} withArrow>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
onClick={handleEdit}
|
||||
disabled={disabled}
|
||||
title={t("editableSecretField.edit", "Edit")}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
Button,
|
||||
PasswordInput,
|
||||
Group,
|
||||
} from "@mantine/core";
|
||||
import { Modal, Stack, Text, PasswordInput, Group } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type KeyboardEventHandler } from "react";
|
||||
import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from "@app/styles/zIndex";
|
||||
@@ -87,8 +81,8 @@ const EncryptedPdfUnlockModal = ({
|
||||
|
||||
<Group justify="space-between">
|
||||
<Button
|
||||
variant="light"
|
||||
color="var(--mantine-color-gray-8)"
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
onClick={onSkip}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
@@ -97,7 +91,7 @@ const EncryptedPdfUnlockModal = ({
|
||||
<Group gap="xs">
|
||||
{remainingCount > 0 && (
|
||||
<Button
|
||||
variant="light"
|
||||
variant="secondary"
|
||||
onClick={onUnlockAll}
|
||||
loading={isProcessing}
|
||||
disabled={password.trim().length === 0}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Text, Button, Stack } from "@mantine/core";
|
||||
import { Text, Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
@@ -128,7 +129,11 @@ export default class ErrorBoundary extends React.Component<
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Button onClick={this.retry} variant="light" mt="md">
|
||||
<Button
|
||||
onClick={this.retry}
|
||||
variant="secondary"
|
||||
style={{ marginTop: "var(--mantine-spacing-md)" }}
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Stack,
|
||||
Text,
|
||||
Group,
|
||||
Badge,
|
||||
Button,
|
||||
Box,
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { Card, Stack, Text, Group, Badge, Box, Tooltip } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import StorageIcon from "@mui/icons-material/Storage";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
@@ -114,8 +106,8 @@ const FileCard = ({
|
||||
<Tooltip label={t("fileCard.viewInViewer", "View in Viewer")}>
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
variant="tertiary"
|
||||
aria-label={t("fileCard.viewInViewer", "View in Viewer")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onView();
|
||||
@@ -131,8 +123,12 @@ const FileCard = ({
|
||||
>
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
color="orange"
|
||||
variant="tertiary"
|
||||
accent="warning"
|
||||
aria-label={t(
|
||||
"fileCard.openInFileEditor",
|
||||
"Open in File Editor",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
@@ -182,14 +178,14 @@ const FileCard = ({
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
color="red"
|
||||
size="xs"
|
||||
variant="light"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
mt={4}
|
||||
style={{ marginTop: 4 }}
|
||||
>
|
||||
{t("delete", "Remove")}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Menu, Loader, Group, Text, ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { Menu, Loader, Group, Text, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
@@ -102,10 +103,14 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
|
||||
withArrow
|
||||
>
|
||||
<ActionIcon
|
||||
component="div"
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
color="red"
|
||||
as="span"
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
accent="danger"
|
||||
aria-label={t(
|
||||
"fileDropdownMenu.closeFile",
|
||||
"Close file",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFileRemove(file.fileId as FileId);
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Group,
|
||||
Text,
|
||||
Button,
|
||||
TextInput,
|
||||
Select,
|
||||
} from "@mantine/core";
|
||||
import { Box, Flex, Group, Text, TextInput, Select } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import SortIcon from "@mui/icons-material/Sort";
|
||||
@@ -121,7 +114,7 @@ const FileGrid = ({
|
||||
</Group>
|
||||
|
||||
{onDeleteAll && (
|
||||
<Button color="red" size="sm" onClick={onDeleteAll}>
|
||||
<Button accent="danger" size="sm" onClick={onDeleteAll}>
|
||||
{t("fileManager.deleteAll", "Delete All")}
|
||||
</Button>
|
||||
)}
|
||||
@@ -179,7 +172,7 @@ const FileGrid = ({
|
||||
{/* Show All Button */}
|
||||
{hasMoreFiles && onShowAll && (
|
||||
<Group justify="center" mt="md">
|
||||
<Button variant="light" onClick={onShowAll}>
|
||||
<Button variant="secondary" onClick={onShowAll}>
|
||||
{t("fileManager.showAll", "Show All")} ({sortedFiles.length} files)
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Checkbox,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
Badge,
|
||||
SimpleGrid,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DocumentThumbnail from "@app/components/shared/filePreview/DocumentThumbnail";
|
||||
import { FileId } from "@app/types/file";
|
||||
@@ -151,10 +151,10 @@ const FilePickerModal = ({
|
||||
)}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Button size="xs" variant="light" onClick={selectAll}>
|
||||
<Button size="sm" variant="secondary" onClick={selectAll}>
|
||||
{t("pageEdit.selectAll", "Select All")}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={selectNone}>
|
||||
<Button size="sm" variant="secondary" onClick={selectNone}>
|
||||
{t("pageEdit.deselectAll", "Select None")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -249,7 +249,7 @@ const FilePickerModal = ({
|
||||
|
||||
{/* Action buttons */}
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button variant="light" onClick={onClose}>
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{t("close", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Box, Popover, ScrollArea, Text, Loader } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -566,9 +567,10 @@ export function FileSelectorPicker({
|
||||
const meta = buildMeta(stub);
|
||||
const isItemLoading = loadingId === stub.id;
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
key={stub.id}
|
||||
type="button"
|
||||
variant="tertiary"
|
||||
hover={false}
|
||||
className={styles.fileItem}
|
||||
onClick={() => void loadAndSelect(stub)}
|
||||
disabled={!!loadingId}
|
||||
@@ -579,6 +581,9 @@ export function FileSelectorPicker({
|
||||
})
|
||||
}
|
||||
onMouseLeave={() => setHoveredStub(null)}
|
||||
rightSection={
|
||||
isItemLoading ? <Loader size="xs" /> : undefined
|
||||
}
|
||||
>
|
||||
<div className={styles.fileItemContent}>
|
||||
<span className={styles.fileName} title={stub.name}>
|
||||
@@ -586,8 +591,7 @@ export function FileSelectorPicker({
|
||||
</span>
|
||||
{meta && <span className={styles.fileMeta}>{meta}</span>}
|
||||
</div>
|
||||
{isItemLoading && <Loader size="xs" />}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import React, {
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import { Loader, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useFileState, useFileActions } from "@app/contexts/file/fileHooks";
|
||||
@@ -961,26 +962,31 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
|
||||
<span className="file-sidebar-section-label">
|
||||
{t("fileSidebar.files", "Files")}
|
||||
</span>
|
||||
<button
|
||||
<ActionIcon
|
||||
variant="quiet"
|
||||
className="file-sidebar-section-btn file-sidebar-section-btn-external"
|
||||
onClick={() => navigate("/files")}
|
||||
title={t(
|
||||
"fileSidebar.openFileManager",
|
||||
"Browse all files & folders",
|
||||
)}
|
||||
type="button"
|
||||
aria-label={t(
|
||||
"fileSidebar.openFileManager",
|
||||
"Browse all files & folders",
|
||||
)}
|
||||
data-testid="open-files-page"
|
||||
>
|
||||
<OpenInNewIcon sx={{ fontSize: "1rem" }} />
|
||||
</button>
|
||||
<button
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="quiet"
|
||||
className="file-sidebar-section-btn file-sidebar-section-btn-add"
|
||||
onClick={() => nativeFileInputRef.current?.click()}
|
||||
title={t("fileSidebar.addFiles", "Add files")}
|
||||
type="button"
|
||||
aria-label={t("fileSidebar.addFiles", "Add files")}
|
||||
>
|
||||
<AddIcon sx={{ fontSize: "1rem" }} />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
</div>
|
||||
|
||||
{!stubsLoaded ? (
|
||||
@@ -989,35 +995,6 @@ const FileSidebar = forwardRef<HTMLDivElement, FileSidebarProps>(
|
||||
</div>
|
||||
) : filteredFileStubs.length > 0 ? (
|
||||
<div className="file-sidebar-file-list">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => nativeFileInputRef.current?.click()}
|
||||
data-testid="add-files-row"
|
||||
style={{
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
color: "var(--text-muted)",
|
||||
padding: "4px 6px",
|
||||
marginBottom: 4,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
fontSize: 12,
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = "var(--hover-bg)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = "transparent";
|
||||
}}
|
||||
>
|
||||
<AddIcon sx={{ fontSize: "0.9rem" }} />
|
||||
{t("fileSidebar.addFiles", "Add files")}
|
||||
</button>
|
||||
{filteredFileStubs.map((stub) => {
|
||||
const workbenchFileId = state.files.ids.find(
|
||||
(id) => (id as string) === (stub.id as string),
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
border: 1.5px solid var(--border-hover, #52525b);
|
||||
border: 1.5px solid var(--border-hover, var(--border-strong));
|
||||
}
|
||||
|
||||
.file-sidebar-file-item:hover .file-sidebar-file-checkbox-hover {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Menu, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";
|
||||
@@ -369,14 +370,15 @@ export function FileItem({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
className="file-sidebar-eye-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEyeClick(fileId, e);
|
||||
}}
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
aria-label={
|
||||
isViewedInViewer
|
||||
? t("fileSidebar.fileItem.closeViewer", "Close viewer")
|
||||
@@ -391,25 +393,25 @@ export function FileItem({
|
||||
className="file-sidebar-eye-closed"
|
||||
sx={{ fontSize: "1.1rem" }}
|
||||
/>
|
||||
</button>
|
||||
|
||||
</ActionIcon>
|
||||
{(onDelete ||
|
||||
onSaveToCloud ||
|
||||
(hasVersionHistory && onVersionHistory)) && (
|
||||
<Menu position="bottom-end" withinPortal shadow="md" width={190}>
|
||||
<Menu.Target>
|
||||
<button
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
className="file-sidebar-kebab-btn"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
aria-label={t(
|
||||
"fileSidebar.fileItem.moreActions",
|
||||
"More actions",
|
||||
)}
|
||||
>
|
||||
<MoreVertIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown onClick={(e) => e.stopPropagation()}>
|
||||
{hasVersionHistory && onVersionHistory && (
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
import { useRef } from "react";
|
||||
import { FileButton, Button } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FilePicker } from "@app/ui/FilePicker";
|
||||
import type { ButtonVariant } from "@app/ui/Button";
|
||||
|
||||
// Accept both shared DS variants and the legacy Mantine variant names that
|
||||
// existing callers still pass, mapping the latter onto the DS equivalents.
|
||||
type LegacyVariant =
|
||||
| "outline"
|
||||
| "filled"
|
||||
| "light"
|
||||
| "default"
|
||||
| "subtle"
|
||||
| "gradient";
|
||||
|
||||
const VARIANT_MAP: Record<LegacyVariant, ButtonVariant> = {
|
||||
filled: "primary",
|
||||
outline: "secondary",
|
||||
default: "secondary",
|
||||
light: "tertiary",
|
||||
subtle: "tertiary",
|
||||
gradient: "primary",
|
||||
};
|
||||
|
||||
function resolveVariant(variant: ButtonVariant | LegacyVariant): ButtonVariant {
|
||||
return variant in VARIANT_MAP
|
||||
? VARIANT_MAP[variant as LegacyVariant]
|
||||
: (variant as ButtonVariant);
|
||||
}
|
||||
|
||||
interface FileUploadButtonProps {
|
||||
file?: File;
|
||||
@@ -8,7 +34,7 @@ interface FileUploadButtonProps {
|
||||
accept?: string;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
variant?: "outline" | "filled" | "light" | "default" | "subtle" | "gradient";
|
||||
variant?: ButtonVariant | LegacyVariant;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
@@ -18,7 +44,7 @@ const FileUploadButton = ({
|
||||
accept,
|
||||
disabled = false,
|
||||
placeholder,
|
||||
variant = "outline",
|
||||
variant = "secondary",
|
||||
fullWidth = true,
|
||||
}: FileUploadButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -27,18 +53,16 @@ const FileUploadButton = ({
|
||||
const defaultPlaceholder = t("chooseFile", "Choose File");
|
||||
|
||||
return (
|
||||
<FileButton
|
||||
<FilePicker
|
||||
resetRef={resetRef}
|
||||
onChange={onChange}
|
||||
accept={accept}
|
||||
disabled={disabled}
|
||||
variant={resolveVariant(variant)}
|
||||
fullWidth={fullWidth}
|
||||
>
|
||||
{(props) => (
|
||||
<Button {...props} variant={variant} fullWidth={fullWidth} color="blue">
|
||||
{file ? file.name : placeholder || defaultPlaceholder}
|
||||
</Button>
|
||||
)}
|
||||
</FileButton>
|
||||
{file ? file.name : placeholder || defaultPlaceholder}
|
||||
</FilePicker>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
PasswordInput,
|
||||
Button,
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { Modal, Stack, Text, PasswordInput, Alert } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { accountService } from "@app/services/accountService";
|
||||
@@ -195,8 +189,8 @@ export default function FirstLoginModal({
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={
|
||||
!currentPassword ||
|
||||
@@ -205,7 +199,7 @@ export default function FirstLoginModal({
|
||||
newPassword.length < 8 ||
|
||||
confirmPassword.length < 8
|
||||
}
|
||||
mt="md"
|
||||
style={{ marginTop: "var(--mantine-spacing-md)" }}
|
||||
>
|
||||
{t("firstLogin.changePassword", "Change Password")}
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Flex } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useCookieConsent } from "@app/hooks/useCookieConsent";
|
||||
import { useFooterInfo } from "@app/hooks/useFooterInfo";
|
||||
import { Button } from "@app/ui/Button";
|
||||
|
||||
interface FooterProps {
|
||||
privacyPolicy?: string;
|
||||
@@ -53,8 +54,8 @@ export default function Footer({
|
||||
<div
|
||||
style={{
|
||||
height: "var(--footer-height)",
|
||||
backgroundColor: "var(--mantine-color-gray-1)",
|
||||
borderTop: "1px solid var(--mantine-color-gray-2)",
|
||||
backgroundColor: "var(--bg-surface)",
|
||||
borderTop: "1px solid var(--border-subtle)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
@@ -132,13 +133,14 @@ export default function Footer({
|
||||
</a>
|
||||
)}
|
||||
{finalAnalyticsEnabled && (
|
||||
<button
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="footer-link px-3"
|
||||
id="cookieBanner"
|
||||
onClick={showCookiePreferences}
|
||||
>
|
||||
{t("legal.showCookieBanner", "Cookie Preferences")}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { Tooltip } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import styles from "@app/components/shared/HoverActionMenu.module.css";
|
||||
import { Z_INDEX_HOVER_ACTION_MENU } from "@app/styles/zIndex";
|
||||
|
||||
@@ -60,10 +61,9 @@ const HoverActionMenu: React.FC<HoverActionMenuProps> = ({
|
||||
<Tooltip key={action.id} label={action.label}>
|
||||
<ActionIcon
|
||||
size="md"
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
disabled={action.disabled}
|
||||
onClick={action.onClick}
|
||||
c={action.color}
|
||||
aria-label={action.label}
|
||||
style={{ color: action.color || "var(--text-secondary)" }}
|
||||
data-tour={action.dataTour}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Paper, Group, Text, Button, ActionIcon, Stack } from "@mantine/core";
|
||||
import { Paper, Group, Text, Stack } from "@mantine/core";
|
||||
import { Button, type ButtonVariant, type ButtonAccent } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
|
||||
@@ -31,6 +33,40 @@ const toneStyles: Record<
|
||||
},
|
||||
};
|
||||
|
||||
function toSharedButtonVariant(
|
||||
variant: "light" | "filled" | "white" | "outline" | "subtle",
|
||||
): ButtonVariant {
|
||||
switch (variant) {
|
||||
case "filled":
|
||||
return "primary";
|
||||
case "outline":
|
||||
return "secondary";
|
||||
case "subtle":
|
||||
return "tertiary";
|
||||
case "light":
|
||||
case "white":
|
||||
default:
|
||||
return "secondary";
|
||||
}
|
||||
}
|
||||
|
||||
function toSharedButtonAccent(color: string | undefined): ButtonAccent {
|
||||
// Mantine colours may carry a shade suffix (e.g. "orange.7"); use the hue.
|
||||
const hue = (color ?? "").split(".")[0];
|
||||
switch (hue) {
|
||||
case "red":
|
||||
return "danger";
|
||||
case "green":
|
||||
return "success";
|
||||
case "yellow":
|
||||
case "orange":
|
||||
return "warning";
|
||||
case "blue":
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
}
|
||||
|
||||
interface InfoBannerProps {
|
||||
/**
|
||||
* Either a LocalIcon name (string) for the standard sized icon slot, or a
|
||||
@@ -98,7 +134,6 @@ export const InfoBanner: React.FC<InfoBannerProps> = ({
|
||||
|
||||
const iconSize = compact ? "1rem" : "1.2rem";
|
||||
const textSize = compact ? "xs" : "sm";
|
||||
const buttonSize = compact ? "xs" : "xs";
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@@ -167,11 +202,13 @@ export const InfoBanner: React.FC<InfoBannerProps> = ({
|
||||
<Group gap="xs" align="center" wrap="nowrap">
|
||||
{buttonText && onButtonClick && (
|
||||
<Button
|
||||
variant={buttonVariant}
|
||||
color={buttonColor ?? toneStyle.buttonColor}
|
||||
size={buttonSize}
|
||||
onClick={onButtonClick}
|
||||
variant={toSharedButtonVariant(buttonVariant)}
|
||||
accent={toSharedButtonAccent(
|
||||
buttonColor ?? toneStyle.buttonColor,
|
||||
)}
|
||||
size="sm"
|
||||
loading={loading}
|
||||
onClick={onButtonClick}
|
||||
leftSection={
|
||||
<LocalIcon
|
||||
icon={buttonIcon}
|
||||
@@ -179,19 +216,15 @@ export const InfoBanner: React.FC<InfoBannerProps> = ({
|
||||
height={compact ? "0.75rem" : "0.9rem"}
|
||||
/>
|
||||
}
|
||||
styles={
|
||||
buttonTextColor
|
||||
? { label: { color: buttonTextColor } }
|
||||
: undefined
|
||||
}
|
||||
style={buttonTextColor ? { color: buttonTextColor } : undefined}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
)}
|
||||
{dismissible && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color={closeIconColor ? undefined : "gray"}
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
onClick={handleDismiss}
|
||||
aria-label={t("infoBanner.dismiss", "Dismiss")}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
import { Button, Group, Tooltip, ActionIcon } from "@mantine/core";
|
||||
import { Group, Tooltip } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
||||
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
|
||||
@@ -30,7 +32,7 @@ export function LandingActions({
|
||||
<>
|
||||
<Group gap="sm" justify="center" wrap="wrap" mb="xs">
|
||||
<Button
|
||||
classNames={{ root: "landing-btn-primary" }}
|
||||
className="landing-btn-primary"
|
||||
leftSection={
|
||||
<LocalIcon
|
||||
icon={icons.uploadIconName}
|
||||
@@ -48,8 +50,8 @@ export function LandingActions({
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="default"
|
||||
classNames={{ root: "landing-btn-secondary" }}
|
||||
variant="secondary"
|
||||
className="landing-btn-secondary"
|
||||
leftSection={
|
||||
<LocalIcon
|
||||
icon="add"
|
||||
@@ -70,10 +72,9 @@ export function LandingActions({
|
||||
<Tooltip label={terminology.mobileUpload} position="bottom">
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
variant="default"
|
||||
radius="md"
|
||||
variant="secondary"
|
||||
aria-label={terminology.mobileUpload}
|
||||
classNames={{ root: "landing-btn-secondary landing-btn-icon" }}
|
||||
className="landing-btn-secondary landing-btn-icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onMobileUploadClick();
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
/* ── Hero text ───────────────────────────────────────────── */
|
||||
.landing-title {
|
||||
display: block;
|
||||
margin: 1.75rem auto 0.5rem;
|
||||
height: 3rem;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin: 1.75rem 0 0.5rem;
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.landing-subtitle {
|
||||
|
||||
@@ -8,7 +8,6 @@ import MobileUploadModal from "@app/components/shared/MobileUploadModal";
|
||||
import { openFilesFromDisk } from "@app/services/openFilesFromDisk";
|
||||
import { LandingDocumentStack } from "@app/components/shared/LandingDocumentStack";
|
||||
import { LandingActions } from "@app/components/shared/LandingActions";
|
||||
import { Wordmark } from "@app/components/shared/Wordmark";
|
||||
import "@app/components/shared/LandingPage.css";
|
||||
|
||||
const LandingPage = () => {
|
||||
@@ -80,10 +79,9 @@ const LandingPage = () => {
|
||||
>
|
||||
<LandingDocumentStack />
|
||||
|
||||
<Wordmark
|
||||
alt={t("landing.heroTitle", "Stirling PDF")}
|
||||
className="landing-title"
|
||||
/>
|
||||
<h3 className="landing-title">
|
||||
{t("landing.workbenchEmptyStateHero", "Drop a PDF anywhere")}
|
||||
</h3>
|
||||
<p className="landing-subtitle">
|
||||
{t(
|
||||
"landing.heroSubtitle",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Menu, Button, ActionIcon } from "@mantine/core";
|
||||
import { Menu } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { Tooltip } from "@app/components/shared/Tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { supportedLanguages, setUserLanguage } from "@app/i18n";
|
||||
@@ -73,50 +75,26 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
fullWidth
|
||||
onClick={disabled ? undefined : onClick}
|
||||
data-selected={isSelected}
|
||||
disabled={disabled}
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: "4px",
|
||||
minHeight: "32px",
|
||||
padding: "4px 8px",
|
||||
justifyContent: "flex-start",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
backgroundColor: isSelected
|
||||
? "light-dark(var(--mantine-color-blue-1), var(--mantine-color-blue-8))"
|
||||
: "transparent",
|
||||
color: disabled
|
||||
? "var(--text-muted)"
|
||||
: isSelected
|
||||
? "light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-3))"
|
||||
: "var(--text-primary)",
|
||||
transition: "all 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
"&:hover": !disabled
|
||||
? {
|
||||
backgroundColor: isSelected
|
||||
? "light-dark(var(--mantine-color-blue-2), var(--mantine-color-blue-7))"
|
||||
: "light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5))",
|
||||
transform: "translateY(-1px)",
|
||||
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
|
||||
}
|
||||
: {},
|
||||
},
|
||||
label: {
|
||||
fontSize: "13px",
|
||||
fontWeight: isSelected ? 600 : 400,
|
||||
textAlign: "left",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
position: "relative",
|
||||
zIndex: 2,
|
||||
},
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
fontWeight: isSelected ? 600 : 400,
|
||||
backgroundColor: isSelected
|
||||
? "light-dark(var(--mantine-color-blue-1), var(--mantine-color-blue-8))"
|
||||
: undefined,
|
||||
color: disabled
|
||||
? "var(--text-muted)"
|
||||
: isSelected
|
||||
? "light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-3))"
|
||||
: "var(--text-primary)",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@@ -272,43 +250,23 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
<Menu.Target>
|
||||
{compact ? (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
data-testid="language-selector-button"
|
||||
title={!opened && tooltip ? tooltip : undefined}
|
||||
styles={{
|
||||
root: {
|
||||
color: "var(--text-secondary)",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
"light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5))",
|
||||
},
|
||||
},
|
||||
}}
|
||||
aria-label={tooltip ?? currentLanguage}
|
||||
>
|
||||
<LocalIcon icon="language" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<Button
|
||||
variant="subtle"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
size="sm"
|
||||
data-testid="language-selector-button"
|
||||
leftSection={
|
||||
<LocalIcon icon="language" width="1.5rem" height="1.5rem" />
|
||||
}
|
||||
styles={{
|
||||
root: {
|
||||
border: "none",
|
||||
color: "var(--text-primary)",
|
||||
transition:
|
||||
"background-color 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
"light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5))",
|
||||
},
|
||||
},
|
||||
label: { fontSize: "12px", fontWeight: 500 },
|
||||
}}
|
||||
>
|
||||
<span className={styles.languageText}>{currentLanguage}</span>
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
Modal,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Markdown, { type Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
@@ -188,10 +188,10 @@ export default function LoginAgreementModal() {
|
||||
)}
|
||||
</Text>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Button variant="default" onClick={handleDecline}>
|
||||
<Button variant="secondary" onClick={handleDecline}>
|
||||
{t("loginAgreementDecline", "Decline")}
|
||||
</Button>
|
||||
<Button onClick={handleAccept}>
|
||||
<Button variant="primary" onClick={handleAccept}>
|
||||
{t("loginAgreementAccept", "Accept")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Box, Group, Text, Button } from "@mantine/core";
|
||||
import { Box, Group, Text } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface MultiSelectControlsProps {
|
||||
@@ -36,20 +37,20 @@ const MultiSelectControls = ({
|
||||
{selectedCount} {t("fileManager.filesSelected", "files")}
|
||||
</Text>
|
||||
<Group>
|
||||
<Button size="xs" variant="light" onClick={onClearSelection}>
|
||||
<Button variant="secondary" size="sm" onClick={onClearSelection}>
|
||||
{t("fileManager.clearSelection", "Clear files")}
|
||||
</Button>
|
||||
|
||||
{onAddToUpload && (
|
||||
<Button size="xs" color="green" onClick={onAddToUpload}>
|
||||
<Button accent="success" size="sm" onClick={onAddToUpload}>
|
||||
{t("fileManager.addToUpload", "Add to Upload")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onOpenInFileEditor && (
|
||||
<Button
|
||||
size="xs"
|
||||
color="orange"
|
||||
accent="warning"
|
||||
size="sm"
|
||||
onClick={onOpenInFileEditor}
|
||||
disabled={selectedCount === 0}
|
||||
>
|
||||
@@ -59,8 +60,7 @@ const MultiSelectControls = ({
|
||||
|
||||
{onOpenInPageEditor && (
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
size="sm"
|
||||
onClick={onOpenInPageEditor}
|
||||
disabled={selectedCount === 0}
|
||||
>
|
||||
@@ -69,7 +69,7 @@ const MultiSelectControls = ({
|
||||
)}
|
||||
|
||||
{onDeleteAll && (
|
||||
<Button size="xs" color="red" onClick={onDeleteAll}>
|
||||
<Button accent="danger" size="sm" onClick={onDeleteAll}>
|
||||
{t("fileManager.deleteAll", "Delete All")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { Modal, Text, Button, Group, Stack } from "@mantine/core";
|
||||
import { Modal, Text, Group, Stack } from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import { useNavigationGuard } from "@app/contexts/NavigationContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
@@ -103,10 +104,12 @@ const NavigationWarningModal = () => {
|
||||
<Group justify="space-between" gap="xl" visibleFrom="md">
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
variant="light"
|
||||
color="var(--mantine-color-gray-8)"
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
onClick={handleKeepWorking}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{
|
||||
width: BUTTON_WIDTH,
|
||||
}}
|
||||
leftSection={<ArrowBackIcon fontSize="small" />}
|
||||
>
|
||||
{t("keepWorking", "Keep Working")}
|
||||
@@ -114,19 +117,19 @@ const NavigationWarningModal = () => {
|
||||
</Group>
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="var(--mantine-color-red-9)"
|
||||
accent="danger"
|
||||
onClick={handleDiscardChanges}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{
|
||||
width: BUTTON_WIDTH,
|
||||
}}
|
||||
leftSection={<DeleteOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("discardChanges", "Discard Changes")}
|
||||
</Button>
|
||||
{hasApply && (
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleApplyAndContinue}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{ width: BUTTON_WIDTH }}
|
||||
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("applyAndContinue", "Apply & Leave")}
|
||||
@@ -134,9 +137,8 @@ const NavigationWarningModal = () => {
|
||||
)}
|
||||
{hasExport && (
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleExportAndContinue}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{ width: BUTTON_WIDTH }}
|
||||
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("exportAndContinue", "Export & Leave")}
|
||||
@@ -148,28 +150,28 @@ const NavigationWarningModal = () => {
|
||||
{/* Mobile layout: centered stack of 4 buttons */}
|
||||
<Stack align="center" gap="sm" hiddenFrom="md">
|
||||
<Button
|
||||
variant="light"
|
||||
color="var(--mantine-color-gray-8)"
|
||||
variant="secondary"
|
||||
accent="neutral"
|
||||
onClick={handleKeepWorking}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{ width: BUTTON_WIDTH }}
|
||||
leftSection={<ArrowBackIcon fontSize="small" />}
|
||||
>
|
||||
{t("keepWorking", "Keep Working")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="var(--mantine-color-red-9)"
|
||||
accent="danger"
|
||||
onClick={handleDiscardChanges}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{
|
||||
width: BUTTON_WIDTH,
|
||||
}}
|
||||
leftSection={<DeleteOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("discardChanges", "Discard Changes")}
|
||||
</Button>
|
||||
{hasApply && (
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleApplyAndContinue}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{ width: BUTTON_WIDTH }}
|
||||
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("applyAndContinue", "Apply & Leave")}
|
||||
@@ -177,9 +179,8 @@ const NavigationWarningModal = () => {
|
||||
)}
|
||||
{hasExport && (
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleExportAndContinue}
|
||||
w={BUTTON_WIDTH}
|
||||
style={{ width: BUTTON_WIDTH }}
|
||||
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
|
||||
>
|
||||
{t("exportAndContinue", "Export & Leave")}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import styles from "@app/components/shared/ObscuredOverlay/ObscuredOverlay.module.css";
|
||||
|
||||
type ObscuredOverlayProps = {
|
||||
@@ -34,13 +35,13 @@ export default function ObscuredOverlay({
|
||||
<div className={styles.overlayMessage}>{overlayMessage}</div>
|
||||
)}
|
||||
{buttonText && onButtonClick && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={onButtonClick}
|
||||
className={styles.overlayButton}
|
||||
>
|
||||
{buttonText}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Alert,
|
||||
TextInput,
|
||||
Paper,
|
||||
Select,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import ContentCopyRoundedIcon from "@mui/icons-material/ContentCopyRounded";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -253,8 +253,8 @@ const ShareFileModal: React.FC<ShareFileModalProps> = ({
|
||||
label={t("storageShare.linkLabel", "Share link")}
|
||||
rightSection={
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
leftSection={
|
||||
<ContentCopyRoundedIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
@@ -307,7 +307,7 @@ const ShareFileModal: React.FC<ShareFileModalProps> = ({
|
||||
</Paper>
|
||||
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={onClose} disabled={isWorking}>
|
||||
<Button variant="secondary" onClick={onClose} disabled={isWorking}>
|
||||
{t("cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
Button,
|
||||
Group,
|
||||
Alert,
|
||||
TextInput,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
ScrollArea,
|
||||
Select,
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@app/ui/Button";
|
||||
import ContentCopyRoundedIcon from "@mui/icons-material/ContentCopyRounded";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
@@ -648,7 +648,7 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
</Text>
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
onClick={() => setShowEmailWarning(false)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
@@ -731,9 +731,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
{confirmRemoveUser === user.username ? (
|
||||
<Group gap="xs">
|
||||
<Button
|
||||
variant="filled"
|
||||
size="xs"
|
||||
color="red"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
void handleRemoveUser(user.username);
|
||||
}}
|
||||
@@ -742,8 +741,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
{t("confirm", "Confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => setConfirmRemoveUser(null)}
|
||||
>
|
||||
{t("cancel", "Cancel")}
|
||||
@@ -751,9 +750,9 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
</Group>
|
||||
) : (
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="secondary"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
leftSection={
|
||||
<DeleteIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
@@ -813,8 +812,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
label={t("storageShare.linkLabel", "Share link")}
|
||||
rightSection={
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
leftSection={
|
||||
<ContentCopyRoundedIcon
|
||||
style={{ fontSize: 16 }}
|
||||
@@ -874,8 +873,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
</Stack>
|
||||
<Group gap="xs">
|
||||
<Button
|
||||
variant={isSelected ? "filled" : "light"}
|
||||
size="xs"
|
||||
variant={isSelected ? "secondary" : "primary"}
|
||||
size="sm"
|
||||
leftSection={
|
||||
<HistoryIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
@@ -898,9 +897,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
{confirmRevokeToken === link.token ? (
|
||||
<Group gap="xs">
|
||||
<Button
|
||||
variant="filled"
|
||||
size="xs"
|
||||
color="red"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
void handleRevokeLink(link.token);
|
||||
}}
|
||||
@@ -909,8 +907,8 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
{t("confirm", "Confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => setConfirmRevokeToken(null)}
|
||||
>
|
||||
{t("cancel", "Cancel")}
|
||||
@@ -918,9 +916,9 @@ const ShareManagementModal: React.FC<ShareManagementModalProps> = ({
|
||||
</Group>
|
||||
) : (
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="secondary"
|
||||
accent="danger"
|
||||
size="sm"
|
||||
leftSection={
|
||||
<DeleteIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import styles from "@app/components/shared/textInput/TextInput.module.css";
|
||||
|
||||
@@ -110,14 +111,14 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
{...props}
|
||||
/>
|
||||
{shouldShowClearButton && (
|
||||
<button
|
||||
type="button"
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
className={styles.clearButton}
|
||||
onClick={handleClear}
|
||||
aria-label={t("textInput.clear", "Clear input")}
|
||||
>
|
||||
<LocalIcon icon="close-rounded" width="1.25rem" height="1.25rem" />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { ActionIcon } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import "@app/components/shared/ToolPanelHeader.css";
|
||||
|
||||
@@ -38,12 +38,12 @@ export function ToolPanelHeader({
|
||||
{onClose && (
|
||||
<ActionIcon
|
||||
className="sui-panelhdr__close"
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
radius="xl"
|
||||
variant="tertiary"
|
||||
accent="neutral"
|
||||
shape="circle"
|
||||
size="md"
|
||||
onClick={onClose}
|
||||
aria-label={closeLabel}
|
||||
aria-label={closeLabel ?? ""}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -7,6 +7,7 @@ import React, {
|
||||
} from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { addEventListenerWithCleanup } from "@app/utils/genericUtils";
|
||||
import { useTooltipPosition } from "@app/hooks/useTooltipPosition";
|
||||
@@ -396,7 +397,8 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
}
|
||||
>
|
||||
{shouldShowCloseButton && (
|
||||
<button
|
||||
<ActionIcon
|
||||
variant="tertiary"
|
||||
className={styles["tooltip-pin-button"]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -407,7 +409,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
||||
aria-label={t("tooltip.close", "Close tooltip")}
|
||||
>
|
||||
<LocalIcon icon="close-rounded" width="1.25rem" height="1.25rem" />
|
||||
</button>
|
||||
</ActionIcon>
|
||||
)}
|
||||
{arrow && !sidebarTooltip && (
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useCallback, useMemo } from "react";
|
||||
import { SegmentedControl, Loader } from "@mantine/core";
|
||||
import { Loader } from "@mantine/core";
|
||||
import { SegmentedControl } from "@app/ui/SegmentedControl";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||
import GridViewIcon from "@mui/icons-material/GridView";
|
||||
import FolderIcon from "@mui/icons-material/Folder";
|
||||
@@ -205,46 +206,16 @@ const TopControls = ({
|
||||
<div className="flex justify-center">
|
||||
<SegmentedControl
|
||||
data-tour="view-switcher"
|
||||
data={viewOptions}
|
||||
options={viewOptions}
|
||||
value={currentView}
|
||||
onChange={handleViewChange}
|
||||
color="blue"
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
style={{
|
||||
transition: "all 0.2s ease",
|
||||
opacity: switchingTo ? 0.8 : 1,
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: "0 0 16px 16px",
|
||||
height: "1.8rem",
|
||||
backgroundColor: "var(--bg-toolbar)",
|
||||
border: "1px solid var(--border-default)",
|
||||
borderTop: "none",
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||||
outline: "1px solid rgba(0, 0, 0, 0.1)",
|
||||
outlineOffset: "-1px",
|
||||
padding: "0 0",
|
||||
gap: "0",
|
||||
},
|
||||
control: {
|
||||
borderRadius: "0 0 16px 16px",
|
||||
padding: "0",
|
||||
border: "none",
|
||||
},
|
||||
indicator: {
|
||||
borderRadius: "0 0 16px 16px",
|
||||
height: "100%",
|
||||
top: "0rem",
|
||||
margin: "0",
|
||||
border: "none",
|
||||
},
|
||||
label: {
|
||||
paddingTop: "0",
|
||||
paddingBottom: "0",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user