npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vestow/theme

v0.10.0

Published

Theme của WoodBlock: Design Token dạng CSS thuần, registry chín trục, controller vanilla và definePreset. Không biết framework nào.

Readme

@vestow/theme

Theme của WoodBlock, trọn gói: giá trị token dạng CSS thuần, và tầng chạy đọc lựa chọn đã lưu rồi ghi tám trục lên <html>.

| | dạng | nạp thế nào | |---|---|---| | giá trị token, tám trục theme, tầng token component | CSS thuần | @import "@vestow/theme/css/index.css" | | entry cho package component (Tailwind + @layer dùng chung) | CSS | @import "@vestow/theme/css/app.css" | | registry tám trục, controller, definePreset | TypeScript | import { themeController } from '@vestow/theme' |

Hai nửa độc lập: nạp CSS không kéo về một byte JavaScript, và import JS không kéo về một byte CSS. Một app Angular chỉ cần @import bộ token thì không phải chạm tới nửa kia.

Phần TS là vanilla — không import framework nào, không chạm document ở tầng module, nên import được trong SSR.

Ba entry CSS, xếp từ dưới lên

| file | thêm gì | ai nạp | |---|---|---| | css/index.css | 8 file token, CSS thuần — không một at-rule Tailwind nào | app không dùng Tailwind | | css/tailwind.css | @custom-variant dark + @theme inline | app dùng Tailwind, tự khai @layer | | css/app.css | @import "tailwindcss" source(none), tw-animate-css, và các khối @layer dùng chung (.scroll-edge, .scroll-area-x, reset base) | @vestow/vue |

Hai file đứng ngoài index.css vì cái giá đo được: charts.css (3.084 B, trục data-chart) và typeset.css (2.703 B, lớp văn xuôi .typeset). App nào cần thì tự @import.

css/primitives.css là file TỰ SINH từ tailwindcss/theme.css — đừng sửa tay; pnpm -F @vestow/theme gen sinh lại, và pnpm lint bắt file cũ.

Controller

import { themeController } from '@vestow/theme'

themeController.init() // đọc localStorage, áp lên <html>, nghe `storage`
themeController.setPalette('solar')
themeController.subscribe(state => console.log(state.isDark))

Vỏ bọc cho từng framework chỉ còn việc nối subscribe() vào hệ reactivity của nó. Bản Vue ở vue/packages/ui/src/composables/useThemeController.ts dài 48 dòng — đó là toàn bộ phần code riêng của Vue cho tám trục.

Viết vỏ bọc mới thì đọc src/controller.ts trước; đừng viết lại logic. Chỉ cần một bên quên style.colorScheme hay gõ sai khoá localStorage là người dùng đổi framework sẽ mất lựa chọn đã lưu — mà cả hai lỗi đó đều không làm gì gãy, chỉ âm thầm sai.

Preset có tên — @vestow/theme/presets

Năm preset, đúng năm bảng màu: Hyperion (mặc định), Solar, Nebula, Aurora, Eclipse. Mỗi cái là một ThemePreset — object thường, tự dựng hay spread được:

import { Solar } from '@vestow/theme/presets'

themeController.setDefaults(Solar.axes)   // TRƯỚC init()
themeController.init()

Bên Vue thì app.use(createWoodBlock({ theme: { preset: Solar } })) làm đúng việc đó.

setDefaults khác setPalette ở đúng một chỗ, và đó là toàn bộ lý do nó tồn tại: nó KHÔNG ghi localStorage. Preset là giao diện ban đầu; người dùng đã chọn bảng màu khác thì lựa chọn đó thắng ở mọi lần tải trang sau. Một preset gọi setPalette thì mỗi lần tải trang lại đè lựa chọn của người dùng.

Mỗi preset chỉ chốt palette, cố ý không chốt base: bảng màu đã tự mang họ trung tính của nó, và bộ số AA của palettes.css đo trên đúng tổ hợp đó.

Tuỳ biến theo cách PrimeVue — một preset gốc, một object ghi đè:

import { definePreset } from '@vestow/theme'
import { Nebula } from '@vestow/theme/presets'

const MyBrand = definePreset(Nebula, {
  name: 'MyBrand',
  axes: { base: 'stone', style: 'nova' },                     // gộp nông
  tokens: { base: { button: { radius: '2px' } } },            // gộp sâu
})

Hàm này thuần tuý: trả preset mới, không sửa Nebula, không đụng DOM.

base: 'slate' không ép slate lên bảng màu khác Hyperion: nó là mặc định nên không có data-base nào để CSS bám vào, và nền rơi về họ của chính bảng màu (Solar + slate ra stone). Bảy họ còn lại luôn thắng họ của bảng màu.

Ngoài năm preset bảng màu, module còn xuất một preset cho mỗi giá trị của hai trục kia — hình khối (Vega · Nova · Maia · Lyra · Mira · Luma · Sera · Rhea), họ trung tính (Slate · Zinc · Stone · Mist · Taupe · Olive · Mauve · Neutral) — cùng ba mảng palettePresets / stylePresets / basePresets, theo đúng thứ tự registry (presets là tên cũ của palettePresets). Chúng sinh ra để dựng bộ chọn giao diện (lặp mảng, hiện name, gọi setStyle), không phải để lồng definePreset(definePreset(...)) — cấu hình cố định thì viết axes như trên.

definePreset — một tham số: ghi token lên trang

Ghi đè token lúc chạy, cùng vai với definePreset của PrimeVue. Dùng cho đúng một ca: giá trị chỉ biết lúc chạy — màu thương hiệu đọc từ API, theme người dùng lưu trong tài khoản, bản xem trước trong trình dựng theme. Ngoài ca đó thì viết :root { --primary: … } trong CSS của app vẫn gọn hơn.

