hlmnhkl-ui 0.35.0 → 0.35.2

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/README.md CHANGED
@@ -9,6 +9,10 @@ React components, measured down to the part you never see: the 44px tap area, th
9
9
 
10
10
  **[Docs](https://ui.hlmnhkl.my)** · [Installation](https://ui.hlmnhkl.my/installation) · [Components](https://ui.hlmnhkl.my/button) · [What's new](https://ui.hlmnhkl.my/changelog)
11
11
 
12
+ <img alt="Four hlmnhkl/ui controls in use: a Dial turned to a new oven temperature, a RulerScrubber dragged and landing on a mark, a SwipeDeck card thrown right and the next thrown left, and a SplitFlap board flipping to new times." src="https://raw.githubusercontent.com/hilmanhaikal/hlmnhkl-ui/main/.github/assets/signature.gif" width="600">
13
+
14
+ Controls that feel like real ones: Dial, RulerScrubber, SwipeDeck, SplitFlap and more. Plus the everyday parts every app needs: buttons, inputs, sheets, tables, menus and 60 more.
15
+
12
16
  ## Install
13
17
 
14
18
  From your app's folder, set it up once, then copy the components you need:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hlmnhkl-ui",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "React components in black, white and grey with one blue accent. Copy them into your app with npx hlmnhkl-ui add <name>.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ui.hlmnhkl.my",
@@ -25,7 +25,8 @@
25
25
  ],
26
26
  "scripts": {
27
27
  "typecheck": "tsc -p tsconfig.json",
28
- "add": "node scripts/add.js"
28
+ "add": "node scripts/add.js",
29
+ "prepublishOnly": "node scripts/check-publish.mjs"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/react": "^19.3.0",
package/scripts/add.js CHANGED
@@ -41,6 +41,17 @@ const KINDS = [
41
41
  { key: 'theme', plural: 'Themes', folder: 'themes', pro: true, single: true },
42
42
  ].map((k) => ({ ...k, dir: join(SRC, k.folder) }))
43
43
 
44
+ // The import lines as they read from the app's entry file. A folder inside src/ is written
45
+ // relative to src/ (Vite, Next and most setups keep the entry there), so it can be pasted as is.
46
+ function fromEntry(folder, both = true) {
47
+ const clean = folder.replace(/^\.\//, '').replace(/\/$/, '')
48
+ const inSrc = clean.startsWith('src/')
49
+ const rel = inSrc ? clean.slice(4) : clean
50
+ const where = inSrc ? 'in your entry file inside src/ (for example src/main.tsx):' : 'in a file at your project root:'
51
+ const lines = [`import './${rel}/tokens.css'`, ...(both ? [`import './${rel}/base.css'`] : [])]
52
+ return `${where}\n${lines.map((l) => ` ${l}`).join('\n')}`
53
+ }
54
+
44
55
  function fail(message, usage = true) {
45
56
  console.error(`Error: ${message}`)
46
57
  if (usage) console.error(`\n${USAGE}`)
@@ -352,7 +363,7 @@ function runInit(opts) {
352
363
  if (!opts.dryRun) writeFileSync(config, `${JSON.stringify({ to: folder }, null, 2)}\n`)
353
364
  console.log(` ${CONFIG}: added, so later commands put files in ${folder}`)
354
365
  }
355
- console.log(`\nNext:\n 1. Import both once at your app root, tokens first:\n import './${folder.replace(/^\.\//, '')}/tokens.css'\n import './${folder.replace(/^\.\//, '')}/base.css'`)
366
+ console.log(`\nNext:\n 1. Import both once, tokens first, ${fromEntry(folder)}`)
356
367
  console.log(` 2. Add your first component: npx hlmnhkl-ui add button`)
357
368
  console.log(` See everything there is: npx hlmnhkl-ui list, or ${SITE}`)
358
369
  }
@@ -417,7 +428,7 @@ function runAdd(opts) {
417
428
  for (const s of skipped) console.log(` ${s}`)
418
429
  if (skipped.some((s) => /^(tokens|base)\.css /.test(s))) console.log(' tokens.css and base.css are never replaced, so your theme edits stay.')
419
430
  }
420
- if (tokensNew) console.log(`Next: import ${join(opts.to || readConfig()?.to || DEFAULT_TO, 'tokens.css')} once at your app root (or run init).`)
431
+ if (tokensNew) console.log(`Next: import tokens.css once ${fromEntry(opts.to || readConfig()?.to || DEFAULT_TO, false)} (or run init).`)
421
432
  if (items.some((i) => i.kind === 'theme')) console.log('Themes: import each one after tokens.css, then set data-theme-brand or data-density on <html>.')
422
433
  }
423
434
 
@@ -147,8 +147,16 @@
147
147
  margin-inline: auto;
148
148
  }
149
149
 
150
+ /* Registered so the fill can ease back after a throw instead of snapping off in one frame. */
151
+ @property --fill {
152
+ syntax: '<number>';
153
+ inherits: true;
154
+ initial-value: 0;
155
+ }
156
+
150
157
  .choice {
151
158
  --fill: 0;
159
+ transition: --fill var(--hl-motion-base) var(--hl-ease-out);
152
160
  position: relative;
153
161
  display: grid;
154
162
  min-block-size: var(--hl-control-h-lg);
@@ -172,6 +180,10 @@
172
180
  white-space: nowrap;
173
181
  }
174
182
 
183
+ .root:has(.deck[data-dragging]) .choice {
184
+ transition: none;
185
+ }
186
+
175
187
  /* The fill pours in from the button's inner edge toward the side the card goes, and the word under it turns to its ink. */
176
188
  .fill {
177
189
  position: absolute;
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useEffect, useLayoutEffect, useRef, useState, type ComponentPropsWithRef, type KeyboardEvent, type PointerEvent, type ReactNode } from 'react'
4
+ import { flushSync } from 'react-dom'
4
5
  import { cx } from '../../internal/cx'
5
6
  import { clamp, magnetTo, pull, speedTracker, springTo } from '../../internal/feel'
6
7
  import { UndoIcon } from '../../internal/icons'
@@ -133,8 +134,10 @@ export function SwipeDeck<T>({
133
134
 
134
135
  const finish = (side: Side) => {
135
136
  stop.current = () => {}
137
+ // The next card must be in place before the top card's transform resets, or the thrown card
138
+ // shows in the middle for a frame.
139
+ flushSync(() => commit(side))
136
140
  paint(0)
137
- commit(side)
138
141
  }
139
142
 
140
143
  const fly = (side: Side, velocity: number) => {