@0xgf/boneyard 1.0.0

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.
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Controls how `snapshotBones` extracts bones from the DOM.
3
+ * Pass as `snapshotConfig` on `<Skeleton>` or as the third arg to `snapshotBones()`.
4
+ *
5
+ * @example
6
+ * <Skeleton loading={isLoading} snapshotConfig={{ leafTags: ['p', 'h1', 'h2', 'li'] }}>
7
+ * <MyComponent />
8
+ * </Skeleton>
9
+ */
10
+ export interface SnapshotConfig {
11
+ /**
12
+ * HTML tags always captured as a single atomic bone, regardless of children.
13
+ * Use this for block-level text elements in your design system.
14
+ * Default: ['p','h1','h2','h3','h4','h5','h6','li','tr']
15
+ */
16
+ leafTags?: string[];
17
+ /**
18
+ * When true, containers with a visible border AND border-radius are captured
19
+ * as container bones — even with a white or transparent background.
20
+ * This catches white cards (`bg-white rounded-xl border`) that would otherwise
21
+ * produce no container bone.
22
+ * Default: true
23
+ */
24
+ captureRoundedBorders?: boolean;
25
+ /**
26
+ * HTML tags to skip entirely — no bone emitted, children not walked.
27
+ * Useful for decorative elements, icons, or custom components you don't
28
+ * want represented in the skeleton.
29
+ *
30
+ * @example ['nav', 'footer', 'aside']
31
+ */
32
+ excludeTags?: string[];
33
+ /**
34
+ * CSS selectors to skip entirely. Any element matching a selector is
35
+ * excluded along with all its descendants. Supports any valid CSS selector —
36
+ * classes, IDs, attributes, tags, or combinations.
37
+ *
38
+ * @example
39
+ * excludeSelectors: [
40
+ * '.icon', // skip by class
41
+ * '[data-no-skeleton]', // skip by data attribute
42
+ * '#sidebar', // skip by ID
43
+ * 'nav', // skip by tag
44
+ * '.card .badge', // skip by nested selector
45
+ * ]
46
+ */
47
+ excludeSelectors?: string[];
48
+ }
49
+ /** A single skeleton bone — a rounded rect placeholder */
50
+ export interface Bone {
51
+ x: number;
52
+ y: number;
53
+ w: number;
54
+ h: number;
55
+ r: number | string;
56
+ /** True if this bone is a background container — rendered lighter so children stand out */
57
+ c?: boolean;
58
+ }
59
+ /** Skeleton layout result for a component at a specific width */
60
+ export interface SkeletonResult {
61
+ name: string;
62
+ viewportWidth: number;
63
+ width: number;
64
+ height: number;
65
+ bones: Bone[];
66
+ }
67
+ /**
68
+ * Describes a component's visual structure for skeleton generation.
69
+ * Auto-extracted from the DOM via `fromElement()`, or hand-authored for
70
+ * SSR/build-time paths where no DOM is available.
71
+ * `computeLayout` uses pretext to measure text and compute bone positions
72
+ * at any container width — no DOM needed at render time.
73
+ *
74
+ * For the simpler browser path, use `snapshotBones()` or `<Skeleton>` instead.
75
+ *
76
+ * @example
77
+ * const card: SkeletonDescriptor = {
78
+ * display: 'flex', flexDirection: 'column', padding: 16, gap: 12,
79
+ * children: [
80
+ * { aspectRatio: 16/9 },
81
+ * { text: 'Title text here', font: '700 18px Inter', lineHeight: 24 },
82
+ * { text: 'Body text content', font: '14px Inter', lineHeight: 20 },
83
+ * { height: 44, borderRadius: 8 },
84
+ * ]
85
+ * }
86
+ */
87
+ export interface SkeletonDescriptor {
88
+ /** Display mode (default: 'block') */
89
+ display?: 'block' | 'flex';
90
+ /** Flex direction (default: 'row') */
91
+ flexDirection?: 'row' | 'column';
92
+ /** Align items — cross-axis alignment */
93
+ alignItems?: string;
94
+ /** Justify content — main-axis alignment */
95
+ justifyContent?: string;
96
+ /** Explicit width in px */
97
+ width?: number;
98
+ /** Explicit height in px */
99
+ height?: number;
100
+ /** CSS aspect-ratio (e.g. 16/9) */
101
+ aspectRatio?: number;
102
+ /** Padding — single number or per-side (missing sides default to 0) */
103
+ padding?: number | {
104
+ top?: number;
105
+ right?: number;
106
+ bottom?: number;
107
+ left?: number;
108
+ };
109
+ /** Margin — single number or per-side (missing sides default to 0) */
110
+ margin?: number | {
111
+ top?: number;
112
+ right?: number;
113
+ bottom?: number;
114
+ left?: number;
115
+ };
116
+ /** Gap between flex children */
117
+ gap?: number;
118
+ /** Row gap (overrides gap for vertical) */
119
+ rowGap?: number;
120
+ /** Column gap (overrides gap for horizontal) */
121
+ columnGap?: number;
122
+ /** Border radius (default: 8, use '50%' for circles) */
123
+ borderRadius?: number | string;
124
+ /** CSS font string for pretext measurement (e.g. '700 18px Inter') */
125
+ font?: string;
126
+ /** Line height in px */
127
+ lineHeight?: number;
128
+ /** Text content — pretext measures this to compute height */
129
+ text?: string;
130
+ /** Max width constraint (px) */
131
+ maxWidth?: number;
132
+ /** Whether this is a leaf bone (auto-detected if not set) */
133
+ leaf?: boolean;
134
+ /** Child descriptors */
135
+ children?: SkeletonDescriptor[];
136
+ }
137
+ /**
138
+ * A responsive skeleton descriptor — maps min-width breakpoints to
139
+ * structural variants. The layout engine picks the best match for
140
+ * the current container width.
141
+ *
142
+ * @example
143
+ * const card: ResponsiveDescriptor = {
144
+ * // Mobile: single column
145
+ * 0: { display: 'flex', flexDirection: 'column', ... },
146
+ * // Tablet+: row with sidebar
147
+ * 768: { display: 'flex', flexDirection: 'row', ... },
148
+ * }
149
+ */
150
+ export type ResponsiveDescriptor = Record<number, SkeletonDescriptor>;
151
+ /**
152
+ * Responsive bones — a set of SkeletonResults captured at different viewport widths.
153
+ * Generated by `boneyard build`, passed as `initialBones` to `<Skeleton>`.
154
+ *
155
+ * `<Skeleton>` picks the nearest matching breakpoint for the current container width.
156
+ * Keys are the min-widths at which each snapshot was taken (e.g. 375, 768, 1280).
157
+ *
158
+ * @example
159
+ * // Generated by: npx boneyard capture http://localhost:3000 --out ./src/bones
160
+ * // Then import:
161
+ * import blogBones from './src/bones/blog-card.bones.json'
162
+ * // { "breakpoints": { "375": {...}, "768": {...}, "1280": {...} } }
163
+ *
164
+ * <Skeleton loading={isLoading} initialBones={blogBones}>
165
+ * <BlogCard />
166
+ * </Skeleton>
167
+ */
168
+ export interface ResponsiveBones {
169
+ breakpoints: Record<number, SkeletonResult>;
170
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@0xgf/boneyard",
3
+ "version": "1.0.0",
4
+ "description": "Pixel-perfect skeleton loading screens. Wrap your component in <Skeleton> and boneyard snapshots the real DOM layout — no manual descriptors, no configuration.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./layout": {
14
+ "types": "./dist/layout.d.ts",
15
+ "import": "./dist/layout.js"
16
+ },
17
+ "./react": {
18
+ "types": "./dist/react.d.ts",
19
+ "import": "./dist/react.js"
20
+ }
21
+ },
22
+ "bin": {
23
+ "boneyard": "./bin/boneyard.mjs"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "bin"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "test": "bun test",
32
+ "prepublishOnly": "tsc"
33
+ },
34
+ "keywords": [
35
+ "skeleton",
36
+ "skeleton-screen",
37
+ "loading",
38
+ "placeholder",
39
+ "react",
40
+ "zero-layout-shift"
41
+ ],
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/0xGF/boneyard"
46
+ },
47
+ "homepage": "https://github.com/0xGF/boneyard",
48
+ "optionalDependencies": {
49
+ "@chenglou/pretext": "^0.0.3"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=18"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "react": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.19.15",
61
+ "@types/react": "^19.0.0",
62
+ "canvas": "^3.2.2",
63
+ "playwright": "^1.58.2",
64
+ "react": "^19.0.0",
65
+ "typescript": "^5.7.0"
66
+ }
67
+ }