@geomak/ui 7.17.0 → 7.18.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # @geomak/ui · Oxygen Design System
6
6
 
7
- **60+ production-grade React components for enterprise apps** — dashboards, CRMs, internal tools, and landing pages. Token-driven, accessible, light/dark first-class, and properly tree-shakeable.
7
+ **100+ production-grade React components for enterprise apps** — dashboards, CRMs, internal tools, and landing pages. Token-driven, accessible, light/dark first-class, and properly tree-shakeable.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@geomak/ui?color=0466c8&label=npm)](https://www.npmjs.com/package/@geomak/ui)
10
10
  [![types](https://img.shields.io/npm/types/@geomak/ui?color=0466c8)](https://www.npmjs.com/package/@geomak/ui)
@@ -64,7 +64,7 @@ import { ChevronDown, Search, createIcon } from '@geomak/ui/icons'
64
64
 
65
65
  ## Components
66
66
 
67
- 60+ components across these groups — all with **live controls and a written guide** in [Storybook](https://oxygenui.com).
67
+ 100+ components across these groups — all with **live controls and a written guide** in [Storybook](https://oxygenui.com).
68
68
 
69
69
  | Group | Components |
70
70
  |---|---|
@@ -135,6 +135,71 @@ The standard **gray / slate / zinc** ramps and **black** stay available alongsid
135
135
 
136
136
  ---
137
137
 
138
+ ## AI toolchain (Claude Code)
139
+
140
+ OxygenUI ships a built-in MCP server and a set of Claude Code skills and commands that let AI assistants look up component APIs, search by use-case, and scaffold new components — all grounded in the real MDX documentation.
141
+
142
+ ### MCP server
143
+
144
+ The server is deployed alongside Storybook on Netlify as a Netlify Function at `/mcp`. It exposes four tools:
145
+
146
+ | Tool | What it does |
147
+ |---|---|
148
+ | `list_components` | Browse all 100+ components with slug, category, and description |
149
+ | `get_component` | Fetch the full props API and usage examples for one component by slug |
150
+ | `find_component` | Keyword search across names, categories, and descriptions |
151
+ | `get_token` | Look up CSS custom properties by name or category prefix |
152
+
153
+ **Connect it:**
154
+
155
+ ```jsonc
156
+ // .mcp.json (already in the repo — update the URL after first deploy)
157
+ {
158
+ "mcpServers": {
159
+ "oxygen-ui": {
160
+ "type": "http",
161
+ "url": "https://your-site.netlify.app/mcp"
162
+ },
163
+ "oxygen-ui-local": {
164
+ "type": "http",
165
+ "url": "http://localhost:8888/mcp"
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ Or add it from the CLI: `claude mcp add oxygen-ui --transport http https://your-site.netlify.app/mcp`
172
+
173
+ ### Claude Code commands
174
+
175
+ Four slash commands are registered in `.claude/commands/`:
176
+
177
+ | Command | Usage |
178
+ |---|---|
179
+ | `/ui-lookup <name>` | Fetch the full docs for a component — e.g. `/ui-lookup wizard` |
180
+ | `/ui-find <query>` | Search by use-case — e.g. `/ui-find virtualized table` |
181
+ | `/ui-scaffold <name>` | Scaffold a new component with source + story + MDX guide |
182
+ | `/ui-story <name>` | Add story coverage for an existing component |
183
+
184
+ ### Context skill
185
+
186
+ The `/oxygen-ui` skill loads the full design system context into any Claude Code session — import patterns, token usage, Tailwind utilities, form API, and repository conventions. Invoke it at the start of any session where you plan to build against `@geomak/ui`.
187
+
188
+ ### Local development with the MCP server
189
+
190
+ ```bash
191
+ npm install -g netlify-cli # one-time global install
192
+ yarn mcp:dev # generates manifest + starts netlify dev on port 8888
193
+ ```
194
+
195
+ The function is then available at `http://localhost:8888/mcp`. Claude Code picks up the `oxygen-ui-local` entry from `.mcp.json` automatically when you open the project.
196
+
197
+ ### How the manifest is built
198
+
199
+ At build time, `scripts/generate-ai-manifest.mjs` scans all 101 MDX guide files in `src/docs/` and the co-located MDX files in `src/components/`, strips Storybook boilerplate, and emits `netlify/functions/ai-manifest.json`. The Netlify Function bundles this JSON via esbuild — no database, no runtime file I/O, no cold-start penalty.
200
+
201
+ ---
202
+
138
203
  ## Development
139
204
 
140
205
  ```bash
package/dist/index.cjs CHANGED
@@ -5092,12 +5092,52 @@ function useTargetBbox(ref) {
5092
5092
  }
5093
5093
  var TOOLTIP_WIDTH = 280;
5094
5094
  var TOOLTIP_GAP = 12;
5095
- function tooltipStyleFor(bbox, placement) {
5096
- const pl = placement ?? "right";
5097
- if (pl === "right") return { left: bbox.right + TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
5098
- if (pl === "left") return { left: bbox.left - TOOLTIP_WIDTH - TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
5099
- if (pl === "bottom") return { left: bbox.left + bbox.width / 2, top: bbox.bottom + TOOLTIP_GAP, transform: "translateX(-50%)", width: TOOLTIP_WIDTH };
5100
- return { left: bbox.left + bbox.width / 2, top: bbox.top - TOOLTIP_GAP, transform: "translate(-50%, -100%)", width: TOOLTIP_WIDTH };
5095
+ function tooltipStyleFor(bbox, placement, tooltipHeight) {
5096
+ const GAP2 = TOOLTIP_GAP;
5097
+ const W = TOOLTIP_WIDTH;
5098
+ const H = tooltipHeight;
5099
+ const vw = window.innerWidth;
5100
+ const vh = window.innerHeight;
5101
+ let side = placement ?? "right";
5102
+ if (side === "right" && bbox.right + GAP2 + W > vw) side = "left";
5103
+ if (side === "left" && bbox.left - W - GAP2 < 0) side = "right";
5104
+ if (side === "bottom" && bbox.bottom + GAP2 + H > vh) side = "top";
5105
+ if (side === "top" && bbox.top - GAP2 - H < 0) side = "bottom";
5106
+ let left;
5107
+ let top;
5108
+ let transform;
5109
+ if (side === "right") {
5110
+ left = bbox.right + GAP2;
5111
+ top = bbox.top + bbox.height / 2;
5112
+ transform = "translateY(-50%)";
5113
+ } else if (side === "left") {
5114
+ left = bbox.left - W - GAP2;
5115
+ top = bbox.top + bbox.height / 2;
5116
+ transform = "translateY(-50%)";
5117
+ } else if (side === "bottom") {
5118
+ left = bbox.left + bbox.width / 2;
5119
+ top = bbox.bottom + GAP2;
5120
+ transform = "translateX(-50%)";
5121
+ } else {
5122
+ left = bbox.left + bbox.width / 2;
5123
+ top = bbox.top - GAP2;
5124
+ transform = "translate(-50%, -100%)";
5125
+ }
5126
+ if (side === "top" || side === "bottom") {
5127
+ left = Math.max(GAP2 + W / 2, Math.min(left, vw - W / 2 - GAP2));
5128
+ } else {
5129
+ left = Math.max(GAP2, Math.min(left, vw - W - GAP2));
5130
+ }
5131
+ if (H > 0) {
5132
+ if (side === "right" || side === "left") {
5133
+ top = Math.max(GAP2 + H / 2, Math.min(top, vh - GAP2 - H / 2));
5134
+ } else if (side === "bottom") {
5135
+ top = Math.max(GAP2, Math.min(top, vh - GAP2 - H));
5136
+ } else {
5137
+ top = Math.max(GAP2 + H, Math.min(top, vh - GAP2));
5138
+ }
5139
+ }
5140
+ return { left, top, transform, width: W };
5101
5141
  }
5102
5142
  function useFocusTrap(containerRef, active) {
5103
5143
  React36.useEffect(() => {
@@ -5145,6 +5185,11 @@ function Wizard({
5145
5185
  const reduced = framerMotion.useReducedMotion();
5146
5186
  const [open, setOpen] = React36.useState(() => steps.length > 0 && !readDismissed(storageKey));
5147
5187
  const [activeIndex, setActiveIndex] = React36.useState(0);
5188
+ const [tooltipHeight, setTooltipHeight] = React36.useState(0);
5189
+ React36.useLayoutEffect(() => {
5190
+ const h = tooltipRef.current?.offsetHeight ?? 0;
5191
+ if (h > 0) setTooltipHeight((prev) => prev === h ? prev : h);
5192
+ }, [activeIndex, open]);
5148
5193
  const step = steps[activeIndex];
5149
5194
  const bbox = useTargetBbox(step?.stepRef);
5150
5195
  useFocusTrap(tooltipRef, open);
@@ -5197,7 +5242,7 @@ function Wizard({
5197
5242
  right: 0,
5198
5243
  height: bbox.height + SPOT_PAD * 2
5199
5244
  } : { display: "none" };
5200
- const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement) : { display: "none" };
5245
+ const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement, tooltipHeight) : { display: "none" };
5201
5246
  const isLast = activeIndex === steps.length - 1;
5202
5247
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5203
5248
  children,