@muten/shadcn 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/kit.css +133 -11
- package/package.json +1 -1
- package/registry/heatmap.muten +30 -0
- package/registry/progress.muten +5 -2
- package/registry/rank-item.muten +32 -0
- package/registry/share-bar.muten +22 -0
- package/registry/stacked-bars.js +75 -0
- package/registry/stacked-bars.muten +13 -0
- package/registry/stat-band.muten +5 -1
- package/registry/stat.muten +19 -7
- package/registry/veil.muten +18 -0
- package/registry.json +38 -0
package/AGENTS.md
CHANGED
|
@@ -45,6 +45,11 @@ its own bugs, and is one more thing to fix. So before you write any UI piece, wa
|
|
|
45
45
|
| a menu, a searchable picker | `DropdownMenu`, `Combobox` | a native `<select>` |
|
|
46
46
|
| dates and times, an agenda | `Calendar`, `DateField`, `TimeField`, `AgendaLive` | an input with a library |
|
|
47
47
|
| a KPI, a price, a status | `StatCard`, `Price`, `Badge` / `StatusBadge` | a styled number |
|
|
48
|
+
| a chart: bars, line, area, pie, donut, scatter | muten's `Chart` primitive (styled by the library) | an SVG or a chart library |
|
|
49
|
+
| bars over time with stacked series, a pickable bar, an axis in money | `StackedBars(rows, series, selected, unit, empty, busy, onPick)` | bars of divs |
|
|
50
|
+
| how a whole splits (citas vs pedidos, new vs returning) | `ShareBar(parts)` | two flex boxes |
|
|
51
|
+
| how much happens where two things cross (day × hour) | `Heatmap(rows, cols, cells)` | a grid of coloured cells |
|
|
52
|
+
| a bar per row (channels, a ranking) | `Progress(value, tone)` | a width in % |
|
|
48
53
|
| a legal or long document | `DocHead`, `DocToc` + `DocTocLink`, `DocSection` | a wall of paragraphs |
|
|
49
54
|
| a hero background, a product tour | `GradientBlinds(colors, from)`, `ScreenTour` | your own canvas or gradient |
|
|
50
55
|
| text with the library's type scale | `Txt(value, variant: "title" \| "heading" \| "muted" \| "caption")` | font sizes in your CSS |
|
package/kit.css
CHANGED
|
@@ -5006,22 +5006,144 @@ a.cx-bc-item:hover { color: var(--foreground); }
|
|
|
5006
5006
|
/* ScreenTour: the step's bar is placed by left/right; the progress part's own width: 100% pushed it 40px past the step. */
|
|
5007
5007
|
.cx-tour-steps > li > .cx-stp-bar { width: auto; }
|
|
5008
5008
|
|
|
5009
|
-
/* ═════ CHART (muten's native primitive) ═════
|
|
5009
|
+
/* ═════ CHART (muten's native primitive) and THE SERIES PALETTE ═════
|
|
5010
|
+
One palette for every chart of the library: Chart's series (mu-chart-sN) and the tone of Progress, ShareBar,
|
|
5011
|
+
StackedBars and Heatmap (data-tone="brand|coral|teal|amber|blue|pink"), so a series keeps its colour everywhere. */
|
|
5012
|
+
/* (Chart)
|
|
5010
5013
|
One series wears the skin's accent (the area's fill follows it, it is currentColor); a series per colour (pie, donut,
|
|
5011
5014
|
color(field)) takes the palette below, chosen to stay apart from the accent in light and dark. The legend is swatch + name. */
|
|
5012
5015
|
.mu-chart-wrap { width: 100%; max-width: 640px; margin: 0; gap: 14px; }
|
|
5013
5016
|
.mu-chart { color: var(--selected, var(--primary)); }
|
|
5014
5017
|
.mu-chart-bar, .mu-chart-dot { fill: currentColor; }
|
|
5015
5018
|
.mu-chart-line { stroke: currentColor; }
|
|
5016
|
-
.mu-chart-s0 { --
|
|
5017
|
-
.mu-chart-s1 { --
|
|
5018
|
-
.mu-chart-s2 { --
|
|
5019
|
-
.mu-chart-s3 { --
|
|
5020
|
-
.mu-chart-s4 { --
|
|
5021
|
-
.mu-chart-s5 { --
|
|
5022
|
-
.mu-chart-s6 { --
|
|
5023
|
-
.mu-chart-s7 { --
|
|
5024
|
-
|
|
5019
|
+
.mu-chart-s0 { --series: var(--selected, var(--primary)); }
|
|
5020
|
+
.mu-chart-s1, [data-tone="coral"] { --series: oklch(0.66 0.19 22); }
|
|
5021
|
+
.mu-chart-s2, [data-tone="teal"] { --series: oklch(0.7 0.14 168); }
|
|
5022
|
+
.mu-chart-s3, [data-tone="amber"] { --series: oklch(0.78 0.15 78); }
|
|
5023
|
+
.mu-chart-s4, [data-tone="blue"] { --series: oklch(0.68 0.13 238); }
|
|
5024
|
+
.mu-chart-s5, [data-tone="pink"] { --series: oklch(0.7 0.16 330); }
|
|
5025
|
+
.mu-chart-s6 { --series: oklch(0.62 0.05 260); }
|
|
5026
|
+
.mu-chart-s7 { --series: oklch(0.8 0.1 120); }
|
|
5027
|
+
[data-tone="brand"] { --series: var(--selected, var(--primary)); }
|
|
5028
|
+
:is(.mu-chart-bar, .mu-chart-dot, .mu-chart-slice)[class*="mu-chart-s"] { fill: var(--series); }
|
|
5025
5029
|
.mu-chart-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 6px 18px; font-size: 13px; color: var(--muted-foreground); }
|
|
5026
5030
|
.mu-chart-legend-item { display: flex; flex-direction: row; align-items: center; gap: 7px; }
|
|
5027
|
-
.mu-chart-swatch { width: 10px; height: 10px; border-radius: 3px; background: var(--
|
|
5031
|
+
.mu-chart-swatch { width: 10px; height: 10px; border-radius: 3px; background: var(--series); flex: none; }
|
|
5032
|
+
|
|
5033
|
+
/* Progress with a tone: the bar takes the series colour */
|
|
5034
|
+
.cn-progress[data-tone]:not([data-tone=""]) > .cn-progress-indicator { background: var(--series); }
|
|
5035
|
+
|
|
5036
|
+
/* ShareBar (share-bar.muten): one whole cut into its parts, each part's value and name under it */
|
|
5037
|
+
.cx-split { display: flex; flex-direction: column; gap: 14px; width: 100%; min-width: 0; }
|
|
5038
|
+
.cx-split-bar { display: flex; flex-direction: row; gap: 3px; height: 12px; border-radius: 999px; overflow: hidden; background: var(--muted); }
|
|
5039
|
+
.cx-split-seg { flex: var(--f, 0) 1 0; min-width: 0; background: var(--series, var(--primary)); transition: flex-grow .5s cubic-bezier(.22, 1, .36, 1); }
|
|
5040
|
+
.cx-split-seg[style*="--f: 0;"], .cx-split-seg[style*="--f:0;"] { display: none; }
|
|
5041
|
+
.cx-split-keys { display: flex; flex-direction: row; flex-wrap: wrap; gap: 10px 32px; }
|
|
5042
|
+
.cx-split-key { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
5043
|
+
.cx-split-v { font-size: 18px; font-weight: 650; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
|
|
5044
|
+
.cx-split-l { display: flex; flex-direction: row; align-items: center; gap: 7px; font-size: 13px; color: var(--muted-foreground); }
|
|
5045
|
+
.cx-split-l::before { content: ""; width: 9px; height: 9px; border-radius: 3px; background: var(--series, var(--primary)); flex: none; }
|
|
5046
|
+
@media (prefers-reduced-motion: reduce) { .cx-split-seg { transition: none; } }
|
|
5047
|
+
|
|
5048
|
+
/* Heatmap (heatmap.muten): rows × columns, seven levels of the tone, a scale under it */
|
|
5049
|
+
.cx-heat { display: flex; flex-direction: column; gap: 12px; width: 100%; min-width: 0; }
|
|
5050
|
+
.cx-heat-grid { position: relative; display: grid; grid-template-columns: 40px repeat(var(--n, 12), minmax(0, 1fr)); gap: 4px; align-items: center; }
|
|
5051
|
+
.cx-heat-x { font-size: 11.5px; color: var(--muted-foreground); text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
5052
|
+
.cx-heat-y { font-size: 12px; color: var(--muted-foreground); }
|
|
5053
|
+
.cx-heat-c { display: block; aspect-ratio: 1.4; min-width: 0; border-radius: 5px; background: var(--muted); }
|
|
5054
|
+
.cx-heat-c[data-level="1"] { background: color-mix(in oklab, var(--series) 18%, var(--muted)); }
|
|
5055
|
+
.cx-heat-c[data-level="2"] { background: color-mix(in oklab, var(--series) 34%, var(--muted)); }
|
|
5056
|
+
.cx-heat-c[data-level="3"] { background: color-mix(in oklab, var(--series) 52%, var(--muted)); }
|
|
5057
|
+
.cx-heat-c[data-level="4"] { background: color-mix(in oklab, var(--series) 72%, var(--muted)); }
|
|
5058
|
+
.cx-heat-c[data-level="5"] { background: var(--series); }
|
|
5059
|
+
.cx-heat-c[data-level="6"] { background: color-mix(in oklab, var(--series) 78%, black); }
|
|
5060
|
+
.cx-heat-grid > .cx-heat-c:hover { outline: 2px solid var(--foreground); outline-offset: 1px; }
|
|
5061
|
+
.cx-heat-scale { display: flex; flex-direction: row; align-items: center; gap: 5px; font-size: 12.5px; color: var(--muted-foreground); }
|
|
5062
|
+
.cx-heat-scale > .cx-heat-c { width: 16px; aspect-ratio: auto; height: 12px; border-radius: 3px; }
|
|
5063
|
+
.cx-heat-scale > span:first-child { margin-right: 3px; } .cx-heat-scale > span:last-child { margin-left: 3px; }
|
|
5064
|
+
|
|
5065
|
+
/* StackedBars (stacked-bars.js): series stacked per bar, an axis in the page's unit, a bar that can be picked */
|
|
5066
|
+
.cx-stb { display: flex; flex-direction: column; gap: 10px; width: 100%; min-width: 0; }
|
|
5067
|
+
.cx-stb-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end; gap: 6px 16px; font-size: 13px; color: var(--muted-foreground); }
|
|
5068
|
+
.cx-stb-legend li { display: flex; align-items: center; gap: 7px; }
|
|
5069
|
+
.cx-stb-legend li::before { content: ""; width: 9px; height: 9px; border-radius: 3px; background: var(--series); }
|
|
5070
|
+
.cx-stb-body { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 10px; }
|
|
5071
|
+
.cx-stb-y { display: flex; flex-direction: column; justify-content: space-between; height: 220px; font-size: 11.5px; color: var(--muted-foreground); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
5072
|
+
.cx-stb-y > span { line-height: 1; transform: translateY(-50%); } .cx-stb-y > span:first-child { transform: none; } .cx-stb-y > span:last-child { transform: translateY(0); }
|
|
5073
|
+
.cx-stb-plot { position: relative; height: 220px; }
|
|
5074
|
+
.cx-stb-grid { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between; pointer-events: none; }
|
|
5075
|
+
.cx-stb-grid i { border-top: 1px solid color-mix(in oklab, var(--border) 70%, transparent); }
|
|
5076
|
+
.cx-stb-bars { position: relative; height: 100%; display: grid; grid-template-columns: repeat(var(--n, 12), minmax(0, 1fr)); gap: clamp(4px, 1.2vw, 14px); align-items: end; }
|
|
5077
|
+
.cx-stb-bar { height: 100%; display: flex; flex-direction: column; justify-content: flex-end; padding: 0; border: 0; background: none; cursor: pointer; border-radius: 6px; }
|
|
5078
|
+
.cx-stb-bar:disabled { cursor: default; }
|
|
5079
|
+
.cx-stb-bar:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
|
|
5080
|
+
.cx-stb-stack { display: flex; flex-direction: column-reverse; gap: 2px; border-radius: 6px 6px 3px 3px; overflow: hidden; transform-origin: bottom; opacity: .55; transition: height .5s cubic-bezier(.22, 1, .36, 1), opacity .2s; }
|
|
5081
|
+
.cx-stb-bars:not(:has([aria-pressed="true"])) .cx-stb-stack, .cx-stb-bar[aria-pressed="true"] .cx-stb-stack, .cx-stb-bar:hover .cx-stb-stack { opacity: 1; }
|
|
5082
|
+
.cx-stb-seg { display: block; flex: 1 1 0; min-height: 2px; background: var(--series); }
|
|
5083
|
+
.cx-stb-sk { display: block; width: 100%; border-radius: 6px 6px 3px 3px; }
|
|
5084
|
+
.cx-stb-x { display: grid; grid-template-columns: repeat(var(--n, 12), minmax(0, 1fr)); gap: clamp(4px, 1.2vw, 14px); font-size: 12px; color: var(--muted-foreground); text-align: center; }
|
|
5085
|
+
.cx-stb-x > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
5086
|
+
.cx-stb-x > span[data-on] { color: var(--foreground); font-weight: 700; }
|
|
5087
|
+
@media (prefers-reduced-motion: reduce) { .cx-stb-stack { transition: none; } }
|
|
5088
|
+
|
|
5089
|
+
/* StatCard: the line under the number, the one thing to do at its foot, the skeleton while it loads */
|
|
5090
|
+
.cx-stat { display: flex; flex-direction: column; }
|
|
5091
|
+
.cx-stat > .cx-stat-body { flex: 1; }
|
|
5092
|
+
.cx-stat-line:empty, .cx-stat-delta:has(> .cn-badge:empty):has(> .cx-text:empty), .cx-stat-delta > .cn-badge:empty { display: none; }
|
|
5093
|
+
.cx-stat-go { margin-top: auto; padding-top: 8px; align-self: flex-start; display: inline-flex; align-items: center; gap: 4px; font-size: 13.5px; font-weight: 600; color: var(--selected, var(--primary)); text-decoration: none; }
|
|
5094
|
+
.cx-stat-go::after { content: "→"; transition: transform .15s; }
|
|
5095
|
+
.cx-stat-go:hover::after { transform: translateX(2px); }
|
|
5096
|
+
.cx-stat-go:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 4px; }
|
|
5097
|
+
.cx-stat-sk-v { width: 55%; height: 26px; border-radius: 6px; }
|
|
5098
|
+
.cx-stat-sk-l { width: 80%; height: 12px; border-radius: 6px; }
|
|
5099
|
+
|
|
5100
|
+
/* RankItem (rank-item.muten): a ranking row or a thing to act on */
|
|
5101
|
+
.cx-rank { padding-inline: 0; align-items: center; }
|
|
5102
|
+
.cx-rank-lead { font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; min-width: 32px; width: auto; padding-inline: 6px; }
|
|
5103
|
+
.cx-rank-mid { gap: 6px; min-width: 0; }
|
|
5104
|
+
.cx-rank-t { display: flex; flex-direction: row; align-items: center; gap: 8px; min-width: 0; }
|
|
5105
|
+
.cx-rank-t > .cn-item-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5106
|
+
.cx-rank-t > .cn-badge { flex: none; }
|
|
5107
|
+
.cx-rank-sub:empty { display: none; }
|
|
5108
|
+
.cx-rank-end { flex-direction: row; align-items: center; gap: 10px; }
|
|
5109
|
+
.cx-rank-v { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
5110
|
+
.cx-rank-v:empty { display: none; }
|
|
5111
|
+
.cx-rank-sk-t { width: 60%; height: 13px; border-radius: 6px; }
|
|
5112
|
+
.cx-rank-sk-s { width: 35%; height: 11px; border-radius: 6px; }
|
|
5113
|
+
.cx-rank-sk-v { width: 64px; height: 13px; border-radius: 6px; }
|
|
5114
|
+
|
|
5115
|
+
/* Veil (veil.muten): a note over content that has nothing to show yet */
|
|
5116
|
+
.cx-veil { position: relative; width: 100%; display: block; }
|
|
5117
|
+
.cx-veil-body { display: flex; flex-direction: column; align-items: stretch; gap: 2px; }
|
|
5118
|
+
.cx-veil-body > * { width: 100%; }
|
|
5119
|
+
.cx-veil[data-on="true"] > .cx-veil-body { opacity: .45; filter: saturate(.4); pointer-events: none; user-select: none; }
|
|
5120
|
+
.cx-veil-note { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 16px; text-align: center; pointer-events: none; }
|
|
5121
|
+
.cx-veil-note > * { max-width: 34ch; }
|
|
5122
|
+
.cx-veil-t { font-size: 15px; font-weight: 650; }
|
|
5123
|
+
.cx-veil-s { font-size: 13.5px; color: var(--muted-foreground); line-height: 1.45; }
|
|
5124
|
+
.cx-veil-note * { text-wrap: balance; }
|
|
5125
|
+
|
|
5126
|
+
/* the one empty note of the charts (Veil, Heatmap, StackedBars): a floating box over the faded content */
|
|
5127
|
+
.cx-veil-box { display: flex; flex-direction: column; gap: 4px; max-width: 36ch; padding: 14px 18px; border-radius: 14px; background: var(--popover, var(--background)); box-shadow: 0 10px 30px -14px rgb(0 0 0 / .3), 0 0 0 1px var(--border); text-align: center; }
|
|
5128
|
+
.cx-veil-t:empty { display: none; }
|
|
5129
|
+
.cx-heat-grid > .cx-veil-note { inset: 0 0 0 44px; }
|
|
5130
|
+
.cx-heat:has(.cx-veil-note) .cx-heat-c:not(.cx-heat-scale > *) { background: var(--muted); }
|
|
5131
|
+
/* skeletons */
|
|
5132
|
+
.cx-heat[data-busy="true"] .cx-heat-grid > .cx-heat-c { background: var(--muted); animation: cx-sk-pulse 1.4s ease-in-out infinite; }
|
|
5133
|
+
.cx-split[data-busy="true"] .cx-split-seg { visibility: hidden; }
|
|
5134
|
+
.cx-split[data-busy="true"] .cx-split-bar { animation: cx-sk-pulse 1.4s ease-in-out infinite; }
|
|
5135
|
+
.cx-split[data-busy="true"] .cx-split-v { color: transparent; border-radius: 6px; background: var(--muted); animation: cx-sk-pulse 1.4s ease-in-out infinite; }
|
|
5136
|
+
@keyframes cx-sk-pulse { 50% { opacity: .55; } }
|
|
5137
|
+
@media (prefers-reduced-motion: reduce) { .cx-heat[data-busy="true"] .cx-heat-c, .cx-split[data-busy="true"] .cx-split-bar, .cx-split[data-busy="true"] .cx-split-v { animation: none; } }
|
|
5138
|
+
|
|
5139
|
+
.cx-stats { width: 100%; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-auto-rows: 1fr; }
|
|
5140
|
+
|
|
5141
|
+
/* StatBand look "tiles": each number on a soft tile, left-aligned (a card inside an app) */
|
|
5142
|
+
.cx-statband[data-look="tiles"] { grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); gap: 12px; border-block: 0; }
|
|
5143
|
+
.cx-statband[data-look="tiles"] > .cx-statband-cell { align-items: flex-start; text-align: left; gap: 2px; padding: 14px 16px; border-radius: calc(var(--radius) * 1.2); background: var(--muted); }
|
|
5144
|
+
.cx-statband[data-look="tiles"] > .cx-statband-cell + .cx-statband-cell { border-left: 0; }
|
|
5145
|
+
.cx-statband[data-look="tiles"] .cx-statband-value { font-size: 26px; }
|
|
5146
|
+
.cx-statband[data-look="tiles"] .cx-statband-value > .cn-skeleton { width: 48px; height: 26px; border-radius: 6px; background: color-mix(in oklab, var(--foreground) 12%, var(--muted)); }
|
|
5147
|
+
/* StackedBars in a narrow card (a phone): twelve month labels still fit, a size smaller and never cut with «…» */
|
|
5148
|
+
.cx-stb { container-type: inline-size; }
|
|
5149
|
+
@container (max-width: 520px) { .cx-stb-x { gap: 2px; font-size: 10px; } .cx-stb-x > span { text-overflow: clip; overflow: visible; } .cx-stb-bars { gap: 4px; } .cx-stb-y { font-size: 10.5px; } }
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Heatmap - how much happens where two things cross: rows × columns (weekday × hour, week × day), each cell darker the
|
|
2
|
+
# more it holds, a «less … more» scale under it. A cell says its count to a screen reader and on hover. With `empty`,
|
|
3
|
+
# a calm note sits over the grid until there is something to show (emptyTitle on top of it). busy: true shows the grid
|
|
4
|
+
# as a skeleton while it loads.
|
|
5
|
+
# Heatmap(rows: days, cols: hours, cells: heat, less: "Menos", more: "Más citas", label: "Citas por día y hora")
|
|
6
|
+
# rows {id, label} · cols {label} (an empty label keeps the column unlabelled) · cells {row, level 0-6, tip}, row by row,
|
|
7
|
+
# as many per row as there are cols. tone: the scale's colour (brand by default; coral | teal | amber | blue | pink).
|
|
8
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-heat).
|
|
9
|
+
part Heatmap(rows: list, cols: list, cells: list, label: text = "", less: text = "Menos", more: text = "Más", empty: text = "", emptyTitle: text = "", tone: text = "brand", busy: bool = false) {
|
|
10
|
+
Stack class("cx-heat") data(tone: $tone, busy: $busy) aria(busy: $busy) {
|
|
11
|
+
Stack class("cx-heat-grid") style(n: "{$cols.length}") aria(role: "img", label: $label) {
|
|
12
|
+
Span "" class("cx-heat-corner")
|
|
13
|
+
each $cols as col { Span "{col.label}" class("cx-heat-x") }
|
|
14
|
+
each $rows as row {
|
|
15
|
+
Span "{row.label}" class("cx-heat-y")
|
|
16
|
+
each $cells as cell where cell.row == row.id { Span "" class("cx-heat-c") data(level: cell.level, name: cell.tip) aria(label: cell.tip) }
|
|
17
|
+
}
|
|
18
|
+
when $empty != "" and not $busy { Stack class("cx-veil-note") { Stack class("cx-veil-box") { Span "{$emptyTitle}" class("cx-veil-t") Span "{$empty}" class("cx-veil-s") } } }
|
|
19
|
+
}
|
|
20
|
+
Stack class("cx-heat-scale") aria(hidden: true) {
|
|
21
|
+
Span "{$less}"
|
|
22
|
+
Span "" class("cx-heat-c") data(level: 0)
|
|
23
|
+
Span "" class("cx-heat-c") data(level: 2)
|
|
24
|
+
Span "" class("cx-heat-c") data(level: 4)
|
|
25
|
+
Span "" class("cx-heat-c") data(level: 5)
|
|
26
|
+
Span "" class("cx-heat-c") data(level: 6)
|
|
27
|
+
Span "{$more}"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/registry/progress.muten
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Progress - a determinate bar, value 0-100; it eases between values. Progress(value: done, label: "Pasos hechos")
|
|
2
|
+
# tone: the bar's colour from the series palette: brand (the skin's accent) | coral | teal | amber | blue | pink. Empty: the
|
|
3
|
+
# primary colour. A row of channels or a ranking is a Progress per row, each in its tone.
|
|
4
|
+
# Progress(value: 46, tone: "coral", label: "Instagram")
|
|
2
5
|
# shadcn-for-muten · part · no deps · Nova (.cn-progress) + kit.css (the value rides --w).
|
|
3
|
-
part Progress(value: number, label: text = "Progreso") {
|
|
4
|
-
Stack class("cn-progress") data(slot: "progress") aria(role: "progressbar", label: $label, valuemin: 0, valuemax: 100, valuenow: $value) {
|
|
6
|
+
part Progress(value: number, label: text = "Progreso", tone: text = "") {
|
|
7
|
+
Stack class("cn-progress") data(slot: "progress", tone: $tone) aria(role: "progressbar", label: $label, valuemin: 0, valuemax: 100, valuenow: $value) {
|
|
5
8
|
Stack class("cn-progress-indicator") data(slot: "progress-indicator") style(w: "{$value}%")
|
|
6
9
|
}
|
|
7
10
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# RankItem - one row of a ranking or a short list of things to act on: a lead (the rank «1», or a count «13»), the title
|
|
2
|
+
# with an optional tag, a line under it, an optional bar for its share, and the value on the right. Whatever goes in
|
|
3
|
+
# the slot sits at the end of the row (an action: «Agregar», «Responder»).
|
|
4
|
+
# RankItem(lead: "1", title: "Corte clásico", tag: "Servicio", sub: "38 veces", value: "USD 760", share: 100)
|
|
5
|
+
# RankItem(lead: "13", title: "«tinte»", sub: "No está entre tus servicios") { Btn(variant: "outline", size: "sm", onClick: addService) { Span "Agregar" } }
|
|
6
|
+
# share: 0-100 draws a Progress under the title (leave it at -1 for none). tone: the bar's colour (series palette).
|
|
7
|
+
# busy: true keeps the row's shape with a skeleton.
|
|
8
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-rank) + Item, Badge, Progress.
|
|
9
|
+
part RankItem(lead: text, title: text = "", tag: text = "", sub: text = "", value: text = "", share: number = -1, tone: text = "brand", busy: bool = false) {
|
|
10
|
+
Stack class("cn-item cn-item-variant-default cn-item-size-sm cx-rank") data(slot: "item", size: "sm", busy: $busy) {
|
|
11
|
+
Stack class("cn-item-media cn-item-media-variant-icon cx-rank-lead") data(slot: "item-media", variant: "icon") { Span "{$lead}" }
|
|
12
|
+
Stack class("cn-item-content cx-rank-mid") data(slot: "item-content") {
|
|
13
|
+
when $busy {
|
|
14
|
+
Skeleton() class("cx-rank-sk-t")
|
|
15
|
+
Skeleton() class("cx-rank-sk-s")
|
|
16
|
+
}
|
|
17
|
+
when not $busy {
|
|
18
|
+
Stack class("cx-rank-t") {
|
|
19
|
+
Span "{$title}" class("cn-item-title") data(slot: "item-title")
|
|
20
|
+
when $tag != "" { Span "{$tag}" class("cn-badge cn-badge-variant-secondary group/badge") data(slot: "badge") }
|
|
21
|
+
}
|
|
22
|
+
Text "{$sub}" class("cn-item-description cx-rank-sub") data(slot: "item-description")
|
|
23
|
+
when $share >= 0 { Progress(value: $share, label: $title, tone: $tone) }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
Stack class("cn-item-actions cx-rank-end") data(slot: "item-actions") {
|
|
27
|
+
when $busy { Skeleton() class("cx-rank-sk-v") }
|
|
28
|
+
when not $busy { Span "{$value}" class("cx-rank-v") }
|
|
29
|
+
slot
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ShareBar - how one whole splits: a bar at 100 % cut into its parts, each in its tone, and under it each part's value
|
|
2
|
+
# and name. Citas against pedidos, new clients against returning ones. All parts at 0 leave the empty track. busy: true
|
|
3
|
+
# shows the bar and the values as a skeleton while they load.
|
|
4
|
+
# ShareBar(parts: split, label: "Ganado por tipo")
|
|
5
|
+
# parts {label, value (a number: the part's weight), text (what is written: "USD 3.240", "28"), tone}
|
|
6
|
+
# tone: brand | coral | teal | amber | blue | pink (the series palette; Chart and StackedBars use the same colours).
|
|
7
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-split).
|
|
8
|
+
part ShareBar(parts: list, label: text = "", busy: bool = false) {
|
|
9
|
+
Stack class("cx-split") data(busy: $busy) aria(busy: $busy) {
|
|
10
|
+
Stack class("cx-split-bar") aria(role: "img", label: $label) {
|
|
11
|
+
each $parts as part { Span "" class("cx-split-seg") data(tone: part.tone) style(f: "{part.value}") }
|
|
12
|
+
}
|
|
13
|
+
Stack class("cx-split-keys") {
|
|
14
|
+
each $parts as part {
|
|
15
|
+
Stack class("cx-split-key") {
|
|
16
|
+
Span "{part.text}" class("cx-split-v")
|
|
17
|
+
Span "{part.label}" class("cx-split-l") data(tone: part.tone)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// StackedBars (muten Custom) - bars over time with one or more series stacked in each (citas + pedidos by month), an
|
|
2
|
+
// axis in the page's unit (USD 8K), a bar you can pick (it says so with aria-pressed and sends its id), a note over the
|
|
3
|
+
// plot while there is nothing to show, and a skeleton while it loads. The data changes in place: the bars slide to their
|
|
4
|
+
// new height, nothing blinks. inputs: rows [{id, label, <key>: number, …}], series [{key, label, tone}], selected,
|
|
5
|
+
// unit ("USD", "$" or empty for plain numbers), locale, empty, emptyTitle, busy, readonly, label. on: pick(id).
|
|
6
|
+
const NS = 'cx-stb';
|
|
7
|
+
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
8
|
+
const list = (v) => (Array.isArray(v) ? v : []);
|
|
9
|
+
const SKELETON = [38, 52, 44, 61, 57, 70, 48, 66, 59, 74, 30, 22];
|
|
10
|
+
|
|
11
|
+
// a round top for the axis: 4 steps of 1, 2, 2.5 or 5 × a power of ten
|
|
12
|
+
const niceTop = (max) => {
|
|
13
|
+
if (max <= 0) return 4;
|
|
14
|
+
const rough = max / 4, mag = Math.pow(10, Math.floor(Math.log10(rough))), norm = rough / mag;
|
|
15
|
+
const step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * mag;
|
|
16
|
+
return step * 4;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function mount(el, inputs, on) {
|
|
20
|
+
let state = { ...inputs }, sel = inputs.selected ?? '', drawn = false;
|
|
21
|
+
const num = (locale) => new Intl.NumberFormat(locale || 'es', { useGrouping: 'always' }); // 1.800, not 1800 (es leaves 4 digits ungrouped)
|
|
22
|
+
// a word unit takes a space (USD 40), a symbol does not ($40)
|
|
23
|
+
const withUnit = (text, unit) => (!unit ? text : /^[A-Za-z]{2,}$/.test(unit) ? `${unit} ${text}` : `${unit}${text}`);
|
|
24
|
+
const fmt = (v, unit, locale) => withUnit(num(locale).format(v), unit);
|
|
25
|
+
const short = (v, unit, locale) => {
|
|
26
|
+
const a = Math.abs(v), r = (x) => num(locale).format(Math.round(x * 10) / 10);
|
|
27
|
+
const s = a >= 1e6 ? r(v / 1e6) + 'M' : a >= 1e3 ? r(v / 1e3) + 'K' : num(locale).format(v);
|
|
28
|
+
return v === 0 ? '0' : withUnit(s, unit);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const paint = () => {
|
|
32
|
+
const rows = list(state.rows), series = list(state.series), unit = state.unit || '', locale = state.locale || 'es';
|
|
33
|
+
const totals = rows.map((r) => series.reduce((sum, s) => sum + (Number(r[s.key]) || 0), 0));
|
|
34
|
+
const top = niceTop(Math.max(0, ...totals)), busy = !!state.busy, none = !busy && totals.every((t) => t === 0);
|
|
35
|
+
const legend = series.length > 1 ? `<ul class="${NS}-legend">${series.map((s) => `<li data-tone="${esc(s.tone || 'brand')}">${esc(s.label)}</li>`).join('')}</ul>` : '';
|
|
36
|
+
// nothing to measure yet: only the 0 stays on the axis
|
|
37
|
+
const ticks = [4, 3, 2, 1, 0].map((k) => `<span>${busy || (none && k) ? '' : esc(short((top / 4) * k, unit, locale))}</span>`).join('');
|
|
38
|
+
const bars = busy
|
|
39
|
+
? SKELETON.slice(0, Math.max(rows.length, 7)).map((h) => `<span class="${NS}-bar"><span class="cn-skeleton ${NS}-sk" style="height:${h}%"></span></span>`).join('')
|
|
40
|
+
: rows.map((r, i) => {
|
|
41
|
+
const tip = `${r.label}: ${series.map((s) => `${s.label} ${fmt(Number(r[s.key]) || 0, unit, locale)}`).join(' · ')}`;
|
|
42
|
+
const segs = series.map((s) => { const v = Number(r[s.key]) || 0; return v > 0 ? `<span class="${NS}-seg" data-tone="${esc(s.tone || 'brand')}" style="flex-grow:${v}"></span>` : ''; }).join('');
|
|
43
|
+
return `<button type="button" class="${NS}-bar" data-id="${esc(r.id)}" aria-pressed="${String(r.id) === String(sel)}" aria-label="${esc(tip)}" data-name="${esc(tip)}"${totals[i] && !state.readonly ? '' : ' disabled'}><span class="${NS}-stack" style="height:${(totals[i] / top) * 100}%">${segs}</span></button>`;
|
|
44
|
+
}).join('');
|
|
45
|
+
const xs = (busy ? SKELETON.slice(0, Math.max(rows.length, 7)).map(() => ({ label: '' })) : rows).map((r) => `<span data-id="${esc(r.id ?? '')}"${String(r.id) === String(sel) ? ' data-on' : ''}>${esc(r.label)}</span>`).join('');
|
|
46
|
+
const n = busy ? Math.max(rows.length, 7) : rows.length;
|
|
47
|
+
el.innerHTML = `${legend}<div class="${NS}-body"><div class="${NS}-y" aria-hidden="true">${ticks}</div><div class="${NS}-plot"><div class="${NS}-grid" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div><div class="${NS}-bars" role="group" aria-label="${esc(state.label || '')}" style="--n:${n}"${busy ? ' aria-busy="true"' : ''}>${bars}</div>${none && state.empty ? `<div class="cx-veil-note"><div class="cx-veil-box">${state.emptyTitle ? `<span class="cx-veil-t">${esc(state.emptyTitle)}</span>` : ''}<span class="cx-veil-s">${esc(state.empty)}</span></div></div>` : ''}</div><span></span><div class="${NS}-x" aria-hidden="true" style="--n:${n}">${xs}</div></div>`;
|
|
48
|
+
if (!drawn && !busy && rows.length && !matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
49
|
+
el.querySelectorAll(`.${NS}-stack`).forEach((s, i) => s.animate([{ transform: 'scaleY(0)' }, { transform: 'scaleY(1)' }], { duration: 480, delay: i * 24, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'backwards' }));
|
|
50
|
+
drawn = true;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// only the pressed state moves on a pick: the bars stay where they are
|
|
55
|
+
const mark = () => {
|
|
56
|
+
el.querySelectorAll(`.${NS}-bar[data-id]`).forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.id === String(sel))));
|
|
57
|
+
el.querySelectorAll(`.${NS}-x > span`).forEach((x) => x.toggleAttribute('data-on', x.dataset.id === String(sel)));
|
|
58
|
+
};
|
|
59
|
+
el.addEventListener('click', (e) => {
|
|
60
|
+
const bar = e.target.closest(`.${NS}-bar[data-id]`);
|
|
61
|
+
if (!bar || bar.disabled) return;
|
|
62
|
+
sel = bar.dataset.id; mark(); on?.pick?.(sel);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
paint();
|
|
66
|
+
return (next) => {
|
|
67
|
+
const selChanged = (next.selected ?? '') !== (state.selected ?? '');
|
|
68
|
+
state = { ...next };
|
|
69
|
+
if (selChanged) sel = next.selected ?? '';
|
|
70
|
+
// same rows and series, only the pick moved: no repaint
|
|
71
|
+
const old = el.querySelectorAll(`.${NS}-bar[data-id]`).length;
|
|
72
|
+
paint();
|
|
73
|
+
if (old && !state.busy) mark();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# StackedBars - bars over time with one or more series stacked in each: the year by month, citas and pedidos together.
|
|
2
|
+
# The axis is in the page's unit (USD 8K), a bar can be picked (it is pressed, and onPick gets its id), while nothing
|
|
3
|
+
# is there yet a calm note sits over the plot (`empty`, with `emptyTitle` on top), and while it loads (`busy`) a skeleton keeps the shape. When the
|
|
4
|
+
# data changes the bars slide to their new height. Each bar says its values to a screen reader and on hover.
|
|
5
|
+
# StackedBars(rows: @months, series: @kinds, selected: monthId, unit: "USD", onPick: pickMonth, label: "Ventas por mes")
|
|
6
|
+
# rows {id, label, <key>: number…} · series {key, label, tone}: tone brand | coral | teal | amber | blue | pink, the
|
|
7
|
+
# series palette (the first series sits at the bottom). One series is a plain bar chart. A legend shows from two.
|
|
8
|
+
# readonly: true shows the pick but lets no bar be pressed (a plan that cannot change month).
|
|
9
|
+
# For a single quick chart with no picking, muten's own `Chart` primitive is enough.
|
|
10
|
+
# shadcn-for-muten · Custom · no deps · host JS stacked-bars.js · kit.css (.cx-stb).
|
|
11
|
+
part StackedBars(rows: list, series: list, selected: text = "", unit: text = "", locale: text = "es", empty: text = "", emptyTitle: text = "", busy: bool = false, readonly: bool = false, label: text = "", onPick: action) {
|
|
12
|
+
Custom StackedBars inputs(rows: $rows, series: $series, selected: $selected, unit: $unit, locale: $locale, empty: $empty, emptyTitle: $emptyTitle, busy: $busy, readonly: $readonly, label: $label) on(pick: $onPick) class("cx-stb")
|
|
13
|
+
}
|
package/registry/stat-band.muten
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# StatBand - a row of big numbers split by hairlines (two per row on a phone). Each Stat holds its value in the slot
|
|
2
2
|
# (a Span, or a CountUp that counts up when it comes into view) and its label under it.
|
|
3
3
|
# StatBand { Stat(label: "citas al mes") { CountUp(to: "12,400") } Stat(label: "tiendas") { Span "850" } }
|
|
4
|
+
# look: "tiles" puts each number on its own soft tile, left-aligned, for a card inside an app (what the bot did this
|
|
5
|
+
# month: «0 reservas que cerró solo»); the default is the landing's band, centred between hairlines.
|
|
6
|
+
# StatBand(look: "tiles") { Stat(label: "reservas que cerró solo") { Span "12" } Stat(label: "pedidos que tomó solo") { Span "5" } }
|
|
7
|
+
# While the numbers load, put a Skeleton in each Stat's slot.
|
|
4
8
|
# shadcn-for-muten · parts · no deps · kit.css (.cx-statband).
|
|
5
|
-
part StatBand() { Stack class("cx-statband") { slot } }
|
|
9
|
+
part StatBand(look: text = "") { Stack class("cx-statband") data(look: $look) { slot } }
|
|
6
10
|
part Stat(label: text) { Stack class("cx-statband-cell") { Stack class("cx-statband-value") { slot } Span "{$label}" class("cx-statband-label") } }
|
package/registry/stat.muten
CHANGED
|
@@ -2,21 +2,33 @@
|
|
|
2
2
|
# its line. The icon goes in the slot (or leave it empty). `tone` says if the change is good (ok) or bad (bad) -
|
|
3
3
|
# for a cost or a wait, going down is the good one.
|
|
4
4
|
# StatCard(label: "Ventas", value: "$1.284.000", delta: "▲ 12 %", tone: "ok", compare: "vs. semana pasada", series: "3,5,4,6,5,8,9") { Icon "lucide:credit-card" }
|
|
5
|
+
# A card that asks for something (what is owed, orders to confirm): `line` says what the number means and `to` + `cta`
|
|
6
|
+
# is the one thing to do about it, written at the foot of the card.
|
|
7
|
+
# StatCard(label: "Por cobrar", value: "USD 305", line: "4 citas sin marcar como pagadas", to: "/close-day", cta: "Cerrar el día") { Icon "lucide:clock" }
|
|
8
|
+
# busy: true keeps the card's shape with a skeleton while the number loads (the label stays).
|
|
5
9
|
# Lay several in a StatGrid. Spark draws the series alone: Spark(series: "3,5,4", tone: "success").
|
|
6
10
|
# shadcn-for-muten · part + Custom (Spark.js) · kit.css (.cx-stat).
|
|
7
|
-
part StatCard(label: text, value: text, delta: text, tone: text = "ok", compare: text = "", series: text = "") {
|
|
8
|
-
Section class("cn-card cx-stat") data(slot: "card", size: "sm") {
|
|
11
|
+
part StatCard(label: text, value: text = "", delta: text = "", tone: text = "ok", compare: text = "", series: text = "", line: text = "", to: text = "", cta: text = "", busy: bool = false) {
|
|
12
|
+
Section class("cn-card cx-stat") data(slot: "card", size: "sm", busy: $busy) {
|
|
9
13
|
Stack class("cx-stat-body") {
|
|
10
14
|
Stack class("cx-stat-row") {
|
|
11
15
|
Span "{$label}" class("cx-text") data(slot: "text", variant: "muted")
|
|
12
16
|
Stack class("cx-stat-ic") { slot }
|
|
13
17
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
when $busy {
|
|
19
|
+
Skeleton() class("cx-stat-sk-v")
|
|
20
|
+
Skeleton() class("cx-stat-sk-l")
|
|
21
|
+
}
|
|
22
|
+
when not $busy {
|
|
23
|
+
Span "{$value}" class("cx-text") data(slot: "text", variant: "title")
|
|
24
|
+
Span "{$line}" class("cx-text cx-stat-line") data(slot: "text", variant: "caption")
|
|
25
|
+
Stack class("cx-stat-row cx-stat-delta") {
|
|
26
|
+
Span "{$delta}" class("cn-badge cn-badge-variant-ghost group/badge") data(slot: "badge", variant: "status", tone: $tone)
|
|
27
|
+
Span "{$compare}" class("cx-text") data(slot: "text", variant: "caption")
|
|
28
|
+
}
|
|
29
|
+
Spark(series: $series, tone: $tone)
|
|
30
|
+
when $to != "" { Link "{$cta}" -> "{$to}" class("cx-stat-go") }
|
|
18
31
|
}
|
|
19
|
-
Spark(series: $series, tone: $tone)
|
|
20
32
|
}
|
|
21
33
|
}
|
|
22
34
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Veil - a calm note over a card's content while it has nothing to show yet: the content stays behind, faded (ghost
|
|
2
|
+
# rows, an empty chart), and a title and a line say what will appear. With `on` false it is the content alone.
|
|
3
|
+
# Veil(on: noSales, title: "Nada vendido todavía", text: "Lo que más te reserven y te compren aparece aquí.") { … }
|
|
4
|
+
# StackedBars and Heatmap carry their own (empty / emptyTitle); use Veil for anything else.
|
|
5
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-veil).
|
|
6
|
+
part Veil(on: bool, title: text = "", text: text = "") {
|
|
7
|
+
Stack class("cx-veil") data(on: $on) {
|
|
8
|
+
Stack class("cx-veil-body") aria(hidden: $on) { slot }
|
|
9
|
+
when $on {
|
|
10
|
+
Stack class("cx-veil-note") {
|
|
11
|
+
Stack class("cx-veil-box") {
|
|
12
|
+
Span "{$title}" class("cx-veil-t")
|
|
13
|
+
Span "{$text}" class("cx-veil-s")
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/registry.json
CHANGED
|
@@ -1144,6 +1144,44 @@
|
|
|
1144
1144
|
"part": "SiteFooter",
|
|
1145
1145
|
"file": "registry/site-footer.muten",
|
|
1146
1146
|
"description": "A site's footer: the brand and its two ways in over a hairline, every group of links on one row, then a bar with the company and contacts (FooterHead, FooterBrand, FooterActions, FooterLinks, FooterCol, FooterBar)."
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
"name": "share-bar",
|
|
1150
|
+
"deps": [],
|
|
1151
|
+
"part": "ShareBar",
|
|
1152
|
+
"file": "registry/share-bar.muten",
|
|
1153
|
+
"description": "How one whole splits: a bar at 100 % cut into its parts, each in its tone, with each part's value and name under it."
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
"name": "heatmap",
|
|
1157
|
+
"deps": [],
|
|
1158
|
+
"part": "Heatmap",
|
|
1159
|
+
"file": "registry/heatmap.muten",
|
|
1160
|
+
"description": "Rows × columns (weekday × hour), each cell darker the more it holds, a less…more scale, a note while empty."
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
"name": "stacked-bars",
|
|
1164
|
+
"deps": [],
|
|
1165
|
+
"part": "StackedBars",
|
|
1166
|
+
"file": "registry/stacked-bars.muten",
|
|
1167
|
+
"description": "Bars over time with series stacked, an axis in the page's unit, a bar you can pick, an empty note and a skeleton.",
|
|
1168
|
+
"component": "StackedBars"
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
"name": "rank-item",
|
|
1172
|
+
"deps": [
|
|
1173
|
+
"progress"
|
|
1174
|
+
],
|
|
1175
|
+
"part": "RankItem",
|
|
1176
|
+
"file": "registry/rank-item.muten",
|
|
1177
|
+
"description": "A ranking row or a thing to act on: a lead (rank or count), title, tag, a line, an optional share bar, the value, and an action in the slot."
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
"name": "veil",
|
|
1181
|
+
"deps": [],
|
|
1182
|
+
"part": "Veil",
|
|
1183
|
+
"file": "registry/veil.muten",
|
|
1184
|
+
"description": "A calm note (title and line) over a card's content while it has nothing to show yet; the content stays faded behind."
|
|
1147
1185
|
}
|
|
1148
1186
|
],
|
|
1149
1187
|
"classComponents": {
|