/
45case
/
ptable
Обзор
Документация
Войти
/
45case
/
ptable
Код
Запросы
3
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
dev
src/entities/element/ui/ElementCard.vue
282 строки
8 KB
Olya
Страница Электроны + сайдбар с характеритстиками (Степень окисления, Конфигурация, Развёрнутая, Уровни энергии, ВЗМО)
24 июл 2026, 11:54
24 июл 2026, 11:54
3cf1d48
Код
Авторство
О чём код?
<script setup lang="ts"> import { computed } from "vue"; import type { ElementState } from "@/entities/temperature"; import { formatElementMass } from "../lib/formatElementMass"; import { getElementNameFitStyle } from "../lib/getElementNameFitStyle"; import type { ElementPhaseAtStp, PeriodicElement } from "../model/elements"; import { getElementPhaseLabel } from "../model/phase"; const props = defineProps<{ element: PeriodicElement; dimmed?: boolean; highlighted?: boolean; isHighlighted?: boolean; isMuted?: boolean; isSelected?: boolean; isWide?: boolean; /** Ptable Discovered: element not yet known at the selected year. */ isUndiscovered?: boolean; phase?: ElementPhaseAtStp; state?: ElementState; temperatureState?: ElementState; /** When false, category color classes are not applied (Electrons page uses blocks). */ useCategoryColor?: boolean; blockClass?: string; blockHighlighted?: boolean; blockDimmed?: boolean; cardTitle?: string; /** Replaces atomic mass in the card footer (e.g. oxidation states on Electrons). */ valueText?: string; /** When set with valueText, footer is styled as an article link (opens via card select). */ valueHref?: string; /** Appends `u` to the default atomic-mass footer (Atomic mass property). */ showMassUnit?: boolean; /** Overrides category background (numeric property heatmaps). */ surfaceColor?: string; /** Ink when surfaceColor needs contrast (e.g. Madelung exception red). */ surfaceInk?: string; }>(); defineEmits<{ select: [element: PeriodicElement]; }>(); const resolvedPhase = computed<ElementPhaseAtStp>( () => props.temperatureState ?? props.state ?? props.phase ?? "unknown", ); const phaseLabel = computed(() => getElementPhaseLabel(resolvedPhase.value)); const displayMass = computed(() => formatElementMass(props.element.atomicMass, { atomicNumber: props.element.atomicNumber, source: props.element.atomicMassWeight, ...(props.showMassUnit ? { unit: "u" } : {}), }), ); const displayValue = computed(() => props.valueText !== undefined ? props.valueText : displayMass.value, ); const nameFitStyle = computed(() => getElementNameFitStyle(props.element.name, props.isWide)); const resolvedTitle = computed( () => props.cardTitle ?? `${props.element.name} · ${phaseLabel.value}`, ); const applyCategoryColor = computed( () => props.useCategoryColor !== false && !props.surfaceColor, ); const hasCustomValue = computed(() => props.valueText !== undefined); const surfaceStyle = computed(() => { if (!props.surfaceColor) { return undefined; } const style: Record<string, string> = { "--element-card-background": props.surfaceColor, }; if (props.surfaceInk) { style.color = props.surfaceInk; style["--element-card-ink-strong"] = props.surfaceInk; } return style; }); </script> <template> <article :title="resolvedTitle" :data-state="state" class="element-card cursor-pointer overflow-hidden border text-left shadow-[var(--table-shadow-rest)]" :class="[ applyCategoryColor ? element.category : null, blockClass, { 'element-card--wide': isWide, 'element-card--custom-value': hasCustomValue, highlighted, dimmed, 'is-muted': isMuted, 'is-highlighted': isHighlighted, 'is-selected': isSelected, 'is-undiscovered': isUndiscovered, 'block-highlighted': blockHighlighted, 'block-dimmed': blockDimmed, }, ]" :style="surfaceStyle" @click="$emit('select', element)" > <b class="element-card__number">{{ element.atomicNumber }}</b> <abbr class="element-card__symbol" :title="element.name">{{ element.symbol }}</abbr> <em class="element-card__name" :style="nameFitStyle">{{ element.name }}</em> <data v-if="displayValue !== ''" class="element-card__mass" :class="{ 'element-card__mass--link': Boolean(valueHref) }" :value="element.atomicMass ?? undefined" > {{ displayValue }} </data> </article> </template> <style scoped> .element-card { display: block; box-sizing: border-box; width: 100%; height: 100%; aspect-ratio: 1; min-width: 0; padding: var(--table-element-pad, 0.07rem 0.05rem 0.11rem); border-radius: var(--table-element-radius, 0.38rem); font-family: system-ui, -apple-system, "Segoe UI", sans-serif; font-size: var(--table-cell-font, calc(var(--table-cell-size) * 0.348)); line-height: 1; white-space: nowrap; overflow: hidden; box-shadow: 0 1px 0 color-mix(in oklab, black 8%, transparent); transition: transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease; } .element-card.is-selected { position: relative; z-index: 3; border-color: color-mix( in oklab, var(--element-card-background, var(--table-surface)) 48%, var(--color-selected-border) ); box-shadow: var(--table-shadow-rest), inset 0 0 0 2px color-mix(in oklab, var(--element-card-accent-ink) 72%, var(--color-selected-border)); } .element-card.is-selected:hover { box-shadow: var(--table-shadow-hover), inset 0 0 0 2px color-mix(in oklab, var(--element-card-accent-ink) 72%, var(--color-selected-border)); } .element-card__number, .element-card__symbol, .element-card__name, .element-card__mass { display: block; line-height: 1; font-style: normal; font-weight: normal; letter-spacing: 0; } .element-card__number { font-size: 0.65em; } .element-card__symbol { font-size: 0.9em; font-weight: 600; text-decoration: none; } .element-card__name { font-size: 0.55em; overflow: hidden; transform-origin: left center; } .element-card__mass { font-size: 0.55em; line-height: 1.08; padding-bottom: 0.04em; } .element-card--custom-value .element-card__mass { font-size: 0.48em; white-space: normal; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; word-break: break-word; } /* Electron configuration / expanded: denser multi-token footers. */ .element-card--wide.element-card--custom-value .element-card__mass { font-size: 0.42em; letter-spacing: -0.02em; -webkit-line-clamp: 3; line-clamp: 3; } .element-card--custom-value .element-card__mass--link { display: block; max-width: 100%; min-width: 0; white-space: nowrap; text-overflow: ellipsis; word-break: normal; -webkit-line-clamp: unset; line-clamp: unset; } .element-card__mass--link { color: var(--element-article-link, inherit); text-decoration: underline; text-underline-offset: 0.08em; } .element-card__mass--link:hover, .element-card__mass--link:focus-visible { text-decoration-thickness: 0.1em; outline: none; } .element-card:not([data-state]) { color: var(--element-card-ink-strong); } .element-card:not([data-state]) .element-card__number, .element-card:not([data-state]) .element-card__symbol, .element-card:not([data-state]) .element-card__name, .element-card:not([data-state]) .element-card__mass:not(.element-card__mass--link) { color: inherit; } .element-card[data-state="solid"] { color: var(--element-card-ink-strong); } .element-card[data-state="solid"] .element-card__number, .element-card[data-state="solid"] .element-card__symbol, .element-card[data-state="solid"] .element-card__name, .element-card[data-state="solid"] .element-card__mass { color: inherit; } .element-card[data-state="liquid"], .element-card[data-state="gas"], .element-card[data-state="unknown"] { color: var(--table-wide-state-ink, #ffffff); } .element-card[data-state="liquid"] .element-card__number, .element-card[data-state="liquid"] .element-card__symbol, .element-card[data-state="liquid"] .element-card__name, .element-card[data-state="liquid"] .element-card__mass, .element-card[data-state="gas"] .element-card__number, .element-card[data-state="gas"] .element-card__symbol, .element-card[data-state="gas"] .element-card__name, .element-card[data-state="gas"] .element-card__mass, .element-card[data-state="unknown"] .element-card__number, .element-card[data-state="unknown"] .element-card__symbol, .element-card[data-state="unknown"] .element-card__name, .element-card[data-state="unknown"] .element-card__mass { color: inherit; } </style>