ZERADO
This commit is contained in:
@@ -1,32 +1,79 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<!-- TOOLBAR (padrão Sakai CRUD) -->
|
||||
<Toolbar class="mb-4">
|
||||
<template #start>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-xl font-semibold leading-none">Grupos de Pacientes</div>
|
||||
<small class="text-color-secondary mt-1">
|
||||
Organize seus pacientes por grupos. Alguns grupos são padrões do sistema e não podem ser alterados.
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
<Toast />
|
||||
|
||||
<template #end>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
label="Excluir selecionados"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
outlined
|
||||
:disabled="!selectedGroups || !selectedGroups.length"
|
||||
@click="confirmDeleteSelected"
|
||||
/>
|
||||
<Button label="Adicionar" icon="pi pi-plus" @click="openCreate" />
|
||||
</div>
|
||||
</template>
|
||||
</Toolbar>
|
||||
<!-- Sentinel para detecção de sticky -->
|
||||
<div ref="headerSentinelRef" class="grp-sentinel" />
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-4">
|
||||
<!-- Hero Header sticky -->
|
||||
<div ref="headerEl" class="grp-hero mx-3 md:mx-5 mb-4" :class="{ 'grp-hero--stuck': headerStuck }">
|
||||
<div class="grp-hero__blobs" aria-hidden="true">
|
||||
<div class="grp-hero__blob grp-hero__blob--1" />
|
||||
<div class="grp-hero__blob grp-hero__blob--2" />
|
||||
</div>
|
||||
|
||||
<!-- Linha 1 -->
|
||||
<div class="grp-hero__row1">
|
||||
<div class="grp-hero__brand">
|
||||
<div class="grp-hero__icon"><i class="pi pi-sitemap text-lg" /></div>
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="grp-hero__title">Grupos</div>
|
||||
<Tag :value="`${groups.length}`" severity="secondary" />
|
||||
</div>
|
||||
<div class="grp-hero__sub">Organize seus pacientes por grupos temáticos ou clínicos</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop (≥1200px) -->
|
||||
<div class="hidden xl:flex items-center gap-2 shrink-0">
|
||||
<Button
|
||||
v-if="selectedGroups?.length"
|
||||
label="Excluir selecionados"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
outlined
|
||||
class="rounded-full"
|
||||
@click="confirmDeleteSelected"
|
||||
/>
|
||||
<Button label="Novo" icon="pi pi-plus" class="rounded-full" @click="openCreate" />
|
||||
<Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" title="Recarregar" @click="fetchAll" />
|
||||
</div>
|
||||
|
||||
<!-- Mobile (<1200px) -->
|
||||
<div class="flex xl:hidden items-center shrink-0">
|
||||
<Button label="Ações" icon="pi pi-ellipsis-v" severity="secondary" size="small" class="rounded-full" @click="(e) => grpMobileMenuRef.toggle(e)" />
|
||||
<Menu ref="grpMobileMenuRef" :model="grpMobileMenuItems" :popup="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divisor -->
|
||||
<Divider class="grp-hero__divider my-2" />
|
||||
|
||||
<!-- Linha 2: busca (oculta no mobile) -->
|
||||
<div class="grp-hero__row2">
|
||||
<InputGroup class="w-72">
|
||||
<InputGroupAddon><i class="pi pi-search" /></InputGroupAddon>
|
||||
<InputText v-model="filters.global.value" placeholder="Buscar grupo..." :disabled="loading" />
|
||||
<Button v-if="filters.global.value" icon="pi pi-trash" severity="danger" title="Limpar" @click="filters.global.value = null" />
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog de busca (mobile) -->
|
||||
<Dialog v-model:visible="grpSearchDlgOpen" modal :draggable="false" header="Buscar grupo" class="w-[94vw] max-w-sm">
|
||||
<div class="pt-1">
|
||||
<InputGroup>
|
||||
<InputGroupAddon><i class="pi pi-search" /></InputGroupAddon>
|
||||
<InputText v-model="filters.global.value" placeholder="Nome do grupo..." autofocus />
|
||||
<Button v-if="filters.global.value" icon="pi pi-trash" severity="danger" @click="filters.global.value = null" />
|
||||
</InputGroup>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="Fechar" severity="secondary" outlined class="rounded-full" @click="grpSearchDlgOpen = false" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-4 px-3 md:px-5 mb-5">
|
||||
<!-- LEFT: TABLE -->
|
||||
<div class="w-full lg:basis-[70%] lg:max-w-[70%]">
|
||||
<Card class="h-full">
|
||||
@@ -48,16 +95,9 @@
|
||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} grupos"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-wrap gap-2 items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">Lista de Grupos</span>
|
||||
<Tag :value="`${groups.length} grupos`" severity="secondary" />
|
||||
</div>
|
||||
|
||||
<IconField>
|
||||
<InputIcon><i class="pi pi-search" /></InputIcon>
|
||||
<InputText v-model="filters.global.value" placeholder="Buscar grupos..." class="w-64" />
|
||||
</IconField>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">Lista de Grupos</span>
|
||||
<Tag :value="`${groups.length} grupos`" severity="secondary" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -73,7 +113,18 @@
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="nome" header="Nome" sortable style="min-width: 16rem" />
|
||||
<Column field="nome" header="Nome" sortable style="min-width: 16rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="data.cor"
|
||||
class="inline-block w-3 h-3 rounded-full flex-shrink-0"
|
||||
:style="colorStyle(data.cor)"
|
||||
/>
|
||||
<span>{{ data.nome }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Origem" sortable sortField="is_system" style="min-width: 12rem">
|
||||
<template #body="{ data }">
|
||||
@@ -116,14 +167,24 @@
|
||||
outlined
|
||||
rounded
|
||||
disabled
|
||||
v-tooltip.top="'Grupo padrão do sistema (inalterável)'"
|
||||
title="Grupo padrão do sistema (inalterável)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty>
|
||||
Nenhum grupo encontrado.
|
||||
<div class="py-10 text-center">
|
||||
<div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-2xl bg-[var(--primary-color)]/10 text-[var(--primary-color)]">
|
||||
<i class="pi pi-search text-xl" />
|
||||
</div>
|
||||
<div class="font-semibold">Nenhum grupo encontrado</div>
|
||||
<div class="mt-1 text-sm text-color-secondary">Tente limpar o filtro ou crie um novo grupo.</div>
|
||||
<div class="mt-4 flex justify-center gap-2">
|
||||
<Button severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar filtro" @click="filters.global.value = null" />
|
||||
<Button icon="pi pi-plus" label="Criar grupo" @click="openCreate" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
@@ -192,31 +253,118 @@
|
||||
|
||||
<!-- DIALOG CREATE / EDIT -->
|
||||
<Dialog
|
||||
v-model:visible="dlg.open"
|
||||
:header="dlg.mode === 'create' ? 'Criar Grupo' : 'Editar Grupo'"
|
||||
modal
|
||||
:style="{ width: '520px', maxWidth: '92vw' }"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
<label class="block mb-2">Nome do Grupo</label>
|
||||
<InputText v-model="dlg.nome" class="w-full" :disabled="dlg.saving" />
|
||||
<small class="text-color-secondary">
|
||||
Grupos “Padrão” são do sistema e não podem ser editados.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
v-model:visible="dlg.open"
|
||||
modal
|
||||
:draggable="false"
|
||||
:closable="!dlg.saving"
|
||||
:dismissableMask="!dlg.saving"
|
||||
class="grp-dialog w-[96vw] max-w-lg"
|
||||
:pt="{ content: { class: 'p-0' }, header: { class: 'pb-0' } }"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex w-full items-center justify-between gap-3 px-1">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="grp-dlg-dot shrink-0" :style="{ backgroundColor: dlgPreviewColor }" />
|
||||
<div class="min-w-0">
|
||||
<div class="text-base font-semibold truncate">
|
||||
{{ dlg.nome || (dlg.mode === 'create' ? 'Novo grupo' : 'Editar grupo') }}
|
||||
</div>
|
||||
<div class="text-xs opacity-50">
|
||||
{{ dlg.mode === 'create' ? 'Criar tipo de grupo' : 'Editar tipo de grupo' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button label="Cancelar" text :disabled="dlg.saving" @click="dlg.open = false" />
|
||||
<Button
|
||||
:label="dlg.mode === 'create' ? 'Criar' : 'Salvar'"
|
||||
:loading="dlg.saving"
|
||||
@click="saveDialog"
|
||||
:disabled="!String(dlg.nome || '').trim()"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
outlined
|
||||
class="rounded-full"
|
||||
:disabled="dlg.saving"
|
||||
@click="dlg.open = false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
label="Salvar"
|
||||
icon="pi pi-check"
|
||||
class="rounded-full"
|
||||
:loading="dlg.saving"
|
||||
:disabled="!String(dlg.nome || '').trim()"
|
||||
@click="saveDialog"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Banner de preview -->
|
||||
<div class="grp-dlg-banner" :style="{ backgroundColor: dlgPreviewColor }">
|
||||
<span class="grp-dlg-banner__pill">{{ dlg.nome || 'Nome do grupo' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Corpo -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
|
||||
<!-- Nome -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon>
|
||||
<i class="pi pi-sitemap" />
|
||||
</InputIcon>
|
||||
|
||||
<InputText
|
||||
id="grp-nome"
|
||||
v-model="dlg.nome"
|
||||
class="w-full"
|
||||
variant="filled"
|
||||
:disabled="dlg.saving"
|
||||
@keydown.enter.prevent="saveDialog"
|
||||
/>
|
||||
</IconField>
|
||||
|
||||
<label for="grp-nome">Nome do grupo *</label>
|
||||
</FloatLabel>
|
||||
|
||||
<!-- Cor -->
|
||||
<div class="grp-dlg-section">
|
||||
<div class="grp-dlg-section__label">Cor</div>
|
||||
|
||||
<div class="grp-dlg-palette">
|
||||
|
||||
<button
|
||||
v-for="p in dlgPresetColors"
|
||||
:key="p.bg"
|
||||
class="grp-dlg-swatch"
|
||||
:class="{ 'grp-dlg-swatch--active': dlg.cor === p.bg }"
|
||||
:style="{ backgroundColor: `#${p.bg}` }"
|
||||
:title="p.name"
|
||||
:disabled="dlg.saving"
|
||||
@click="dlg.cor = p.bg"
|
||||
>
|
||||
<i v-if="dlg.cor === p.bg" class="pi pi-check grp-dlg-swatch__check" />
|
||||
</button>
|
||||
|
||||
<!-- Custom ColorPicker -->
|
||||
<div class="grp-dlg-swatch grp-dlg-swatch--custom" title="Cor personalizada">
|
||||
<ColorPicker v-model="dlg.cor" format="hex" :disabled="dlg.saving" />
|
||||
</div>
|
||||
|
||||
<!-- Limpar cor -->
|
||||
<button
|
||||
v-if="dlg.cor"
|
||||
class="grp-dlg-swatch grp-dlg-swatch--clear"
|
||||
title="Sem cor"
|
||||
:disabled="dlg.saving"
|
||||
@click="dlg.cor = ''"
|
||||
>
|
||||
<i class="pi pi-times text-xs" />
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<!-- ✅ DIALOG PACIENTES (com botão Abrir) -->
|
||||
<Dialog
|
||||
@@ -253,8 +401,12 @@
|
||||
</Message>
|
||||
|
||||
<div v-else>
|
||||
<div v-if="patientsDialog.items.length === 0" class="text-color-secondary">
|
||||
Nenhum paciente associado a este grupo.
|
||||
<div v-if="patientsDialog.items.length === 0" class="py-10 text-center">
|
||||
<div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-2xl bg-[var(--primary-color)]/10 text-[var(--primary-color)]">
|
||||
<i class="pi pi-users text-xl" />
|
||||
</div>
|
||||
<div class="font-semibold">Nenhum paciente neste grupo</div>
|
||||
<div class="mt-1 text-sm text-color-secondary">Associe pacientes a este grupo na página de pacientes.</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
@@ -299,7 +451,16 @@
|
||||
</Column>
|
||||
|
||||
<template #empty>
|
||||
<div class="text-color-secondary py-5">Nenhum resultado para "{{ patientsDialog.search }}".</div>
|
||||
<div class="py-10 text-center">
|
||||
<div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-2xl bg-[var(--primary-color)]/10 text-[var(--primary-color)]">
|
||||
<i class="pi pi-search text-xl" />
|
||||
</div>
|
||||
<div class="font-semibold">Nenhum resultado</div>
|
||||
<div class="mt-1 text-sm text-color-secondary">Nenhum paciente corresponde a "{{ patientsDialog.search }}".</div>
|
||||
<div class="mt-4">
|
||||
<Button severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar busca" @click="patientsDialog.search = ''" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
@@ -311,17 +472,17 @@
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog />
|
||||
</div>
|
||||
<ConfirmDialog />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import Menu from 'primevue/menu'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
import {
|
||||
@@ -335,6 +496,24 @@ const router = useRouter()
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
|
||||
// ── Hero sticky ───────────────────────────────────────────
|
||||
const headerEl = ref(null)
|
||||
const headerSentinelRef = ref(null)
|
||||
const headerStuck = ref(false)
|
||||
let _observer = null
|
||||
|
||||
// ── Mobile menu ───────────────────────────────────────────
|
||||
const grpMobileMenuRef = ref(null)
|
||||
const grpSearchDlgOpen = ref(false)
|
||||
|
||||
const grpMobileMenuItems = computed(() => [
|
||||
{ label: 'Adicionar grupo', icon: 'pi pi-plus', command: () => openCreate() },
|
||||
{ label: 'Buscar', icon: 'pi pi-search', command: () => { grpSearchDlgOpen.value = true } },
|
||||
{ separator: true },
|
||||
...(selectedGroups.value?.length ? [{ label: 'Excluir selecionados', icon: 'pi pi-trash', command: () => confirmDeleteSelected() }, { separator: true }] : []),
|
||||
{ label: 'Recarregar', icon: 'pi pi-refresh', command: () => fetchAll() }
|
||||
])
|
||||
|
||||
const dt = ref(null)
|
||||
const loading = ref(false)
|
||||
const groups = ref([])
|
||||
@@ -350,9 +529,30 @@ const dlg = reactive({
|
||||
mode: 'create', // 'create' | 'edit'
|
||||
id: '',
|
||||
nome: '',
|
||||
cor: '',
|
||||
saving: false
|
||||
})
|
||||
|
||||
const dlgPresetColors = [
|
||||
{ bg: '6366f1', name: 'Índigo' },
|
||||
{ bg: '8b5cf6', name: 'Violeta' },
|
||||
{ bg: 'ec4899', name: 'Rosa' },
|
||||
{ bg: 'ef4444', name: 'Vermelho' },
|
||||
{ bg: 'f97316', name: 'Laranja' },
|
||||
{ bg: 'eab308', name: 'Amarelo' },
|
||||
{ bg: '22c55e', name: 'Verde' },
|
||||
{ bg: '14b8a6', name: 'Teal' },
|
||||
{ bg: '3b82f6', name: 'Azul' },
|
||||
{ bg: '06b6d4', name: 'Ciano' },
|
||||
{ bg: '64748b', name: 'Ardósia' },
|
||||
{ bg: '292524', name: 'Escuro' },
|
||||
]
|
||||
|
||||
const dlgPreviewColor = computed(() => {
|
||||
if (!dlg.cor) return '#64748b'
|
||||
return dlg.cor.startsWith('#') ? dlg.cor : `#${dlg.cor}`
|
||||
})
|
||||
|
||||
const patientsDialog = reactive({
|
||||
open: false,
|
||||
loading: false,
|
||||
@@ -428,6 +628,12 @@ function patientsLabel (n) {
|
||||
return n === 1 ? '1 paciente' : `${n} pacientes`
|
||||
}
|
||||
|
||||
function colorStyle (cor) {
|
||||
if (!cor) return {}
|
||||
const hex = String(cor).startsWith('#') ? cor : '#' + cor
|
||||
return { background: hex }
|
||||
}
|
||||
|
||||
function humanizeError (err) {
|
||||
const msg = err?.message || err?.error_description || String(err) || 'Erro inesperado.'
|
||||
const code = err?.code
|
||||
@@ -482,6 +688,7 @@ function openCreate () {
|
||||
dlg.mode = 'create'
|
||||
dlg.id = ''
|
||||
dlg.nome = ''
|
||||
dlg.cor = ''
|
||||
}
|
||||
|
||||
function openEdit (row) {
|
||||
@@ -489,6 +696,7 @@ function openEdit (row) {
|
||||
dlg.mode = 'edit'
|
||||
dlg.id = row.id
|
||||
dlg.nome = row.nome
|
||||
dlg.cor = row.cor || ''
|
||||
}
|
||||
|
||||
async function saveDialog () {
|
||||
@@ -502,13 +710,16 @@ async function saveDialog () {
|
||||
return
|
||||
}
|
||||
|
||||
const corRaw = String(dlg.cor || '').trim()
|
||||
const cor = corRaw ? (corRaw.startsWith('#') ? corRaw : `#${corRaw}`) : null
|
||||
|
||||
dlg.saving = true
|
||||
try {
|
||||
if (dlg.mode === 'create') {
|
||||
await createGroup(nome)
|
||||
await createGroup(nome, cor)
|
||||
toast.add({ severity: 'success', summary: 'Sucesso', detail: 'Grupo criado.', life: 2500 })
|
||||
} else {
|
||||
await updateGroup(dlg.id, nome)
|
||||
await updateGroup(dlg.id, nome, cor)
|
||||
toast.add({ severity: 'success', summary: 'Sucesso', detail: 'Grupo atualizado.', life: 2500 })
|
||||
}
|
||||
dlg.open = false
|
||||
@@ -653,12 +864,125 @@ function abrirPaciente (patient) {
|
||||
router.push(`/features/patients/cadastro/${patient.id}`)
|
||||
}
|
||||
|
||||
onMounted(fetchAll)
|
||||
onMounted(() => {
|
||||
const rootMargin = `${document.querySelector('.l2-main') ? '0px' : '-56px'} 0px 0px 0px`
|
||||
_observer = new IntersectionObserver(
|
||||
([entry]) => { headerStuck.value = !entry.isIntersecting },
|
||||
{ threshold: 0, rootMargin }
|
||||
)
|
||||
if (headerSentinelRef.value) _observer.observe(headerSentinelRef.value)
|
||||
fetchAll()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => { _observer?.disconnect() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active { transition: opacity .14s ease; }
|
||||
.fade-enter-from,
|
||||
.fade-leave-to { opacity: 0; }
|
||||
/* ── Hero ────────────────────────────────────────── */
|
||||
.grp-sentinel { height: 1px; }
|
||||
|
||||
.grp-hero {
|
||||
position: sticky;
|
||||
top: var(--layout-sticky-top, 56px);
|
||||
z-index: 20;
|
||||
overflow: hidden;
|
||||
border-radius: 1.75rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
background: var(--surface-card);
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
.grp-hero--stuck {
|
||||
margin-left: 0; margin-right: 0;
|
||||
border-top-left-radius: 0; border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.grp-hero__blobs { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
|
||||
.grp-hero__blob { position: absolute; border-radius: 50%; filter: blur(70px); }
|
||||
.grp-hero__blob--1 { width: 18rem; height: 18rem; top: -4rem; right: -3rem; background: rgba(16,185,129,0.10); }
|
||||
.grp-hero__blob--2 { width: 20rem; height: 20rem; top: 0.5rem; left: -5rem; background: rgba(99,102,241,0.09); }
|
||||
|
||||
.grp-hero__row1 { position: relative; z-index: 1; display: flex; align-items: center; gap: 1rem; }
|
||||
.grp-hero__brand { display: flex; align-items: center; gap: 0.75rem; flex: 1; min-width: 0; }
|
||||
.grp-hero__icon {
|
||||
display: grid; place-items: center;
|
||||
width: 2.5rem; height: 2.5rem; border-radius: 0.875rem; flex-shrink: 0;
|
||||
background: color-mix(in srgb, var(--p-primary-500, #6366f1) 12%, transparent);
|
||||
color: var(--p-primary-500, #6366f1);
|
||||
}
|
||||
.grp-hero__title { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.02em; color: var(--text-color); }
|
||||
.grp-hero__sub { font-size: 0.78rem; color: var(--text-color-secondary); margin-top: 2px; }
|
||||
|
||||
.grp-hero__row2 {
|
||||
position: relative; z-index: 1;
|
||||
display: flex; align-items: center; gap: 0.75rem;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.grp-hero__divider,
|
||||
.grp-hero__row2 { display: none; }
|
||||
}
|
||||
|
||||
/* ── Dialog ──────────────────────────────────────── */
|
||||
.grp-dlg-dot {
|
||||
width: 14px; height: 14px; border-radius: 50%;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
box-shadow: 0 0 0 3px rgba(0,0,0,0.08);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.grp-dlg-banner {
|
||||
height: 72px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: background-color 0.25s ease;
|
||||
}
|
||||
.grp-dlg-banner__pill {
|
||||
font-size: 1rem; font-weight: 700; letter-spacing: -0.02em;
|
||||
padding: 0.35rem 1.1rem;
|
||||
background: rgba(0,0,0,0.15);
|
||||
border-radius: 999px;
|
||||
backdrop-filter: blur(4px);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grp-dlg-section {
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 1.25rem;
|
||||
background: var(--surface-card);
|
||||
padding: 1rem;
|
||||
}
|
||||
.grp-dlg-section__label {
|
||||
font-size: 0.7rem; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
opacity: 0.45; margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.grp-dlg-palette { display: flex; flex-wrap: wrap; gap: 0.45rem; }
|
||||
|
||||
.grp-dlg-swatch {
|
||||
width: 28px; height: 28px; border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
display: grid; place-items: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
|
||||
}
|
||||
.grp-dlg-swatch:hover:not(:disabled) { transform: scale(1.18); box-shadow: 0 3px 10px rgba(0,0,0,0.2); }
|
||||
.grp-dlg-swatch--active {
|
||||
border-color: var(--surface-0, #fff);
|
||||
box-shadow: 0 0 0 2px var(--text-color);
|
||||
}
|
||||
.grp-dlg-swatch__check { font-size: 0.6rem; color: #fff; font-weight: 900; }
|
||||
.grp-dlg-swatch--custom {
|
||||
background: conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
|
||||
overflow: hidden;
|
||||
}
|
||||
.grp-dlg-swatch--custom :deep(.p-colorpicker-preview) {
|
||||
width: 100%; height: 100%; border: none; border-radius: 50%; opacity: 0;
|
||||
}
|
||||
.grp-dlg-swatch--clear {
|
||||
background: var(--surface-border);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
/* Fade */
|
||||
.fade-enter-active, .fade-leave-active { transition: opacity .14s ease; }
|
||||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user