import { definePreset } from '@vestow/theme'

definePreset({
  base: { radius: '2px' },
  light: { primary: { $: 'oklch(0.55 0.2 30)', foreground: '#fff' } },
  dark: { primary: '#7dd3fc' },
})

Tên token có kiểu: gõ sai là lỗi biên dịch kèm gợi ý "did you mean", không phải một biến rỗng và một cái nút trong suốt. 221 tên, sinh từ chính CSS của @vestow/theme.

Hai dạng khoá, dùng lẫn nhau được

| | | |---|---| | cây | { button: { radius: '2px' } } — dạng button.border.radius của PrimeVue | | phẳng | { '--button-radius': '2px' } — đúng tên CSS |

Không mơ hồ, vì tên custom property luôn bắt đầu bằng -- còn nhánh thì không bao giờ. Trộn hai dạng trong một object cũng được.

Dạng phẳng không bỏ đi: nó là thứ duy nhất viết được một tên tính lúc chạy, và khi debug thì nó đọc thẳng ra tên CSS thật, khỏi phải dịch ngược.

Khoá $ là giá trị của chính nhánh. Cần nó vì 56/221 tên vừa là lá vừa là nhánh — --accent cạnh --accent-foreground, --button-radius cạnh --button-radius-sm — mà một khoá không thể vừa mang chuỗi vừa mang object. Đúng vai root trong preset của PrimeVue, chỉ ngắn hơn và không bao giờ đụng một đoạn tên thật.

{ button: { radius: '2px' } }                 // chỉ --button-radius
{ button: { radius: { $: '2px', sm: '4px' } } } // cả --button-radius và -sm

Chuỗi trần đặt vào một nhánh là lối tắt cho $.

Cây SUY RA từ tên CSS, không phải ngược lại. Đó là chỗ khác PrimeVue: bên họ tên CSS sinh từ object cấu hình, ở đây object sinh từ CSS — nên không có nguồn thứ hai để mà trôi, và thêm một token vào CSS là cây tự có nhánh mới.

Render phía server thì dùng presetToCss() — bản thuần, không đụng DOM. Nhúng kết quả vào một thẻ <style id="woodblock-preset"> ngay trong HTML trả về: trang không chớp màu, và definePreset() phía trình duyệt nhận lại đúng thẻ đó thay vì thêm thẻ thứ hai.

Ba điều phải biết trước khi dùng, chi tiết ở đầu src/preset.ts:

  • Giá trị là chuỗi CSS, JS không hiểu nó. Cố ý — token ở đây viết bằng color-mix(in oklab, …) và oklch(from …); mô hình hoá lại mấy phép đó trong JS là viết lại một engine màu, và đã đo được là nó ra số khác trình duyệt.
  • Preset ghi ở :root, nên nó không đè được token mà một rule khác khai lại trên phần tử CON — khai báo trực tiếp luôn thắng giá trị thừa kế.
  • Đặc hiệu là :root lặp 5 lần, con số ĐO chứ không chọn cho tròn: rule đặc hiệu nhất đang khai token ở tầng :root là (0,4,0).

Tám trục

| trục | ghi lên <html> | khoá localStorage | mặc định | |---|---|---|---| | chế độ màu | class .dark + style.color-scheme | hyperion-color-mode | system | | bảng màu | data-palette | hyperion-palette | hyperion | | hình khối | data-style | hyperion-style | vega | | họ trung tính | data-base | hyperion-base | slate | | bộ màu biểu đồ | data-chart | hyperion-chart-palette | theme | | màu menu | data-menu-color | hyperion-menu-color | default | | bề mặt menu | data-menu-appearance | hyperion-menu-appearance | solid | | highlight menu | data-menu-accent | hyperion-menu-accent | subtle |

Giá trị mặc định không có khối CSS nào, nên controller gỡ hẳn thuộc tính thay vì ghi vào. Các trục độc lập — Solar + lyra hay Nebula + stone đều dùng được.

Kính đã gỡ hẳn. Trục data-surface gỡ ở 0.8.0; từ 0.9.0 prop glass của @vestow/vue cũng gỡ, cùng mọi token --glass-* / --liquid-* và hai thuộc tính data-glass / data-glass-level trên <html>. Khoá hyperion-surface, hyperion-glass-enabled / hyperion-glass-level còn trong localStorage của người dùng cũ thì vô hại: không còn gì đọc chúng. Trục data-menu-appearance="translucent" thì ở lại — nó là trục của menu, có token riêng.

Tiền tố hyperion- là một định danh lưu trữ, không phải tên thương hiệu. Đổi nó là xoá lựa chọn đã lưu của mọi người dùng hiện có.

Lệnh

pnpm -F @vestow/theme gen     # sinh lại primitives.css + token-names.ts từ CSS
pnpm -F @vestow/theme check   # kiểm nó không trôi (chạy sẵn trong `pnpm lint`)
pnpm -F @vestow/theme build   # biên dịch TS ra dist/ (chỉ cần khi publish)

Publish

exports trỏ vào src/ cho tiện phát triển trong workspace; publishConfig đổi sang dist/ lúc pnpm publish, và prepack chạy gen + build trước.

files đóng gói css/ và dist/. Phần CSS đi thẳng, không qua build; phần TS thì prepack biên dịch trước.

Hai dependency runtime — tailwindcss và tw-animate-css — chỉ phục vụ css/app.css. App nạp css/index.css (CSS thuần) không chạm tới chúng.