@structuralists/scaffolding 0.0.1 → 0.0.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.
@@ -0,0 +1,66 @@
1
+ name: Release & Publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ release:
9
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ registry-url: 'https://registry.npmjs.org'
23
+
24
+ - run: npm install -g npm@latest
25
+
26
+ - uses: oven-sh/setup-bun@v2
27
+ with:
28
+ bun-version: latest
29
+
30
+ - run: bun install --frozen-lockfile
31
+
32
+ - run: bun run typecheck
33
+ - run: bun run lint
34
+ - run: bun run test
35
+
36
+ - name: Configure git
37
+ run: |
38
+ git config user.name 'github-actions[bot]'
39
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
40
+
41
+ - name: Determine bump type from commit message
42
+ id: bump
43
+ run: |
44
+ MSG=$(git log -1 --pretty=%B)
45
+ if echo "$MSG" | grep -qE "BREAKING CHANGE|^[a-z]+(\([^)]+\))?!:"; then
46
+ echo "type=major" >> "$GITHUB_OUTPUT"
47
+ elif echo "$MSG" | grep -qE "^feat(\(|:)"; then
48
+ echo "type=minor" >> "$GITHUB_OUTPUT"
49
+ else
50
+ echo "type=patch" >> "$GITHUB_OUTPUT"
51
+ fi
52
+
53
+ - name: Bump version
54
+ id: version
55
+ run: |
56
+ NEW_TAG=$(npm version ${{ steps.bump.outputs.type }} -m "release: %s [skip ci]")
57
+ echo "tag=$NEW_TAG" >> "$GITHUB_OUTPUT"
58
+
59
+ - name: Push commit and tag
60
+ run: git push --follow-tags
61
+
62
+ - run: npm publish
63
+
64
+ - run: gh release create "${{ steps.version.outputs.tag }}" --generate-notes
65
+ env:
66
+ GH_TOKEN: ${{ github.token }}
package/README.md CHANGED
@@ -1,7 +1,86 @@
1
1
  # @structuralists/scaffolding
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@structuralists/scaffolding.svg)](https://www.npmjs.com/package/@structuralists/scaffolding)
4
+ [![publish](https://github.com/structuralists/scaffolding/actions/workflows/publish.yml/badge.svg)](https://github.com/structuralists/scaffolding/actions/workflows/publish.yml)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@structuralists/scaffolding)](https://bundlephobia.com/package/@structuralists/scaffolding)
6
+
3
7
  `@structuralists/scaffolding` is a bundle of react components that can make scaffolding a UI quick and efficient. Unlike component libraries like a material UI, it does not target being used for production grade applications, but instead targets getting "something" up and running, where you don't care what exactly it looks like.
4
8
 
5
9
  It is not designed to be customized. it's not pluggable. It is not designed to composed into another design system (though it is react, you can write you own components and pass them as children or whatever to your heart's content.)
6
10
 
7
11
  It is designed to get you something working, and if you then do care what that looks like, build or import a design system and convert to that.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @structuralists/scaffolding
17
+ # or: bun add / pnpm add / yarn add
18
+ ```
19
+
20
+ Peer deps (you almost certainly already have these): `react@^19`, `react-dom@^19`, `react-router@^7`.
21
+
22
+ ## Setup
23
+
24
+ Import the design tokens once, at your app entry. They define the CSS custom properties every component reads:
25
+
26
+ ```ts
27
+ import '@structuralists/scaffolding/tokens.css';
28
+ ```
29
+
30
+ That's it — no provider, no theme object. Theme switching is driven by `data-theme` on any ancestor (typically `<html>`):
31
+
32
+ ```html
33
+ <html data-theme="dark-warm">
34
+ ```
35
+
36
+ Available themes: `light-warm`, `light-paper`, `light-sepia`, `dark-warm`, `dark-neutral`, `dark-dimmed`. Omit the attribute to follow system preference.
37
+
38
+ ## Use it
39
+
40
+ ```tsx
41
+ import { Stack, Heading, Button, Input, Field } from '@structuralists/scaffolding';
42
+
43
+ export const SignupCard = () => {
44
+ return (
45
+ <Stack gap={3}>
46
+ <Heading level={1}>Sign up</Heading>
47
+ <Field label="Email">
48
+ <Input type="email" />
49
+ </Field>
50
+ <Button variant="primary">Continue</Button>
51
+ </Stack>
52
+ );
53
+ };
54
+ ```
55
+
56
+ Browse every component (with live controls) in the Storybook: `bun run storybook`.
57
+
58
+ ## Consumer notes
59
+
60
+ This package ships TypeScript source directly — `main` and `types` both point at `./index.ts`. Modern bundlers handle that out of the box:
61
+
62
+ - **Vite / Remix / Bun / Astro**: works with no config.
63
+ - **Next.js**: add the package to `transpilePackages`:
64
+ ```js
65
+ // next.config.js
66
+ module.exports = {
67
+ transpilePackages: ['@structuralists/scaffolding'],
68
+ };
69
+ ```
70
+ - **Plain Node / CRA**: not supported — you need a bundler that understands `.ts`/`.tsx` and CSS Modules.
71
+
72
+ ## Releasing
73
+
74
+ Releases are fully automated. Open a PR, merge it, done — CI bumps the version, publishes to npm with provenance, and creates a GitHub release with auto-generated notes.
75
+
76
+ The repo is configured for **squash-only merging**, and the **PR title becomes the squash-commit title**. That title drives the version bump:
77
+
78
+ | PR title pattern | Bump |
79
+ | ----------------------------------------------- | --------- |
80
+ | `feat: ...` or `feat(scope): ...` | **minor** |
81
+ | `feat!: ...` or any commit with `BREAKING CHANGE` in the body | **major** |
82
+ | anything else (`fix:`, `chore:`, `docs:`, etc.) | **patch** |
83
+
84
+ Match the [conventional commits](https://www.conventionalcommits.org/) style when writing PR titles.
85
+
86
+ To skip a release for a specific merge, include `[skip ci]` in the PR title.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structuralists/scaffolding",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "main": "./index.ts",
5
5
  "types": "./index.ts",
6
6
  "exports": {
@@ -2,8 +2,6 @@ import { describe, test, expect, mock } from 'bun:test';
2
2
  import { render, fireEvent, cleanup } from '@testing-library/react';
3
3
  import { MediumModal } from './index';
4
4
 
5
- // happy-dom implements showMediumModal/close but we call cleanup between tests
6
- // manually (bun:test has no afterEach-by-default in this setup).
7
5
  const setup = (props: Partial<React.ComponentProps<typeof MediumModal>> = {}) => {
8
6
  const onClose = mock(() => {});
9
7
  const utils = render(
@@ -11,9 +9,12 @@ const setup = (props: Partial<React.ComponentProps<typeof MediumModal>> = {}) =>
11
9
  <button type="button">inside</button>
12
10
  </MediumModal>,
13
11
  );
14
- const dialog = utils.container.ownerDocument.querySelector('dialog');
12
+ const doc = utils.container.ownerDocument;
13
+ const dialog = doc.querySelector('[role="dialog"]');
15
14
  if (!dialog) throw new Error('dialog not rendered');
16
- return { ...utils, onClose, dialog };
15
+ const backdrop = doc.querySelector('div[aria-hidden="true"]');
16
+ if (!backdrop) throw new Error('backdrop not rendered');
17
+ return { ...utils, onClose, dialog, backdrop };
17
18
  };
18
19
 
19
20
  describe('MediumModal', () => {
@@ -24,9 +25,9 @@ describe('MediumModal', () => {
24
25
  cleanup();
25
26
  });
26
27
 
27
- test('calls onClose when the backdrop (the dialog itself) is clicked', () => {
28
- const { onClose, dialog } = setup();
29
- fireEvent.click(dialog);
28
+ test('calls onClose when the backdrop is clicked', () => {
29
+ const { onClose, backdrop } = setup();
30
+ fireEvent.click(backdrop);
30
31
  expect(onClose).toHaveBeenCalledTimes(1);
31
32
  cleanup();
32
33
  });
@@ -38,10 +39,9 @@ describe('MediumModal', () => {
38
39
  cleanup();
39
40
  });
40
41
 
41
- test('calls onClose on the native cancel event (Escape)', () => {
42
- const { onClose, dialog } = setup();
43
- const cancel = new Event('cancel', { cancelable: true });
44
- dialog.dispatchEvent(cancel);
42
+ test('calls onClose on Escape', () => {
43
+ const { onClose } = setup();
44
+ fireEvent.keyDown(document.body, { key: 'Escape' });
45
45
  expect(onClose).toHaveBeenCalledTimes(1);
46
46
  cleanup();
47
47
  });