From 5809e3eebc52a338c2937247f865a991fb8a5f20 Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Fri, 8 May 2026 16:09:02 +0800 Subject: [PATCH] feat: remove drag-and-drop functionality for TOTP and website rows; update styles and translations for improved user experience --- package-lock.json | 80 +--------- package.json | 3 - webapp/src/components/TotpCodesPage.tsx | 142 ++--------------- webapp/src/components/vault/VaultEditor.tsx | 159 ++++++-------------- webapp/src/lib/i18n/locales/en.ts | 3 +- webapp/src/lib/i18n/locales/es.ts | 3 +- webapp/src/lib/i18n/locales/ru.ts | 3 +- webapp/src/lib/i18n/locales/zh-CN.ts | 3 +- webapp/src/lib/i18n/locales/zh-TW.ts | 3 +- webapp/src/styles/management.css | 39 +---- webapp/src/styles/vault.css | 44 +----- 11 files changed, 80 insertions(+), 402 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce88264..b048615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,6 @@ "version": "1.5.1", "license": "LGPL-3.0", "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", "@noble/hashes": "^2.0.1", "@tanstack/react-query": "^5.90.21", "@zip.js/zip.js": "^2.8.22", @@ -525,59 +522,6 @@ "node": ">=12" } }, - "node_modules/@dnd-kit/accessibility": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", - "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/core": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.3.1.tgz", - "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "license": "MIT", - "dependencies": { - "@dnd-kit/accessibility": "^3.1.1", - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/sortable": { - "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz", - "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", - "license": "MIT", - "dependencies": { - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.3.0", - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/utilities": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz", - "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -3518,19 +3462,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", @@ -3688,13 +3619,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", @@ -3944,7 +3868,9 @@ "version": "2.8.1", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "dev": true, + "license": "0BSD", + "optional": true }, "node_modules/tsx": { "version": "4.21.0", diff --git a/package.json b/package.json index 105378f..b85a477 100644 --- a/package.json +++ b/package.json @@ -56,9 +56,6 @@ "wrangler": "^4.71.0" }, "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", "@noble/hashes": "^2.0.1", "@tanstack/react-query": "^5.90.21", "@zip.js/zip.js": "^2.8.22", diff --git a/webapp/src/components/TotpCodesPage.tsx b/webapp/src/components/TotpCodesPage.tsx index 3935c8a..9ee9c80 100644 --- a/webapp/src/components/TotpCodesPage.tsx +++ b/webapp/src/components/TotpCodesPage.tsx @@ -1,22 +1,5 @@ -import type { JSX } from 'preact'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; -import { Clipboard, Globe, GripVertical } from 'lucide-preact'; -import { - closestCenter, - DndContext, - type DragEndEvent, - PointerSensor, - TouchSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { - arrayMove, - rectSortingStrategy, - SortableContext, - useSortable, -} from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; +import { Clipboard, Globe } from 'lucide-preact'; import { copyTextToClipboard as copyTextWithFeedback } from '@/lib/clipboard'; import { calcTotpNow } from '@/lib/crypto'; import { t } from '@/lib/i18n'; @@ -34,7 +17,6 @@ interface TotpCodesPageProps { const TOTP_PERIOD_SECONDS = 30; const TOTP_RING_RADIUS = 14; const TOTP_RING_CIRCUMFERENCE = 2 * Math.PI * TOTP_RING_RADIUS; -const TOTP_ORDER_STORAGE_KEY = 'nodewarden.totp-order'; const TOTP_REFRESH_BATCH_SIZE = 16; function getTotpTimeState(): { windowId: number; remain: number } { const epoch = Math.floor(Date.now() / 1000); @@ -55,39 +37,18 @@ function TotpListIcon({ cipher }: { cipher: Cipher }) { return } />; } -interface SortableTotpRowProps { +interface TotpRowProps { cipher: Cipher; live: { code: string; remain: number } | null; onCopy: (value: string) => void; } -function SortableTotpRow(props: SortableTotpRowProps) { - const { attributes, listeners, setActivatorNodeRef, setNodeRef, transform, transition, isDragging } = useSortable({ - id: props.cipher.id, - }); - const dragButtonAttributes = attributes as JSX.HTMLAttributes; - - const style = { - transform: CSS.Transform.toString(transform), - transition, - }; - +function TotpRow(props: TotpRowProps) { const name = props.cipher.decName || props.cipher.name || t('txt_no_name'); const username = props.cipher.login?.decUsername || ''; return ( -
- +
@@ -135,30 +96,7 @@ export default function TotpCodesPage(props: TotpCodesPageProps) { const [totpCodes, setTotpCodes] = useState>({}); const [remainingSeconds, setRemainingSeconds] = useState(() => getTotpTimeState().remain); const [columnCount, setColumnCount] = useState(1); - const [orderedIds, setOrderedIds] = useState(() => { - if (typeof window === 'undefined') return []; - try { - const parsed = JSON.parse(String(window.localStorage.getItem(TOTP_ORDER_STORAGE_KEY) || '[]')); - return Array.isArray(parsed) ? parsed.map((id) => String(id || '').trim()).filter(Boolean) : []; - } catch { - return []; - } - }); const listRef = useRef(null); - const hasLoadedTotpItemsRef = useRef(false); - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - distance: 6, - }, - }), - useSensor(TouchSensor, { - activationConstraint: { - delay: 120, - tolerance: 8, - }, - }), - ); async function copyToClipboard(value: string): Promise { await copyTextWithFeedback(value, { successMessage: t('txt_code_copied') }); @@ -169,7 +107,7 @@ export default function TotpCodesPage(props: TotpCodesPageProps) { [] ); - const baseTotpItems = useMemo( + const totpItems = useMemo( () => props.ciphers .filter((cipher) => isCipherVisibleInNormalVault(cipher) && !!cipher.login?.decTotp) @@ -181,46 +119,6 @@ export default function TotpCodesPage(props: TotpCodesPageProps) { [props.ciphers, nameCollator] ); - const totpItems = useMemo(() => { - if (!baseTotpItems.length) return []; - const orderMap = new Map(orderedIds.map((id, index) => [id, index])); - return [...baseTotpItems].sort((a, b) => { - const orderA = orderMap.get(a.id); - const orderB = orderMap.get(b.id); - if (orderA != null && orderB != null) return orderA - orderB; - if (orderA != null) return -1; - if (orderB != null) return 1; - const nameA = (a.decName || a.name || '').trim(); - const nameB = (b.decName || b.name || '').trim(); - return nameCollator.compare(nameA, nameB); - }); - }, [baseTotpItems, orderedIds, nameCollator]); - - const sortableTotpItems = useMemo(() => totpItems.map((cipher) => cipher.id), [totpItems]); - - useEffect(() => { - if (!baseTotpItems.length) return; - hasLoadedTotpItemsRef.current = true; - const validIds = new Set(baseTotpItems.map((cipher) => cipher.id)); - setOrderedIds((prev) => { - const filtered = prev.filter((id) => validIds.has(id)); - const missing = baseTotpItems.map((cipher) => cipher.id).filter((id) => !filtered.includes(id)); - const next = [...filtered, ...missing]; - if (next.length === prev.length && next.every((id, index) => id === prev[index])) return prev; - return next; - }); - }, [baseTotpItems]); - - useEffect(() => { - if (typeof window === 'undefined') return; - if (!hasLoadedTotpItemsRef.current) return; - try { - window.localStorage.setItem(TOTP_ORDER_STORAGE_KEY, JSON.stringify(orderedIds)); - } catch { - // ignore storage write failures - } - }, [orderedIds]); - useEffect(() => { if (!totpItems.length) { setTotpCodes({}); @@ -307,16 +205,6 @@ export default function TotpCodesPage(props: TotpCodesPageProps) { return () => observer.disconnect(); }, []); - const handleDragEnd = (event: DragEndEvent) => { - const activeId = String(event.active.id); - const overId = event.over ? String(event.over.id) : null; - if (!overId || activeId === overId) return; - const fromIndex = orderedIds.indexOf(activeId); - const toIndex = orderedIds.indexOf(overId); - if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return; - setOrderedIds((prev) => arrayMove(prev, fromIndex, toIndex)); - }; - return (
@@ -330,18 +218,14 @@ export default function TotpCodesPage(props: TotpCodesPageProps) { > {!totpItems.length && props.loading && } {!totpItems.length && !props.loading &&
{t('txt_no_verification_codes')}
} - - - {totpItems.map((cipher) => ( - void copyToClipboard(value)} - /> - ))} - - + {totpItems.map((cipher) => ( + void copyToClipboard(value)} + /> + ))}
diff --git a/webapp/src/components/vault/VaultEditor.tsx b/webapp/src/components/vault/VaultEditor.tsx index 02e0acf..2648525 100644 --- a/webapp/src/components/vault/VaultEditor.tsx +++ b/webapp/src/components/vault/VaultEditor.tsx @@ -1,25 +1,8 @@ -import type { JSX, RefObject } from 'preact'; +import type { RefObject } from 'preact'; import { createPortal } from 'preact/compat'; -import { CheckCheck, Download, GripVertical, Paperclip, Plus, QrCode, RefreshCw, Star, StarOff, Trash2, Upload, X } from 'lucide-preact'; +import { ArrowDown, ArrowUp, CheckCheck, Download, Paperclip, Plus, QrCode, RefreshCw, Star, StarOff, Trash2, Upload, X } from 'lucide-preact'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useDialogLifecycle } from '@/components/ConfirmDialog'; -import { - closestCenter, - DndContext, - type DragEndEvent, - type DragStartEvent, - PointerSensor, - TouchSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { - SortableContext, - arrayMove, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; import type { Cipher, Folder, VaultDraft, VaultDraftField } from '@/lib/types'; import { t } from '@/lib/i18n'; import { @@ -67,46 +50,45 @@ interface VaultEditorProps { onDeleteSelected: () => void; } -interface SortableWebsiteRowProps { - id: string; +interface WebsiteRowProps { uriEntry: VaultDraft['loginUris'][number]; index: number; canRemove: boolean; - isDragging: boolean; + canMoveUp: boolean; + canMoveDown: boolean; onUpdateUri: (index: number, value: string) => void; onUpdateMatch: (index: number, value: number | null) => void; + onMove: (fromIndex: number, toIndex: number) => void; onRemove: (index: number) => void; } -function SortableWebsiteRow(props: SortableWebsiteRowProps) { +function WebsiteRow(props: WebsiteRowProps) { const websiteMatchOptions = getWebsiteMatchOptions(); - const { attributes, listeners, setActivatorNodeRef, setNodeRef, transform, transition, isDragging } = useSortable({ - id: props.id, - }); - const dragButtonAttributes = attributes as JSX.HTMLAttributes; - - const style = { - transform: CSS.Transform.toString(transform), - transition, - }; return ( -
- +
+
+ + +
(null); const totpQrFileRef = useRef(null); const totpQrStreamRef = useRef(null); const totpQrFrameRef = useRef(null); - const [uriItemIds, setUriItemIds] = useState([]); - const [activeUriId, setActiveUriId] = useState(null); const [totpQrOpen, setTotpQrOpen] = useState(false); const [totpQrStatus, setTotpQrStatus] = useState(''); const [totpQrBusy, setTotpQrBusy] = useState(false); useDialogLifecycle(totpQrOpen, () => setTotpQrOpen(false)); - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - distance: 6, - }, - }), - useSensor(TouchSensor, { - activationConstraint: { - delay: 120, - tolerance: 8, - }, - }), - ); - - const createUriId = () => `login-uri-${uriIdSeedRef.current++}`; const stopTotpQrScanner = () => { if (totpQrFrameRef.current != null) { @@ -222,21 +186,6 @@ export default function VaultEditor(props: VaultEditorProps) { } }; - useEffect(() => { - setUriItemIds((prev) => { - if (prev.length === props.draft.loginUris.length) return prev; - if (prev.length < props.draft.loginUris.length) { - return [...prev, ...Array.from({ length: props.draft.loginUris.length - prev.length }, () => createUriId())]; - } - return prev.slice(0, props.draft.loginUris.length); - }); - }, [props.draft.loginUris.length]); - - useEffect(() => { - setUriItemIds(props.draft.loginUris.map(() => createUriId())); - setActiveUriId(null); - }, [props.draft.id, props.isCreating]); - useEffect(() => { if (!totpQrOpen) { stopTotpQrScanner(); @@ -324,28 +273,15 @@ export default function VaultEditor(props: VaultEditorProps) { }); const addLoginUri = () => { - setUriItemIds((prev) => [...prev, createUriId()]); props.onUpdateDraft({ loginUris: [...props.draft.loginUris, createEmptyLoginUri()] }); }; const removeLoginUri = (index: number) => { - setUriItemIds((prev) => prev.filter((_, itemIndex) => itemIndex !== index)); props.onUpdateDraft({ loginUris: props.draft.loginUris.filter((_, itemIndex) => itemIndex !== index) }); }; - const handleWebsiteDragStart = (event: DragStartEvent) => { - setActiveUriId(String(event.active.id)); - }; - - const handleWebsiteDragEnd = (event: DragEndEvent) => { - const activeId = String(event.active.id); - const overId = event.over ? String(event.over.id) : null; - setActiveUriId(null); - if (!overId || activeId === overId) return; - const fromIndex = uriItemIds.indexOf(activeId); - const toIndex = uriItemIds.indexOf(overId); - if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return; - setUriItemIds((prev) => arrayMove(prev, fromIndex, toIndex)); + const moveLoginUri = (fromIndex: number, toIndex: number) => { + if (fromIndex < 0 || toIndex < 0 || fromIndex >= props.draft.loginUris.length || toIndex >= props.draft.loginUris.length || fromIndex === toIndex) return; props.onReorderDraftLoginUri(fromIndex, toIndex); }; @@ -435,23 +371,20 @@ export default function VaultEditor(props: VaultEditorProps) { {t('txt_add_website')}
- - - {props.draft.loginUris.map((uriEntry, index) => ( - 1} - isDragging={activeUriId === uriItemIds[index]} - onUpdateUri={props.onUpdateDraftLoginUri} - onUpdateMatch={props.onUpdateDraftLoginUriMatch} - onRemove={removeLoginUri} - /> - ))} - - + {props.draft.loginUris.map((uriEntry, index) => ( + 0} + canMoveDown={index < props.draft.loginUris.length - 1} + canRemove={props.draft.loginUris.length > 1} + onUpdateUri={props.onUpdateDraftLoginUri} + onUpdateMatch={props.onUpdateDraftLoginUriMatch} + onMove={moveLoginUri} + onRemove={removeLoginUri} + /> + ))} {props.draft.loginFido2Credentials.length > 0 && ( <>
diff --git a/webapp/src/lib/i18n/locales/en.ts b/webapp/src/lib/i18n/locales/en.ts index 6aeb7e4..ebb60be 100644 --- a/webapp/src/lib/i18n/locales/en.ts +++ b/webapp/src/lib/i18n/locales/en.ts @@ -550,8 +550,9 @@ const en: Record = { "txt_master_password_reprompt_2": "Master Password Reprompt", "txt_max_access_count": "Max Access Count", "txt_middle_name": "Middle Name", - "txt_drag_to_reorder": "Drag to reorder", "txt_move": "Move", + "txt_move_up": "Move up", + "txt_move_down": "Move down", "txt_move_selected_items": "Move Selected Items", "txt_moved_selected_items": "Moved selected items", "txt_name": "Name", diff --git a/webapp/src/lib/i18n/locales/es.ts b/webapp/src/lib/i18n/locales/es.ts index b711653..b6d968e 100644 --- a/webapp/src/lib/i18n/locales/es.ts +++ b/webapp/src/lib/i18n/locales/es.ts @@ -550,8 +550,9 @@ const es: Record = { "txt_master_password_reprompt_2": "Solicitar contraseña maestra de nuevo", "txt_max_access_count": "Número máximo de accesos", "txt_middle_name": "Segundo nombre", - "txt_drag_to_reorder": "Arrastre para reordenar", "txt_move": "Mover", + "txt_move_up": "Mover arriba", + "txt_move_down": "Mover abajo", "txt_move_selected_items": "Mover elementos seleccionados", "txt_moved_selected_items": "Elementos seleccionados movidos", "txt_name": "Nombre", diff --git a/webapp/src/lib/i18n/locales/ru.ts b/webapp/src/lib/i18n/locales/ru.ts index 2829b14..ed1fdf7 100644 --- a/webapp/src/lib/i18n/locales/ru.ts +++ b/webapp/src/lib/i18n/locales/ru.ts @@ -550,8 +550,9 @@ const ru: Record = { "txt_master_password_reprompt_2": "Повторный запрос мастер-пароля", "txt_max_access_count": "Максимальное количество доступов", "txt_middle_name": "Второе имя", - "txt_drag_to_reorder": "Перетащите, чтобы изменить порядок", "txt_move": "Переместить", + "txt_move_up": "Переместить вверх", + "txt_move_down": "Переместить вниз", "txt_move_selected_items": "Переместить выбранные элементы", "txt_moved_selected_items": "Перемещены выбранные элементы", "txt_name": "Имя", diff --git a/webapp/src/lib/i18n/locales/zh-CN.ts b/webapp/src/lib/i18n/locales/zh-CN.ts index 54abf07..cdf373f 100644 --- a/webapp/src/lib/i18n/locales/zh-CN.ts +++ b/webapp/src/lib/i18n/locales/zh-CN.ts @@ -550,8 +550,9 @@ const zhCN: Record = { "txt_master_password_reprompt_2": "主密码二次确认", "txt_max_access_count": "最大访问次数", "txt_middle_name": "中间名", - "txt_drag_to_reorder": "拖动调整顺序", "txt_move": "移动", + "txt_move_up": "上移", + "txt_move_down": "下移", "txt_move_selected_items": "移动所选项目", "txt_moved_selected_items": "已移动所选项目", "txt_name": "名称", diff --git a/webapp/src/lib/i18n/locales/zh-TW.ts b/webapp/src/lib/i18n/locales/zh-TW.ts index 71da06b..7d6a240 100644 --- a/webapp/src/lib/i18n/locales/zh-TW.ts +++ b/webapp/src/lib/i18n/locales/zh-TW.ts @@ -550,8 +550,9 @@ const zhTW: Record = { "txt_master_password_reprompt_2": "主密碼二次確認", "txt_max_access_count": "最大訪問次數", "txt_middle_name": "中間名", - "txt_drag_to_reorder": "拖動調整順序", "txt_move": "移動", + "txt_move_up": "上移", + "txt_move_down": "下移", "txt_move_selected_items": "移動所選項目", "txt_moved_selected_items": "已移動所選項目", "txt_name": "名稱", diff --git a/webapp/src/styles/management.css b/webapp/src/styles/management.css index bd06b23..f22ace6 100644 --- a/webapp/src/styles/management.css +++ b/webapp/src/styles/management.css @@ -587,46 +587,21 @@ opacity var(--dur-fast) var(--ease-smooth); } -.website-row.is-dragging { - @apply opacity-50; - border-color: rgba(37, 99, 235, 0.24); - background: color-mix(in srgb, var(--panel-soft) 92%, white 8%); - box-shadow: var(--shadow-sm); +.website-order-actions { + @apply grid gap-1; } -.website-drag-btn { - @apply relative h-12 w-7 min-w-7 cursor-grab gap-0 overflow-visible rounded-[10px] p-0 opacity-[0.82]; +.website-order-btn { + @apply h-[22px] w-7 min-w-7 gap-0 rounded-[8px] p-0; color: var(--muted); - border-color: transparent; - background: transparent; - box-shadow: none; - touch-action: none; - -webkit-user-select: none; - user-select: none; } -.website-drag-btn:hover { +.website-order-btn:hover:not(:disabled) { color: var(--primary-strong); - border-color: transparent; - background: transparent; - box-shadow: none; - opacity: 1; } -.website-drag-btn:active { - cursor: grabbing; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.website-drag-btn::before { - content: ''; - @apply absolute -inset-2 rounded-xl; -} - -.website-drag-btn .btn-icon { - @apply opacity-90; +.website-order-btn:disabled { + @apply opacity-35; } .website-match-select { diff --git a/webapp/src/styles/vault.css b/webapp/src/styles/vault.css index 81a2b1c..63cfdc1 100644 --- a/webapp/src/styles/vault.css +++ b/webapp/src/styles/vault.css @@ -779,7 +779,7 @@ .totp-code-row { @apply grid w-full min-w-0 max-w-none items-center gap-2.5 rounded-xl border p-3; - grid-template-columns: auto minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr) auto; border-color: #e2e8f0; background: #f8fafc; transition: @@ -790,52 +790,10 @@ opacity var(--dur-fast) var(--ease-smooth); } -.totp-code-row.is-dragging { - @apply z-[2]; - border-color: rgba(37, 99, 235, 0.3); - background: color-mix(in srgb, var(--panel) 88%, white 12%); - box-shadow: 0 18px 36px rgba(15, 23, 42, 0.14); -} - .totp-code-info { @apply flex min-w-0 items-center gap-2.5; } -.totp-drag-btn { - @apply relative h-[34px] w-6 min-w-6 cursor-grab self-center overflow-visible rounded-[10px] p-0 opacity-[0.82]; - color: var(--muted); - touch-action: none; - -webkit-user-select: none; - user-select: none; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.totp-drag-btn:hover { - color: var(--primary-strong); - border-color: transparent; - background: transparent; - box-shadow: none; - opacity: 1; -} - -.totp-drag-btn:active { - cursor: grabbing; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.totp-drag-btn::before { - content: ''; - @apply absolute -inset-2.5 rounded-xl; -} - -.totp-drag-btn .btn-icon { - @apply opacity-90; -} - .totp-code-main { @apply flex min-w-0 shrink-0 items-center gap-1.5; }