@usableapp/cardds 0.7.62 → 0.7.64
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/CHANGELOG.md +19 -0
- package/README.md +2 -0
- package/cardds.css +1 -0
- package/css/layover.css +5 -2
- package/css/sticky.css +187 -0
- package/css/tokens.css +1 -1
- package/dist/cardds.css +195 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/state.d.ts +1 -1
- package/dist/state.js +6 -4
- package/dist/sticky/Sticky.d.ts +25 -0
- package/dist/sticky/Sticky.js +10 -0
- package/dist/sticky/StickyDate.d.ts +9 -0
- package/dist/sticky/StickyDate.js +6 -0
- package/dist/sticky/StickyField.d.ts +7 -0
- package/dist/sticky/StickyField.js +6 -0
- package/dist/sticky/StickyPick.d.ts +14 -0
- package/dist/sticky/StickyPick.js +9 -0
- package/dist/sticky/StickyThread.d.ts +22 -0
- package/dist/sticky/StickyThread.js +13 -0
- package/dist/sticky/tones.d.ts +2 -0
- package/dist/sticky/tones.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.64 — 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Sticky notes** (Lh 2026-10-06) — a new family, nothing existing moves. `Sticky` is a paper note: a flat pastel block, straight, no pattern, its bottom-right corner folded, a soft shadow. `tone` picks one of nine papers (`STICKY_TONES`: cotton · blush · tea-leaf · custard · salmon · iceberg · cocoa · apricot · water; default custard); a theme retunes a paper with `--sticky-<tone>-bg`. `flat` drops the fold, `square` makes the 1:1 note you write on, `head` and `foot` are the small lines above and under the words, `corner` stands a control (an `IconBtn`) on the bottom-right corner.
|
|
9
|
+
- **`Sticky date` + `StickyDate`** — the day big, the month under it, at the note's top-right: `<Sticky date={<StickyDate day="14" month="ก.พ." />}>`. cardds does not format dates: you pass the words. A picture in `head` beside a date is a small square (`--sticky-pic-size`).
|
|
10
|
+
- **`Sticky tilt`** — `"left"` | `"right"` turns the note 5° (`--sticky-tilt`). A note is straight everywhere else: this is for the one note a start screen hands over (gallery `StickyWelcome`).
|
|
11
|
+
- **`StickyField`** (a bare textarea on a note, `label` names it) · **`StickyPick`** (the tone chooser, `value` + `onChange`) · **`StickyThread`** + **`StickyPost`** (notes as a chat or a feed: `who` · `name` · `time` above the notes, `side="right"` for mine, `rail` draws the dotted line under the avatars).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **`Callout`: the tail joins the card cleanly** — its two strokes ended past the card's border as small nubs. The tail is about 2px shorter. Nothing to change in a screen.
|
|
15
|
+
|
|
16
|
+
## 0.7.63 — 2026-10-06
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **The cursor standby has a switch: `data-standby="off"`** (Lh 2026-10-06) — the default does not move: a sheet come up, a step opened, a stack sheet opened or a `Modal` arrived still has its field waiting with the cursor, and an app writes no focus code. New: put `data-standby="off"` on any box around the part and no field is given the cursor — the part's heading or handle still is, so the keyboard does not come. Omit it and nothing changes. To choose another field than the first, as before: `<Field data-autofocus … />`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **The grab line is 2px again** (Lh 2026-10-06) — `--hairline-w` 1px → 2px: the line of a `Sheet` and of a stack with `mark="line"`. Before (0.7.61–0.7.62): 1px. You change nothing; a theme that wants the thin line sets `--hairline-w: 1px`.
|
|
23
|
+
|
|
5
24
|
## 0.7.62 — 2026-10-06
|
|
6
25
|
|
|
7
26
|
### Added
|
package/README.md
CHANGED
|
@@ -325,6 +325,8 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
325
325
|
opened, a `SheetStack` sheet opened, a `Modal` arrived. Once it has settled: the field marked `data-autofocus`, else its
|
|
326
326
|
first field, else its heading (a step's title, a modal's question) or, for a sheet come onto the screen, its handle. A field
|
|
327
327
|
that already has the cursor keeps it. One mechanic for all of them (`focusWhenActive`, src/state.ts); an app writes no focus code.
|
|
328
|
+
Another field than the first: `<Field data-autofocus … />`. Off: `data-standby="off"` on any box around the part — no field
|
|
329
|
+
is given the cursor (the heading or the handle still is). The gallery sets it on its stories; its Cursor button (C) turns it on.
|
|
328
330
|
- **The signal set — four colours, four meanings, everywhere** (Lh 2026-10-02): `--signal-ok` green (it went right) ·
|
|
329
331
|
`--signal-warn` yellow (look out) · `--signal-error` red (it went wrong) · `--signal-info` blue (for your information).
|
|
330
332
|
The wireframe has them too. A theme may retune a shade, never a meaning. Each is dark enough for words and edges;
|
package/cardds.css
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
@import url("css/people.css"); /* avatar, avatar stack, chooser row */
|
|
15
15
|
@import url("css/choice.css"); /* check, toggle, chip grid, days, calendar, mood, pin, composer */
|
|
16
16
|
@import url("css/lists.css"); /* row, rows, key-value, kv grid, timeline, legend, link */
|
|
17
|
+
@import url("css/sticky.css"); /* sticky note, its field, tone chooser, thread of posts, pile */
|
|
17
18
|
@import url("css/media.css"); /* cover card, quote, tile grid, fold, mosaic, wave */
|
|
18
19
|
@import url("css/layover.css"); /* ask drawer, popover menu, banner, float bar, callout */
|
|
19
20
|
@import url("css/finish.css"); /* light, shadow, edges — only under data-finish="lit" */
|
package/css/layover.css
CHANGED
|
@@ -279,11 +279,14 @@
|
|
|
279
279
|
content: "";
|
|
280
280
|
position: absolute;
|
|
281
281
|
left: 50%;
|
|
282
|
-
bottom:
|
|
282
|
+
bottom: 0;
|
|
283
283
|
width: var(--sp-3);
|
|
284
284
|
height: var(--sp-3);
|
|
285
|
-
|
|
285
|
+
/* centred on the card's padding edge and cut to its lower half, so the tail's
|
|
286
|
+
strokes end flush on the card's border instead of poking past it */
|
|
287
|
+
translate: -50% 50%;
|
|
286
288
|
rotate: 45deg;
|
|
289
|
+
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
|
287
290
|
background: inherit;
|
|
288
291
|
border: inherit;
|
|
289
292
|
border-top: 0;
|
package/css/sticky.css
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/* cardds — sticky notes: the paper note, the field written on it, the tone chooser, a thread of posts.
|
|
2
|
+
A note is its own paper: it brings its colour and its ink, whatever card or palette it lies on. */
|
|
3
|
+
|
|
4
|
+
/* ---- sticky: a paper note — a flat pastel block, straight, the bottom-right corner folded over ----
|
|
5
|
+
Straight unless asked (--tilt-left / --tilt-right: the one note of a start screen), never patterned. The corner is CUT on the diagonal and a flap of a darker shade lies over it.
|
|
6
|
+
<div class="sticky sticky--custard">
|
|
7
|
+
<div class="sticky__paper">
|
|
8
|
+
<div class="sticky__head">12 Oct</div><div class="sticky__body">…</div><div class="sticky__foot">From Noi</div>
|
|
9
|
+
<span class="sticky__fold"></span>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
Two boxes on purpose: the OUTER one holds the tone and the shadow, the PAPER is the one that is cut — a clip-path
|
|
13
|
+
on the outer box would cut its own shadow off. The outer box's ground is kept out of the cut corner by painting
|
|
14
|
+
only its content box, which stops one fold short of the right and bottom edges; the paper's negative margins run
|
|
15
|
+
back out to the full size. The paper takes the tone by `inherit`. */
|
|
16
|
+
.sticky {
|
|
17
|
+
color: var(--sticky-ink, #141414);
|
|
18
|
+
--_muted: color-mix(in srgb, currentColor 62%, transparent); /* muted words: the ink sunk into the paper (currentColor is read where it is used: there it is still the note's ink) */
|
|
19
|
+
padding-inline-end: var(--sticky-fold, var(--sp-6)); /* the knob, read by a real property first (the knob test probes that one); --_fold hands the same value to the paper and the flap */
|
|
20
|
+
--_fold: var(--sticky-fold, var(--sp-6));
|
|
21
|
+
--_min: calc(var(--tap) * 3);
|
|
22
|
+
--_cap: calc(var(--tap) * 7);
|
|
23
|
+
position: relative;
|
|
24
|
+
display: grid;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
width: fit-content; /* hugs its words… */
|
|
27
|
+
min-width: var(--sticky-min-w, var(--_min)); /* …but a word alone is still a note */
|
|
28
|
+
max-width: var(--sticky-max-w, 100%);
|
|
29
|
+
padding: 0 var(--_fold) var(--_fold) 0; /* = the line above, written whole */
|
|
30
|
+
background-clip: content-box;
|
|
31
|
+
filter: drop-shadow(var(--sticky-drop, 0 var(--sp-1) var(--sp-2) rgb(0 0 0 / 0.16))); /* drop-shadow follows the cut; a box-shadow would draw the square */
|
|
32
|
+
}
|
|
33
|
+
/* the tones — the names in src/sticky/tones.ts. A swatch of the chooser wears the same class. */
|
|
34
|
+
.sticky--cotton { background-color: var(--sticky-cotton-bg, #f3efe2); }
|
|
35
|
+
.sticky--blush { background-color: var(--sticky-blush-bg, #f6d5da); }
|
|
36
|
+
.sticky--tea-leaf { background-color: var(--sticky-tea-leaf-bg, #d5e8c5); }
|
|
37
|
+
.sticky--custard { background-color: var(--sticky-custard-bg, #f6e9a0); }
|
|
38
|
+
.sticky--salmon { background-color: var(--sticky-salmon-bg, #f3c7b3); }
|
|
39
|
+
.sticky--iceberg { background-color: var(--sticky-iceberg-bg, #cfe0ee); }
|
|
40
|
+
.sticky--cocoa { background-color: var(--sticky-cocoa-bg, #dccbbd); }
|
|
41
|
+
.sticky--apricot { background-color: var(--sticky-apricot-bg, #f6d49a); }
|
|
42
|
+
.sticky--water { background-color: var(--sticky-water-bg, #bcd6f3); }
|
|
43
|
+
|
|
44
|
+
.sticky__paper {
|
|
45
|
+
position: relative;
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
gap: var(--sticky-gap, var(--sp-3));
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
margin: 0 calc(-1 * var(--_fold)) calc(-1 * var(--_fold)) 0;
|
|
52
|
+
padding: var(--sticky-pad-y, var(--sp-4)) var(--sticky-pad-x, var(--sp-4));
|
|
53
|
+
border-radius: var(--sticky-radius, var(--sp-1));
|
|
54
|
+
background-color: inherit;
|
|
55
|
+
clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--_fold)), calc(100% - var(--_fold)) 100%, 0 100%);
|
|
56
|
+
}
|
|
57
|
+
.sticky__head { display: flex; align-items: center; gap: var(--sticky-head-gap, var(--sp-2)); font: var(--sticky-head-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
|
|
58
|
+
.sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
|
|
59
|
+
.sticky__head:has(> .sticky-date) { align-items: start; }
|
|
60
|
+
.sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
|
|
61
|
+
.sticky__body { flex: 1 1 auto; min-width: 0; font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
|
|
62
|
+
.sticky__foot { font: var(--sticky-foot-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); padding-inline-end: var(--_fold); } /* the line stops before the fold */
|
|
63
|
+
/* the flap: a square in the corner — the paper's cut takes its outer half, what is left is the triangle lying on the note */
|
|
64
|
+
.sticky__fold {
|
|
65
|
+
position: absolute;
|
|
66
|
+
right: 0;
|
|
67
|
+
bottom: 0;
|
|
68
|
+
width: var(--_fold);
|
|
69
|
+
height: var(--_fold);
|
|
70
|
+
border-top-left-radius: var(--sp-1);
|
|
71
|
+
background-color: var(--sticky-fold-bg, color-mix(in srgb, currentColor 14%, transparent)); /* a darker shade of whatever the paper is */
|
|
72
|
+
box-shadow: calc(-1 * var(--sp-0)) calc(-1 * var(--sp-0)) var(--sp-1) rgb(0 0 0 / 0.08);
|
|
73
|
+
}
|
|
74
|
+
.sticky--flat > .sticky__paper { clip-path: none; }
|
|
75
|
+
.sticky--flat > .sticky__paper > .sticky__foot { padding-inline-end: 0; }
|
|
76
|
+
|
|
77
|
+
/* --tilt: the note turned a little on its middle. Straight is the rule; this is for the one note a start screen hands over. */
|
|
78
|
+
.sticky--tilt-left, .sticky--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a box that scrolls (a centre) would cut them */
|
|
79
|
+
.sticky--tilt-left { rotate: calc(-1 * var(--sticky-tilt, 5deg)); }
|
|
80
|
+
.sticky--tilt-right { rotate: var(--sticky-tilt, 5deg); }
|
|
81
|
+
|
|
82
|
+
/* ---- sticky date: the day, big, the month under it — at the end of the note's head (its top-right corner) ----
|
|
83
|
+
<div class="sticky__head">…<time class="sticky-date" datetime="2026-02-14"><span class="sticky-date__day">14</span><span class="sticky-date__month">ก.พ.</span></time></div> */
|
|
84
|
+
.sticky-date { display: grid; justify-items: end; margin-inline-start: auto; text-align: end; color: var(--sticky-ink, #141414); }
|
|
85
|
+
.sticky-date__day { font: var(--sticky-date-day-font, var(--type-stat)); }
|
|
86
|
+
.sticky-date__month { font: var(--sticky-date-month-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
|
|
87
|
+
|
|
88
|
+
/* --square: the note to write on — 1:1, as wide as its column up to a cap, in the middle */
|
|
89
|
+
.sticky--square { width: 100%; max-width: var(--sticky-square-max-w, var(--_cap)); aspect-ratio: 1; margin-inline: auto; }
|
|
90
|
+
|
|
91
|
+
/* --corner: a control stands ON the bottom-right corner, half outside. The paper is notched round it (a mask, so the
|
|
92
|
+
shadow follows the notch and no ground colour is needed); the note has no fold. */
|
|
93
|
+
.sticky__corner {
|
|
94
|
+
position: absolute;
|
|
95
|
+
right: 0;
|
|
96
|
+
bottom: 0;
|
|
97
|
+
translate: 50% 50%;
|
|
98
|
+
display: grid;
|
|
99
|
+
place-items: center;
|
|
100
|
+
width: var(--sticky-corner-size, var(--tap-sm));
|
|
101
|
+
aspect-ratio: 1;
|
|
102
|
+
}
|
|
103
|
+
/* the half of the control that stands outside is the note's own room: it never runs past the column, nor onto what follows */
|
|
104
|
+
.sticky--corner {
|
|
105
|
+
--_corner: var(--sticky-corner-size, var(--tap-sm)); /* the same default as .sticky__corner above: the paper's notch needs it too */
|
|
106
|
+
max-width: calc(var(--sticky-max-w, 100%) - var(--_corner) / 2);
|
|
107
|
+
margin: 0 calc(var(--_corner) / 2) calc(var(--_corner) / 2) 0;
|
|
108
|
+
}
|
|
109
|
+
.sticky--corner > .sticky__paper {
|
|
110
|
+
--_notch: calc(var(--_corner) / 2 + var(--sp-1));
|
|
111
|
+
clip-path: none;
|
|
112
|
+
-webkit-mask: radial-gradient(circle at 100% 100%, transparent calc(var(--_notch) - var(--border-w)), #000 var(--_notch));
|
|
113
|
+
mask: radial-gradient(circle at 100% 100%, transparent calc(var(--_notch) - var(--border-w)), #000 var(--_notch));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ---- sticky field: the words written ON a note — a bare textarea in the note's type and ink, filling it ----
|
|
117
|
+
<div class="sticky__body"><textarea class="sticky-field" aria-label="Note" placeholder="Add text to this note"></textarea></div> */
|
|
118
|
+
.sticky__body:has(> .sticky-field) { display: grid; }
|
|
119
|
+
.sticky-field {
|
|
120
|
+
display: block;
|
|
121
|
+
box-sizing: border-box;
|
|
122
|
+
width: 100%;
|
|
123
|
+
min-height: var(--sticky-field-min-h, var(--_min));
|
|
124
|
+
resize: none;
|
|
125
|
+
border: 0;
|
|
126
|
+
margin: 0;
|
|
127
|
+
padding: 0;
|
|
128
|
+
background: transparent;
|
|
129
|
+
color: inherit;
|
|
130
|
+
font: inherit;
|
|
131
|
+
outline: none; /* the caret is the focus mark, as in the composer */
|
|
132
|
+
}
|
|
133
|
+
.sticky-field::placeholder { color: var(--sticky-muted-ink, var(--_muted)); opacity: 1; }
|
|
134
|
+
|
|
135
|
+
/* ---- sticky pick: the tone chooser — round swatches at tap size, the chosen one ringed ----
|
|
136
|
+
<div class="sticky-pick" role="radiogroup" aria-label="Note colour">
|
|
137
|
+
<label class="sticky-pick__swatch sticky--blush"><input type="radio" name="…" aria-label="blush"></label>…
|
|
138
|
+
</div> */
|
|
139
|
+
.sticky-pick { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sticky-pick-gap, var(--sp-3)); }
|
|
140
|
+
.sticky-pick__swatch {
|
|
141
|
+
position: relative;
|
|
142
|
+
flex: none;
|
|
143
|
+
width: var(--sticky-pick-size, var(--tap));
|
|
144
|
+
aspect-ratio: 1;
|
|
145
|
+
border-radius: var(--r-chip);
|
|
146
|
+
color: var(--sticky-pick-on-ink, var(--on-surface)); /* the ring's colour */
|
|
147
|
+
box-shadow: inset 0 0 0 var(--border-w) var(--ink-tint); /* a pale tone still has an edge on a pale ground */
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
}
|
|
150
|
+
.sticky-pick__swatch > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
|
|
151
|
+
.sticky-pick__swatch:has(> :checked) { outline: var(--rule-w) solid currentColor; outline-offset: var(--sp-0); }
|
|
152
|
+
.sticky-pick__swatch:has(> :focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
153
|
+
|
|
154
|
+
/* ---- sticky thread: a chat or a feed told in notes ----
|
|
155
|
+
<div class="sticky-thread sticky-thread--rail">
|
|
156
|
+
<div class="sticky-post">
|
|
157
|
+
<span class="sticky-post__who"><span class="avatar">…</span></span>
|
|
158
|
+
<div class="sticky-post__head"><span class="sticky-post__name">Noi</span><span class="sticky-post__time">8:15</span></div>
|
|
159
|
+
<span class="sticky-post__rail"></span>
|
|
160
|
+
<div class="sticky-post__notes"><div class="sticky">…</div></div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="sticky-post sticky-post--right">…</div>
|
|
163
|
+
</div>
|
|
164
|
+
Two columns: the avatar, then everything else — so the notes start under the NAME. --right swaps the columns. */
|
|
165
|
+
.sticky-thread { display: grid; gap: var(--sticky-thread-gap, var(--sp-6)); --_thread-gap: var(--sticky-thread-gap, var(--sp-6)); } /* --_thread-gap: the same value, for the rail to run through */
|
|
166
|
+
.sticky-post {
|
|
167
|
+
display: grid;
|
|
168
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
169
|
+
align-items: center;
|
|
170
|
+
column-gap: var(--sticky-post-col-gap, var(--sp-3));
|
|
171
|
+
row-gap: var(--sticky-post-row-gap, var(--sp-2));
|
|
172
|
+
}
|
|
173
|
+
.sticky-post:not(:has(> .sticky-post__who)) { column-gap: 0; } /* nobody pictured: no empty column's gap */
|
|
174
|
+
.sticky-post__who { grid-area: 1 / 1; display: grid; }
|
|
175
|
+
.sticky-post__head { grid-area: 1 / 2; display: flex; align-items: baseline; gap: var(--sticky-post-head-gap, var(--sp-2)); min-width: 0; }
|
|
176
|
+
.sticky-post__name { min-width: 0; font: var(--sticky-post-name-font, var(--type-label)); overflow-wrap: anywhere; }
|
|
177
|
+
.sticky-post__time { margin-inline-start: auto; font: var(--sticky-post-time-font, var(--type-caption)); color: var(--sticky-post-time-ink, var(--on-surface-muted)); white-space: nowrap; }
|
|
178
|
+
.sticky-post__notes { grid-area: 2 / 2; display: grid; justify-items: start; gap: var(--sticky-post-notes-gap, var(--sp-3)); min-width: 0; }
|
|
179
|
+
.sticky-post__notes > .sticky { max-width: var(--sticky-post-max-w, 85%); } /* a note leaves the far side open, as a bubble does */
|
|
180
|
+
.sticky-post__rail { display: none; grid-area: 2 / 1; justify-self: center; align-self: stretch; color: var(--sticky-thread-rail-ink, var(--on-surface-muted)); border-inline-start: var(--rule-w) dotted currentColor; margin-block-end: calc(-1 * var(--_thread-gap)); } /* runs on through the gap to the next avatar */
|
|
181
|
+
.sticky-thread--rail > .sticky-post:not(:last-child) > .sticky-post__rail { display: block; }
|
|
182
|
+
.sticky-post--right { grid-template-columns: minmax(0, 1fr) auto; }
|
|
183
|
+
.sticky-post--right > .sticky-post__who { grid-column: 2; }
|
|
184
|
+
.sticky-post--right > .sticky-post__head { grid-column: 1; flex-direction: row-reverse; }
|
|
185
|
+
.sticky-post--right > .sticky-post__head > .sticky-post__time { margin-inline: 0 auto; }
|
|
186
|
+
.sticky-post--right > .sticky-post__notes { grid-column: 1; justify-items: end; }
|
|
187
|
+
.sticky-post--right > .sticky-post__rail { grid-column: 2; }
|
package/css/tokens.css
CHANGED
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
|
|
87
87
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
88
88
|
--border-w: 1.5px;
|
|
89
|
-
--hairline-w:
|
|
89
|
+
--hairline-w: 2px; /* the thinnest mark: a sheet's grab line (Lh 2026-10-06: 1px, then 2px the same day) */
|
|
90
90
|
--rule-w: 2px; /* dotted connectors, picker rules */
|
|
91
91
|
--scroll-w: 1px; /* the scrollbar of a .bleed track: a hairline of ink */
|
|
92
92
|
--focus: 2px solid currentColor; /* :focus-visible ring */
|
package/dist/cardds.css
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
|
|
128
128
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
129
129
|
--border-w: 1.5px;
|
|
130
|
-
--hairline-w:
|
|
130
|
+
--hairline-w: 2px; /* the thinnest mark: a sheet's grab line (Lh 2026-10-06: 1px, then 2px the same day) */
|
|
131
131
|
--rule-w: 2px; /* dotted connectors, picker rules */
|
|
132
132
|
--scroll-w: 1px; /* the scrollbar of a .bleed track: a hairline of ink */
|
|
133
133
|
--focus: 2px solid currentColor; /* :focus-visible ring */
|
|
@@ -3597,6 +3597,195 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3597
3597
|
.bullets__pic.ph { border-radius: var(--bullets-pic-radius, 0rem); }
|
|
3598
3598
|
.bullets__text { flex: 1; min-width: 0; }
|
|
3599
3599
|
|
|
3600
|
+
/* ---- css/sticky.css — sticky note, its field, tone chooser, thread of posts, pile ---- */
|
|
3601
|
+
/* cardds — sticky notes: the paper note, the field written on it, the tone chooser, a thread of posts.
|
|
3602
|
+
A note is its own paper: it brings its colour and its ink, whatever card or palette it lies on. */
|
|
3603
|
+
|
|
3604
|
+
/* ---- sticky: a paper note — a flat pastel block, straight, the bottom-right corner folded over ----
|
|
3605
|
+
Straight unless asked (--tilt-left / --tilt-right: the one note of a start screen), never patterned. The corner is CUT on the diagonal and a flap of a darker shade lies over it.
|
|
3606
|
+
<div class="sticky sticky--custard">
|
|
3607
|
+
<div class="sticky__paper">
|
|
3608
|
+
<div class="sticky__head">12 Oct</div><div class="sticky__body">…</div><div class="sticky__foot">From Noi</div>
|
|
3609
|
+
<span class="sticky__fold"></span>
|
|
3610
|
+
</div>
|
|
3611
|
+
</div>
|
|
3612
|
+
Two boxes on purpose: the OUTER one holds the tone and the shadow, the PAPER is the one that is cut — a clip-path
|
|
3613
|
+
on the outer box would cut its own shadow off. The outer box's ground is kept out of the cut corner by painting
|
|
3614
|
+
only its content box, which stops one fold short of the right and bottom edges; the paper's negative margins run
|
|
3615
|
+
back out to the full size. The paper takes the tone by `inherit`. */
|
|
3616
|
+
.sticky {
|
|
3617
|
+
color: var(--sticky-ink, #141414);
|
|
3618
|
+
--_muted: color-mix(in srgb, currentColor 62%, transparent); /* muted words: the ink sunk into the paper (currentColor is read where it is used: there it is still the note's ink) */
|
|
3619
|
+
padding-inline-end: var(--sticky-fold, var(--sp-6)); /* the knob, read by a real property first (the knob test probes that one); --_fold hands the same value to the paper and the flap */
|
|
3620
|
+
--_fold: var(--sticky-fold, var(--sp-6));
|
|
3621
|
+
--_min: calc(var(--tap) * 3);
|
|
3622
|
+
--_cap: calc(var(--tap) * 7);
|
|
3623
|
+
position: relative;
|
|
3624
|
+
display: grid;
|
|
3625
|
+
box-sizing: border-box;
|
|
3626
|
+
width: fit-content; /* hugs its words… */
|
|
3627
|
+
min-width: var(--sticky-min-w, var(--_min)); /* …but a word alone is still a note */
|
|
3628
|
+
max-width: var(--sticky-max-w, 100%);
|
|
3629
|
+
padding: 0 var(--_fold) var(--_fold) 0; /* = the line above, written whole */
|
|
3630
|
+
background-clip: content-box;
|
|
3631
|
+
filter: drop-shadow(var(--sticky-drop, 0 var(--sp-1) var(--sp-2) rgb(0 0 0 / 0.16))); /* drop-shadow follows the cut; a box-shadow would draw the square */
|
|
3632
|
+
}
|
|
3633
|
+
/* the tones — the names in src/sticky/tones.ts. A swatch of the chooser wears the same class. */
|
|
3634
|
+
.sticky--cotton { background-color: var(--sticky-cotton-bg, #f3efe2); }
|
|
3635
|
+
.sticky--blush { background-color: var(--sticky-blush-bg, #f6d5da); }
|
|
3636
|
+
.sticky--tea-leaf { background-color: var(--sticky-tea-leaf-bg, #d5e8c5); }
|
|
3637
|
+
.sticky--custard { background-color: var(--sticky-custard-bg, #f6e9a0); }
|
|
3638
|
+
.sticky--salmon { background-color: var(--sticky-salmon-bg, #f3c7b3); }
|
|
3639
|
+
.sticky--iceberg { background-color: var(--sticky-iceberg-bg, #cfe0ee); }
|
|
3640
|
+
.sticky--cocoa { background-color: var(--sticky-cocoa-bg, #dccbbd); }
|
|
3641
|
+
.sticky--apricot { background-color: var(--sticky-apricot-bg, #f6d49a); }
|
|
3642
|
+
.sticky--water { background-color: var(--sticky-water-bg, #bcd6f3); }
|
|
3643
|
+
|
|
3644
|
+
.sticky__paper {
|
|
3645
|
+
position: relative;
|
|
3646
|
+
display: flex;
|
|
3647
|
+
flex-direction: column;
|
|
3648
|
+
gap: var(--sticky-gap, var(--sp-3));
|
|
3649
|
+
box-sizing: border-box;
|
|
3650
|
+
min-width: 0;
|
|
3651
|
+
margin: 0 calc(-1 * var(--_fold)) calc(-1 * var(--_fold)) 0;
|
|
3652
|
+
padding: var(--sticky-pad-y, var(--sp-4)) var(--sticky-pad-x, var(--sp-4));
|
|
3653
|
+
border-radius: var(--sticky-radius, var(--sp-1));
|
|
3654
|
+
background-color: inherit;
|
|
3655
|
+
clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--_fold)), calc(100% - var(--_fold)) 100%, 0 100%);
|
|
3656
|
+
}
|
|
3657
|
+
.sticky__head { display: flex; align-items: center; gap: var(--sticky-head-gap, var(--sp-2)); font: var(--sticky-head-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
|
|
3658
|
+
.sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
|
|
3659
|
+
.sticky__head:has(> .sticky-date) { align-items: start; }
|
|
3660
|
+
.sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
|
|
3661
|
+
.sticky__body { flex: 1 1 auto; min-width: 0; font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
|
|
3662
|
+
.sticky__foot { font: var(--sticky-foot-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); padding-inline-end: var(--_fold); } /* the line stops before the fold */
|
|
3663
|
+
/* the flap: a square in the corner — the paper's cut takes its outer half, what is left is the triangle lying on the note */
|
|
3664
|
+
.sticky__fold {
|
|
3665
|
+
position: absolute;
|
|
3666
|
+
right: 0;
|
|
3667
|
+
bottom: 0;
|
|
3668
|
+
width: var(--_fold);
|
|
3669
|
+
height: var(--_fold);
|
|
3670
|
+
border-top-left-radius: var(--sp-1);
|
|
3671
|
+
background-color: var(--sticky-fold-bg, color-mix(in srgb, currentColor 14%, transparent)); /* a darker shade of whatever the paper is */
|
|
3672
|
+
box-shadow: calc(-1 * var(--sp-0)) calc(-1 * var(--sp-0)) var(--sp-1) rgb(0 0 0 / 0.08);
|
|
3673
|
+
}
|
|
3674
|
+
.sticky--flat > .sticky__paper { clip-path: none; }
|
|
3675
|
+
.sticky--flat > .sticky__paper > .sticky__foot { padding-inline-end: 0; }
|
|
3676
|
+
|
|
3677
|
+
/* --tilt: the note turned a little on its middle. Straight is the rule; this is for the one note a start screen hands over. */
|
|
3678
|
+
.sticky--tilt-left, .sticky--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a box that scrolls (a centre) would cut them */
|
|
3679
|
+
.sticky--tilt-left { rotate: calc(-1 * var(--sticky-tilt, 5deg)); }
|
|
3680
|
+
.sticky--tilt-right { rotate: var(--sticky-tilt, 5deg); }
|
|
3681
|
+
|
|
3682
|
+
/* ---- sticky date: the day, big, the month under it — at the end of the note's head (its top-right corner) ----
|
|
3683
|
+
<div class="sticky__head">…<time class="sticky-date" datetime="2026-02-14"><span class="sticky-date__day">14</span><span class="sticky-date__month">ก.พ.</span></time></div> */
|
|
3684
|
+
.sticky-date { display: grid; justify-items: end; margin-inline-start: auto; text-align: end; color: var(--sticky-ink, #141414); }
|
|
3685
|
+
.sticky-date__day { font: var(--sticky-date-day-font, var(--type-stat)); }
|
|
3686
|
+
.sticky-date__month { font: var(--sticky-date-month-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
|
|
3687
|
+
|
|
3688
|
+
/* --square: the note to write on — 1:1, as wide as its column up to a cap, in the middle */
|
|
3689
|
+
.sticky--square { width: 100%; max-width: var(--sticky-square-max-w, var(--_cap)); aspect-ratio: 1; margin-inline: auto; }
|
|
3690
|
+
|
|
3691
|
+
/* --corner: a control stands ON the bottom-right corner, half outside. The paper is notched round it (a mask, so the
|
|
3692
|
+
shadow follows the notch and no ground colour is needed); the note has no fold. */
|
|
3693
|
+
.sticky__corner {
|
|
3694
|
+
position: absolute;
|
|
3695
|
+
right: 0;
|
|
3696
|
+
bottom: 0;
|
|
3697
|
+
translate: 50% 50%;
|
|
3698
|
+
display: grid;
|
|
3699
|
+
place-items: center;
|
|
3700
|
+
width: var(--sticky-corner-size, var(--tap-sm));
|
|
3701
|
+
aspect-ratio: 1;
|
|
3702
|
+
}
|
|
3703
|
+
/* the half of the control that stands outside is the note's own room: it never runs past the column, nor onto what follows */
|
|
3704
|
+
.sticky--corner {
|
|
3705
|
+
--_corner: var(--sticky-corner-size, var(--tap-sm)); /* the same default as .sticky__corner above: the paper's notch needs it too */
|
|
3706
|
+
max-width: calc(var(--sticky-max-w, 100%) - var(--_corner) / 2);
|
|
3707
|
+
margin: 0 calc(var(--_corner) / 2) calc(var(--_corner) / 2) 0;
|
|
3708
|
+
}
|
|
3709
|
+
.sticky--corner > .sticky__paper {
|
|
3710
|
+
--_notch: calc(var(--_corner) / 2 + var(--sp-1));
|
|
3711
|
+
clip-path: none;
|
|
3712
|
+
-webkit-mask: radial-gradient(circle at 100% 100%, transparent calc(var(--_notch) - var(--border-w)), #000 var(--_notch));
|
|
3713
|
+
mask: radial-gradient(circle at 100% 100%, transparent calc(var(--_notch) - var(--border-w)), #000 var(--_notch));
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
/* ---- sticky field: the words written ON a note — a bare textarea in the note's type and ink, filling it ----
|
|
3717
|
+
<div class="sticky__body"><textarea class="sticky-field" aria-label="Note" placeholder="Add text to this note"></textarea></div> */
|
|
3718
|
+
.sticky__body:has(> .sticky-field) { display: grid; }
|
|
3719
|
+
.sticky-field {
|
|
3720
|
+
display: block;
|
|
3721
|
+
box-sizing: border-box;
|
|
3722
|
+
width: 100%;
|
|
3723
|
+
min-height: var(--sticky-field-min-h, var(--_min));
|
|
3724
|
+
resize: none;
|
|
3725
|
+
border: 0;
|
|
3726
|
+
margin: 0;
|
|
3727
|
+
padding: 0;
|
|
3728
|
+
background: transparent;
|
|
3729
|
+
color: inherit;
|
|
3730
|
+
font: inherit;
|
|
3731
|
+
outline: none; /* the caret is the focus mark, as in the composer */
|
|
3732
|
+
}
|
|
3733
|
+
.sticky-field::placeholder { color: var(--sticky-muted-ink, var(--_muted)); opacity: 1; }
|
|
3734
|
+
|
|
3735
|
+
/* ---- sticky pick: the tone chooser — round swatches at tap size, the chosen one ringed ----
|
|
3736
|
+
<div class="sticky-pick" role="radiogroup" aria-label="Note colour">
|
|
3737
|
+
<label class="sticky-pick__swatch sticky--blush"><input type="radio" name="…" aria-label="blush"></label>…
|
|
3738
|
+
</div> */
|
|
3739
|
+
.sticky-pick { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sticky-pick-gap, var(--sp-3)); }
|
|
3740
|
+
.sticky-pick__swatch {
|
|
3741
|
+
position: relative;
|
|
3742
|
+
flex: none;
|
|
3743
|
+
width: var(--sticky-pick-size, var(--tap));
|
|
3744
|
+
aspect-ratio: 1;
|
|
3745
|
+
border-radius: var(--r-chip);
|
|
3746
|
+
color: var(--sticky-pick-on-ink, var(--on-surface)); /* the ring's colour */
|
|
3747
|
+
box-shadow: inset 0 0 0 var(--border-w) var(--ink-tint); /* a pale tone still has an edge on a pale ground */
|
|
3748
|
+
cursor: pointer;
|
|
3749
|
+
}
|
|
3750
|
+
.sticky-pick__swatch > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
|
|
3751
|
+
.sticky-pick__swatch:has(> :checked) { outline: var(--rule-w) solid currentColor; outline-offset: var(--sp-0); }
|
|
3752
|
+
.sticky-pick__swatch:has(> :focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
3753
|
+
|
|
3754
|
+
/* ---- sticky thread: a chat or a feed told in notes ----
|
|
3755
|
+
<div class="sticky-thread sticky-thread--rail">
|
|
3756
|
+
<div class="sticky-post">
|
|
3757
|
+
<span class="sticky-post__who"><span class="avatar">…</span></span>
|
|
3758
|
+
<div class="sticky-post__head"><span class="sticky-post__name">Noi</span><span class="sticky-post__time">8:15</span></div>
|
|
3759
|
+
<span class="sticky-post__rail"></span>
|
|
3760
|
+
<div class="sticky-post__notes"><div class="sticky">…</div></div>
|
|
3761
|
+
</div>
|
|
3762
|
+
<div class="sticky-post sticky-post--right">…</div>
|
|
3763
|
+
</div>
|
|
3764
|
+
Two columns: the avatar, then everything else — so the notes start under the NAME. --right swaps the columns. */
|
|
3765
|
+
.sticky-thread { display: grid; gap: var(--sticky-thread-gap, var(--sp-6)); --_thread-gap: var(--sticky-thread-gap, var(--sp-6)); } /* --_thread-gap: the same value, for the rail to run through */
|
|
3766
|
+
.sticky-post {
|
|
3767
|
+
display: grid;
|
|
3768
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
3769
|
+
align-items: center;
|
|
3770
|
+
column-gap: var(--sticky-post-col-gap, var(--sp-3));
|
|
3771
|
+
row-gap: var(--sticky-post-row-gap, var(--sp-2));
|
|
3772
|
+
}
|
|
3773
|
+
.sticky-post:not(:has(> .sticky-post__who)) { column-gap: 0; } /* nobody pictured: no empty column's gap */
|
|
3774
|
+
.sticky-post__who { grid-area: 1 / 1; display: grid; }
|
|
3775
|
+
.sticky-post__head { grid-area: 1 / 2; display: flex; align-items: baseline; gap: var(--sticky-post-head-gap, var(--sp-2)); min-width: 0; }
|
|
3776
|
+
.sticky-post__name { min-width: 0; font: var(--sticky-post-name-font, var(--type-label)); overflow-wrap: anywhere; }
|
|
3777
|
+
.sticky-post__time { margin-inline-start: auto; font: var(--sticky-post-time-font, var(--type-caption)); color: var(--sticky-post-time-ink, var(--on-surface-muted)); white-space: nowrap; }
|
|
3778
|
+
.sticky-post__notes { grid-area: 2 / 2; display: grid; justify-items: start; gap: var(--sticky-post-notes-gap, var(--sp-3)); min-width: 0; }
|
|
3779
|
+
.sticky-post__notes > .sticky { max-width: var(--sticky-post-max-w, 85%); } /* a note leaves the far side open, as a bubble does */
|
|
3780
|
+
.sticky-post__rail { display: none; grid-area: 2 / 1; justify-self: center; align-self: stretch; color: var(--sticky-thread-rail-ink, var(--on-surface-muted)); border-inline-start: var(--rule-w) dotted currentColor; margin-block-end: calc(-1 * var(--_thread-gap)); } /* runs on through the gap to the next avatar */
|
|
3781
|
+
.sticky-thread--rail > .sticky-post:not(:last-child) > .sticky-post__rail { display: block; }
|
|
3782
|
+
.sticky-post--right { grid-template-columns: minmax(0, 1fr) auto; }
|
|
3783
|
+
.sticky-post--right > .sticky-post__who { grid-column: 2; }
|
|
3784
|
+
.sticky-post--right > .sticky-post__head { grid-column: 1; flex-direction: row-reverse; }
|
|
3785
|
+
.sticky-post--right > .sticky-post__head > .sticky-post__time { margin-inline: 0 auto; }
|
|
3786
|
+
.sticky-post--right > .sticky-post__notes { grid-column: 1; justify-items: end; }
|
|
3787
|
+
.sticky-post--right > .sticky-post__rail { grid-column: 2; }
|
|
3788
|
+
|
|
3600
3789
|
/* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
|
|
3601
3790
|
/* ============================================================
|
|
3602
3791
|
cardds/media.css — media & display inside cards:
|
|
@@ -4161,11 +4350,14 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
4161
4350
|
content: "";
|
|
4162
4351
|
position: absolute;
|
|
4163
4352
|
left: 50%;
|
|
4164
|
-
bottom:
|
|
4353
|
+
bottom: 0;
|
|
4165
4354
|
width: var(--sp-3);
|
|
4166
4355
|
height: var(--sp-3);
|
|
4167
|
-
|
|
4356
|
+
/* centred on the card's padding edge and cut to its lower half, so the tail's
|
|
4357
|
+
strokes end flush on the card's border instead of poking past it */
|
|
4358
|
+
translate: -50% 50%;
|
|
4168
4359
|
rotate: 45deg;
|
|
4360
|
+
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
|
4169
4361
|
background: inherit;
|
|
4170
4362
|
border: inherit;
|
|
4171
4363
|
border-top: 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,12 @@ export * from './lists/Timeline.js';
|
|
|
72
72
|
export * from './lists/Legend.js';
|
|
73
73
|
export * from './lists/Bullets.js';
|
|
74
74
|
export * from './lists/Bubble.js';
|
|
75
|
+
export * from './sticky/tones.js';
|
|
76
|
+
export * from './sticky/Sticky.js';
|
|
77
|
+
export * from './sticky/StickyDate.js';
|
|
78
|
+
export * from './sticky/StickyField.js';
|
|
79
|
+
export * from './sticky/StickyPick.js';
|
|
80
|
+
export * from './sticky/StickyThread.js';
|
|
75
81
|
export * from './media/Quote.js';
|
|
76
82
|
export * from './media/Postcard.js';
|
|
77
83
|
export * from './media/MapArea.js';
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,12 @@ export * from './lists/Timeline.js';
|
|
|
76
76
|
export * from './lists/Legend.js';
|
|
77
77
|
export * from './lists/Bullets.js';
|
|
78
78
|
export * from './lists/Bubble.js';
|
|
79
|
+
export * from './sticky/tones.js';
|
|
80
|
+
export * from './sticky/Sticky.js';
|
|
81
|
+
export * from './sticky/StickyDate.js';
|
|
82
|
+
export * from './sticky/StickyField.js';
|
|
83
|
+
export * from './sticky/StickyPick.js';
|
|
84
|
+
export * from './sticky/StickyThread.js';
|
|
79
85
|
export * from './media/Quote.js';
|
|
80
86
|
export * from './media/Postcard.js';
|
|
81
87
|
export * from './media/MapArea.js';
|
package/dist/state.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const warnOnce: (key: string, text: string) => void;
|
|
|
23
23
|
export declare const reducedMotion: () => boolean;
|
|
24
24
|
/** give `box` the cursor once it has settled: after its own `translate` transition (600ms at most), or after `wait` ms. It lands on the field
|
|
25
25
|
* marked `data-autofocus`, else the first field inside `scope` (a selector under `box`; default: all of it), else `fallback`. A field in
|
|
26
|
-
* `box` that already has the cursor keeps it. Nothing moves the page (`preventScroll`). Returns the cancel — `true` when it had already
|
|
26
|
+
* `box` that already has the cursor keeps it. Inside `[data-standby="off"]` no field is looked for: `fallback` only. Nothing moves the page (`preventScroll`). Returns the cancel — `true` when it had already
|
|
27
27
|
* landed (so a component can tell a move cut short from a finished one). */
|
|
28
28
|
export declare function focusWhenActive(box: HTMLElement | null | undefined, { scope, fallback, wait, then }?: {
|
|
29
29
|
scope?: string;
|
package/dist/state.js
CHANGED
|
@@ -56,11 +56,13 @@ export const warnOnce = (key, text) => { if (!warned.has(key)) {
|
|
|
56
56
|
/** the member asked for less motion (false where there is no window, e.g. a server render) */
|
|
57
57
|
export const reducedMotion = () => typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
58
58
|
/* THE RULE (Lh 2026-09-29): a part of the screen that has just become ACTIVE — a sheet come up, a step opened, a stack sheet opened, a
|
|
59
|
-
modal asking — has its field waiting with the cursor. ONE mechanic for every such part; the component only says WHEN it became active.
|
|
59
|
+
modal asking — has its field waiting with the cursor. ONE mechanic for every such part; the component only says WHEN it became active.
|
|
60
|
+
THE SWITCH (Lh 2026-10-06): under `data-standby="off"` (on any box around the part) no field is given the cursor — the part's
|
|
61
|
+
`fallback` still is. The gallery sets it: a page of many stories, each with a sheet up, is not one screen a member is typing in. */
|
|
60
62
|
const FIELD = 'input:not([type=checkbox], [type=radio], [type=hidden], [disabled], [tabindex="-1"]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]';
|
|
61
63
|
/** give `box` the cursor once it has settled: after its own `translate` transition (600ms at most), or after `wait` ms. It lands on the field
|
|
62
64
|
* marked `data-autofocus`, else the first field inside `scope` (a selector under `box`; default: all of it), else `fallback`. A field in
|
|
63
|
-
* `box` that already has the cursor keeps it. Nothing moves the page (`preventScroll`). Returns the cancel — `true` when it had already
|
|
65
|
+
* `box` that already has the cursor keeps it. Inside `[data-standby="off"]` no field is looked for: `fallback` only. Nothing moves the page (`preventScroll`). Returns the cancel — `true` when it had already
|
|
64
66
|
* landed (so a component can tell a move cut short from a finished one). */
|
|
65
67
|
export function focusWhenActive(box, { scope, fallback, wait = 'translate', then } = {}) {
|
|
66
68
|
if (!box)
|
|
@@ -74,8 +76,8 @@ export function focusWhenActive(box, { scope, fallback, wait = 'translate', then
|
|
|
74
76
|
const at = document.activeElement;
|
|
75
77
|
if (!(at instanceof HTMLElement && box.contains(at) && at.matches(FIELD))) {
|
|
76
78
|
const root = scope ? box.querySelector(scope) : box;
|
|
77
|
-
const target = box.querySelector('[data-autofocus]:not([inert] *)')
|
|
78
|
-
?? root?.querySelector(`:is(${FIELD}):not([inert] *)`) ?? fallback?.();
|
|
79
|
+
const target = (box.closest('[data-standby="off"]') ? null : box.querySelector('[data-autofocus]:not([inert] *)')
|
|
80
|
+
?? root?.querySelector(`:is(${FIELD}):not([inert] *)`)) ?? fallback?.();
|
|
79
81
|
target?.focus({ preventScroll: true });
|
|
80
82
|
}
|
|
81
83
|
then?.();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import type { StickyTone } from './tones.js';
|
|
3
|
+
export interface StickyProps extends ComponentProps<'div'> {
|
|
4
|
+
/** the paper's colour — one of STICKY_TONES (default custard) */
|
|
5
|
+
tone?: StickyTone;
|
|
6
|
+
/** no folded corner */
|
|
7
|
+
flat?: boolean;
|
|
8
|
+
/** 1:1, filling its column up to a cap (the write screen); otherwise the note hugs its words */
|
|
9
|
+
square?: boolean;
|
|
10
|
+
/** a row at the top of the note — a date, a small picture, a label */
|
|
11
|
+
head?: ReactNode;
|
|
12
|
+
/** a StickyDate at the end of the head — the note's top-right corner */
|
|
13
|
+
date?: ReactNode;
|
|
14
|
+
/** turned a little on its middle (the one note of a start screen); a note is straight otherwise */
|
|
15
|
+
tilt?: 'left' | 'right';
|
|
16
|
+
/** a small muted line at the bottom ("From …") */
|
|
17
|
+
foot?: ReactNode;
|
|
18
|
+
/** a control (an IconBtn) standing on the bottom-right corner, half outside the note; the note then has no fold */
|
|
19
|
+
corner?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Sticky — a paper note: a flat pastel block, straight, its bottom-right corner folded over, a soft shadow under it.
|
|
23
|
+
* The outer box holds the tone and the shadow; the paper inside is the part that is cut (a clip would cut the shadow too).
|
|
24
|
+
*/
|
|
25
|
+
export declare function Sticky({ tone, flat, square, head, date, tilt, foot, corner, className, children, ...rest }: StickyProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sticky — a paper note: a flat pastel block, straight, its bottom-right corner folded over, a soft shadow under it.
|
|
5
|
+
* The outer box holds the tone and the shadow; the paper inside is the part that is cut (a clip would cut the shadow too).
|
|
6
|
+
*/
|
|
7
|
+
export function Sticky({ tone = 'custard', flat, square, head, date, tilt, foot, corner, className, children, ...rest }) {
|
|
8
|
+
const notched = corner != null;
|
|
9
|
+
return (_jsxs("div", { className: cx('sticky', `sticky--${tone}`, flat && 'sticky--flat', square && 'sticky--square', notched && 'sticky--corner', tilt && `sticky--tilt-${tilt}`, className), ...rest, children: [_jsxs("div", { className: "sticky__paper", children: [(head != null || date != null) && _jsxs("div", { className: "sticky__head", children: [head, date] }), _jsx("div", { className: "sticky__body", children: children }), foot != null && _jsx("div", { className: "sticky__foot", children: foot }), !flat && !notched && _jsx("span", { className: "sticky__fold", "aria-hidden": "true" })] }), notched && _jsx("div", { className: "sticky__corner", children: corner })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
export interface StickyDateProps extends ComponentProps<'time'> {
|
|
3
|
+
/** the day of the month, shown big */
|
|
4
|
+
day: ReactNode;
|
|
5
|
+
/** the month, short, under the day — the app's own words ("ก.พ.", "Feb") */
|
|
6
|
+
month: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/** StickyDate — a date for a Sticky's `date`: the day big, the month under it, at the note's top-right corner. */
|
|
9
|
+
export declare function StickyDate({ day, month, className, ...rest }: StickyDateProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/** StickyDate — a date for a Sticky's `date`: the day big, the month under it, at the note's top-right corner. */
|
|
4
|
+
export function StickyDate({ day, month, className, ...rest }) {
|
|
5
|
+
return (_jsxs("time", { className: cx('sticky-date', className), ...rest, children: [_jsx("span", { className: "sticky-date__day", children: day }), _jsx("span", { className: "sticky-date__month", children: month })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export interface StickyFieldProps extends ComponentProps<'textarea'> {
|
|
3
|
+
/** what is being written — the field's accessible name (it has no visible label) */
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
/** StickyField — the words written ON a Sticky: a bare textarea in the note's own type and ink, filling the note. */
|
|
7
|
+
export declare function StickyField({ label, className, ...rest }: StickyFieldProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/** StickyField — the words written ON a Sticky: a bare textarea in the note's own type and ink, filling the note. */
|
|
4
|
+
export function StickyField({ label, className, ...rest }) {
|
|
5
|
+
return _jsx("textarea", { className: cx('sticky-field', className), "aria-label": label, ...rest });
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import type { StickyTone } from './tones.js';
|
|
3
|
+
export interface StickyPickProps extends Omit<ComponentProps<'div'>, 'onChange'> {
|
|
4
|
+
/** CONTROLLED: the chosen tone — with `onChange` */
|
|
5
|
+
value: StickyTone;
|
|
6
|
+
/** the tone the member tapped */
|
|
7
|
+
onChange?: (tone: StickyTone) => void;
|
|
8
|
+
/** the tones on offer, in order (default: all of STICKY_TONES) */
|
|
9
|
+
tones?: readonly StickyTone[];
|
|
10
|
+
/** the group's accessible name */
|
|
11
|
+
label: string;
|
|
12
|
+
}
|
|
13
|
+
/** StickyPick — the tone chooser: a row of round swatches at tap size, the chosen one ringed. Native radios: the arrows move the choice. */
|
|
14
|
+
export declare function StickyPick({ value, onChange, tones, label, className, ...rest }: StickyPickProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { cx } from '../cx.js';
|
|
4
|
+
import { STICKY_TONES } from './tones.js';
|
|
5
|
+
/** StickyPick — the tone chooser: a row of round swatches at tap size, the chosen one ringed. Native radios: the arrows move the choice. */
|
|
6
|
+
export function StickyPick({ value, onChange, tones = STICKY_TONES, label, className, ...rest }) {
|
|
7
|
+
const name = useId();
|
|
8
|
+
return (_jsx("div", { className: cx('sticky-pick', className), role: "radiogroup", "aria-label": label, ...rest, children: tones.map((tone) => (_jsx("label", { className: cx('sticky-pick__swatch', `sticky--${tone}`), children: _jsx("input", { type: "radio", name: name, value: tone, "aria-label": tone, checked: tone === value, onChange: () => onChange?.(tone) }) }, tone))) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
export interface StickyThreadProps extends ComponentProps<'div'> {
|
|
3
|
+
/** a dotted line running down under the avatars, post to post (a feed) */
|
|
4
|
+
rail?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** StickyThread — a stack of StickyPosts: a chat or a feed told in notes. */
|
|
7
|
+
export declare function StickyThread({ rail, className, ...rest }: StickyThreadProps): import("react").JSX.Element;
|
|
8
|
+
export interface StickyPostProps extends ComponentProps<'div'> {
|
|
9
|
+
/** who wrote it (an Avatar) */
|
|
10
|
+
who?: ReactNode;
|
|
11
|
+
/** their name, beside the avatar */
|
|
12
|
+
name?: ReactNode;
|
|
13
|
+
/** when, at the far end of the head row */
|
|
14
|
+
time?: ReactNode;
|
|
15
|
+
/** whose: theirs on the left (default), mine on the right */
|
|
16
|
+
side?: 'left' | 'right';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* StickyPost — one person's turn: a head row (avatar · name · time at the far end) and, under the name, their Sticky notes.
|
|
20
|
+
* `side="right"` mirrors it. The rail mark is always there; only a `StickyThread rail` draws it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function StickyPost({ who, name, time, side, className, children, ...rest }: StickyPostProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../cx.js';
|
|
3
|
+
/** StickyThread — a stack of StickyPosts: a chat or a feed told in notes. */
|
|
4
|
+
export function StickyThread({ rail, className, ...rest }) {
|
|
5
|
+
return _jsx("div", { className: cx('sticky-thread', rail && 'sticky-thread--rail', className), ...rest });
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* StickyPost — one person's turn: a head row (avatar · name · time at the far end) and, under the name, their Sticky notes.
|
|
9
|
+
* `side="right"` mirrors it. The rail mark is always there; only a `StickyThread rail` draws it.
|
|
10
|
+
*/
|
|
11
|
+
export function StickyPost({ who, name, time, side = 'left', className, children, ...rest }) {
|
|
12
|
+
return (_jsxs("div", { className: cx('sticky-post', side === 'right' && 'sticky-post--right', className), ...rest, children: [who != null && _jsx("span", { className: "sticky-post__who", children: who }), _jsxs("div", { className: "sticky-post__head", children: [name != null && _jsx("span", { className: "sticky-post__name", children: name }), time != null && _jsx("span", { className: "sticky-post__time", children: time })] }), _jsx("span", { className: "sticky-post__rail", "aria-hidden": "true" }), _jsx("div", { className: "sticky-post__notes", children: children })] }));
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.64",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|