mac-human-design 0.1.2 → 0.1.5
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 +51 -9
- package/changelog.md +55 -0
- package/docs/macos-base-ui-coverage.md +114 -0
- package/examples/macos-base-ui-gallery/index.html +12 -0
- package/examples/macos-base-ui-gallery/src/main.tsx +13 -0
- package/package.json +28 -16
- package/scripts/verify-base-ui-coverage.mjs +273 -0
- package/scripts/verify-macos-design.mjs +104 -0
- package/src/components/AppWindowShell.tsx +4 -18
- package/src/components/MacBaseUI.tsx +481 -0
- package/src/components/MacBaseUIGallery.tsx +428 -0
- package/src/components/MacSegmentedControl.tsx +14 -17
- package/src/components/StatusMessage.tsx +2 -42
- package/src/components/SymbolIconButton.tsx +26 -29
- package/src/components/ViewDragRegion.tsx +2 -4
- package/src/components/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/styles/macosBaseUi.css +1158 -0
- package/src/styles/macosTauri.css +70 -0
- package/src/tauri/macosWindowConfig.ts +20 -0
- package/src/theme/macosTheme.ts +58 -568
- package/src/types/css.d.ts +1 -0
- package/src-tauri/Cargo.toml +1 -0
- package/src-tauri/src/lib.rs +39 -1
- package/src-tauri/templates/macos-transparent-overlay-window.json +16 -0
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
# human-design
|
|
1
|
+
# mac-human-design
|
|
2
2
|
|
|
3
3
|
A reusable shared library for Tauri macOS apps. It includes:
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
- macOS-styled wrappers for every public `@base-ui/react` component family.
|
|
6
|
+
- `MacSegmentedControl` and macOS design tokens/classes.
|
|
6
7
|
- A reusable SF Symbols fetch layer with React hooks/components.
|
|
7
8
|
- A Rust/Tauri plugin crate that exposes a native command:
|
|
8
9
|
`system_symbol_png_data_url`.
|
|
@@ -10,6 +11,7 @@ A reusable shared library for Tauri macOS apps. It includes:
|
|
|
10
11
|
## Structure
|
|
11
12
|
|
|
12
13
|
- `src/` - TypeScript/React library exports
|
|
14
|
+
- `docs/macos-base-ui-coverage.md` - Base UI component coverage and variants
|
|
13
15
|
- `src-tauri/` - Rust plugin crate (`human-design-tauri-system-symbols`)
|
|
14
16
|
|
|
15
17
|
## Install
|
|
@@ -17,7 +19,17 @@ A reusable shared library for Tauri macOS apps. It includes:
|
|
|
17
19
|
From the consumer app:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
npm i human-design
|
|
22
|
+
npm i mac-human-design @base-ui/react react react-dom
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@base-ui/react`, `react`, and `react-dom` are peer dependencies. Install
|
|
26
|
+
`@tauri-apps/api` too when using the SF Symbols helpers or Tauri integration.
|
|
27
|
+
|
|
28
|
+
Consumer apps should also include the macOS Base UI stylesheet once near their
|
|
29
|
+
app root:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import "mac-human-design/styles/macos-base-ui.css";
|
|
21
33
|
```
|
|
22
34
|
|
|
23
35
|
In the Tauri Rust side, add the local path dependency while developing:
|
|
@@ -42,8 +54,9 @@ fn main() {
|
|
|
42
54
|
|
|
43
55
|
```ts
|
|
44
56
|
import { useState } from "react";
|
|
45
|
-
import { MacSegmentedControl } from "human-design";
|
|
46
|
-
import { SystemSymbolImage } from "human-design/symbols";
|
|
57
|
+
import { MacButton, MacSegmentedControl, MacSelect } from "mac-human-design";
|
|
58
|
+
import { SystemSymbolImage } from "mac-human-design/symbols";
|
|
59
|
+
import "mac-human-design/styles/macos-base-ui.css";
|
|
47
60
|
|
|
48
61
|
export function Demo() {
|
|
49
62
|
const [tab, setTab] = useState<"files" | "settings">("files");
|
|
@@ -59,18 +72,50 @@ export function Demo() {
|
|
|
59
72
|
value={tab}
|
|
60
73
|
onChange={setTab}
|
|
61
74
|
/>
|
|
75
|
+
<MacButton data-variant="primary">Continue</MacButton>
|
|
76
|
+
<MacSelect.Root items={[{ label: "Files", value: "files" }]}>
|
|
77
|
+
<MacSelect.Trigger>
|
|
78
|
+
<MacSelect.Value placeholder="Choose" />
|
|
79
|
+
</MacSelect.Trigger>
|
|
80
|
+
</MacSelect.Root>
|
|
62
81
|
</div>
|
|
63
82
|
);
|
|
64
83
|
}
|
|
65
84
|
```
|
|
66
85
|
|
|
86
|
+
## Verify
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm run check
|
|
90
|
+
npm run dev:gallery -- --port 5177
|
|
91
|
+
```
|
|
92
|
+
|
|
67
93
|
## Why it works cross-platform
|
|
68
94
|
|
|
95
|
+
The React components stay browser-safe. The optional Tauri plugin is the only
|
|
96
|
+
native layer:
|
|
97
|
+
|
|
98
|
+
- On macOS, Rust command renders the symbol to PNG via `NSImage`.
|
|
99
|
+
- On non-macOS, command returns `None` so invocations stay safe and predictable.
|
|
100
|
+
|
|
69
101
|
## Shared UI components moved in
|
|
70
102
|
|
|
71
103
|
The following components are now in the shared library and can be imported by any app:
|
|
72
104
|
|
|
73
105
|
- `AppWindowShell`
|
|
106
|
+
- `MacAccordion`, `MacAlertDialog`, `MacAutocomplete`, `MacAvatar`,
|
|
107
|
+
`MacButton`, `MacPrimaryButton`, `MacSecondaryButton`,
|
|
108
|
+
`MacDestructiveButton`, `MacPlainButton`, `MacIconButton`,
|
|
109
|
+
`MacHelpButton`, `MacCheckbox`, `MacCheckboxGroup`, `MacCollapsible`,
|
|
110
|
+
`MacCombobox`, `MacContextMenu`, `MacDialog`, `MacDrawer`, `MacField`,
|
|
111
|
+
`MacFieldset`, `MacForm`, `MacInput`, `MacTextField`, `MacSearchField`,
|
|
112
|
+
`MacMenu`, `MacMenubar`, `MacMeter`, `MacNavigationMenu`,
|
|
113
|
+
`MacNumberField`, `MacOTPField`, `MacPopover`,
|
|
114
|
+
`MacPreviewCard`, `MacProgress`, `MacRadio`, `MacRadioGroup`,
|
|
115
|
+
`MacScrollArea`, `MacSelect`, `MacSeparator`, `MacSlider`, `MacSwitch`,
|
|
116
|
+
`MacTabs`, `MacToast`, `MacToggle`, `MacToolbarToggle`,
|
|
117
|
+
`MacToggleGroup`, `MacSegmentedToggleGroup`,
|
|
118
|
+
`MacSeparatedSegmentedToggleGroup`, `MacToolbar`, `MacTooltip`
|
|
74
119
|
- `StatusMessage`
|
|
75
120
|
- `SymbolIconButton`
|
|
76
121
|
- `ViewDragRegion`
|
|
@@ -85,8 +130,5 @@ import {
|
|
|
85
130
|
SymbolIconButton,
|
|
86
131
|
ViewDragRegion,
|
|
87
132
|
isDraggableAreaTarget,
|
|
88
|
-
} from "human-design";
|
|
133
|
+
} from "mac-human-design";
|
|
89
134
|
```
|
|
90
|
-
|
|
91
|
-
- On macOS, Rust command renders the symbol to PNG via `NSImage`.
|
|
92
|
-
- On non-macOS, command returns `None` so invocations stay safe and predictable.
|
package/changelog.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file tracks changes between published npm versions of `mac-human-design`.
|
|
4
|
+
|
|
5
|
+
## 0.1.5
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Replaced the previous Base Web `baseui` foundation with `@base-ui/react`.
|
|
10
|
+
- Updated the package peer dependencies so consumers install `@base-ui/react`,
|
|
11
|
+
`react`, and `react-dom` alongside `mac-human-design`.
|
|
12
|
+
- Updated README and coverage documentation to use the published package name
|
|
13
|
+
`mac-human-design` in install and import examples.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added macOS-styled wrappers for every public renderable `@base-ui/react`
|
|
18
|
+
component family in version 1.5.0:
|
|
19
|
+
`Accordion`, `AlertDialog`, `Autocomplete`, `Avatar`, `Button`,
|
|
20
|
+
`Checkbox`, `CheckboxGroup`, `Collapsible`, `Combobox`, `ContextMenu`,
|
|
21
|
+
`CSPProvider`, `Dialog`, `DirectionProvider`, `Drawer`, `Field`,
|
|
22
|
+
`Fieldset`, `Form`, `Input`, `Menu`, `Menubar`, `Meter`,
|
|
23
|
+
`NavigationMenu`, `NumberField`, `OTPField`, `Popover`, `PreviewCard`,
|
|
24
|
+
`Progress`, `Radio`, `RadioGroup`, `ScrollArea`, `Select`, `Separator`,
|
|
25
|
+
`Slider`, `Switch`, `Tabs`, `Toast`, `Toggle`, `ToggleGroup`, `Toolbar`,
|
|
26
|
+
and `Tooltip`.
|
|
27
|
+
- Added macOS-native variants for common platform control styles, including
|
|
28
|
+
primary, secondary, destructive, plain, icon, and help buttons; text and
|
|
29
|
+
search fields; pop-up and pull-down select triggers; segmented controls;
|
|
30
|
+
toolbar buttons; sheet dialogs; and sidebar drawers.
|
|
31
|
+
- Added `src/styles/macosBaseUi.css`, which defines the macOS control styling,
|
|
32
|
+
system font stack, light/dark tokens, focus rings, reduced-motion behavior,
|
|
33
|
+
popup/menu/dialog surfaces, and state styling used by the wrappers.
|
|
34
|
+
- Added `MacBaseUIGallery` and a Vite example app at
|
|
35
|
+
`examples/macos-base-ui-gallery` to visually exercise the wrapped component
|
|
36
|
+
families and variants.
|
|
37
|
+
- Added `docs/macos-base-ui-coverage.md` to document Base UI family coverage,
|
|
38
|
+
intentional utility exclusions, native variants, and verification commands.
|
|
39
|
+
- Added verification scripts for Base UI coverage, macOS design constraints,
|
|
40
|
+
gallery buildability, CSS selector coverage, stale Base Web references, and
|
|
41
|
+
npm package contents.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- Removed the old `baseui` dependency path from package metadata, source,
|
|
46
|
+
docs, examples, and the lockfile.
|
|
47
|
+
- Removed any dependency on Base Web or Styletron as the shared UI foundation.
|
|
48
|
+
|
|
49
|
+
### Verification
|
|
50
|
+
|
|
51
|
+
- `npm run check` typechecks the package, verifies all public Base UI component
|
|
52
|
+
wrappers and namespace parts, validates macOS design constraints, and builds
|
|
53
|
+
the gallery.
|
|
54
|
+
- `npm pack --dry-run --json` confirms the npm package includes source, styles,
|
|
55
|
+
docs, examples, scripts, README, license, and this changelog.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# macOS Base UI Coverage
|
|
2
|
+
|
|
3
|
+
`mac-human-design` wraps every public component family exported by `@base-ui/react`
|
|
4
|
+
1.5.0 with macOS-oriented class names. Import the CSS once:
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import "mac-human-design/styles/macos-base-ui.css";
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Consumer apps install `@base-ui/react`, `react`, and `react-dom` as peer
|
|
11
|
+
dependencies alongside this package.
|
|
12
|
+
|
|
13
|
+
## Component Families
|
|
14
|
+
|
|
15
|
+
| Base UI family | macOS export |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `accordion` | `MacAccordion` |
|
|
18
|
+
| `alert-dialog` | `MacAlertDialog` |
|
|
19
|
+
| `autocomplete` | `MacAutocomplete` |
|
|
20
|
+
| `avatar` | `MacAvatar` |
|
|
21
|
+
| `button` | `MacButton` |
|
|
22
|
+
| `checkbox` | `MacCheckbox` |
|
|
23
|
+
| `checkbox-group` | `MacCheckboxGroup` |
|
|
24
|
+
| `collapsible` | `MacCollapsible` |
|
|
25
|
+
| `combobox` | `MacCombobox` |
|
|
26
|
+
| `context-menu` | `MacContextMenu` |
|
|
27
|
+
| `csp-provider` | `MacCSPProvider` |
|
|
28
|
+
| `dialog` | `MacDialog` |
|
|
29
|
+
| `direction-provider` | `MacDirectionProvider` |
|
|
30
|
+
| `drawer` | `MacDrawer` |
|
|
31
|
+
| `field` | `MacField` |
|
|
32
|
+
| `fieldset` | `MacFieldset` |
|
|
33
|
+
| `form` | `MacForm` |
|
|
34
|
+
| `input` | `MacInput` |
|
|
35
|
+
| `menu` | `MacMenu` |
|
|
36
|
+
| `menubar` | `MacMenubar` |
|
|
37
|
+
| `meter` | `MacMeter` |
|
|
38
|
+
| `navigation-menu` | `MacNavigationMenu` |
|
|
39
|
+
| `number-field` | `MacNumberField` |
|
|
40
|
+
| `otp-field` | `MacOTPField` |
|
|
41
|
+
| `popover` | `MacPopover` |
|
|
42
|
+
| `preview-card` | `MacPreviewCard` |
|
|
43
|
+
| `progress` | `MacProgress` |
|
|
44
|
+
| `radio` | `MacRadio` |
|
|
45
|
+
| `radio-group` | `MacRadioGroup` |
|
|
46
|
+
| `scroll-area` | `MacScrollArea` |
|
|
47
|
+
| `select` | `MacSelect` |
|
|
48
|
+
| `separator` | `MacSeparator` |
|
|
49
|
+
| `slider` | `MacSlider` |
|
|
50
|
+
| `switch` | `MacSwitch` |
|
|
51
|
+
| `tabs` | `MacTabs` |
|
|
52
|
+
| `toast` | `MacToast` |
|
|
53
|
+
| `toggle` | `MacToggle` |
|
|
54
|
+
| `toggle-group` | `MacToggleGroup` |
|
|
55
|
+
| `toolbar` | `MacToolbar` |
|
|
56
|
+
| `tooltip` | `MacTooltip` |
|
|
57
|
+
|
|
58
|
+
Support exports such as `merge-props`, `use-render`, and
|
|
59
|
+
`unstable-use-media-query` are intentionally not wrapped because they do not
|
|
60
|
+
render UI.
|
|
61
|
+
|
|
62
|
+
## Native Variants
|
|
63
|
+
|
|
64
|
+
| Native role | Export |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Default push button | `MacButton` |
|
|
67
|
+
| Blue default button | `MacPrimaryButton` |
|
|
68
|
+
| Secondary push button | `MacSecondaryButton` |
|
|
69
|
+
| Destructive button | `MacDestructiveButton` |
|
|
70
|
+
| Borderless button | `MacPlainButton` |
|
|
71
|
+
| Icon button | `MacIconButton` |
|
|
72
|
+
| Help button | `MacHelpButton` |
|
|
73
|
+
| Text field | `MacTextField` / `MacInput` |
|
|
74
|
+
| Search field | `MacSearchField` |
|
|
75
|
+
| Pop-up button | `MacSelect.PopUpButtonTrigger` |
|
|
76
|
+
| Pull-down button | `MacSelect.PullDownButtonTrigger` |
|
|
77
|
+
| Segmented control | `MacSegmentedToggleGroup` |
|
|
78
|
+
| Separated segmented control | `MacSeparatedSegmentedToggleGroup` |
|
|
79
|
+
| Toolbar toggle | `MacToolbarToggle` |
|
|
80
|
+
| Toolbar icon button | `MacToolbar.IconButton` |
|
|
81
|
+
| Window sheet | `MacDialog.SheetPopup` |
|
|
82
|
+
| Sidebar drawer | `MacDrawer.SidebarPopup` |
|
|
83
|
+
|
|
84
|
+
The gallery at `examples/macos-base-ui-gallery` renders these wrappers across
|
|
85
|
+
normal, selected, disabled, mixed, popup, dialog, and narrow-width states.
|
|
86
|
+
|
|
87
|
+
## Verification
|
|
88
|
+
|
|
89
|
+
Run the full local gate before publishing wrapper changes:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm run check
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The gate typechecks the library and verifies:
|
|
96
|
+
|
|
97
|
+
- every public `@base-ui/react` component family has a `Mac...` wrapper export,
|
|
98
|
+
- every renderable namespace part in each wrapped family has a matching macOS part,
|
|
99
|
+
- every component family is listed in this coverage document,
|
|
100
|
+
- required native variants are exported,
|
|
101
|
+
- nested native variant parts such as pop-up buttons, sheets, and sidebar drawers are exported,
|
|
102
|
+
- every class assigned by the wrapper module has a matching CSS selector,
|
|
103
|
+
- every public wrapper export is represented in the visual gallery source,
|
|
104
|
+
- macOS design features are present: system font stack, dark mode, reduced motion,
|
|
105
|
+
keyboard focus rings, accent tokens, styled `hd-mac` classes, and typography
|
|
106
|
+
constraints,
|
|
107
|
+
- the visual gallery can be bundled for production from the shipped source,
|
|
108
|
+
- old UI-foundation references have not returned.
|
|
109
|
+
|
|
110
|
+
Run the visual gallery with:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run dev:gallery -- --port 5177
|
|
114
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>human-design macOS Base UI gallery</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { AppWindowShell, MacBaseUIGallery } from "../../../src";
|
|
4
|
+
import "../../../src/styles/macosBaseUi.css";
|
|
5
|
+
import "../../../src/styles/macosTauri.css";
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById("root") as HTMLElement).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<AppWindowShell>
|
|
10
|
+
<MacBaseUIGallery />
|
|
11
|
+
</AppWindowShell>
|
|
12
|
+
</StrictMode>,
|
|
13
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mac-human-design",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Reusable macOS-oriented UI primitives and SF Symbols bridge for Tauri apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -11,10 +11,17 @@
|
|
|
11
11
|
"./theme": "./src/theme/index.ts",
|
|
12
12
|
"./symbols": "./src/symbols/index.ts",
|
|
13
13
|
"./tauri": "./src/tauri/index.ts",
|
|
14
|
+
"./tauri/macos-window-config": "./src/tauri/macosWindowConfig.ts",
|
|
15
|
+
"./styles/macos-base-ui.css": "./src/styles/macosBaseUi.css",
|
|
14
16
|
"./utils": "./src/utils/index.ts",
|
|
17
|
+
"./styles/macos-tauri.css": "./src/styles/macosTauri.css",
|
|
15
18
|
"./rust": "./src-tauri"
|
|
16
19
|
},
|
|
17
20
|
"files": [
|
|
21
|
+
"changelog.md",
|
|
22
|
+
"docs/**/*",
|
|
23
|
+
"examples/**/*",
|
|
24
|
+
"scripts/**/*",
|
|
18
25
|
"src/**/*",
|
|
19
26
|
"src-tauri/**/*",
|
|
20
27
|
"README.md",
|
|
@@ -29,31 +36,36 @@
|
|
|
29
36
|
"components"
|
|
30
37
|
],
|
|
31
38
|
"scripts": {
|
|
32
|
-
"
|
|
39
|
+
"check": "npm run typecheck && npm run verify:base-ui && npm run verify:design && npm run verify:gallery",
|
|
40
|
+
"dev:gallery": "vite --host 127.0.0.1 examples/macos-base-ui-gallery",
|
|
41
|
+
"publish": "npm publish --access public --ignore-scripts",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"verify:base-ui": "node scripts/verify-base-ui-coverage.mjs",
|
|
44
|
+
"verify:design": "node scripts/verify-macos-design.mjs",
|
|
45
|
+
"verify:gallery": "rm -rf .verify/macos-base-ui-gallery && vite build examples/macos-base-ui-gallery --outDir ../../.verify/macos-base-ui-gallery --emptyOutDir && rm -rf .verify/macos-base-ui-gallery"
|
|
33
46
|
},
|
|
34
47
|
"author": "human-design",
|
|
35
48
|
"license": "MIT",
|
|
36
49
|
"peerDependencies": {
|
|
50
|
+
"@base-ui/react": "^1.5.0",
|
|
37
51
|
"@tauri-apps/api": "^2",
|
|
38
|
-
"baseui": ">=18",
|
|
39
52
|
"react": ">=18",
|
|
40
|
-
"react-dom": ">=18"
|
|
41
|
-
"styletron-react": "^6.1.1",
|
|
42
|
-
"styletron-standard": "^3.1.0"
|
|
53
|
+
"react-dom": ">=18"
|
|
43
54
|
},
|
|
44
55
|
"peerDependenciesMeta": {
|
|
45
56
|
"@tauri-apps/api": {
|
|
46
57
|
"optional": true
|
|
47
|
-
},
|
|
48
|
-
"baseui": {
|
|
49
|
-
"optional": true
|
|
50
|
-
},
|
|
51
|
-
"styletron-react": {
|
|
52
|
-
"optional": true
|
|
53
|
-
},
|
|
54
|
-
"styletron-standard": {
|
|
55
|
-
"optional": true
|
|
56
58
|
}
|
|
57
59
|
},
|
|
58
|
-
"
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@base-ui/react": "^1.5.0",
|
|
62
|
+
"@tauri-apps/api": "^2.11.0",
|
|
63
|
+
"@types/react": "^19.2.17",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
66
|
+
"react": "^19.2.7",
|
|
67
|
+
"react-dom": "^19.2.7",
|
|
68
|
+
"typescript": "^6.0.3",
|
|
69
|
+
"vite": "^8.0.16"
|
|
70
|
+
}
|
|
59
71
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
const packageJsonPath = path.join(repoRoot, "node_modules", "@base-ui", "react", "package.json");
|
|
7
|
+
const wrapperPath = path.join(repoRoot, "src", "components", "MacBaseUI.tsx");
|
|
8
|
+
const galleryPath = path.join(repoRoot, "src", "components", "MacBaseUIGallery.tsx");
|
|
9
|
+
const stylesPath = path.join(repoRoot, "src", "styles", "macosBaseUi.css");
|
|
10
|
+
const docsPath = path.join(repoRoot, "docs", "macos-base-ui-coverage.md");
|
|
11
|
+
|
|
12
|
+
const nonComponentExports = new Set([
|
|
13
|
+
".",
|
|
14
|
+
"esm",
|
|
15
|
+
"merge-props",
|
|
16
|
+
"types",
|
|
17
|
+
"unstable-use-media-query",
|
|
18
|
+
"use-render",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const acronymParts = new Map([
|
|
22
|
+
["csp", "CSP"],
|
|
23
|
+
["otp", "OTP"],
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function readText(filePath) {
|
|
27
|
+
return fs.readFileSync(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toMacExportName(family) {
|
|
31
|
+
return `Mac${family
|
|
32
|
+
.split("-")
|
|
33
|
+
.map((part) => acronymParts.get(part) ?? `${part[0].toUpperCase()}${part.slice(1)}`)
|
|
34
|
+
.join("")}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getBaseUiFamilies() {
|
|
38
|
+
const packageJson = JSON.parse(readText(packageJsonPath));
|
|
39
|
+
return Object.keys(packageJson.exports)
|
|
40
|
+
.filter((exportKey) => exportKey.startsWith("./"))
|
|
41
|
+
.map((exportKey) => exportKey.slice(2))
|
|
42
|
+
.filter((exportName) => !exportName.startsWith("internals/"))
|
|
43
|
+
.filter((exportName) => exportName !== "package.json")
|
|
44
|
+
.filter((exportName) => !nonComponentExports.has(exportName))
|
|
45
|
+
.sort();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getMacExports(source) {
|
|
49
|
+
return new Set(
|
|
50
|
+
Array.from(source.matchAll(/^export const (Mac[A-Za-z0-9]+)\b/gm), (match) => match[1]),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getBaseUiParts(family) {
|
|
55
|
+
const partsPath = path.join(repoRoot, "node_modules", "@base-ui", "react", family, "index.parts.d.ts");
|
|
56
|
+
if (!fs.existsSync(partsPath)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const partsSource = readText(partsPath);
|
|
61
|
+
const parts = new Set();
|
|
62
|
+
|
|
63
|
+
for (const match of partsSource.matchAll(/export\s+\{\s*[A-Za-z0-9_$]+\s+as\s+([A-Z][A-Za-z0-9_$]*)\s*\}/g)) {
|
|
64
|
+
parts.add(match[1]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const match of partsSource.matchAll(/export\s+\{\s*([A-Z][A-Za-z0-9_$]*)\s*\}/g)) {
|
|
68
|
+
parts.add(match[1]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Array.from(parts).sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getMacObjectPartKeys(source, macExport) {
|
|
75
|
+
const objectStart = source.match(new RegExp(`export const ${macExport} = \\\\{`));
|
|
76
|
+
if (!objectStart) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const startIndex = objectStart.index + objectStart[0].length;
|
|
81
|
+
let depth = 1;
|
|
82
|
+
let index = startIndex;
|
|
83
|
+
|
|
84
|
+
while (index < source.length && depth > 0) {
|
|
85
|
+
const char = source[index];
|
|
86
|
+
if (char === "{") {
|
|
87
|
+
depth += 1;
|
|
88
|
+
} else if (char === "}") {
|
|
89
|
+
depth -= 1;
|
|
90
|
+
}
|
|
91
|
+
index += 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const objectBody = source.slice(startIndex, index - 1);
|
|
95
|
+
return new Set(
|
|
96
|
+
Array.from(objectBody.matchAll(/^\s*([A-Z][A-Za-z0-9_$]*):/gm), (match) => match[1]),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function fail(message, details = []) {
|
|
101
|
+
console.error(message);
|
|
102
|
+
for (const detail of details) {
|
|
103
|
+
console.error(`- ${detail}`);
|
|
104
|
+
}
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const families = getBaseUiFamilies();
|
|
109
|
+
const wrapperSource = readText(wrapperPath);
|
|
110
|
+
const gallerySource = readText(galleryPath);
|
|
111
|
+
const stylesSource = readText(stylesPath);
|
|
112
|
+
const docsSource = readText(docsPath);
|
|
113
|
+
const macExports = getMacExports(wrapperSource);
|
|
114
|
+
|
|
115
|
+
const missingWrappers = families
|
|
116
|
+
.map((family) => [family, toMacExportName(family)])
|
|
117
|
+
.filter(([, macExport]) => !macExports.has(macExport))
|
|
118
|
+
.map(([family, macExport]) => `${family} -> ${macExport}`);
|
|
119
|
+
|
|
120
|
+
if (missingWrappers.length > 0) {
|
|
121
|
+
fail("Missing macOS wrappers for public @base-ui/react component families.", missingWrappers);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const missingPartWrappers = [];
|
|
125
|
+
for (const family of families) {
|
|
126
|
+
const macExport = toMacExportName(family);
|
|
127
|
+
const baseParts = getBaseUiParts(family);
|
|
128
|
+
if (baseParts.length === 0) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const macParts = getMacObjectPartKeys(wrapperSource, macExport);
|
|
133
|
+
if (!macParts) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const part of baseParts) {
|
|
138
|
+
if (!macParts.has(part)) {
|
|
139
|
+
missingPartWrappers.push(`${family}.${part} -> ${macExport}.${part}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (missingPartWrappers.length > 0) {
|
|
145
|
+
fail("Missing macOS wrappers for public @base-ui/react namespace parts.", missingPartWrappers);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const missingDocsRows = families
|
|
149
|
+
.map((family) => [family, toMacExportName(family)])
|
|
150
|
+
.filter(([family, macExport]) => {
|
|
151
|
+
return !docsSource.includes(`| \`${family}\` | \`${macExport}\` |`);
|
|
152
|
+
})
|
|
153
|
+
.map(([family, macExport]) => `${family} -> ${macExport}`);
|
|
154
|
+
|
|
155
|
+
if (missingDocsRows.length > 0) {
|
|
156
|
+
fail("Missing coverage documentation rows.", missingDocsRows);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const requiredVariants = [
|
|
160
|
+
"MacPrimaryButton",
|
|
161
|
+
"MacSecondaryButton",
|
|
162
|
+
"MacDestructiveButton",
|
|
163
|
+
"MacPlainButton",
|
|
164
|
+
"MacIconButton",
|
|
165
|
+
"MacHelpButton",
|
|
166
|
+
"MacTextField",
|
|
167
|
+
"MacSearchField",
|
|
168
|
+
"MacToolbarToggle",
|
|
169
|
+
"MacSegmentedToggleGroup",
|
|
170
|
+
"MacSeparatedSegmentedToggleGroup",
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const missingVariants = requiredVariants.filter((variant) => !macExports.has(variant));
|
|
174
|
+
if (missingVariants.length > 0) {
|
|
175
|
+
fail("Missing expected native macOS variant exports.", missingVariants);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const requiredNestedVariants = [
|
|
179
|
+
"MacSelect.PopUpButtonTrigger",
|
|
180
|
+
"MacSelect.PullDownButtonTrigger",
|
|
181
|
+
"MacDialog.SheetPopup",
|
|
182
|
+
"MacDrawer.SidebarPopup",
|
|
183
|
+
"MacToolbar.IconButton",
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const missingNestedVariants = requiredNestedVariants.filter((variantPath) => {
|
|
187
|
+
const [exportName, partName] = variantPath.split(".");
|
|
188
|
+
return !new RegExp(`export const ${exportName} = [\\s\\S]*?^\\s*${partName}:`, "m").test(
|
|
189
|
+
wrapperSource,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (missingNestedVariants.length > 0) {
|
|
194
|
+
fail("Missing expected native macOS nested variant exports.", missingNestedVariants);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const missingGalleryCoverage = Array.from(macExports)
|
|
198
|
+
.sort()
|
|
199
|
+
.filter((macExport) => !gallerySource.includes(macExport));
|
|
200
|
+
|
|
201
|
+
if (missingGalleryCoverage.length > 0) {
|
|
202
|
+
fail("Missing gallery coverage for public macOS wrapper exports.", missingGalleryCoverage);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const wrapperClasses = new Set(
|
|
206
|
+
Array.from(wrapperSource.matchAll(/"([^"]*hd-mac[^"]*)"/g))
|
|
207
|
+
.flatMap((match) => match[1].split(/\s+/))
|
|
208
|
+
.filter(Boolean),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const missingClassStyles = Array.from(wrapperClasses)
|
|
212
|
+
.sort()
|
|
213
|
+
.filter((className) => {
|
|
214
|
+
const escapedClassName = className.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
215
|
+
return !new RegExp(`\\.${escapedClassName}(?![a-zA-Z0-9_-])`).test(stylesSource);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (missingClassStyles.length > 0) {
|
|
219
|
+
fail("Missing CSS selectors for macOS wrapper classes.", missingClassStyles);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const forbiddenPatterns = [
|
|
223
|
+
/\bfrom\s+["']baseui(?:\/[^"']*)?["']/,
|
|
224
|
+
/\bbaseui\b/,
|
|
225
|
+
/\bstyletron\b/i,
|
|
226
|
+
/Base Web/,
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
function listFiles(directory, relativePrefix = "") {
|
|
230
|
+
const files = [];
|
|
231
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
232
|
+
const relativePath = path.join(relativePrefix, entry.name);
|
|
233
|
+
const absolutePath = path.join(directory, entry.name);
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
files.push(...listFiles(absolutePath, relativePath));
|
|
236
|
+
} else if (entry.isFile()) {
|
|
237
|
+
files.push(relativePath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return files;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sourceFiles = [
|
|
244
|
+
"package.json",
|
|
245
|
+
"package-lock.json",
|
|
246
|
+
"README.md",
|
|
247
|
+
"docs/macos-base-ui-coverage.md",
|
|
248
|
+
...listFiles(path.join(repoRoot, "src")).map((filePath) => path.join("src", filePath)),
|
|
249
|
+
...listFiles(path.join(repoRoot, "examples")).map((filePath) => path.join("examples", filePath)),
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const forbiddenHits = [];
|
|
253
|
+
for (const relativeFile of sourceFiles) {
|
|
254
|
+
const absoluteFile = path.join(repoRoot, relativeFile);
|
|
255
|
+
const text = readText(absoluteFile);
|
|
256
|
+
for (const pattern of forbiddenPatterns) {
|
|
257
|
+
if (pattern.test(text)) {
|
|
258
|
+
forbiddenHits.push(`${relativeFile}: ${pattern}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (forbiddenHits.length > 0) {
|
|
264
|
+
fail("Forbidden Base Web or Styletron references found.", forbiddenHits);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (process.exitCode) {
|
|
268
|
+
process.exit();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(
|
|
272
|
+
`Verified ${families.length} @base-ui/react component families, namespace parts, ${requiredVariants.length} native variants, ${requiredNestedVariants.length} nested variants, ${wrapperClasses.size} wrapper classes, gallery coverage, and no Base Web references.`,
|
|
273
|
+
);
|