mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 13:26:41 +02:00
feat(contacts): add duplicate detection, photo upload, and batch operations
- Add duplicate detection service with merge functionality - Find duplicates by email, phone, or name - Merge contacts with field selection UI - Dismiss false positives - Add contact photo upload - Upload photos to MinIO/S3 storage - Display photos in all contact views - Delete photo functionality - Add batch operations for multiple contacts - Selection mode with checkboxes in all views - Batch delete, archive, and favorite actions - Fixed-height action bar to prevent layout shifts - Add multiple contact view modes - List view, Grid view, Alphabet view - View mode toggle component - Sort by first/last name - Increase avatar sizes across all views 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
23c2d85f6e
commit
dd40bb40e7
41 changed files with 5174 additions and 619 deletions
|
|
@ -21,87 +21,39 @@
|
|||
class: className = '',
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
// Base card classes using Tailwind
|
||||
const baseCardClasses =
|
||||
'rounded-2xl overflow-hidden shadow-md border backdrop-blur-xl ' +
|
||||
'bg-white/85 border-black/10 ' +
|
||||
'dark:bg-white/[0.06] dark:border-white/10 dark:shadow-lg';
|
||||
|
||||
const dangerCardClasses =
|
||||
'rounded-2xl overflow-hidden shadow-md border backdrop-blur-xl ' +
|
||||
'bg-red-500/[0.08] border-red-500/30 ' +
|
||||
'dark:bg-red-500/[0.12] dark:border-red-500/25 dark:shadow-lg';
|
||||
|
||||
const headerClasses =
|
||||
'px-5 py-4 border-b border-black/[0.08] dark:border-white/10';
|
||||
|
||||
const dangerHeaderClasses =
|
||||
'px-5 py-4 border-b border-red-500/20 bg-red-500/10';
|
||||
</script>
|
||||
|
||||
<div class="settings-card settings-card--{variant} {className}">
|
||||
<div class="{variant === 'danger' ? dangerCardClasses : baseCardClasses} {className}">
|
||||
{#if title || description}
|
||||
<header class="settings-card__header">
|
||||
<header class="{variant === 'danger' ? dangerHeaderClasses : headerClasses}">
|
||||
{#if title}
|
||||
<h3 class="settings-card__title">{title}</h3>
|
||||
<h3 class="text-base font-semibold text-foreground {variant === 'danger' ? 'text-red-500 dark:text-red-400' : ''}">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="settings-card__description">{description}</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">{description}</p>
|
||||
{/if}
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<div class="settings-card__content">
|
||||
<div class="flex flex-col">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-card {
|
||||
/* Glass effect */
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.settings-card--danger {
|
||||
border-color: hsl(var(--destructive) / 0.3);
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card--danger {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
|
||||
.settings-card__header {
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-card__header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-card--danger .settings-card__header {
|
||||
border-bottom-color: hsl(var(--destructive) / 0.2);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.settings-card__title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-card--danger .settings-card__title {
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.settings-card__description {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.25rem 0 0 0;
|
||||
}
|
||||
|
||||
.settings-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -35,91 +35,112 @@
|
|||
}: Props = $props();
|
||||
|
||||
const isClickable = $derived(!!href || !!onclick);
|
||||
|
||||
// Tailwind classes
|
||||
const baseRowClasses =
|
||||
'flex items-center justify-between gap-4 px-5 py-4 bg-transparent w-full text-left no-underline transition-all duration-200';
|
||||
|
||||
const borderClasses = 'border-b border-black/[0.08] dark:border-white/10 last:border-b-0';
|
||||
|
||||
const clickableClasses = 'cursor-pointer hover:bg-black/[0.04] dark:hover:bg-white/[0.06]';
|
||||
|
||||
const disabledClasses = 'opacity-50 cursor-not-allowed pointer-events-none';
|
||||
|
||||
const iconClasses =
|
||||
'flex items-center justify-center flex-shrink-0 w-9 h-9 rounded-[0.625rem] bg-black/[0.04] dark:bg-white/[0.08] text-primary';
|
||||
|
||||
function getRowClasses(isBordered: boolean, isClick: boolean, isDisabled: boolean): string {
|
||||
let classes = baseRowClasses;
|
||||
if (isBordered) classes += ' ' + borderClasses;
|
||||
if (isClick) classes += ' ' + clickableClasses;
|
||||
if (isDisabled) classes += ' ' + disabledClasses;
|
||||
return classes;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
class="settings-row {border ? 'settings-row--border' : ''} settings-row--clickable {disabled
|
||||
? 'settings-row--disabled'
|
||||
: ''} {className}"
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
<a {href} class="{getRowClasses(border, true, disabled)} {className}">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
<span class={iconClasses}>
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-[0.9375rem] font-medium text-gray-700 dark:text-gray-100">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
<span class="text-[0.8125rem] text-gray-500 dark:text-gray-400 leading-snug"
|
||||
>{description}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row__control">
|
||||
<div class="flex items-center flex-shrink-0">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<svg class="settings-row__chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
{:else if onclick}
|
||||
<button
|
||||
type="button"
|
||||
{onclick}
|
||||
class="settings-row {border ? 'settings-row--border' : ''} settings-row--clickable {disabled
|
||||
? 'settings-row--disabled'
|
||||
: ''} {className}"
|
||||
{disabled}
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
<button type="button" {onclick} class="{getRowClasses(border, true, disabled)} {className}" {disabled}>
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
<span class={iconClasses}>
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-[0.9375rem] font-medium text-gray-700 dark:text-gray-100">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
<span class="text-[0.8125rem] text-gray-500 dark:text-gray-400 leading-snug"
|
||||
>{description}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row__control">
|
||||
<div class="flex items-center flex-shrink-0">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<svg class="settings-row__chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{:else}
|
||||
<div
|
||||
class="settings-row {border ? 'settings-row--border' : ''} {disabled
|
||||
? 'settings-row--disabled'
|
||||
: ''} {className}"
|
||||
>
|
||||
<div class="settings-row__content">
|
||||
<div class="{getRowClasses(border, false, disabled)} {className}">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
{#if icon}
|
||||
<span class="settings-row__icon">
|
||||
<span class={iconClasses}>
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-row__text">
|
||||
<span class="settings-row__label">{label}</span>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-[0.9375rem] font-medium text-gray-700 dark:text-gray-100">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-row__description">{description}</span>
|
||||
<span class="text-[0.8125rem] text-gray-500 dark:text-gray-400 leading-snug"
|
||||
>{description}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if children}
|
||||
<div class="settings-row__control">
|
||||
<div class="flex items-center flex-shrink-0">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -127,120 +148,9 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
.settings-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-row--border {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row--border {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-row--border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-row--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-row--clickable:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row--clickable:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.settings-row--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.settings-row__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-row__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-row__icon :global(svg) {
|
||||
/* Keep SVG sizing for icons passed via snippet */
|
||||
:global(svg) {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.settings-row__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-row__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-row__description {
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__description {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.settings-row__control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-row__chevron {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-row__chevron {
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,72 +15,23 @@
|
|||
let { title, icon, class: className = '', children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<section class="settings-section {className}">
|
||||
<section class="flex flex-col gap-3 {className}">
|
||||
{#if title}
|
||||
<header class="settings-section__header">
|
||||
<header class="flex items-center gap-2 pl-1">
|
||||
{#if icon}
|
||||
<span class="settings-section__icon">
|
||||
<span
|
||||
class="flex items-center justify-center w-7 h-7 rounded-lg bg-black/[0.04] dark:bg-white/[0.08] text-primary [&>svg]:w-4 [&>svg]:h-4"
|
||||
>
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<h2 class="settings-section__title">{title}</h2>
|
||||
<h2 class="text-[0.9375rem] font-semibold text-gray-700 dark:text-gray-100 m-0 tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
<div class="settings-section__content">
|
||||
<div class="flex flex-col gap-2">
|
||||
{@render children()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.settings-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.settings-section__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-section__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-section__icon :global(svg) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.settings-section__title {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-section__title {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-section__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -36,23 +36,31 @@
|
|||
onToggle(!isOn);
|
||||
}
|
||||
}
|
||||
|
||||
// Tailwind classes
|
||||
const baseClasses = 'flex items-center justify-between gap-4 px-5 py-4';
|
||||
const borderClasses = 'border-b border-black/[0.08] dark:border-white/10 last:border-b-0';
|
||||
const disabledClasses = 'opacity-50 cursor-not-allowed';
|
||||
|
||||
const iconClasses =
|
||||
'flex items-center justify-center flex-shrink-0 w-9 h-9 rounded-[0.625rem] bg-black/[0.04] dark:bg-white/[0.08] text-primary [&>svg]:w-[1.125rem] [&>svg]:h-[1.125rem]';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="settings-toggle {border ? 'settings-toggle--border' : ''} {disabled
|
||||
? 'settings-toggle--disabled'
|
||||
: ''} {className}"
|
||||
class="{baseClasses} {border ? borderClasses : ''} {disabled ? disabledClasses : ''} {className}"
|
||||
>
|
||||
<div class="settings-toggle__content">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
{#if icon}
|
||||
<span class="settings-toggle__icon">
|
||||
<span class={iconClasses}>
|
||||
{@render icon()}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="settings-toggle__text">
|
||||
<span class="settings-toggle__label">{label}</span>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-[0.9375rem] font-medium text-gray-700 dark:text-gray-100">{label}</span>
|
||||
{#if description}
|
||||
<span class="settings-toggle__description">{description}</span>
|
||||
<span class="text-[0.8125rem] text-gray-500 dark:text-gray-400 leading-snug"
|
||||
>{description}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,165 +68,20 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={handleToggle}
|
||||
class="settings-toggle__switch {isOn ? 'settings-toggle__switch--on' : ''}"
|
||||
class="relative w-12 h-7 rounded-full border flex-shrink-0 transition-all duration-200
|
||||
{isOn
|
||||
? 'bg-primary border-primary shadow-[0_0_0_2px_hsl(var(--primary)/0.2)] dark:shadow-[0_0_0_2px_hsl(var(--primary)/0.3)]'
|
||||
: 'bg-black/[0.08] border-black/10 dark:bg-white/[0.12] dark:border-white/[0.15]'}
|
||||
{!disabled ? 'cursor-pointer hover:border-black/20 dark:hover:border-white/25' : 'cursor-not-allowed'}
|
||||
focus-visible:outline-2 focus-visible:outline-primary/40 focus-visible:outline-offset-2"
|
||||
role="switch"
|
||||
aria-checked={isOn}
|
||||
aria-label={label}
|
||||
{disabled}
|
||||
>
|
||||
<span class="settings-toggle__thumb"></span>
|
||||
<span
|
||||
class="absolute top-[1px] left-[1px] w-6 h-6 rounded-full bg-white shadow-md transition-transform duration-200
|
||||
{isOn ? 'translate-x-5' : ''}"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.settings-toggle--border {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle--border {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-toggle--border:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-toggle--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-toggle__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-toggle__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.625rem;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__icon {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-toggle__icon :global(svg) {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.settings-toggle__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-toggle__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__label {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.settings-toggle__description {
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__description {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Toggle Switch - Glass style */
|
||||
.settings-toggle__switch {
|
||||
position: relative;
|
||||
width: 3rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__switch {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.settings-toggle__switch:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-toggle__switch--on {
|
||||
background: hsl(var(--primary));
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__switch--on {
|
||||
background: hsl(var(--primary));
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.3);
|
||||
}
|
||||
|
||||
.settings-toggle__thumb {
|
||||
position: absolute;
|
||||
top: 0.0625rem;
|
||||
left: 0.0625rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 9999px;
|
||||
background-color: white;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.15),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-toggle__switch--on .settings-toggle__thumb {
|
||||
transform: translateX(1.25rem);
|
||||
}
|
||||
|
||||
.settings-toggle__switch:hover:not(:disabled) {
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
:global(.dark) .settings-toggle__switch:hover:not(:disabled) {
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.settings-toggle__switch--on:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.settings-toggle__switch:focus-visible {
|
||||
outline: 2px solid hsl(var(--primary) / 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue