makersclaw 0.3.2 → 0.4.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.
Files changed (81) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/r/annotate.json +3 -3
  4. package/r/approval-request.json +2 -2
  5. package/r/audio-player.json +2 -2
  6. package/r/auth-form.json +2 -2
  7. package/r/board.json +2 -2
  8. package/r/calendar-view.json +3 -3
  9. package/r/cart.json +2 -2
  10. package/r/chart.json +2 -2
  11. package/r/chat.json +2 -2
  12. package/r/code-editor.json +2 -2
  13. package/r/comment-box.json +3 -3
  14. package/r/contact-section.json +2 -2
  15. package/r/core.json +12 -6
  16. package/r/csv-import.json +3 -3
  17. package/r/data-table.json +2 -2
  18. package/r/diagram.json +2 -2
  19. package/r/doc-editor.json +2 -2
  20. package/r/doc-page.json +3 -3
  21. package/r/email-render.json +2 -2
  22. package/r/faq.json +2 -2
  23. package/r/file-upload.json +2 -2
  24. package/r/flow-canvas.json +2 -2
  25. package/r/footer.json +2 -2
  26. package/r/gantt.json +3 -3
  27. package/r/job-panel.json +8 -5
  28. package/r/json-editor.json +3 -3
  29. package/r/map.json +2 -2
  30. package/r/menubar.json +2 -2
  31. package/r/mind-map.json +3 -3
  32. package/r/navigation-menu.json +2 -2
  33. package/r/order-detail.json +2 -2
  34. package/r/org-chart.json +3 -3
  35. package/r/page-builder.json +2 -2
  36. package/r/payment-methods.json +2 -2
  37. package/r/pdf-document.json +2 -2
  38. package/r/pdf-viewer.json +2 -2
  39. package/r/product-detail.json +2 -2
  40. package/r/product-grid.json +3 -3
  41. package/r/record-form.json +2 -2
  42. package/r/record-notes.json +4 -4
  43. package/r/registry.json +131 -123
  44. package/r/search-dialog.json +2 -2
  45. package/r/settings-list.json +2 -2
  46. package/r/setup-checklist.json +2 -2
  47. package/r/share-dialog.json +2 -2
  48. package/r/sidebar.json +2 -2
  49. package/r/signature-pad.json +2 -2
  50. package/r/sketch-canvas.json +2 -2
  51. package/r/spreadsheet.json +2 -2
  52. package/r/stat-cards.json +3 -3
  53. package/r/task-list.json +3 -3
  54. package/r/task-model.json +2 -2
  55. package/r/testimonials.json +2 -2
  56. package/r/transaction-list.json +2 -2
  57. package/r/whiteboard.json +3 -3
  58. package/registry/blocks/job-panel/README.md +40 -3
  59. package/registry/blocks/job-panel/block.json +2 -2
  60. package/registry/blocks/job-panel/demo.jsx +75 -6
  61. package/registry/blocks/job-panel/job-panel.jsx +60 -11
  62. package/src/shadcn.mjs +1 -1
  63. package/templates/next/files/README.md +339 -73
  64. package/templates/next/files/app/auto-refresh.jsx +72 -10
  65. package/templates/next/files/app/claw-alive/route.js +17 -0
  66. package/templates/next/files/app/error-relay.jsx +9 -4
  67. package/templates/next/files/app/escape-relay.jsx +27 -0
  68. package/templates/next/files/app/frame.js +218 -0
  69. package/templates/next/files/app/layout.jsx +18 -3
  70. package/templates/next/files/check/run.mjs +25 -8
  71. package/templates/next/files/components/parts/fixture.jsx +29 -4
  72. package/templates/next/files/components/parts/index.js +2 -1
  73. package/templates/next/files/components/parts/live-work.jsx +235 -0
  74. package/templates/next/files/components/ui/button.jsx +6 -1
  75. package/templates/next/files/components/ui/date-picker.jsx +15 -2
  76. package/templates/next/files/components/ui/icon.jsx +37 -2
  77. package/templates/next/files/lib/claw.js +127 -28
  78. package/templates/next/files/lib/hooks.js +454 -33
  79. package/templates/next/files/lib/platform.js +113 -15
  80. package/templates/next/files/surfaces/README.md +3 -0
  81. package/templates/next/template.json +17 -6
package/README.md CHANGED
@@ -97,7 +97,9 @@ test/ npm test · npm run check:regress · npm run test
97
97
  - `npm test` — the CLI against scratch folders.
98
98
  - `npm run check:regress` — the template's `npm run check`, case by case.
99
99
  - `npm run test:app` — a real app: init, every block added with its demo as
100
- a surface, `npm install`, `npm run check` (build, start, every surface).
100
+ a surface, `npm install`, the kit rendered by React with its warnings on
101
+ (`test/kit-render.test.mjs`, which `npm test` skips), `npm run check`
102
+ (build, start, every surface).
101
103
  - `npm run build:registry` — `r/`, the shadcn registry (`--base <url>
102
104
  --out <dir>` to serve it locally for a test).
103
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makersclaw",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "The Makersclaw app template, its kit and its blocks: init an app, add the blocks a surface needs, search the registry.",
5
5
  "type": "module",
6
6
  "bin": {
package/r/annotate.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "title": "Annotate",
6
6
  "description": "Mark up a screenshot: drop, pick or paste one, then draw arrows, boxes, highlights, redaction bars and numbered notes on it (Excalidraw underneath, via sketch-canvas). A side list holds the words of each numbered note; a click on a note's number shows its pin. Starter templates (bug report, design review, walkthrough) on a fresh screenshot. The annotation is plain JSON saved through onChange / an async onSave; onSubmit hands over a PNG of the marked-up screenshot with the notes and the sketch.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/sketch-canvas.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/sketch-canvas.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -55,7 +55,7 @@
55
55
  "docs": "Installed to components/makersclaw/blocks/annotate. Import: import { Annotate, ANNOTATE_TOOLS, ANNOTATE_TEMPLATES, emptyAnnotation, toAnnotation, annotationNotes, annotationToText, noteMarkers, nextNoteNumber, markerSkeleton } from \"@/components/makersclaw/blocks/annotate\". Props and an example: components/makersclaw/blocks/annotate/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
56
56
  "meta": {
57
57
  "makersclaw": {
58
- "version": "0.3.2",
58
+ "version": "0.4.0",
59
59
  "useWhen": "Someone needs to point at parts of a screen and say something about each: a bug report, design feedback, a support question, a walkthrough, a request to an agent about what to change. Not a blank drawing board (whiteboard or sketch-canvas), not a wireframe (sketch-to-app), not an image upload alone (file-upload)."
60
60
  }
61
61
  }
@@ -5,7 +5,7 @@
5
5
  "title": "ApprovalRequest",
6
6
  "description": "One request waiting on a decision: what is asked for as label/value facts (an amount in large type), who asked and when, why, a status badge, and Approve / Reject (reject asks for a reason). The decision goes out through async onApprove / onReject; the block shows it working, a failure, then the outcome in place of the buttons. A card alone, or a page of detail sections beside the card.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -45,7 +45,7 @@
45
45
  "docs": "Installed to components/makersclaw/blocks/approval-request. Import: import { ApprovalRequest } from \"@/components/makersclaw/blocks/approval-request\". Props and an example: components/makersclaw/blocks/approval-request/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
46
46
  "meta": {
47
47
  "makersclaw": {
48
- "version": "0.3.2",
48
+ "version": "0.4.0",
49
49
  "useWhen": "Someone must say yes or no to one thing another person asked for: a purchase, a refund, time off, a discount, an access request, a published post. Not for a queue of many requests (a data-table with row actions) or a yes/no about the founder's own action (kit Confirm)."
50
50
  }
51
51
  }
@@ -5,7 +5,7 @@
5
5
  "title": "AudioPlayer",
6
6
  "description": "A waveform audio player (wavesurfer.js) in the workspace theme: play, skip back and forward, speed, mute; regions (spans of the audio in a theme tone, labelled in a lane under the waveform, click to play one) and markers (pins at moments, click to jump); a hover time readout; drag across the waveform to mark a new region; add a marker at the playhead; keyboard like a video player (K, J, L, M, arrows). Time events and a handle (seek, play a range) for syncing a transcript.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "wavesurfer.js@8.0.0",
@@ -67,7 +67,7 @@
67
67
  "docs": "Installed to components/makersclaw/blocks/audio-player. Import: import { AudioPlayer, useAudioPlayer, formatTime, formatRange, spokenTime, regionAt, TONES, RATES } from \"@/components/makersclaw/blocks/audio-player\". Props and an example: components/makersclaw/blocks/audio-player/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
68
68
  "meta": {
69
69
  "makersclaw": {
70
- "version": "0.3.2",
70
+ "version": "0.4.0",
71
71
  "useWhen": "A screen plays a recording: a call or meeting recording, a voice note, a podcast clip, a voicemail, a sound file on a record. The call-review block is built on it. Not for video, not for recording audio from the microphone."
72
72
  }
73
73
  }
package/r/auth-form.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "AuthForm",
6
6
  "description": "The account screens in one form: sign in (password, email only, or provider buttons for Google, GitHub, Apple and Microsoft), sign up with extra fields, a magic link, a reset link, and changing a password against a live rules checklist. The auth is yours: each submit is an async callback that returns { ok } or an error the form shows.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -48,7 +48,7 @@
48
48
  "docs": "Installed to components/makersclaw/blocks/auth-form. Import: import { AuthForm, PASSWORD_RULES } from \"@/components/makersclaw/blocks/auth-form\". Props and an example: components/makersclaw/blocks/auth-form/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
49
49
  "meta": {
50
50
  "makersclaw": {
51
- "version": "0.3.2",
51
+ "version": "0.4.0",
52
52
  "useWhen": "An app needs a sign-in, sign-up, forgot-password or change-password screen, or a settings page where a signed-in person changes their password."
53
53
  }
54
54
  }
package/r/board.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Board",
6
6
  "description": "Records as cards in columns, one per stage; drag a card to another column (mouse, touch, or the card's menu from the keyboard) and the stage is saved. Optional column icons, a per-column total (\"13pt\"), a category colour on each card's edge, and a stacked layout of folding sections with compact rows.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -48,7 +48,7 @@
48
48
  "docs": "Installed to components/makersclaw/blocks/board. Import: import { Board } from \"@/components/makersclaw/blocks/board\". Props and an example: components/makersclaw/blocks/board/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
49
49
  "meta": {
50
50
  "makersclaw": {
51
- "version": "0.3.2",
51
+ "version": "0.4.0",
52
52
  "useWhen": "A process where each record sits in one stage and moves forward: a pipeline, a hiring funnel, a sprint, a content calendar by status, outreach by reply state. Not for records with no stages (a table) or items placed on a timeline."
53
53
  }
54
54
  }
@@ -5,8 +5,8 @@
5
5
  "title": "Calendar",
6
6
  "description": "Tasks on a calendar, like Google Calendar, Cron or Notion Calendar: month, week, day and agenda views over task-model's work items. Month shows all-day tasks and spans as tinted bars across their days (stacked in lanes, \"2 more\" past three) and timed ones as a dot and a time; week and day show an all-day strip over an hour grid where timed tasks are blocks placed by their times, overlapping ones side by side, with a line at now. Drag an item to another day or hour (it snaps to the quarter hour and the day columns as it moves), drag its bottom or end to change how long it runs, click an empty day or hour (or drag across hours) to type a new task in place, drag tasks without a date in from an unscheduled tray (or drop one there to take its date off). Arrow keys move a focused block, Shift+arrows resize it, Delete removes with Undo; T, J/K, M/W/D/A and C for today, next/previous, the views and a new task. Search and filters from the shared ViewToolbar, a colour per project or status, a real phone layout (a dotted month with the tapped day listed, a three-day week, a sheet for the tray). An available-time layer (closed hours muted, open slots as boxes you pick, from `availability`, `slots` or an async `getSlots`, with pure `freeSlots` / `availabilityOn` helpers) is what a booking page builds on. One async onChange saves every change; failures roll back with a notice.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/task-model.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/task-model.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -98,7 +98,7 @@
98
98
  "docs": "Installed to components/makersclaw/blocks/calendar-view. Import: import { CalendarView, CALENDAR_MODES, MODE_LABELS, DAY_MINUTES, UNSCHEDULE, snapMinutes, durationOf, eventOf, eventsOf, unscheduledOf, layoutTimed, layoutLanes, moveEventPatch, resizeEventPatch, placeEventPatch, timedPatch, dayPatch, allDayPatch, extendPatch, rangeFor, stepAnchor, rangeLabel, titleFor, hourLabel, minutesLabel, eventTimeLabel, availabilityOn, closedOn, freeSlots, normalizeSlots } from \"@/components/makersclaw/blocks/calendar-view\". Props and an example: components/makersclaw/blocks/calendar-view/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
99
99
  "meta": {
100
100
  "makersclaw": {
101
- "version": "0.3.2",
101
+ "version": "0.4.0",
102
102
  "useWhen": "A screen where dated work or events are seen by day and moved around: a team's deadlines, a content calendar, a launch plan, shifts, a personal schedule, meetings on tasks, or the slot picker of a booking page (modes week/day, slots from getSlots). Not for dependencies over a long timeline (gantt), for undated work (task-list, board), or for a single date field (the kit's DatePicker)."
103
103
  }
104
104
  }
package/r/cart.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Cart",
6
6
  "description": "A shopping cart: line items with picture, option, quantity and remove, a summary (subtotal, discount code, shipping, tax, total) and Checkout. step=\"checkout\" is the checkout page: contact and delivery details beside the order summary, ending at onPay, which hands off to Paddle. No card fields. Empty and loading states built in.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -55,7 +55,7 @@
55
55
  "docs": "Installed to components/makersclaw/blocks/cart. Import: import { Cart } from \"@/components/makersclaw/blocks/cart\". Props and an example: components/makersclaw/blocks/cart/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
56
56
  "meta": {
57
57
  "makersclaw": {
58
- "version": "0.3.2",
58
+ "version": "0.4.0",
59
59
  "useWhen": "An app that sells things: the basket before paying, and the page that collects the buyer's email and address before the payment provider takes over. Not for a subscription plan picker (a pricing table) or for an order already placed (order-detail)."
60
60
  }
61
61
  }
package/r/chart.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Chart",
6
6
  "description": "Line, area (stacked), bar (grouped or stacked, columns or bars, value labels at the ends, a track behind), a muted or dashed series for a previous period to compare against, pie and donut charts, sparklines and heatmaps (a grid shaded by value, with a scale legend), drawn in SVG (the heatmap as an HTML table) with the workspace's chart colours: round ticks, gridlines, a tooltip and crosshair under the pointer or the arrow keys, a legend that hides a series, and a data table for screen readers.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -76,7 +76,7 @@
76
76
  "docs": "Installed to components/makersclaw/blocks/chart. Import: import { LineChart, AreaChart, BarChart, PieChart, Sparkline, ChartLegend, HeatmapChart, ScaleLegend, scaleStep, scaleShade } from \"@/components/makersclaw/blocks/chart\". Props and an example: components/makersclaw/blocks/chart/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
77
77
  "meta": {
78
78
  "makersclaw": {
79
- "version": "0.3.2",
79
+ "version": "0.4.0",
80
80
  "useWhen": "Numbers the founder should see as a shape: revenue by month, deals by stage, pipeline by source, a KPI's trend. Not for a single figure with no history (a stat), exact lookups (a table), or records on a timeline (a calendar)."
81
81
  }
82
82
  }
package/r/chat.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Chat",
6
6
  "description": "The parts of a chat: messages with avatars, times, grouping, sending/failed/streaming states, hover actions, thumbs up/down feedback, in-place editing and a folding reasoning section; a scroller that sticks to the bottom with day separators and older-message loading; a composer (a box or an \"ask anything\" pill) with attachments, a + menu, tools, suggestion chips, Enter to send and Stop; a header; and ChatThread, which composes them from messages + onSend.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -88,7 +88,7 @@
88
88
  "docs": "Installed to components/makersclaw/blocks/chat. Import: import { ChatThread, ChatHeader, Message, Bubble, Attachment, MessageScroller, Composer } from \"@/components/makersclaw/blocks/chat\". Props and an example: components/makersclaw/blocks/chat/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
89
89
  "meta": {
90
90
  "makersclaw": {
91
- "version": "0.3.2",
91
+ "version": "0.4.0",
92
92
  "useWhen": "A conversation lives inside the app's own screen: a support inbox reply pane, a customer-facing assistant, comments between teammates on a record, a thread per object. Not the app window's Agent tab (the OS draws that) and not a one-off job the founder commissions (job-panel)."
93
93
  }
94
94
  }
@@ -5,7 +5,7 @@
5
5
  "title": "CodeEditor",
6
6
  "description": "A code editor (CodeMirror 6) in the workspace theme: SQL with the database's tables and columns in autocomplete, JSON checked as you type with plain-words errors, or plain text; line numbers, problems underlined in the text with a dot in the gutter, find and replace, folding, and a run hook on Cmd/Ctrl+Enter that runs the selection or the statement under the cursor. A quiet status line shows problems, the cursor, the mode and a Run button. Helpers split SQL into statements and check or format JSON anywhere, the server included.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "@codemirror/state@6.7.6",
@@ -87,7 +87,7 @@
87
87
  "docs": "Installed to components/makersclaw/blocks/code-editor. Import: import { CodeEditor, useCodeEditor, LANGUAGES, SQL_DIALECTS, splitStatements, statementAt, sqlNamespace, checkJson, formatJson, stringifyJson, lineCol, offsetOf, normalizeDiagnostics } from \"@/components/makersclaw/blocks/code-editor\". Props and an example: components/makersclaw/blocks/code-editor/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
88
88
  "meta": {
89
89
  "makersclaw": {
90
- "version": "0.3.2",
90
+ "version": "0.4.0",
91
91
  "useWhen": "A screen edits code or structured text: a SQL console, a query in a report, a JSON config or payload, a webhook body, a template, a script. json-editor and sql-console are built on it. Not for prose (doc-editor), one line of text (the kit's Input), or showing a diagram (diagram)."
92
92
  }
93
93
  }
@@ -5,8 +5,8 @@
5
5
  "title": "CommentBox",
6
6
  "description": "A minimal rich comment field (DocEditor in minimal mode): bold, italic, links and lists from the selection toolbar or markdown shortcuts, @mentions as chips, Cmd/Ctrl+Enter to post. It posts through an async onPost(doc) that answers { ok }, keeps the draft and says why when it fails, and clears when it lands. With `comments` it draws the thread above it: avatar, name, relative time, the rich body. Also exports the composer alone, the read-only body renderer and the timestamp.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/doc-editor.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/doc-editor.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -57,7 +57,7 @@
57
57
  "docs": "Installed to components/makersclaw/blocks/comment-box. Import: import { CommentBox, CommentComposer, CommentBody, CommentTime } from \"@/components/makersclaw/blocks/comment-box\". Props and an example: components/makersclaw/blocks/comment-box/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
58
58
  "meta": {
59
59
  "makersclaw": {
60
- "version": "0.3.2",
60
+ "version": "0.4.0",
61
61
  "useWhen": "People comment on something in the app: a task, a design, a document, an approval, a support ticket; a reply field with @mentions under a thread. Not dated notes kept on a record with edit and delete (record-notes), not a conversation with an assistant (chat), not a long document (doc-editor, doc-page)."
62
62
  }
63
63
  }
@@ -5,7 +5,7 @@
5
5
  "title": "Contact Section",
6
6
  "description": "A public \"contact us\" section: a form (name, email, message; optional phone, company, subject, a topic as chips or radios, a consent box) and/or the ways to reach you (email, phone, address, hours, offices). The form alone, beside an info panel (plain or filled), or with info cards above or beside it; an optional picture beside or behind the form. Validates in place, submits through an async onSubmit that returns { ok }, and shows the failure or a sent state.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -46,7 +46,7 @@
46
46
  "docs": "Installed to components/makersclaw/blocks/contact-section. Import: import { ContactSection } from \"@/components/makersclaw/blocks/contact-section\". Props and an example: components/makersclaw/blocks/contact-section/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
47
47
  "meta": {
48
48
  "makersclaw": {
49
- "version": "0.3.2",
49
+ "version": "0.4.0",
50
50
  "useWhen": "The contact page or contact section of a public site: sales enquiries, support requests, a studio's \"start a project\" form. Not for a signed-in user's support inbox or ticket thread, or a general-purpose record form (use a form of your own from the kit)."
51
51
  }
52
52
  }
package/r/core.json CHANGED
@@ -3,19 +3,25 @@
3
3
  "name": "core",
4
4
  "type": "registry:ui",
5
5
  "title": "Makersclaw kit",
6
- "description": "The kit every Makersclaw block is built from — Button, Field, Select, Sheet, Tabs, Menu, Notice, Icon, Depiction — and the parts bound to data (RecordSwitcher, useFixture). Native elements under the project's own theme tokens.",
6
+ "description": "The kit every Makersclaw block is built from — Button, Field, Select, Sheet, Tabs, Menu, Notice, Icon, Depiction — and the parts bound to data (RecordSwitcher, useFixture, LiveEmpty). Native elements under the project's own theme tokens.",
7
7
  "files": [
8
8
  {
9
9
  "path": "templates/next/files/components/parts/fixture.jsx",
10
10
  "type": "registry:ui",
11
11
  "target": "@components/makersclaw/parts/fixture.jsx",
12
- "content": "\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport { markSample } from \"./sample\";\nimport { Button, Notice } from \"../ui\";\n\n// The worked example, never persisted. A surface opens populated (README\n// §3: the template test) — but populated is not persisted. The example is\n// rows in the app's code, shown while the real collection is empty and\n// never INSERTed: the workspace database is shared by every app, so an\n// invented row there is a real row somewhere else. Clearing writes\n// nothing; the first real write is the user's.\n//\n// const { rows, isFixture, clear } = useFixture(realRows, SAMPLE);\n// {isFixture ? <FixtureNotice onClear={clear} /> : null}\n//\n// A fixture row carries `_sample: true` and a `sample-N` id. Editing one\n// promotes it: the surface copies its values into a new draft and saves\n// THAT — it never updateRow()s a sample id (the row does not exist).\n//\n// `rows` keeps its identity between renders (an effect may depend on it);\n// declare SAMPLE at module level, not inside the component, so it does too.\n\nconst NONE = [];\n\nexport function useFixture(rows, sample, { idColumn = \"id\" } = {}) {\n const [cleared, setCleared] = useState(false);\n const isFixture = !cleared && (!rows || rows.length === 0) && Array.isArray(sample) && sample.length > 0;\n const fixtureRows = useMemo(() => markSample(sample, idColumn), [sample, idColumn]);\n return {\n rows: isFixture ? fixtureRows : rows ?? NONE,\n isFixture,\n clear: () => setCleared(true),\n restore: () => setCleared(false),\n };\n}\n\nexport function isSample(row) {\n return Boolean(row && row._sample);\n}\n\n/** The line above a fixture, with the one control it earns. */\nexport function FixtureNotice({ onClear, children, className }) {\n return (\n <Notice\n tone=\"info\"\n title=\"Example only\"\n className={className}\n action={\n onClear ? (\n <Button variant=\"quiet\" size=\"sm\" onClick={onClear}>\n Clear the example\n </Button>\n ) : null\n }\n >\n {children ?? \"Edit it or start your own. Nothing here is saved until you save it.\"}\n </Notice>\n );\n}\n"
12
+ "content": "\"use client\";\n\nimport { createContext, useContext, useMemo, useState } from \"react\";\nimport { markSample } from \"./sample\";\nimport { Button, Notice } from \"../ui\";\n\n// The worked example, never persisted. A surface opens populated (README\n// §3: the template test) — but populated is not persisted. The example is\n// rows in the app's code, shown while the real collection is empty and\n// never INSERTed: the workspace database is shared by every app, so an\n// invented row there is a real row somewhere else. Clearing writes\n// nothing; the first real write is the user's.\n//\n// const { rows, isFixture, clear } = useFixture(realRows, SAMPLE);\n// {isFixture ? <FixtureNotice onClear={clear} /> : null}\n//\n// A fixture row carries `_sample: true` and a `sample-N` id. Editing one\n// promotes it: the surface copies its values into a new draft and saves\n// THAT — it never updateRow()s a sample id (the row does not exist).\n//\n// `rows` keeps its identity between renders (an effect may depend on it);\n// declare SAMPLE at module level, not inside the component, so it does too.\n//\n// Examples show only OUTSIDE a Makersclaw workspace: the store's authoring\n// loop, a shadcn project. Inside one, an app never shows example data (up\n// to 0.3.x it did, until its table had a row): an empty table stays empty\n// and the screen shows what its agent is doing instead. app/layout.jsx\n// decides it per request and says so through <Examples allowed>; with no\n// provider at all — a shadcn project — the answer is yes. The provider\n// lives here, not in lib/, so the parts still travel as one folder.\n\nconst NONE = [];\nconst nothing = () => {};\n\nconst ExamplesContext = createContext(true);\n\n/** Whether the parts below may show an example. The layout wraps the app\n * in it; `allowed` is false inside a workspace. */\nexport function Examples({ allowed, children }) {\n return <ExamplesContext.Provider value={Boolean(allowed)}>{children}</ExamplesContext.Provider>;\n}\n\n/** true unless an <Examples allowed={false}> is above. */\nexport function useExamples() {\n return useContext(ExamplesContext);\n}\n\nexport function useFixture(rows, sample, { idColumn = \"id\" } = {}) {\n const allowed = useExamples();\n const [cleared, setCleared] = useState(false);\n const isFixture = allowed && !cleared && (!rows || rows.length === 0) && Array.isArray(sample) && sample.length > 0;\n const fixtureRows = useMemo(() => markSample(sample, idColumn), [sample, idColumn]);\n // With examples off there is no example to clear or bring back: the\n // real rows, however few, are all a surface gets.\n return {\n rows: isFixture ? fixtureRows : rows ?? NONE,\n isFixture,\n clear: allowed ? () => setCleared(true) : nothing,\n restore: allowed ? () => setCleared(false) : nothing,\n };\n}\n\nexport function isSample(row) {\n return Boolean(row && row._sample);\n}\n\n/** The line above a fixture, with the one control it earns. */\nexport function FixtureNotice({ onClear, children, className }) {\n return (\n <Notice\n tone=\"info\"\n title=\"Example only\"\n className={className}\n action={\n onClear ? (\n <Button variant=\"quiet\" size=\"sm\" onClick={onClear}>\n Clear the example\n </Button>\n ) : null\n }\n >\n {children ?? \"Edit it or start your own. Nothing here is saved until you save it.\"}\n </Notice>\n );\n}\n"
13
13
  },
14
14
  {
15
15
  "path": "templates/next/files/components/parts/index.js",
16
16
  "type": "registry:ui",
17
17
  "target": "@components/makersclaw/parts/index.js",
18
- "content": "// Parts: the kit's pieces bound to this template's data layer. Each\n// exists because an app got it wrong once; each carries the behaviour\n// that makes it right. README §3b.\nexport { useFixture, isSample, FixtureNotice } from \"./fixture\";\nexport { RecordSwitcher } from \"./record-switcher\";\n"
18
+ "content": "// Parts: the kit's pieces bound to this template's data layer. Each\n// exists because an app got it wrong once; each carries the behaviour\n// that makes it right. README §3b.\nexport { Examples, useExamples, useFixture, isSample, FixtureNotice } from \"./fixture\";\nexport { LiveWork, LiveEmpty, liveWords } from \"./live-work\";\nexport { RecordSwitcher } from \"./record-switcher\";\n"
19
+ },
20
+ {
21
+ "path": "templates/next/files/components/parts/live-work.jsx",
22
+ "type": "registry:ui",
23
+ "target": "@components/makersclaw/parts/live-work.jsx",
24
+ "content": "\"use client\";\n\nimport { isValidElement, useState } from \"react\";\nimport { Button, Icon, StatusDot, cx } from \"../ui\";\n\n// The empty state that shows the work coming, never an example. Inside a\n// Makersclaw workspace a table opens empty (fixture.jsx: examples show\n// only outside one), and a table is usually empty because its agent has\n// not got to it yet — or is on it right now. So the empty block says\n// which: the ask in flight on this surface (its line, whose ask it is,\n// what it waits behind, the one press it needs), or, with nothing in\n// flight, what would appear here and the one way to make it. Up to 0.3.x\n// an example filled that gap; since 0.4.0 no app shows example data in a\n// workspace (claw's docs/JARVIS-PLATFORM.md §9.6 and §12, promise 6).\n//\n// const work = useRequests({ surface: \"deals\" }); // lib/hooks.js\n// if (!rows.length) {\n// return <LiveEmpty work={work} surface=\"deals\" icon=\"inbox\" title=\"No deals yet\"\n// line=\"Ask the agent to find some, or add one.\" action={{ label: \"Add a deal\", onClick: add }} />;\n// }\n//\n// Props in, callbacks out: `work` is useRequests()'s answer as it stands,\n// and the row calls back through it — work.open(request) opens the\n// conversation in the desk's agent column (where a need is answered),\n// work.stop(id) stops an ask. The part imports nothing from lib/, so the\n// parts still travel as one folder; in a shadcn project, with no `work`,\n// it is the plain empty block.\n\n// A request's status is one of eleven (claw's shared/src/requests.ts;\n// lib/hooks.js keeps the same list — this folder may not import it).\n// Every step before the agent is on it reads as Working: the member asked\n// for the result, not for its plumbing.\nconst WORDS = new Map([\n [\"routing\", \"Working\"],\n [\"queued\", \"Working\"],\n [\"installing\", \"Working\"],\n [\"setting_up\", \"Working\"],\n [\"building\", \"Working\"],\n [\"working\", \"Working\"],\n [\"needs_you\", \"Needs you\"],\n [\"answered\", \"Answered\"],\n [\"done\", \"Done\"],\n [\"stopped\", \"Stopped\"],\n [\"didnt_finish\", \"Didn't finish\"],\n]);\n\n/** The seven statuses in which the work still goes. `answered` is open\n * but not live: the agent answered and waits, and no row is coming. */\nconst LIVE = new Set([\"routing\", \"queued\", \"installing\", \"setting_up\", \"building\", \"working\", \"needs_you\"]);\n\n/** A scheduled run's statuses while it goes (a brief's, not a request's:\n * _claw/requests returns the run only in one of these). */\nconst RUN_LIVE = new Set([\"queued\", \"planning\", \"awaiting_approval\", \"in_progress\"]);\n\n/** The status word for a request or a bare status: \"Working\", \"Needs\n * you\", \"Answered\", \"Done\", \"Stopped\", \"Didn't finish\". An unknown\n * status is \"\" — a word the platform has not taught this part yet is\n * better unsaid than guessed. */\nexport function liveWords(requestOrStatus) {\n const status = typeof requestOrStatus === \"string\" ? requestOrStatus : requestOrStatus?.status;\n return WORDS.get(status) ?? \"\";\n}\n\nfunction runGoing(run) {\n return Boolean(run && typeof run === \"object\" && (run.status == null || RUN_LIVE.has(run.status)));\n}\n\n/**\n * One piece of work in flight, as a row: a pulsing dot, the request's\n * line, whose request it is when it is not yours, what it waits behind.\n * A need gets the warning tone and Answer (it is answered in the desk's\n * column, where the conversation lives); your own ask gets Stop. With no\n * request, a scheduled run that is going shows its title.\n *\n * request one of useRequests().live (lib/hooks.js)\n * run useRequests().run, shown when there is no request\n * onOpen (request) → open its conversation; Answer needs it\n * onStop (id) → stop it; Stop needs it, and shows only to the asker\n */\nexport function LiveWork({ request, run, onOpen, onStop, className }) {\n const [stopping, setStopping] = useState(false);\n\n if (request && typeof request === \"object\") {\n const status = request.status;\n const word = liveWords(request);\n const needs = status === \"needs_you\";\n const queued = status === \"queued\";\n const theirs = request.mine === false;\n const main = request.line || word || request.title || \"\";\n const meta = [];\n if (theirs) meta.push(`from ${request.asker_name || \"a teammate\"}'s request`);\n // claw's line for a queued request already says \"Queued · after …\"\n // (lineOf, shared/src/requests.ts); say it once, under a line that\n // does not.\n if (queued && !/^Queued\\b/.test(main)) meta.push(`Queued · after ${request.queued?.after_title || \"the job before it\"}`);\n // Only the asker answers: a teammate's need opens on \"Waiting on …\"\n // in the desk, so it is shown, not offered.\n const answer = needs && !theirs && onOpen;\n const going = LIVE.has(status);\n const stop = !needs && going && request.mine === true && onStop;\n const onStopClick = async () => {\n setStopping(true);\n try {\n await onStop(request.id);\n } finally {\n setStopping(false);\n }\n };\n return (\n <Row\n tone={needs ? \"warning\" : going && !queued ? \"primary\" : \"neutral\"}\n pulse={going && !queued}\n word={word}\n warn={needs}\n main={main}\n meta={meta}\n className={className}\n >\n {answer ? (\n <Button size=\"sm\" variant=\"primary\" onClick={() => onOpen(request)}>\n Answer\n </Button>\n ) : stop ? (\n <Button size=\"sm\" variant=\"quiet\" loading={stopping} onClick={onStopClick}>\n Stop\n </Button>\n ) : null}\n </Row>\n );\n }\n\n if (runGoing(run)) {\n return (\n <Row tone=\"primary\" pulse word=\"Working\" main={run.title || \"Working\"} meta={run.title ? [\"Working\"] : []} className={className} />\n );\n }\n return null;\n}\n\nfunction Row({ tone, pulse, word, warn, main, meta, className, children }) {\n return (\n <div\n className={cx(\n \"flex items-center gap-3 rounded-lg border bg-card px-3 py-2.5 text-left text-sm text-card-foreground\",\n warn ? \"border-warning/50\" : \"border-border\",\n className,\n )}\n >\n <StatusDot tone={tone} pulse={pulse} label={word || \"Working\"} hideLabel />\n <div aria-live=\"polite\" className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium\">{main}</p>\n {meta.length ? <p className=\"truncate text-xs text-muted-foreground\">{meta.join(\" · \")}</p> : null}\n </div>\n {children ? <div className=\"flex shrink-0 items-center gap-2\">{children}</div> : null}\n </div>\n );\n}\n\n/** Does a request belong to this empty state? Each filter given is a way\n * in (lib/hooks.js reads it the same way): an ask made on this surface,\n * or work on this job wherever it was asked for. No filter: any. */\nfunction belongs(request, surface, job) {\n if (!surface && !job) return true;\n return Boolean((surface && request.surface === surface) || (job && request.job_key === job));\n}\n\n// The one to show when several are in flight: what needs an answer, then\n// what is working, then what waits in the queue; yours before a\n// teammate's; the newest first.\nconst rank = (r) => (r.status === \"needs_you\" ? 0 : r.status === \"queued\" ? 2 : 1);\n\nfunction pickRequest(live, surface, job) {\n const list = (Array.isArray(live) ? live : [])\n .map((r, i) => ({ r, i }))\n .filter(({ r }) => r && typeof r === \"object\" && LIVE.has(r.status) && belongs(r, surface, job));\n list.sort(\n (a, b) =>\n rank(a.r) - rank(b.r) ||\n Number(b.r.mine === true) - Number(a.r.mine === true) ||\n String(b.r.created_at ?? \"\").localeCompare(String(a.r.created_at ?? \"\")) ||\n a.i - b.i,\n );\n return list[0]?.r ?? null;\n}\n\nconst FRAME = \"grid place-items-center rounded-lg border border-dashed border-border px-6 py-10 text-center\";\n\n/**\n * What an empty collection shows in a workspace. With work in flight for\n * this surface or job — or the app's scheduled run going — the work, as\n * a LiveWork row, and \"Rows appear here as they land\". With none, the\n * empty block: icon, title, line and the one action that fills it.\n *\n * work useRequests()'s answer ({ live, run, open, stop }); absent\n * in a shadcn project, where only the empty block shows\n * surface the surface key its asks carry; job — the job key whose\n * work lands here. Either is a way in; neither: any live work\n * icon the empty block's glyph (inbox)\n * title what would be here: \"No deals yet\"\n * line how it gets here: \"Ask the agent to find some, or add one.\"\n * action { label, onClick, href, icon, variant } for a Button, or an\n * element as it is\n * children anything the surface adds under either state\n */\nexport function LiveEmpty({ work, surface, job, icon = \"inbox\", title, line, action, children, className }) {\n const request = pickRequest(work?.live, surface, job);\n const run = !request && runGoing(work?.run) ? work.run : null;\n\n if (request || run) {\n return (\n <div className={cx(FRAME, \"gap-3\", className)}>\n <LiveWork request={request} run={run} onOpen={work.open} onStop={work.stop} className=\"w-full max-w-md\" />\n <p className=\"text-sm text-muted-foreground\">Rows appear here as they land</p>\n {children}\n </div>\n );\n }\n\n const button = isValidElement(action) ? (\n action\n ) : action && typeof action === \"object\" && action.label ? (\n <Button variant={action.variant ?? \"secondary\"} icon={action.icon} href={action.href} onClick={action.onClick}>\n {action.label}\n </Button>\n ) : null;\n return (\n <div className={cx(FRAME, \"gap-2\", className)}>\n <Icon name={icon} size={22} className=\"text-muted-foreground\" />\n {title ? <p className=\"text-sm font-medium\">{title}</p> : null}\n {line ? <p className=\"max-w-prose text-sm text-muted-foreground\">{line}</p> : null}\n {button ? <div className=\"mt-2\">{button}</div> : null}\n {children}\n </div>\n );\n}\n"
19
25
  },
20
26
  {
21
27
  "path": "templates/next/files/components/parts/record-switcher.jsx",
@@ -51,7 +57,7 @@
51
57
  "path": "templates/next/files/components/ui/button.jsx",
52
58
  "type": "registry:ui",
53
59
  "target": "@components/makersclaw/ui/button.jsx",
54
- "content": "import Link from \"next/link\";\nimport { cx } from \"./cx\";\nimport { Icon } from \"./icon\";\nimport { Spinner } from \"./feedback\";\n\n// Button — every action in the app. One shape, 44px targets\n// (mobile first: a thumb hits it). `href` makes it a next/link (base\n// path applied); `icon` names one from ./icon. Never style a <button>\n// by hand in a surface: the look is the workspace's, the behaviour is\n// the browser's, and this is the only place both meet.\n//\n// variant primary (the one main action per surface) · secondary ·\n// quiet (no fill) · danger (destructive, red text) ·\n// link (text only: inline, no box, padding or min-height — it\n// wraps and truncates with the sentence it sits in) · nav (a navigation row: quiet, full\n// width, start-aligned, normal weight; aria-current=\"page\"\n// fills it and sets it medium)\n// size md (44px) · sm (36px, for dense rows and rails); a link has\n// no height, so for it md and sm are text-sm and `inherit`\n// takes the surrounding text's size and weight\n// align center (the default) · start (label at the leading edge, for\n// a full-width row of your own; nav is always start)\n// loading true while its action runs: a spinner over the label (the\n// width does not jump), aria-busy, and disabled so a second\n// click cannot fire it again\n// tone a link's colour: primary (the default, text-primary) · muted\n// (text-muted-foreground, foreground on hover — a secondary\n// link beside a heading, \"What's new\", \"View all\"); only a\n// link reads it\n// iconOnly a square at the size (IconButton sets it; a Button whose\n// only child is an <Icon> should too)\n//\n// cx does not merge classes: a className can add, never override, what a\n// variant or size already sets. Anything a surface needs to vary is a\n// prop here, not a className fight.\n\nconst VARIANTS = {\n primary: \"bg-primary text-primary-foreground hover:opacity-90 border-transparent\",\n secondary: \"bg-secondary text-secondary-foreground border-border hover:bg-accent\",\n quiet: \"bg-transparent text-foreground border-transparent hover:bg-accent\",\n danger: \"bg-transparent text-destructive border-border hover:bg-accent\",\n link: \"underline-offset-4 hover:underline\",\n nav:\n \"w-full min-w-0 bg-transparent text-foreground border-transparent hover:bg-accent \" +\n \"[&[aria-current]:not([aria-current=false])]:bg-accent [&[aria-current]:not([aria-current=false])]:text-accent-foreground\",\n};\n\n// A link's colour, apart from its variant so a secondary link is a prop\n// and not a text-muted-foreground laid over text-primary.\nconst LINK_TONES = {\n primary: \"text-primary\",\n muted: \"text-muted-foreground hover:text-foreground\",\n};\n\n// The shape each variant is drawn in. A nav row spaces its icon wider.\n// A link is text in a line: inline, not a flex box — no border, padding\n// or height, it wraps where the sentence wraps and truncates with it. Its\n// icons sit on the text's line: `icon` leads, an <Icon> child trails.\nconst SHAPE = {\n box: \"inline-flex items-center gap-2 rounded-md border whitespace-nowrap\",\n nav: \"inline-flex items-center gap-3 rounded-md border whitespace-nowrap\",\n link: \"inline rounded-sm [&>svg]:ml-1 [&>svg]:inline-block [&>svg]:align-[-0.125em]\",\n};\n\n// Weight lives apart from the shape so a nav row can be normal weight\n// (its current row medium) without laying one font weight over another.\nconst WEIGHT = {\n box: \"font-medium\",\n nav: \"font-normal [&[aria-current]:not([aria-current=false])]:font-medium\",\n};\n\nconst SIZES = {\n md: \"min-h-11 px-4 text-sm\",\n sm: \"min-h-9 px-3 text-sm\",\n};\n\n// A link's sizes are only its text: md and sm as every other button,\n// inherit for a link inside a sentence.\nconst LINK_SIZES = {\n md: \"text-sm font-medium\",\n sm: \"text-sm font-medium\",\n inherit: \"\",\n};\n\n// IconButton's squares. Their own entries, never a px-0 laid over a\n// size's padding: cx does not merge classes, so which of two paddings\n// wins is CSS order, not the order they were written in.\nconst ICON_SIZES = {\n md: \"size-11 px-0 text-sm\",\n sm: \"size-9 px-0 text-sm\",\n};\n\nconst ALIGN = {\n center: \"justify-center\",\n start: \"justify-start text-left\",\n};\n\nexport function Button({\n variant = \"secondary\",\n size = \"md\",\n icon,\n href,\n className,\n children,\n type = \"button\",\n loading = false,\n disabled,\n iconOnly = false,\n align = \"center\",\n tone = \"primary\",\n ...rest\n}) {\n const look = VARIANTS[variant] ? variant : \"secondary\";\n const link = look === \"link\";\n const nav = look === \"nav\";\n const sizeClass = link\n ? LINK_SIZES[size] ?? LINK_SIZES.md\n : iconOnly\n ? ICON_SIZES[size] ?? ICON_SIZES.md\n : SIZES[size] ?? SIZES.md;\n const classes = cx(\n SHAPE[link ? \"link\" : nav ? \"nav\" : \"box\"],\n !link && (nav ? WEIGHT.nav : WEIGHT.box),\n !link && !iconOnly && (ALIGN[nav ? \"start\" : align] ?? ALIGN.center),\n iconOnly && ALIGN.center,\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n VARIANTS[look],\n link && (LINK_TONES[tone] ?? LINK_TONES.primary),\n sizeClass,\n // Busy is not unavailable: the button keeps its full colour.\n loading && \"relative disabled:aria-busy:opacity-100 disabled:aria-busy:cursor-progress\",\n loading && href && \"pointer-events-none opacity-50\",\n className,\n );\n const lead = icon ? (\n link ? (\n <span className=\"mr-1 inline-block align-[-0.125em]\">\n <Icon name={icon} />\n </span>\n ) : (\n <Icon name={icon} />\n )\n ) : null;\n const content = (\n <>\n {lead}\n {children}\n </>\n );\n // Loading: the label stays in place (transparent, still the accessible\n // name) so the button keeps its width; the spinner sits on top of it.\n const body = loading ? (\n <>\n <span className=\"inline-flex items-center gap-2 opacity-0\">{content}</span>\n <span className=\"absolute inset-0 grid place-items-center\">\n <Spinner size=\"sm\" label={null} />\n </span>\n </>\n ) : (\n content\n );\n if (href) {\n return (\n <Link\n href={href}\n className={classes}\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n tabIndex={loading ? -1 : undefined}\n disabled={disabled}\n {...rest}\n >\n {body}\n </Link>\n );\n }\n return (\n <button type={type} className={classes} disabled={loading || disabled} aria-busy={loading || undefined} {...rest}>\n {body}\n </button>\n );\n}\n\n/** A square button holding only an icon; `label` is required and is what\n * a screen reader says. */\nexport function IconButton({ icon, label, size = \"md\", variant = \"quiet\", className, ...rest }) {\n return (\n <Button\n variant={variant}\n size={size}\n aria-label={label}\n title={label}\n iconOnly\n className={cx(\"shrink-0\", className)}\n {...rest}\n >\n <Icon name={icon} size={size === \"sm\" ? 16 : 18} />\n </Button>\n );\n}\n"
60
+ "content": "import Link from \"next/link\";\nimport { cx } from \"./cx\";\nimport { Icon } from \"./icon\";\nimport { Spinner } from \"./feedback\";\n\n// Button — every action in the app. One shape, 44px targets\n// (mobile first: a thumb hits it). `href` makes it a next/link (base\n// path applied); `icon` names one from ./icon. Never style a <button>\n// by hand in a surface: the look is the workspace's, the behaviour is\n// the browser's, and this is the only place both meet.\n//\n// variant primary (the one main action per surface, filled with\n// --primary, the app's accent) · secondary (the default, the\n// workspace's neutral --secondary: a Button that does not say\n// primary is never the accent — a surface's \"Add guest\"\n// written without variant=\"primary\" came out dark grey on a\n// coral app, 2026-09-25) ·\n// quiet (no fill) · danger (destructive, red text) ·\n// link (text only: inline, no box, padding or min-height — it\n// wraps and truncates with the sentence it sits in) · nav (a navigation row: quiet, full\n// width, start-aligned, normal weight; aria-current=\"page\"\n// fills it and sets it medium)\n// size md (44px) · sm (36px, for dense rows and rails); a link has\n// no height, so for it md and sm are text-sm and `inherit`\n// takes the surrounding text's size and weight\n// align center (the default) · start (label at the leading edge, for\n// a full-width row of your own; nav is always start)\n// loading true while its action runs: a spinner over the label (the\n// width does not jump), aria-busy, and disabled so a second\n// click cannot fire it again\n// tone a link's colour: primary (the default, text-primary) · muted\n// (text-muted-foreground, foreground on hover — a secondary\n// link beside a heading, \"What's new\", \"View all\"); only a\n// link reads it\n// iconOnly a square at the size (IconButton sets it; a Button whose\n// only child is an <Icon> should too)\n//\n// cx does not merge classes: a className can add, never override, what a\n// variant or size already sets. Anything a surface needs to vary is a\n// prop here, not a className fight.\n\nconst VARIANTS = {\n primary: \"bg-primary text-primary-foreground hover:opacity-90 border-transparent\",\n secondary: \"bg-secondary text-secondary-foreground border-border hover:bg-accent\",\n quiet: \"bg-transparent text-foreground border-transparent hover:bg-accent\",\n danger: \"bg-transparent text-destructive border-border hover:bg-accent\",\n link: \"underline-offset-4 hover:underline\",\n nav:\n \"w-full min-w-0 bg-transparent text-foreground border-transparent hover:bg-accent \" +\n \"[&[aria-current]:not([aria-current=false])]:bg-accent [&[aria-current]:not([aria-current=false])]:text-accent-foreground\",\n};\n\n// A link's colour, apart from its variant so a secondary link is a prop\n// and not a text-muted-foreground laid over text-primary.\nconst LINK_TONES = {\n primary: \"text-primary\",\n muted: \"text-muted-foreground hover:text-foreground\",\n};\n\n// The shape each variant is drawn in. A nav row spaces its icon wider.\n// A link is text in a line: inline, not a flex box — no border, padding\n// or height, it wraps where the sentence wraps and truncates with it. Its\n// icons sit on the text's line: `icon` leads, an <Icon> child trails.\nconst SHAPE = {\n box: \"inline-flex items-center gap-2 rounded-md border whitespace-nowrap\",\n nav: \"inline-flex items-center gap-3 rounded-md border whitespace-nowrap\",\n link: \"inline rounded-sm [&>svg]:ml-1 [&>svg]:inline-block [&>svg]:align-[-0.125em]\",\n};\n\n// Weight lives apart from the shape so a nav row can be normal weight\n// (its current row medium) without laying one font weight over another.\nconst WEIGHT = {\n box: \"font-medium\",\n nav: \"font-normal [&[aria-current]:not([aria-current=false])]:font-medium\",\n};\n\nconst SIZES = {\n md: \"min-h-11 px-4 text-sm\",\n sm: \"min-h-9 px-3 text-sm\",\n};\n\n// A link's sizes are only its text: md and sm as every other button,\n// inherit for a link inside a sentence.\nconst LINK_SIZES = {\n md: \"text-sm font-medium\",\n sm: \"text-sm font-medium\",\n inherit: \"\",\n};\n\n// IconButton's squares. Their own entries, never a px-0 laid over a\n// size's padding: cx does not merge classes, so which of two paddings\n// wins is CSS order, not the order they were written in.\nconst ICON_SIZES = {\n md: \"size-11 px-0 text-sm\",\n sm: \"size-9 px-0 text-sm\",\n};\n\nconst ALIGN = {\n center: \"justify-center\",\n start: \"justify-start text-left\",\n};\n\nexport function Button({\n variant = \"secondary\",\n size = \"md\",\n icon,\n href,\n className,\n children,\n type = \"button\",\n loading = false,\n disabled,\n iconOnly = false,\n align = \"center\",\n tone = \"primary\",\n ...rest\n}) {\n const look = VARIANTS[variant] ? variant : \"secondary\";\n const link = look === \"link\";\n const nav = look === \"nav\";\n const sizeClass = link\n ? LINK_SIZES[size] ?? LINK_SIZES.md\n : iconOnly\n ? ICON_SIZES[size] ?? ICON_SIZES.md\n : SIZES[size] ?? SIZES.md;\n const classes = cx(\n SHAPE[link ? \"link\" : nav ? \"nav\" : \"box\"],\n !link && (nav ? WEIGHT.nav : WEIGHT.box),\n !link && !iconOnly && (ALIGN[nav ? \"start\" : align] ?? ALIGN.center),\n iconOnly && ALIGN.center,\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n VARIANTS[look],\n link && (LINK_TONES[tone] ?? LINK_TONES.primary),\n sizeClass,\n // Busy is not unavailable: the button keeps its full colour.\n loading && \"relative disabled:aria-busy:opacity-100 disabled:aria-busy:cursor-progress\",\n loading && href && \"pointer-events-none opacity-50\",\n className,\n );\n const lead = icon ? (\n link ? (\n <span className=\"mr-1 inline-block align-[-0.125em]\">\n <Icon name={icon} />\n </span>\n ) : (\n <Icon name={icon} />\n )\n ) : null;\n const content = (\n <>\n {lead}\n {children}\n </>\n );\n // Loading: the label stays in place (transparent, still the accessible\n // name) so the button keeps its width; the spinner sits on top of it.\n const body = loading ? (\n <>\n <span className=\"inline-flex items-center gap-2 opacity-0\">{content}</span>\n <span className=\"absolute inset-0 grid place-items-center\">\n <Spinner size=\"sm\" label={null} />\n </span>\n </>\n ) : (\n content\n );\n if (href) {\n return (\n <Link\n href={href}\n className={classes}\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n tabIndex={loading ? -1 : undefined}\n disabled={disabled}\n {...rest}\n >\n {body}\n </Link>\n );\n }\n return (\n <button type={type} className={classes} disabled={loading || disabled} aria-busy={loading || undefined} {...rest}>\n {body}\n </button>\n );\n}\n\n/** A square button holding only an icon; `label` is required and is what\n * a screen reader says. */\nexport function IconButton({ icon, label, size = \"md\", variant = \"quiet\", className, ...rest }) {\n return (\n <Button\n variant={variant}\n size={size}\n aria-label={label}\n title={label}\n iconOnly\n className={cx(\"shrink-0\", className)}\n {...rest}\n >\n <Icon name={icon} size={size === \"sm\" ? 16 : 18} />\n </Button>\n );\n}\n"
55
61
  },
56
62
  {
57
63
  "path": "templates/next/files/components/ui/calendar.jsx",
@@ -99,7 +105,7 @@
99
105
  "path": "templates/next/files/components/ui/date-picker.jsx",
100
106
  "type": "registry:ui",
101
107
  "target": "@components/makersclaw/ui/date-picker.jsx",
102
- "content": "\"use client\";\n\nimport { useState, useSyncExternalStore } from \"react\";\nimport { cx } from \"./cx\";\nimport { Icon } from \"./icon\";\nimport { Button, IconButton } from \"./button\";\nimport { Input } from \"./field\";\nimport { Popover } from \"./popover\";\nimport {\n Calendar,\n addDays,\n addMonths,\n endOfMonth,\n formatDate,\n parseDate,\n plainSpaces,\n startOfMonth,\n useLocale,\n useToday,\n} from \"./calendar\";\n\n// DatePicker — a field that reads like an input and opens a Calendar in a\n// Popover: a due date, a report's span. The trigger shows the day in the\n// reader's words (\"Mar 12, 2026\"); the value stays a \"YYYY-MM-DD\" string\n// (calendar.jsx says why). Put it in <Field id> with the same `id`: the\n// label points at the trigger.\n//\n// mode single (value \"2026-03-12\" | null) · range (value\n// { from, to } | null)\n// value, onChange(value) — controlled; or `defaultValue`\n// placeholder \"Pick a date\" · \"Pick a range\"\n// presets true (the defaults for the mode: Today, Tomorrow, In a\n// week… · Today, Last 7 days, This month…) or your own\n// [{ label, value }] — `value` a date, a { from, to }, or\n// (today) → one. A list beside the calendar; a row above\n// it on a phone\n// clearable an × in the field once it holds a date (true)\n// format Intl.DateTimeFormat options for the field ({ dateStyle:\n// \"medium\" }); a range is written with formatRange\n// disabled true: the whole field; a function (date) → true: those days\n// min, max, numberOfMonths (range: 2 from 640px; always 1 on a phone),\n// captionLayout, showWeekNumbers, weekStartsOn, locale, today — as\n// Calendar\n// name a hidden input for a form; a range submits two under it\n// invalid the red edge — pair it with <Field error>\n// size md (44px) · sm (36px)\n// label what a screen reader calls the panel (\"Choose a date\")\n// defaultOpen open on first render (a demo; a form never needs it)\n//\n// It closes when a day, a finished range or a preset is picked; the focus\n// lands on the chosen day when it opens and back on the field when it\n// closes.\n//\n// DateInput below is the typed way in: the browser's own date field\n// (segments in the reader's order, digits only, never a free-text parse)\n// with this Calendar one button away. Typing a date into a text box and\n// parsing it is not robust across locales (\"03/04\" is two different\n// days), which is why there is no text DateInput.\n\nconst FIELD =\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 text-left text-sm text-foreground \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 \" +\n \"aria-[invalid=true]:border-destructive\";\nconst HEIGHT = { md: \"min-h-11\", sm: \"min-h-9\" };\n// Presets: on a phone a scrolling row exactly as wide as the calendar (w-0\n// plus a min width of its box: it never widens the panel); a column beside\n// the calendar from 640px.\nconst PRESETS =\n \"-mx-3 flex w-0 min-w-[calc(100%+1.5rem)] gap-1 overflow-x-auto px-3 pb-1 \" +\n \"sm:m-0 sm:w-36 sm:min-w-0 sm:flex-col sm:overflow-visible sm:border-r sm:border-border sm:p-0 sm:pr-3\";\n\nfunction useWide() {\n return useSyncExternalStore(\n (cb) => {\n const q = window.matchMedia(\"(min-width: 640px)\");\n q.addEventListener(\"change\", cb);\n return () => q.removeEventListener(\"change\", cb);\n },\n () => window.matchMedia(\"(min-width: 640px)\").matches,\n () => false,\n );\n}\n\n/** The default presets. Each value is (today) → a date or a { from, to }. */\nexport const DATE_PRESETS = {\n single: [\n { label: \"Today\", value: (t) => t },\n { label: \"Tomorrow\", value: (t) => addDays(t, 1) },\n { label: \"In a week\", value: (t) => addDays(t, 7) },\n { label: \"In a month\", value: (t) => addMonths(t, 1) },\n ],\n range: [\n { label: \"Today\", value: (t) => ({ from: t, to: t }) },\n { label: \"Last 7 days\", value: (t) => ({ from: addDays(t, -6), to: t }) },\n { label: \"Last 30 days\", value: (t) => ({ from: addDays(t, -29), to: t }) },\n { label: \"This month\", value: (t) => ({ from: startOfMonth(t), to: endOfMonth(t) }) },\n { label: \"Last month\", value: (t) => ({ from: startOfMonth(addMonths(t, -1)), to: endOfMonth(addMonths(t, -1)) }) },\n { label: \"This year\", value: (t) => ({ from: `${t.slice(0, 4)}-01-01`, to: `${t.slice(0, 4)}-12-31` }) },\n ],\n};\n\nconst same = (a, b) => (a && b && typeof a === \"object\" ? a.from === b.from && a.to === b.to : a === b);\n\n/** The value in words: a day, or a span through formatRange. */\nfunction words(mode, value, format, locale) {\n if (mode !== \"range\") return value ? formatDate(value, format, locale) : \"\";\n const { from, to } = value ?? {};\n if (!from) return \"\";\n if (!to) return `${formatDate(from, format, locale)} – …`;\n const f = new Intl.DateTimeFormat(locale, { ...format, timeZone: \"UTC\" });\n return plainSpaces(f.formatRange ? f.formatRange(parseDate(from), parseDate(to)) : `${f.format(parseDate(from))} – ${f.format(parseDate(to))}`);\n}\n\nexport function DatePicker({\n id,\n mode = \"single\",\n value: valueProp,\n defaultValue = null,\n onChange,\n placeholder,\n presets = false,\n clearable = true,\n format = { dateStyle: \"medium\" },\n disabled,\n min,\n max,\n numberOfMonths,\n captionLayout,\n showWeekNumbers,\n weekStartsOn,\n locale: localeProp,\n today: todayProp,\n name,\n invalid,\n size = \"md\",\n align = \"start\",\n label,\n defaultOpen = false,\n className,\n}) {\n const locale = useLocale(localeProp);\n const today = useToday(todayProp);\n const wide = useWide();\n const [own, setOwn] = useState(defaultValue);\n const value = valueProp !== undefined ? valueProp : own;\n const [open, setOpen] = useState(defaultOpen);\n const [month, setMonth] = useState(null);\n const range = mode === \"range\";\n const off = disabled === true;\n\n const first = range ? value?.from : value;\n const text = words(mode, value, format, locale);\n const list = presets === true ? DATE_PRESETS[range ? \"range\" : \"single\"] : Array.isArray(presets) ? presets : [];\n const shown = list.map((p) => ({ label: p.label, value: typeof p.value === \"function\" ? (today ? p.value(today) : null) : p.value }));\n\n function set(v) {\n if (valueProp === undefined) setOwn(v);\n onChange?.(v);\n }\n\n // Each opening starts on the chosen day's month (or today's).\n function onOpenChange(next) {\n if (next) setMonth(startOfMonth(first) ?? startOfMonth(today));\n setOpen(next);\n }\n\n function choose(v) {\n set(v);\n if (!range || (v?.from && v?.to)) setOpen(false);\n }\n\n const months = wide ? numberOfMonths ?? (range ? 2 : 1) : 1;\n const showClear = clearable && !off && (range ? !!value?.from : !!value);\n\n return (\n <div className={cx(\"relative w-full\", className)}>\n <Popover\n open={open}\n onOpenChange={onOpenChange}\n align={align}\n width=\"auto\"\n className=\"p-3\"\n label={label ?? (range ? \"Choose a date range\" : \"Choose a date\")}\n trigger={(props) => (\n <button\n {...props}\n id={id}\n type=\"button\"\n disabled={off}\n aria-invalid={invalid ? true : undefined}\n className={cx(FIELD, HEIGHT[size] ?? HEIGHT.md, showClear && \"pr-11\")}\n >\n <Icon name=\"calendar\" className=\"shrink-0 text-muted-foreground\" />\n <span className={cx(\"min-w-0 flex-1 truncate\", !text && \"text-muted-foreground\")}>\n {text || placeholder || (range ? \"Pick a range\" : \"Pick a date\")}\n </span>\n </button>\n )}\n >\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n {shown.length ? (\n <div\n role=\"group\"\n aria-label=\"Presets\"\n className={PRESETS}\n >\n {shown.map((p) => {\n const on = !!p.value && same(p.value, value);\n return (\n <Button\n key={p.label}\n size={wide ? \"sm\" : \"md\"}\n variant={on ? \"secondary\" : \"quiet\"}\n aria-pressed={on}\n disabled={!p.value}\n className=\"shrink-0 justify-start\"\n onClick={() => {\n choose(p.value);\n setMonth(startOfMonth(range ? p.value.from : p.value));\n }}\n >\n {p.label}\n </Button>\n );\n })}\n </div>\n ) : null}\n <Calendar\n mode={range ? \"range\" : \"single\"}\n value={value}\n onChange={choose}\n month={month ?? undefined}\n onMonthChange={(m) => setMonth(`${m}-01`)}\n numberOfMonths={months}\n min={min}\n max={max}\n disabled={typeof disabled === \"function\" ? disabled : undefined}\n captionLayout={captionLayout}\n showWeekNumbers={showWeekNumbers}\n weekStartsOn={weekStartsOn}\n locale={locale}\n today={today ?? undefined}\n autoFocus={open}\n label={label ?? (range ? \"Choose a date range\" : \"Choose a date\")}\n />\n </div>\n </Popover>\n {showClear ? (\n <IconButton\n icon=\"x\"\n label=\"Clear date\"\n size=\"sm\"\n onClick={() => set(null)}\n className={cx(\"absolute right-1\", size === \"sm\" ? \"top-0\" : \"top-1\")}\n />\n ) : null}\n {name && !range ? <input type=\"hidden\" name={name} value={value ?? \"\"} /> : null}\n {name && range ? (\n <>\n <input type=\"hidden\" name={name} value={value?.from ?? \"\"} />\n <input type=\"hidden\" name={name} value={value?.to ?? \"\"} />\n </>\n ) : null}\n </div>\n );\n}\n\n/**\n * A date typed or picked. The browser's own date field — segments in the\n * reader's order, digits only, the value a \"YYYY-MM-DD\" string or \"\" while\n * incomplete — with its built-in picker swapped for a button that opens\n * this Calendar. onChange(date | null). Chrome and Safari hide their own\n * picker icon; Firefox keeps its own beside ours.\n *\n * value, onChange(date | null), defaultValue, min, max, disabled\n * (true, or a function for the Calendar's days), name, invalid, size,\n * locale (the Calendar's; the field itself follows the browser), today\n */\nexport function DateInput({\n id,\n value: valueProp,\n defaultValue = null,\n onChange,\n min,\n max,\n disabled,\n name,\n invalid,\n size = \"md\",\n locale,\n today,\n weekStartsOn,\n className,\n ...rest\n}) {\n const [own, setOwn] = useState(defaultValue);\n const value = valueProp !== undefined ? valueProp : own;\n const [open, setOpen] = useState(false);\n const [month, setMonth] = useState(null);\n const off = disabled === true;\n const out = !!value && ((min && value < min) || (max && value > max));\n\n function set(v) {\n if (valueProp === undefined) setOwn(v);\n onChange?.(v);\n }\n\n return (\n <div className={cx(\"relative w-full\", className)}>\n <Input\n id={id}\n type=\"date\"\n name={name}\n value={value ?? \"\"}\n min={min}\n max={max}\n disabled={off}\n invalid={invalid || out}\n onChange={(e) => set(e.target.value || null)}\n className={cx(\n \"pr-12 [&::-webkit-calendar-picker-indicator]:hidden tabular-nums\",\n size === \"sm\" && \"min-h-9\",\n )}\n {...rest}\n />\n <span className={cx(\"absolute right-1\", size === \"sm\" ? \"top-0\" : \"top-1\")}>\n <Popover\n open={open}\n onOpenChange={(next) => {\n if (next) setMonth(startOfMonth(value) ?? null);\n setOpen(next);\n }}\n align=\"end\"\n width=\"auto\"\n className=\"p-3\"\n label=\"Choose a date\"\n trigger={(props) => <IconButton {...props} icon=\"calendar\" label=\"Open calendar\" size=\"sm\" disabled={off} />}\n >\n <Calendar\n value={value}\n onChange={(v) => {\n set(v);\n setOpen(false);\n }}\n month={month ?? undefined}\n onMonthChange={(m) => setMonth(`${m}-01`)}\n min={min}\n max={max}\n disabled={typeof disabled === \"function\" ? disabled : undefined}\n locale={locale}\n today={today}\n weekStartsOn={weekStartsOn}\n autoFocus={open}\n label=\"Choose a date\"\n />\n </Popover>\n </span>\n </div>\n );\n}\n"
108
+ "content": "\"use client\";\n\nimport { useState, useSyncExternalStore } from \"react\";\nimport { cx } from \"./cx\";\nimport { Icon } from \"./icon\";\nimport { Button, IconButton } from \"./button\";\nimport { Input } from \"./field\";\nimport { Popover } from \"./popover\";\nimport {\n Calendar,\n addDays,\n addMonths,\n endOfMonth,\n formatDate,\n parseDate,\n plainSpaces,\n startOfMonth,\n useLocale,\n useToday,\n} from \"./calendar\";\n\n// DatePicker — a field that reads like an input and opens a Calendar in a\n// Popover: a due date, a report's span. The trigger shows the day in the\n// reader's words (\"Mar 12, 2026\"); the value stays a \"YYYY-MM-DD\" string\n// (calendar.jsx says why). Put it in <Field id> with the same `id`: the\n// label points at the trigger.\n//\n// mode single (value \"2026-03-12\" | null) · range (value\n// { from, to } | null)\n// value, onChange(value) — controlled; or `defaultValue`\n// placeholder \"Pick a date\" · \"Pick a range\"\n// presets true (the defaults for the mode: Today, Tomorrow, In a\n// week… · Today, Last 7 days, This month…) or your own\n// [{ label, value }] — `value` a date, a { from, to }, or\n// (today) → one. A list beside the calendar; a row above\n// it on a phone\n// clearable an × in the field once it holds a date (true)\n// format Intl.DateTimeFormat options for the field ({ dateStyle:\n// \"medium\" }); a range is written with formatRange\n// disabled true: the whole field; a function (date) → true: those days\n// min, max, numberOfMonths (range: 2 from 640px; always 1 on a phone),\n// captionLayout, showWeekNumbers, weekStartsOn, locale, today — as\n// Calendar\n// name a hidden input for a form; a range submits two under it\n// invalid the red edge — pair it with <Field error>\n// size md (44px) · sm (36px)\n// label what a screen reader calls the panel (\"Choose a date\")\n// defaultOpen open on first render (a demo; a form never needs it)\n//\n// It closes when a day, a finished range or a preset is picked; the focus\n// lands on the chosen day when it opens and back on the field when it\n// closes.\n//\n// DateInput below is the typed way in: the browser's own date field\n// (segments in the reader's order, digits only, never a free-text parse)\n// with this Calendar one button away. Typing a date into a text box and\n// parsing it is not robust across locales (\"03/04\" is two different\n// days), which is why there is no text DateInput.\n\nconst FIELD =\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 text-left text-sm text-foreground \" +\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 \" +\n \"aria-[invalid=true]:border-destructive\";\nconst HEIGHT = { md: \"min-h-11\", sm: \"min-h-9\" };\n// Presets: on a phone a scrolling row exactly as wide as the calendar (w-0\n// plus a min width of its box: it never widens the panel); a column beside\n// the calendar from 640px.\nconst PRESETS =\n \"-mx-3 flex w-0 min-w-[calc(100%+1.5rem)] gap-1 overflow-x-auto px-3 pb-1 \" +\n \"sm:m-0 sm:w-36 sm:min-w-0 sm:flex-col sm:overflow-visible sm:border-r sm:border-border sm:p-0 sm:pr-3\";\n\nfunction useWide() {\n return useSyncExternalStore(\n (cb) => {\n const q = window.matchMedia(\"(min-width: 640px)\");\n q.addEventListener(\"change\", cb);\n return () => q.removeEventListener(\"change\", cb);\n },\n () => window.matchMedia(\"(min-width: 640px)\").matches,\n () => false,\n );\n}\n\n/** The default presets. Each value is (today) → a date or a { from, to }. */\nexport const DATE_PRESETS = {\n single: [\n { label: \"Today\", value: (t) => t },\n { label: \"Tomorrow\", value: (t) => addDays(t, 1) },\n { label: \"In a week\", value: (t) => addDays(t, 7) },\n { label: \"In a month\", value: (t) => addMonths(t, 1) },\n ],\n range: [\n { label: \"Today\", value: (t) => ({ from: t, to: t }) },\n { label: \"Last 7 days\", value: (t) => ({ from: addDays(t, -6), to: t }) },\n { label: \"Last 30 days\", value: (t) => ({ from: addDays(t, -29), to: t }) },\n { label: \"This month\", value: (t) => ({ from: startOfMonth(t), to: endOfMonth(t) }) },\n { label: \"Last month\", value: (t) => ({ from: startOfMonth(addMonths(t, -1)), to: endOfMonth(addMonths(t, -1)) }) },\n { label: \"This year\", value: (t) => ({ from: `${t.slice(0, 4)}-01-01`, to: `${t.slice(0, 4)}-12-31` }) },\n ],\n};\n\nconst same = (a, b) => (a && b && typeof a === \"object\" ? a.from === b.from && a.to === b.to : a === b);\n\n/** The value in words: a day, or a span through formatRange. */\nfunction words(mode, value, format, locale) {\n if (mode !== \"range\") return value ? formatDate(value, format, locale) : \"\";\n const { from, to } = value ?? {};\n if (!from) return \"\";\n if (!to) return `${formatDate(from, format, locale)} – …`;\n const f = new Intl.DateTimeFormat(locale, { ...format, timeZone: \"UTC\" });\n return plainSpaces(f.formatRange ? f.formatRange(parseDate(from), parseDate(to)) : `${f.format(parseDate(from))} – ${f.format(parseDate(to))}`);\n}\n\nexport function DatePicker({\n id,\n mode = \"single\",\n value: valueProp,\n defaultValue = null,\n onChange,\n placeholder,\n presets = false,\n clearable = true,\n format = { dateStyle: \"medium\" },\n disabled,\n min,\n max,\n numberOfMonths,\n captionLayout,\n showWeekNumbers,\n weekStartsOn,\n locale: localeProp,\n today: todayProp,\n name,\n invalid,\n size = \"md\",\n align = \"start\",\n label,\n defaultOpen = false,\n className,\n}) {\n const locale = useLocale(localeProp);\n const today = useToday(todayProp);\n const wide = useWide();\n const [own, setOwn] = useState(defaultValue);\n const value = valueProp !== undefined ? valueProp : own;\n const [open, setOpen] = useState(defaultOpen);\n const [month, setMonth] = useState(null);\n const range = mode === \"range\";\n const off = disabled === true;\n\n const first = range ? value?.from : value;\n const text = words(mode, value, format, locale);\n const list = presets === true ? DATE_PRESETS[range ? \"range\" : \"single\"] : Array.isArray(presets) ? presets : [];\n const shown = list.map((p) => ({ label: p.label, value: typeof p.value === \"function\" ? (today ? p.value(today) : null) : p.value }));\n\n function set(v) {\n if (valueProp === undefined) setOwn(v);\n onChange?.(v);\n }\n\n // Each opening starts on the chosen day's month (or today's).\n function onOpenChange(next) {\n if (next) setMonth(startOfMonth(first) ?? startOfMonth(today));\n setOpen(next);\n }\n\n function choose(v) {\n set(v);\n if (!range || (v?.from && v?.to)) setOpen(false);\n }\n\n const months = wide ? numberOfMonths ?? (range ? 2 : 1) : 1;\n const showClear = clearable && !off && (range ? !!value?.from : !!value);\n\n return (\n <div className={cx(\"relative w-full\", className)}>\n <Popover\n open={open}\n onOpenChange={onOpenChange}\n align={align}\n width=\"auto\"\n className=\"p-3\"\n label={label ?? (range ? \"Choose a date range\" : \"Choose a date\")}\n trigger={(props) => (\n <button\n {...props}\n id={id}\n type=\"button\"\n disabled={off}\n aria-invalid={invalid ? true : undefined}\n className={cx(FIELD, HEIGHT[size] ?? HEIGHT.md, showClear && \"pr-11\")}\n >\n <Icon name=\"calendar\" className=\"shrink-0 text-muted-foreground\" />\n <span className={cx(\"min-w-0 flex-1 truncate\", !text && \"text-muted-foreground\")}>\n {text || placeholder || (range ? \"Pick a range\" : \"Pick a date\")}\n </span>\n </button>\n )}\n >\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n {shown.length ? (\n <div\n role=\"group\"\n aria-label=\"Presets\"\n className={PRESETS}\n >\n {shown.map((p) => {\n const on = !!p.value && same(p.value, value);\n return (\n <Button\n key={p.label}\n size={wide ? \"sm\" : \"md\"}\n variant={on ? \"secondary\" : \"quiet\"}\n aria-pressed={on}\n disabled={!p.value}\n className=\"shrink-0 justify-start\"\n onClick={() => {\n choose(p.value);\n setMonth(startOfMonth(range ? p.value.from : p.value));\n }}\n >\n {p.label}\n </Button>\n );\n })}\n </div>\n ) : null}\n <Calendar\n mode={range ? \"range\" : \"single\"}\n value={value}\n onChange={choose}\n month={month ?? undefined}\n onMonthChange={(m) => setMonth(`${m}-01`)}\n numberOfMonths={months}\n min={min}\n max={max}\n disabled={typeof disabled === \"function\" ? disabled : undefined}\n captionLayout={captionLayout}\n showWeekNumbers={showWeekNumbers}\n weekStartsOn={weekStartsOn}\n locale={locale}\n today={today ?? undefined}\n autoFocus={open}\n label={label ?? (range ? \"Choose a date range\" : \"Choose a date\")}\n />\n </div>\n </Popover>\n {showClear ? (\n <IconButton\n icon=\"x\"\n label=\"Clear date\"\n size=\"sm\"\n onClick={() => set(null)}\n className={cx(\"absolute right-1\", size === \"sm\" ? \"top-0\" : \"top-1\")}\n />\n ) : null}\n {name && !range ? <input type=\"hidden\" name={name} value={value ?? \"\"} /> : null}\n {name && range ? (\n <>\n <input type=\"hidden\" name={name} value={value?.from ?? \"\"} />\n <input type=\"hidden\" name={name} value={value?.to ?? \"\"} />\n </>\n ) : null}\n </div>\n );\n}\n\n/**\n * A date typed or picked. The browser's own date field — segments in the\n * reader's order, digits only, the value a \"YYYY-MM-DD\" string or \"\" while\n * incomplete — with its built-in picker swapped for a button that opens\n * this Calendar. onChange(date | null). Chrome and Safari hide their own\n * picker icon; Firefox keeps its own beside ours.\n *\n * value, onChange(date | null), defaultValue, min, max, disabled\n * (true, or a function for the Calendar's days), name, invalid, size,\n * locale (the Calendar's; the field itself follows the browser), today\n * clearable an × beside the calendar button once the field holds a\n * date (false — DatePicker's is true)\n *\n * Every other prop goes onto the <input>, so a prop this part names must\n * be taken out here: until 0.3.3 `clearable` was DatePicker's alone, and\n * a surface that wrote <DateInput clearable> put it on the <input> (\"Received\n * `true` for a non-boolean attribute clearable\", a podcast guest sheet,\n * 2026-09-25). It is DateInput's now too, off unless asked, so a field\n * that did not ask keeps its width.\n */\nexport function DateInput({\n id,\n value: valueProp,\n defaultValue = null,\n onChange,\n min,\n max,\n disabled,\n name,\n invalid,\n size = \"md\",\n locale,\n today,\n weekStartsOn,\n clearable = false,\n className,\n ...rest\n}) {\n const [own, setOwn] = useState(defaultValue);\n const value = valueProp !== undefined ? valueProp : own;\n const [open, setOpen] = useState(false);\n const [month, setMonth] = useState(null);\n const off = disabled === true;\n const out = !!value && ((min && value < min) || (max && value > max));\n const showClear = clearable && !off && !!value;\n\n function set(v) {\n if (valueProp === undefined) setOwn(v);\n onChange?.(v);\n }\n\n return (\n <div className={cx(\"relative w-full\", className)}>\n <Input\n id={id}\n type=\"date\"\n name={name}\n value={value ?? \"\"}\n min={min}\n max={max}\n disabled={off}\n invalid={invalid || out}\n onChange={(e) => set(e.target.value || null)}\n className={cx(\n showClear ? \"pr-20\" : \"pr-12\",\n \"[&::-webkit-calendar-picker-indicator]:hidden tabular-nums\",\n size === \"sm\" && \"min-h-9\",\n )}\n {...rest}\n />\n <span className={cx(\"absolute right-1 flex\", size === \"sm\" ? \"top-0\" : \"top-1\")}>\n {showClear ? <IconButton icon=\"x\" label=\"Clear date\" size=\"sm\" onClick={() => set(null)} /> : null}\n <Popover\n open={open}\n onOpenChange={(next) => {\n if (next) setMonth(startOfMonth(value) ?? null);\n setOpen(next);\n }}\n align=\"end\"\n width=\"auto\"\n className=\"p-3\"\n label=\"Choose a date\"\n trigger={(props) => <IconButton {...props} icon=\"calendar\" label=\"Open calendar\" size=\"sm\" disabled={off} />}\n >\n <Calendar\n value={value}\n onChange={(v) => {\n set(v);\n setOpen(false);\n }}\n month={month ?? undefined}\n onMonthChange={(m) => setMonth(`${m}-01`)}\n min={min}\n max={max}\n disabled={typeof disabled === \"function\" ? disabled : undefined}\n locale={locale}\n today={today}\n weekStartsOn={weekStartsOn}\n autoFocus={open}\n label=\"Choose a date\"\n />\n </Popover>\n </span>\n </div>\n );\n}\n"
103
109
  },
104
110
  {
105
111
  "path": "templates/next/files/components/ui/depiction.jsx",
@@ -135,7 +141,7 @@
135
141
  "path": "templates/next/files/components/ui/icon.jsx",
136
142
  "type": "registry:ui",
137
143
  "target": "@components/makersclaw/ui/icon.jsx",
138
- "content": "// The kit's icons: one inline sprite, no icon package to install. Every\n// glyph is a 24-box stroke path (1.8 wide, round caps, currentColor); add\n// one here, never an <svg> in a surface. `name` is a key below; unknown\n// names render nothing (and `npm run check` names them).\n//\n// BRANDS holds the brand marks: google, github, apple, microsoft,\n// x-logo (the X / Twitter mark; `x` is the close glyph), linkedin,\n// instagram, youtube, facebook. Unlike PATHS they are FILLED shapes\n// (currentColor, even-odd so letters and holes knock out), one colour,\n// drawn by hand and simplified, never copied from an icon library.\n\nconst PATHS = {\n plus: \"M12 5v14M5 12h14\",\n x: \"M18 6 6 18M6 6l12 12\",\n check: \"m5 12 5 5L20 7\",\n \"chevron-down\": \"m6 9 6 6 6-6\",\n \"chevron-up\": \"m18 15-6-6-6 6\",\n \"chevron-left\": \"m15 18-6-6 6-6\",\n \"chevron-right\": \"m9 18 6-6-6-6\",\n \"chevrons-up-down\": \"m7 15 5 5 5-5M7 9l5-5 5 5\",\n \"arrow-left\": \"M19 12H5m7-7-7 7 7 7\",\n \"arrow-right\": \"M5 12h14m-7-7 7 7-7 7\",\n \"arrow-up\": \"M12 19V5m-7 7 7-7 7 7\",\n \"arrow-down\": \"M12 5v14m7-7-7 7-7-7\",\n search: \"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14ZM21 21l-4.3-4.3\",\n trash: \"M3 6h18M8 6V4h8v2m-9 0 1 14h8l1-14\",\n pencil: \"M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z\",\n copy: \"M8 8h12v12H8zM4 16V4h12\",\n more: \"M5 12h.01M12 12h.01M19 12h.01\",\n external: \"M14 3h7v7m0-7L10 14M21 14v7H3V3h7\",\n refresh: \"M21 12a9 9 0 1 1-2.6-6.4M21 3v6h-6\",\n filter: \"M3 5h18l-7 8v6l-4 2v-8Z\",\n calendar: \"M3 5h18v16H3zM16 3v4M8 3v4M3 10h18\",\n clock: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 4v5l3 3\",\n user: \"M20 21a8 8 0 0 0-16 0M12 3a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z\",\n users: \"M17 21a4 4 0 0 0-8 0M13 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM23 21a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8M3 21a4 4 0 0 1 3-3.9M8 3.1a4 4 0 0 0 0 7.8\",\n mail: \"M3 5h18v14H3zM3 7l9 6 9-6\",\n link: \"M10 14a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1M14 10a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1\",\n grip: \"M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01\",\n info: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 5h.01M12 12v4\",\n warning: \"M12 3 2 20h20L12 3Zm0 7v4m0 3h.01\",\n sparkles: \"m12 3 2 6 6 2-6 2-2 6-2-6-6-2 6-2 2-6ZM19 15l1 2 2 1-2 1-1 2-1-2-2-1 2-1 1-2Z\",\n play: \"m6 4 14 8-14 8V4Z\",\n pause: \"M6 4h4v16H6zM14 4h4v16h-4z\",\n download: \"M12 3v12m-5-5 5 5 5-5M4 21h16\",\n upload: \"M12 21V9m-5 5 5-5 5 5M4 3h16\",\n eye: \"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Zm10-3a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z\",\n \"eye-off\": \"M10.6 5.1A10 10 0 0 1 12 5c6 0 10 7 10 7a17 17 0 0 1-2.4 3.2M6.6 6.6C3.8 8.4 2 12 2 12s4 7 10 7a9.7 9.7 0 0 0 5.4-1.6M9.9 9.9a3 3 0 0 0 4.2 4.2M2 2l20 20\",\n send: \"m22 2-7 20-4-9-9-4 20-7Z\",\n home: \"m3 11 9-8 9 8v10h-6v-6H9v6H3V11Z\",\n settings: \"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm8 4-1.5-.5.3-1.6 1.4-.9-1-1.7-1.6.5-1.2-1.2.5-1.6-1.7-1-.9 1.4-1.6-.3L12 4l-.5 1.5-1.6.3-.9-1.4-1.7 1 .5 1.6-1.2 1.2-1.6-.5-1 1.7 1.4.9.3 1.6L4 12l1.5.5-.3 1.6-1.4.9 1 1.7 1.6-.5 1.2 1.2-.5 1.6 1.7 1 .9-1.4 1.6.3L12 20l.5-1.5 1.6-.3.9 1.4 1.7-1-.5-1.6 1.2-1.2 1.6.5 1-1.7-1.4-.9-.3-1.6L20 12Z\",\n reply: \"M9 17 4 12l5-5M4 12h10a6 6 0 0 1 6 6v1\",\n repeat: \"m17 2 4 4-4 4M3 11V9a3 3 0 0 1 3-3h15M7 22l-4-4 4-4M21 13v2a3 3 0 0 1-3 3H3\",\n heart: \"M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1.1-1.1a5.5 5.5 0 0 0-7.8 7.8l1.1 1.1L12 21l7.8-7.5 1.1-1.1a5.5 5.5 0 0 0-.1-7.8Z\",\n chart: \"M3 3v18h18M7 16l4-5 4 3 5-7\",\n share: \"M12 3v12M7 8l5-5 5 5M5 14v6h14v-6\",\n star: \"m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2L12 17.3 6.4 20.2l1.1-6.2L3 9.6l6.2-.9L12 3Z\",\n bell: \"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0\",\n tag: \"M20 12 12 20l-9-9V3h8l9 9ZM7 7h.01\",\n folder: \"M3 7V5h6l2 2h10v13H3V7Z\",\n file: \"M14 3H6v18h12V7l-4-4ZM14 3v4h4\",\n image: \"M3 5h18v14H3zM8 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM21 15l-5-5-9 9\",\n phone: \"M22 16.9v3a2 2 0 0 1-2.2 2A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7l.8 3.6a2 2 0 0 1-.5 1.9L8.1 10.5a16 16 0 0 0 5.4 5.4l1.3-1.3a2 2 0 0 1 1.9-.5l3.6.8a2 2 0 0 1 1.7 2Z\",\n globe: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18\",\n lock: \"M5 11h14v10H5zM8 11V7a4 4 0 0 1 8 0v4\",\n zap: \"M13 2 3 14h8l-1 8 10-12h-8l1-8Z\",\n dollar: \"M12 2v20M17 6H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\",\n map: \"m3 6 6-3 6 3 6-3v15l-6 3-6-3-6 3V6ZM9 3v15M15 6v15\",\n minus: \"M5 12h14\",\n archive: \"M3 4h18v4H3zM5 8v12h14V8M10 12h4\",\n inbox: \"M3 13h5l2 3h4l2-3h5M3 13V5h18v8M3 13v6h18v-6\",\n pin: \"M12 17v5M8 3h8M9 3v7l-3 4v3h12v-3l-3-4V3\",\n bold: \"M7 4h6a4 4 0 0 1 0 8H7zM7 12h7a4 4 0 0 1 0 8H7z\",\n italic: \"M19 4h-9M14 20H5M15 4 9 20\",\n underline: \"M6 4v6a6 6 0 0 0 12 0V4M4 21h16\",\n list: \"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\",\n grid: \"M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z\",\n \"align-left\": \"M3 6h18M3 12h12M3 18h16\",\n \"align-center\": \"M3 6h18M6 12h12M4 18h16\",\n \"align-right\": \"M3 6h18M9 12h12M5 18h16\",\n paperclip: \"m21 11-9 9a6 6 0 0 1-8.5-8.5l8.6-8.6a4 4 0 0 1 5.7 5.7l-8.6 8.5a2 2 0 0 1-2.8-2.8L15 6.9\",\n stop: \"M6 6h12v12H6z\",\n menu: \"M4 6h16M4 12h16M4 18h16\",\n \"panel-left\": \"M3 4h18v16H3zM9 4v16\",\n \"log-out\": \"M9 21H4V3h5M16 17l5-5-5-5M21 12H9\",\n code: \"m16 18 6-6-6-6M8 6l-6 6 6 6\",\n shield: \"M12 3 4 6v6c0 5 3.4 8.2 8 9 4.6-.8 8-4 8-9V6l-8-3Z\",\n \"shield-check\": \"M12 3 4 6v6c0 5 3.4 8.2 8 9 4.6-.8 8-4 8-9V6l-8-3ZM9 12l2 2 4-4\",\n \"circle-check\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-4 9 3 3 5-5\",\n // The same glyph under its older name, so either one renders.\n \"check-circle\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-4 9 3 3 5-5\",\n \"circle-x\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM15 9l-6 6M9 9l6 6\",\n \"message-square\": \"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\",\n \"git-branch\": \"M6 3v12M18 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM6 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 9a9 9 0 0 1-9 9\",\n \"git-pull-request\": \"M6 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM6 9v12M12 6h4a2 2 0 0 1 2 2v7m-3-12-3 3 3 3\",\n server: \"M3 3h18v8H3zM3 13h18v8H3zM7 7h.01M7 17h.01\",\n leaf: \"M5 19C5 10 10 4 20 4c0 10-6 15-15 15ZM3 21 14 10\",\n monitor: \"M3 4h18v12H3zM12 16v4M8 20h8\",\n smartphone: \"M8 3h8v18H8zM11 17.5h2\",\n video: \"M3 6h12v12H3zM15 10l6-3v10l-6-3\",\n tablet: \"M5 2h14v20H5zM11 18.5h2\",\n sun: \"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\",\n moon: \"M20 14.5A8.5 8.5 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5Z\",\n database: \"M4 6a8 3 0 1 0 16 0 8 3 0 1 0-16 0ZM4 6v12a8 3 0 0 0 16 0V6M4 12a8 3 0 0 0 16 0\",\n \"git-commit\": \"M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM3 12h6M15 12h6\",\n lightbulb: \"M12 3a6 6 0 0 0-3.5 10.9c.6.4.9 1.1.9 1.8V16h5.2v-.3c0-.7.3-1.4.9-1.8A6 6 0 0 0 12 3ZM9.5 19h5M10.5 22h3\",\n \"life-buoy\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM5.6 5.6l3.6 3.6M14.8 14.8l3.6 3.6M18.4 5.6l-3.6 3.6M9.2 14.8l-3.6 3.6\",\n \"log-in\": \"M15 3h5v18h-5M10 17l5-5-5-5M15 12H3\",\n \"user-plus\": \"M2 21a7 7 0 0 1 14 0M9 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM19 8v6M16 11h6\",\n palette: \"M12 3a9 9 0 0 0 0 18c1.1 0 2-.9 2-2 0-.5-.2-1-.5-1.3-.3-.4-.5-.8-.5-1.3 0-1.1.9-1.9 2-1.9h2a4 4 0 0 0 4-4C21 6.4 17 3 12 3ZM7.5 11.5h.01M9 7.5h.01M14 6.5h.01M17.5 9.5h.01\",\n layers: \"M12 3 2 8l10 5 10-5-10-5ZM2 12l10 5 10-5M2 16l10 5 10-5\",\n type: \"M4 7V4h16v3M12 4v16M9 20h6\",\n package: \"M12 2 3 7v10l9 5 9-5V7l-9-5ZM3 7l9 5 9-5M12 12v10M7.5 4.5l9 5\",\n // Task states: an empty ring is \"to do\", a broken ring is \"doing\".\n circle: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z\",\n \"circle-dashed\": \"M13.6 3.1A9 9 0 0 1 17.2 4.6M19.4 6.8A9 9 0 0 1 20.9 10.4M20.9 13.6A9 9 0 0 1 19.4 17.2M17.2 19.4A9 9 0 0 1 13.6 20.9M10.4 20.9A9 9 0 0 1 6.8 19.4M4.6 17.2A9 9 0 0 1 3.1 13.6M3.1 10.4A9 9 0 0 1 4.6 6.8M6.8 4.6A9 9 0 0 1 10.4 3.1\",\n loader: \"M12 2v4M12 18v4M2 12h4M18 12h4M4.9 4.9l2.8 2.8M16.3 16.3l2.8 2.8M4.9 19.1l2.8-2.8M16.3 7.7l2.8-2.8\",\n \"trending-up\": \"m3 17 6-6 4 4 8-8M15 7h6v6\",\n \"trending-down\": \"m3 7 6 6 4-4 8 8M15 17h6v-6\",\n \"message-circle\": \"M21 12a9 9 0 0 1-13.3 7.9L3 21l1.1-4.7A9 9 0 1 1 21 12Z\",\n \"credit-card\": \"M2 5h20v14H2zM2 10h20M6 15h4\",\n \"shopping-cart\": \"M2 3h3l2.4 12h11.2L21 7H5.8M8 19.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 1 0-3 0ZM16 19.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 1 0-3 0Z\",\n key: \"M7.5 11a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9ZM10.7 12.3 21 2M16 7l3 3M18.5 4.5l2 2\",\n quote: \"M4 18h5v-6H4v6Zm0-6c0-4 1.5-6 5-7M15 18h5v-6h-5v6Zm0-6c0-4 1.5-6 5-7\",\n truck: \"M2 5h12v11H2zM14 9h4.5L21 12v4h-7M4 18a2 2 0 1 0 4 0 2 2 0 1 0-4 0ZM15 18a2 2 0 1 0 4 0 2 2 0 1 0-4 0Z\",\n laptop: \"M4 5h16v11H4zM2 19h20\",\n receipt: \"M5 3h14v18l-2.3-1.5-2.4 1.5-2.3-1.5-2.3 1.5-2.4-1.5L5 21V3ZM9 8h6M9 12h6M9 16h3\",\n mic: \"M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3ZM5 11a7 7 0 0 0 14 0M12 18v4M8 22h8\",\n \"thumbs-up\": \"M7 10v11H3V10h4Zm0 0 4-8a3 3 0 0 1 3 3v4h5.5a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7\",\n \"thumbs-down\": \"M7 14V3H3v11h4Zm0 0 4 8a3 3 0 0 0 3-3v-4h5.5a2 2 0 0 0 2-2.3l-1.4-8a2 2 0 0 0-2-1.7H7\",\n history: \"M3 12a9 9 0 1 0 2.6-6.4L3 8M3 3v5h5M12 7v5l3 2\",\n wand: \"M3 21 14 10M16 3v4M14 5h4M20 9v3M18.5 10.5h3M9 3v2M8 4h2\",\n building: \"M4 21V3h12v18M16 9h4v12M2 21h20M8 7h4M8 11h4M8 15h4\",\n // Canvas tools (flow-canvas): the rail, the zoom bar, the arrange buttons.\n \"mouse-pointer\": \"M5 3l5.8 16.5 2.4-6.8 6.8-2.4L5 3ZM13.2 12.7 19 18.5\",\n hand: \"M8 12V5.5a1.5 1.5 0 0 1 3 0V11M11 10.5V4a1.5 1.5 0 0 1 3 0v6.5M14 10.5V5.5a1.5 1.5 0 0 1 3 0V14a7 7 0 0 1-7 7h-.4a6 6 0 0 1-4.7-2.3l-2.3-2.9a1.6 1.6 0 0 1 2.4-2.1L8 16\",\n \"sticky-note\": \"M4 4h16v10l-6 6H4V4ZM14 20v-6h6\",\n square: \"M4 4h16v16H4z\",\n frame: \"M7 3v18M17 3v18M3 7h18M3 17h18\",\n connector: \"M4 20C4 11 10 5 19 5m-4-3.5L19 5l-4 3.5M4 20h.01\",\n maximize: \"M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5\",\n undo: \"M9 14 4 9l5-5M4 9h11a5 5 0 0 1 0 10h-3\",\n redo: \"m15 14 5-5-5-5M20 9H9a5 5 0 0 0 0 10h3\",\n keyboard: \"M2 6h20v12H2zM6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10\",\n // Sketch tools (sketch-canvas): the diamond shape and the eraser.\n diamond: \"M12 3 21 12 12 21 3 12Z\",\n eraser: \"M20 20H8.5l-4-4a2 2 0 0 1 0-2.8l8.8-8.8a2 2 0 0 1 2.8 0l3.5 3.5a2 2 0 0 1 0 2.8L12 20M8.5 9.5l6 6\",\n highlighter: \"m14.5 4.5 5 5-7.5 7.5H8.5L7 15.5v-3.5ZM11 8l5 5M7 15.5 4 18.5l2 1.5h5M14 21h6\",\n \"lock-open\": \"M5 11h14v10H5zM8 11V7a4 4 0 0 1 7.7-1.5\",\n \"bring-front\": \"M12 14V3M8 7l4-4 4 4M4 15l8 5 8-5\",\n \"send-back\": \"M12 3v11M8 10l4 4 4-4M4 15l8 5 8-5\",\n // Column types (spreadsheet): a number field, a checkbox field.\n hash: \"M4 9h16M4 15h16M10 3 8 21M16 3l-2 18\",\n \"square-check\": \"M4 4h16v16H4zM8 12l3 3 5-6\",\n target: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10ZM12 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z\",\n flag: \"M5 21V4m0 0h11l-2 4 2 4H5\",\n \"circle-help\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM9.5 9.2a2.6 2.6 0 0 1 5 .9c0 1.7-2.5 2.3-2.5 3.9M12 17h.01\",\n // Playback (audio-player): skip back / forward, sound on / off.\n \"rotate-ccw\": \"M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5\",\n \"rotate-cw\": \"M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5\",\n volume: \"M11 5 6 9H2v6h4l5 4V5ZM15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14\",\n \"volume-x\": \"M11 5 6 9H2v6h4l5 4V5ZM22 9l-6 6M16 9l6 6\",\n navigation: \"M3 11 21 3l-8 18-2-8Z\",\n locate: \"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm0 4a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM12 2v3M12 19v3M2 12h3M19 12h3\",\n route: \"M6 16a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\",\n // View kinds (work-views): a board's columns, a table's grid, a gantt's bars.\n kanban: \"M4 4h4v14H4zM10 4h4v9h-4zM16 4h4v16h-4z\",\n table: \"M3 4h18v16H3zM3 10h18M3 15h18M9 10v10\",\n gantt: \"M4 4v16h16M8 8h6M11 12h7M9 16h5\",\n};\n\nconst BRANDS = {\n google: \"M18.9 6.2A9 9 0 1 0 21 12v-1.8h-8.8v3.4h4.96A5.4 5.4 0 1 1 16.14 8.53Z\",\n github: \"M12 2.5a9.5 9.5 0 1 0 0 19 9.5 9.5 0 0 0 0-19ZM10 21.3v-2.9c-2.8-.4-4.5-2-4.5-5 0-1.1.4-2.1 1.1-2.9-.2-.7-.2-1.7.2-2.7 1 0 2 .5 2.8 1.1a8 8 0 0 1 4.8 0c.8-.6 1.8-1.1 2.8-1.1.4 1 .4 2 .2 2.7.7.8 1.1 1.8 1.1 2.9 0 3-1.7 4.6-4.5 5v2.9ZM10 20.2c-2.4.4-3.5-.5-4.3-2l1-.4c.6 1.1 1.4 1.7 3.3 1.3Z\",\n apple: \"M12 7.8C10.5 6.8 8.8 6.6 7.4 7.4 4.8 8.9 4.4 12.8 5.9 16.2c1 2.4 2.5 4.4 4.1 4.4.9 0 1.3-.5 2-.5s1.1.5 2 .5c1.6 0 3-1.7 4-3.6.5-.8.9-1.4 1.3-2A3.2 3.2 0 0 1 18.3 9.3c-.8-1.2-2-2.1-3.5-2.2-1 0-1.9.3-2.8.7ZM12.2 6.3c0-1.8 1.3-3.2 3.1-3.3 0 1.8-1.3 3.2-3.1 3.3Z\",\n microsoft: \"M3 3h8.5v8.5H3zM12.5 3H21v8.5h-8.5zM3 12.5h8.5V21H3zM12.5 12.5H21V21h-8.5z\",\n \"x-logo\": \"M2.8 3h6l12.4 18h-6ZM5.3 4.3l10.6 15.4h2.8L8.1 4.3ZM19.3 3h1.9l-6.6 7.4-.8-1ZM4.7 21H2.8l6.6-7.4.8 1Z\",\n linkedin: \"M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2ZM6.4 7.6a1.3 1.3 0 1 0 2.6 0 1.3 1.3 0 1 0-2.6 0ZM6.6 10h2.3v7.5H6.6ZM10.8 10H13v1.1c.5-.8 1.4-1.3 2.5-1.3 2 0 3 1.2 3 3.4v4.3h-2.3v-3.9c0-1.1-.4-1.8-1.4-1.8s-1.7.7-1.7 1.8v3.9h-2.3Z\",\n instagram: \"M7.5 2.5h9a5 5 0 0 1 5 5v9a5 5 0 0 1-5 5h-9a5 5 0 0 1-5-5v-9a5 5 0 0 1 5-5ZM7.5 4.3a3.2 3.2 0 0 0-3.2 3.2v9a3.2 3.2 0 0 0 3.2 3.2h9a3.2 3.2 0 0 0 3.2-3.2v-9a3.2 3.2 0 0 0-3.2-3.2ZM12 7.3a4.7 4.7 0 1 0 0 9.4 4.7 4.7 0 0 0 0-9.4Zm0 1.8a2.9 2.9 0 1 1 0 5.8 2.9 2.9 0 0 1 0-5.8ZM16.2 6.7a1.1 1.1 0 1 0 2.2 0 1.1 1.1 0 1 0-2.2 0Z\",\n youtube: \"M2.5 7.5c.2-1.6 1.2-2.6 2.8-2.7 2.1-.2 4.4-.3 6.7-.3s4.6.1 6.7.3c1.6.1 2.6 1.1 2.8 2.7.2 1.5.3 3 .3 4.5s-.1 3-.3 4.5c-.2 1.6-1.2 2.6-2.8 2.7-2.1.2-4.4.3-6.7.3s-4.6-.1-6.7-.3c-1.6-.1-2.6-1.1-2.8-2.7-.2-1.5-.3-3-.3-4.5s.1-3 .3-4.5ZM10 8.8v6.4l5.5-3.2Z\",\n facebook: \"M12 2.5a9.5 9.5 0 1 0 0 19 9.5 9.5 0 0 0 0-19ZM10.3 21.4v-7.6H8.4v-2.5h1.9V9.4c0-2.5 1.4-3.9 3.7-3.9.9 0 1.6.1 1.9.1v2.2h-1.3c-1 0-1.4.6-1.4 1.4v2.1h2.6l-.4 2.5h-2.2v7.6Z\",\n};\n\nexport const ICON_NAMES = [...Object.keys(PATHS), ...Object.keys(BRANDS)];\n\nexport function Icon({ name, size = 16, className, label }) {\n const brand = BRANDS[name];\n const d = PATHS[name] || brand;\n if (!d) return null;\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={brand ? \"currentColor\" : \"none\"}\n fillRule={brand ? \"evenodd\" : undefined}\n stroke={brand ? \"none\" : \"currentColor\"}\n strokeWidth={brand ? undefined : \"1.8\"}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden={label ? undefined : true}\n role={label ? \"img\" : undefined}\n aria-label={label}\n className={className}\n >\n <path d={d} />\n </svg>\n );\n}\n"
144
+ "content": "// The kit's icons: one inline sprite, no icon package to install. Every\n// glyph is a 24-box stroke path (1.8 wide, round caps, currentColor); add\n// one here, never an <svg> in a surface. `name` is a key below; unknown\n// names render nothing (and `npm run check` names them).\n//\n// `size` is pixels (16, the default) or one of the kit's size names:\n// xs 12 · sm 14 · md 16 · lg 20 · xl 24. Every other part of the kit\n// takes size=\"sm\", so a surface writes it here too — and until 0.3.3 a\n// name went onto the <svg> as width=\"sm\", which is not a length: the\n// browser logged \"Expected length, 'sm'\" and drew the glyph at its\n// 300×150 default, a calendar as big as the card around it (a podcast\n// guest board, 2026-09-25).\n//\n// A glyph added for a build takes lucide's name when lucide has one\n// (calendar-check, trash-2), because that is the name a build reaches for\n// first; the path is our own, drawn to the same 24 box, never copied.\n//\n// BRANDS holds the brand marks: google, github, apple, microsoft,\n// x-logo (the X / Twitter mark; `x` is the close glyph), linkedin,\n// instagram, youtube, facebook. Unlike PATHS they are FILLED shapes\n// (currentColor, even-odd so letters and holes knock out), one colour,\n// drawn by hand and simplified, never copied from an icon library.\n\nconst PATHS = {\n plus: \"M12 5v14M5 12h14\",\n x: \"M18 6 6 18M6 6l12 12\",\n check: \"m5 12 5 5L20 7\",\n \"chevron-down\": \"m6 9 6 6 6-6\",\n \"chevron-up\": \"m18 15-6-6-6 6\",\n \"chevron-left\": \"m15 18-6-6 6-6\",\n \"chevron-right\": \"m9 18 6-6-6-6\",\n \"chevrons-up-down\": \"m7 15 5 5 5-5M7 9l5-5 5 5\",\n \"arrow-left\": \"M19 12H5m7-7-7 7 7 7\",\n \"arrow-right\": \"M5 12h14m-7-7 7 7-7 7\",\n \"arrow-up\": \"M12 19V5m-7 7 7-7 7 7\",\n \"arrow-down\": \"M12 5v14m7-7-7 7-7-7\",\n search: \"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14ZM21 21l-4.3-4.3\",\n trash: \"M3 6h18M8 6V4h8v2m-9 0 1 14h8l1-14\",\n pencil: \"M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z\",\n copy: \"M8 8h12v12H8zM4 16V4h12\",\n more: \"M5 12h.01M12 12h.01M19 12h.01\",\n external: \"M14 3h7v7m0-7L10 14M21 14v7H3V3h7\",\n refresh: \"M21 12a9 9 0 1 1-2.6-6.4M21 3v6h-6\",\n filter: \"M3 5h18l-7 8v6l-4 2v-8Z\",\n calendar: \"M3 5h18v16H3zM16 3v4M8 3v4M3 10h18\",\n clock: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 4v5l3 3\",\n user: \"M20 21a8 8 0 0 0-16 0M12 3a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z\",\n users: \"M17 21a4 4 0 0 0-8 0M13 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM23 21a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8M3 21a4 4 0 0 1 3-3.9M8 3.1a4 4 0 0 0 0 7.8\",\n mail: \"M3 5h18v14H3zM3 7l9 6 9-6\",\n link: \"M10 14a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1M14 10a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1\",\n grip: \"M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01\",\n info: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 5h.01M12 12v4\",\n warning: \"M12 3 2 20h20L12 3Zm0 7v4m0 3h.01\",\n sparkles: \"m12 3 2 6 6 2-6 2-2 6-2-6-6-2 6-2 2-6ZM19 15l1 2 2 1-2 1-1 2-1-2-2-1 2-1 1-2Z\",\n play: \"m6 4 14 8-14 8V4Z\",\n pause: \"M6 4h4v16H6zM14 4h4v16h-4z\",\n download: \"M12 3v12m-5-5 5 5 5-5M4 21h16\",\n upload: \"M12 21V9m-5 5 5-5 5 5M4 3h16\",\n eye: \"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Zm10-3a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z\",\n \"eye-off\": \"M10.6 5.1A10 10 0 0 1 12 5c6 0 10 7 10 7a17 17 0 0 1-2.4 3.2M6.6 6.6C3.8 8.4 2 12 2 12s4 7 10 7a9.7 9.7 0 0 0 5.4-1.6M9.9 9.9a3 3 0 0 0 4.2 4.2M2 2l20 20\",\n send: \"m22 2-7 20-4-9-9-4 20-7Z\",\n home: \"m3 11 9-8 9 8v10h-6v-6H9v6H3V11Z\",\n settings: \"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm8 4-1.5-.5.3-1.6 1.4-.9-1-1.7-1.6.5-1.2-1.2.5-1.6-1.7-1-.9 1.4-1.6-.3L12 4l-.5 1.5-1.6.3-.9-1.4-1.7 1 .5 1.6-1.2 1.2-1.6-.5-1 1.7 1.4.9.3 1.6L4 12l1.5.5-.3 1.6-1.4.9 1 1.7 1.6-.5 1.2 1.2-.5 1.6 1.7 1 .9-1.4 1.6.3L12 20l.5-1.5 1.6-.3.9 1.4 1.7-1-.5-1.6 1.2-1.2 1.6.5 1-1.7-1.4-.9-.3-1.6L20 12Z\",\n reply: \"M9 17 4 12l5-5M4 12h10a6 6 0 0 1 6 6v1\",\n repeat: \"m17 2 4 4-4 4M3 11V9a3 3 0 0 1 3-3h15M7 22l-4-4 4-4M21 13v2a3 3 0 0 1-3 3H3\",\n heart: \"M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1.1-1.1a5.5 5.5 0 0 0-7.8 7.8l1.1 1.1L12 21l7.8-7.5 1.1-1.1a5.5 5.5 0 0 0-.1-7.8Z\",\n chart: \"M3 3v18h18M7 16l4-5 4 3 5-7\",\n share: \"M12 3v12M7 8l5-5 5 5M5 14v6h14v-6\",\n star: \"m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2L12 17.3 6.4 20.2l1.1-6.2L3 9.6l6.2-.9L12 3Z\",\n bell: \"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0\",\n tag: \"M20 12 12 20l-9-9V3h8l9 9ZM7 7h.01\",\n folder: \"M3 7V5h6l2 2h10v13H3V7Z\",\n file: \"M14 3H6v18h12V7l-4-4ZM14 3v4h4\",\n image: \"M3 5h18v14H3zM8 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM21 15l-5-5-9 9\",\n phone: \"M22 16.9v3a2 2 0 0 1-2.2 2A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7l.8 3.6a2 2 0 0 1-.5 1.9L8.1 10.5a16 16 0 0 0 5.4 5.4l1.3-1.3a2 2 0 0 1 1.9-.5l3.6.8a2 2 0 0 1 1.7 2Z\",\n globe: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18\",\n lock: \"M5 11h14v10H5zM8 11V7a4 4 0 0 1 8 0v4\",\n zap: \"M13 2 3 14h8l-1 8 10-12h-8l1-8Z\",\n dollar: \"M12 2v20M17 6H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\",\n map: \"m3 6 6-3 6 3 6-3v15l-6 3-6-3-6 3V6ZM9 3v15M15 6v15\",\n minus: \"M5 12h14\",\n archive: \"M3 4h18v4H3zM5 8v12h14V8M10 12h4\",\n inbox: \"M3 13h5l2 3h4l2-3h5M3 13V5h18v8M3 13v6h18v-6\",\n pin: \"M12 17v5M8 3h8M9 3v7l-3 4v3h12v-3l-3-4V3\",\n bold: \"M7 4h6a4 4 0 0 1 0 8H7zM7 12h7a4 4 0 0 1 0 8H7z\",\n italic: \"M19 4h-9M14 20H5M15 4 9 20\",\n underline: \"M6 4v6a6 6 0 0 0 12 0V4M4 21h16\",\n list: \"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\",\n grid: \"M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z\",\n \"align-left\": \"M3 6h18M3 12h12M3 18h16\",\n \"align-center\": \"M3 6h18M6 12h12M4 18h16\",\n \"align-right\": \"M3 6h18M9 12h12M5 18h16\",\n paperclip: \"m21 11-9 9a6 6 0 0 1-8.5-8.5l8.6-8.6a4 4 0 0 1 5.7 5.7l-8.6 8.5a2 2 0 0 1-2.8-2.8L15 6.9\",\n stop: \"M6 6h12v12H6z\",\n menu: \"M4 6h16M4 12h16M4 18h16\",\n \"panel-left\": \"M3 4h18v16H3zM9 4v16\",\n \"log-out\": \"M9 21H4V3h5M16 17l5-5-5-5M21 12H9\",\n code: \"m16 18 6-6-6-6M8 6l-6 6 6 6\",\n shield: \"M12 3 4 6v6c0 5 3.4 8.2 8 9 4.6-.8 8-4 8-9V6l-8-3Z\",\n \"shield-check\": \"M12 3 4 6v6c0 5 3.4 8.2 8 9 4.6-.8 8-4 8-9V6l-8-3ZM9 12l2 2 4-4\",\n \"circle-check\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-4 9 3 3 5-5\",\n // The same glyph under its older name, so either one renders.\n \"check-circle\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-4 9 3 3 5-5\",\n \"circle-x\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM15 9l-6 6M9 9l6 6\",\n \"message-square\": \"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\",\n \"git-branch\": \"M6 3v12M18 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM6 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 9a9 9 0 0 1-9 9\",\n \"git-pull-request\": \"M6 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM6 9v12M12 6h4a2 2 0 0 1 2 2v7m-3-12-3 3 3 3\",\n server: \"M3 3h18v8H3zM3 13h18v8H3zM7 7h.01M7 17h.01\",\n leaf: \"M5 19C5 10 10 4 20 4c0 10-6 15-15 15ZM3 21 14 10\",\n monitor: \"M3 4h18v12H3zM12 16v4M8 20h8\",\n smartphone: \"M8 3h8v18H8zM11 17.5h2\",\n video: \"M3 6h12v12H3zM15 10l6-3v10l-6-3\",\n tablet: \"M5 2h14v20H5zM11 18.5h2\",\n sun: \"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\",\n moon: \"M20 14.5A8.5 8.5 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5Z\",\n database: \"M4 6a8 3 0 1 0 16 0 8 3 0 1 0-16 0ZM4 6v12a8 3 0 0 0 16 0V6M4 12a8 3 0 0 0 16 0\",\n \"git-commit\": \"M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM3 12h6M15 12h6\",\n lightbulb: \"M12 3a6 6 0 0 0-3.5 10.9c.6.4.9 1.1.9 1.8V16h5.2v-.3c0-.7.3-1.4.9-1.8A6 6 0 0 0 12 3ZM9.5 19h5M10.5 22h3\",\n \"life-buoy\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM5.6 5.6l3.6 3.6M14.8 14.8l3.6 3.6M18.4 5.6l-3.6 3.6M9.2 14.8l-3.6 3.6\",\n \"log-in\": \"M15 3h5v18h-5M10 17l5-5-5-5M15 12H3\",\n \"user-plus\": \"M2 21a7 7 0 0 1 14 0M9 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM19 8v6M16 11h6\",\n palette: \"M12 3a9 9 0 0 0 0 18c1.1 0 2-.9 2-2 0-.5-.2-1-.5-1.3-.3-.4-.5-.8-.5-1.3 0-1.1.9-1.9 2-1.9h2a4 4 0 0 0 4-4C21 6.4 17 3 12 3ZM7.5 11.5h.01M9 7.5h.01M14 6.5h.01M17.5 9.5h.01\",\n layers: \"M12 3 2 8l10 5 10-5-10-5ZM2 12l10 5 10-5M2 16l10 5 10-5\",\n type: \"M4 7V4h16v3M12 4v16M9 20h6\",\n package: \"M12 2 3 7v10l9 5 9-5V7l-9-5ZM3 7l9 5 9-5M12 12v10M7.5 4.5l9 5\",\n // Task states: an empty ring is \"to do\", a broken ring is \"doing\".\n circle: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z\",\n \"circle-dashed\": \"M13.6 3.1A9 9 0 0 1 17.2 4.6M19.4 6.8A9 9 0 0 1 20.9 10.4M20.9 13.6A9 9 0 0 1 19.4 17.2M17.2 19.4A9 9 0 0 1 13.6 20.9M10.4 20.9A9 9 0 0 1 6.8 19.4M4.6 17.2A9 9 0 0 1 3.1 13.6M3.1 10.4A9 9 0 0 1 4.6 6.8M6.8 4.6A9 9 0 0 1 10.4 3.1\",\n loader: \"M12 2v4M12 18v4M2 12h4M18 12h4M4.9 4.9l2.8 2.8M16.3 16.3l2.8 2.8M4.9 19.1l2.8-2.8M16.3 7.7l2.8-2.8\",\n \"trending-up\": \"m3 17 6-6 4 4 8-8M15 7h6v6\",\n \"trending-down\": \"m3 7 6 6 4-4 8 8M15 17h6v-6\",\n \"message-circle\": \"M21 12a9 9 0 0 1-13.3 7.9L3 21l1.1-4.7A9 9 0 1 1 21 12Z\",\n \"credit-card\": \"M2 5h20v14H2zM2 10h20M6 15h4\",\n \"shopping-cart\": \"M2 3h3l2.4 12h11.2L21 7H5.8M8 19.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 1 0-3 0ZM16 19.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 1 0-3 0Z\",\n key: \"M7.5 11a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9ZM10.7 12.3 21 2M16 7l3 3M18.5 4.5l2 2\",\n quote: \"M4 18h5v-6H4v6Zm0-6c0-4 1.5-6 5-7M15 18h5v-6h-5v6Zm0-6c0-4 1.5-6 5-7\",\n truck: \"M2 5h12v11H2zM14 9h4.5L21 12v4h-7M4 18a2 2 0 1 0 4 0 2 2 0 1 0-4 0ZM15 18a2 2 0 1 0 4 0 2 2 0 1 0-4 0Z\",\n laptop: \"M4 5h16v11H4zM2 19h20\",\n receipt: \"M5 3h14v18l-2.3-1.5-2.4 1.5-2.3-1.5-2.3 1.5-2.4-1.5L5 21V3ZM9 8h6M9 12h6M9 16h3\",\n mic: \"M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3ZM5 11a7 7 0 0 0 14 0M12 18v4M8 22h8\",\n \"thumbs-up\": \"M7 10v11H3V10h4Zm0 0 4-8a3 3 0 0 1 3 3v4h5.5a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7\",\n \"thumbs-down\": \"M7 14V3H3v11h4Zm0 0 4 8a3 3 0 0 0 3-3v-4h5.5a2 2 0 0 0 2-2.3l-1.4-8a2 2 0 0 0-2-1.7H7\",\n history: \"M3 12a9 9 0 1 0 2.6-6.4L3 8M3 3v5h5M12 7v5l3 2\",\n wand: \"M3 21 14 10M16 3v4M14 5h4M20 9v3M18.5 10.5h3M9 3v2M8 4h2\",\n building: \"M4 21V3h12v18M16 9h4v12M2 21h20M8 7h4M8 11h4M8 15h4\",\n // Canvas tools (flow-canvas): the rail, the zoom bar, the arrange buttons.\n \"mouse-pointer\": \"M5 3l5.8 16.5 2.4-6.8 6.8-2.4L5 3ZM13.2 12.7 19 18.5\",\n hand: \"M8 12V5.5a1.5 1.5 0 0 1 3 0V11M11 10.5V4a1.5 1.5 0 0 1 3 0v6.5M14 10.5V5.5a1.5 1.5 0 0 1 3 0V14a7 7 0 0 1-7 7h-.4a6 6 0 0 1-4.7-2.3l-2.3-2.9a1.6 1.6 0 0 1 2.4-2.1L8 16\",\n \"sticky-note\": \"M4 4h16v10l-6 6H4V4ZM14 20v-6h6\",\n square: \"M4 4h16v16H4z\",\n frame: \"M7 3v18M17 3v18M3 7h18M3 17h18\",\n connector: \"M4 20C4 11 10 5 19 5m-4-3.5L19 5l-4 3.5M4 20h.01\",\n maximize: \"M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5\",\n undo: \"M9 14 4 9l5-5M4 9h11a5 5 0 0 1 0 10h-3\",\n redo: \"m15 14 5-5-5-5M20 9H9a5 5 0 0 0 0 10h3\",\n keyboard: \"M2 6h20v12H2zM6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10\",\n // Sketch tools (sketch-canvas): the diamond shape and the eraser.\n diamond: \"M12 3 21 12 12 21 3 12Z\",\n eraser: \"M20 20H8.5l-4-4a2 2 0 0 1 0-2.8l8.8-8.8a2 2 0 0 1 2.8 0l3.5 3.5a2 2 0 0 1 0 2.8L12 20M8.5 9.5l6 6\",\n highlighter: \"m14.5 4.5 5 5-7.5 7.5H8.5L7 15.5v-3.5ZM11 8l5 5M7 15.5 4 18.5l2 1.5h5M14 21h6\",\n \"lock-open\": \"M5 11h14v10H5zM8 11V7a4 4 0 0 1 7.7-1.5\",\n \"bring-front\": \"M12 14V3M8 7l4-4 4 4M4 15l8 5 8-5\",\n \"send-back\": \"M12 3v11M8 10l4 4 4-4M4 15l8 5 8-5\",\n // Column types (spreadsheet): a number field, a checkbox field.\n hash: \"M4 9h16M4 15h16M10 3 8 21M16 3l-2 18\",\n \"square-check\": \"M4 4h16v16H4zM8 12l3 3 5-6\",\n target: \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10ZM12 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z\",\n flag: \"M5 21V4m0 0h11l-2 4 2 4H5\",\n \"circle-help\": \"M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18ZM9.5 9.2a2.6 2.6 0 0 1 5 .9c0 1.7-2.5 2.3-2.5 3.9M12 17h.01\",\n // Playback (audio-player): skip back / forward, sound on / off.\n \"rotate-ccw\": \"M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5\",\n \"rotate-cw\": \"M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5\",\n volume: \"M11 5 6 9H2v6h4l5 4V5ZM15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14\",\n \"volume-x\": \"M11 5 6 9H2v6h4l5 4V5ZM22 9l-6 6M16 9l6 6\",\n navigation: \"M3 11 21 3l-8 18-2-8Z\",\n locate: \"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm0 4a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM12 2v3M12 19v3M2 12h3M19 12h3\",\n route: \"M6 16a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\",\n // View kinds (work-views): a board's columns, a table's grid, a gantt's bars.\n kanban: \"M4 4h4v14H4zM10 4h4v9h-4zM16 4h4v16h-4z\",\n table: \"M3 4h18v16H3zM3 10h18M3 15h18M9 10v10\",\n gantt: \"M4 4v16h16M8 8h6M11 12h7M9 16h5\",\n // Names builds reached for that the sprite lacked, so `npm run check`\n // refused the ship and the build spent a round renaming them (the\n // launch build test, 2026-09-25, and the local build logs before it):\n // a day that is booked, the bin with its slats, a small tree of nodes.\n \"calendar-check\": \"M3 5h18v16H3zM16 3v4M8 3v4M3 10h18M9 15.5l2 2 4-4\",\n \"trash-2\": \"M3 6h18M8 6V4h8v2m-9 0 1 14h8l1-14M10 11v6M14 11v6\",\n network: \"M9 2h6v6H9zM2 16h6v6H2zM16 16h6v6h-6zM12 8v4M5 16v-4h14v4\",\n};\n\nconst BRANDS = {\n google: \"M18.9 6.2A9 9 0 1 0 21 12v-1.8h-8.8v3.4h4.96A5.4 5.4 0 1 1 16.14 8.53Z\",\n github: \"M12 2.5a9.5 9.5 0 1 0 0 19 9.5 9.5 0 0 0 0-19ZM10 21.3v-2.9c-2.8-.4-4.5-2-4.5-5 0-1.1.4-2.1 1.1-2.9-.2-.7-.2-1.7.2-2.7 1 0 2 .5 2.8 1.1a8 8 0 0 1 4.8 0c.8-.6 1.8-1.1 2.8-1.1.4 1 .4 2 .2 2.7.7.8 1.1 1.8 1.1 2.9 0 3-1.7 4.6-4.5 5v2.9ZM10 20.2c-2.4.4-3.5-.5-4.3-2l1-.4c.6 1.1 1.4 1.7 3.3 1.3Z\",\n apple: \"M12 7.8C10.5 6.8 8.8 6.6 7.4 7.4 4.8 8.9 4.4 12.8 5.9 16.2c1 2.4 2.5 4.4 4.1 4.4.9 0 1.3-.5 2-.5s1.1.5 2 .5c1.6 0 3-1.7 4-3.6.5-.8.9-1.4 1.3-2A3.2 3.2 0 0 1 18.3 9.3c-.8-1.2-2-2.1-3.5-2.2-1 0-1.9.3-2.8.7ZM12.2 6.3c0-1.8 1.3-3.2 3.1-3.3 0 1.8-1.3 3.2-3.1 3.3Z\",\n microsoft: \"M3 3h8.5v8.5H3zM12.5 3H21v8.5h-8.5zM3 12.5h8.5V21H3zM12.5 12.5H21V21h-8.5z\",\n \"x-logo\": \"M2.8 3h6l12.4 18h-6ZM5.3 4.3l10.6 15.4h2.8L8.1 4.3ZM19.3 3h1.9l-6.6 7.4-.8-1ZM4.7 21H2.8l6.6-7.4.8 1Z\",\n linkedin: \"M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2ZM6.4 7.6a1.3 1.3 0 1 0 2.6 0 1.3 1.3 0 1 0-2.6 0ZM6.6 10h2.3v7.5H6.6ZM10.8 10H13v1.1c.5-.8 1.4-1.3 2.5-1.3 2 0 3 1.2 3 3.4v4.3h-2.3v-3.9c0-1.1-.4-1.8-1.4-1.8s-1.7.7-1.7 1.8v3.9h-2.3Z\",\n instagram: \"M7.5 2.5h9a5 5 0 0 1 5 5v9a5 5 0 0 1-5 5h-9a5 5 0 0 1-5-5v-9a5 5 0 0 1 5-5ZM7.5 4.3a3.2 3.2 0 0 0-3.2 3.2v9a3.2 3.2 0 0 0 3.2 3.2h9a3.2 3.2 0 0 0 3.2-3.2v-9a3.2 3.2 0 0 0-3.2-3.2ZM12 7.3a4.7 4.7 0 1 0 0 9.4 4.7 4.7 0 0 0 0-9.4Zm0 1.8a2.9 2.9 0 1 1 0 5.8 2.9 2.9 0 0 1 0-5.8ZM16.2 6.7a1.1 1.1 0 1 0 2.2 0 1.1 1.1 0 1 0-2.2 0Z\",\n youtube: \"M2.5 7.5c.2-1.6 1.2-2.6 2.8-2.7 2.1-.2 4.4-.3 6.7-.3s4.6.1 6.7.3c1.6.1 2.6 1.1 2.8 2.7.2 1.5.3 3 .3 4.5s-.1 3-.3 4.5c-.2 1.6-1.2 2.6-2.8 2.7-2.1.2-4.4.3-6.7.3s-4.6-.1-6.7-.3c-1.6-.1-2.6-1.1-2.8-2.7-.2-1.5-.3-3-.3-4.5s.1-3 .3-4.5ZM10 8.8v6.4l5.5-3.2Z\",\n facebook: \"M12 2.5a9.5 9.5 0 1 0 0 19 9.5 9.5 0 0 0 0-19ZM10.3 21.4v-7.6H8.4v-2.5h1.9V9.4c0-2.5 1.4-3.9 3.7-3.9.9 0 1.6.1 1.9.1v2.2h-1.3c-1 0-1.4.6-1.4 1.4v2.1h2.6l-.4 2.5h-2.2v7.6Z\",\n};\n\nexport const ICON_NAMES = [...Object.keys(PATHS), ...Object.keys(BRANDS)];\n\n// The kit's size names in pixels. A number, or a length the <svg> can\n// read (\"18\", \"1.25em\"), is kept as it was; anything else is the default,\n// never an attribute the browser cannot read.\nconst SIZES = { xs: 12, sm: 14, md: 16, lg: 20, xl: 24 };\nconst LENGTH = /^(\\d+\\.?\\d*|\\.\\d+)(px|em|rem|%)?$/;\n\nfunction pixels(size) {\n if (typeof size === \"number\" && size > 0) return size;\n if (typeof size === \"string\") {\n if (SIZES[size]) return SIZES[size];\n if (LENGTH.test(size)) return size;\n }\n return SIZES.md;\n}\n\nexport function Icon({ name, size = 16, className, label }) {\n const brand = BRANDS[name];\n const d = PATHS[name] || brand;\n if (!d) return null;\n const px = pixels(size);\n return (\n <svg\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill={brand ? \"currentColor\" : \"none\"}\n fillRule={brand ? \"evenodd\" : undefined}\n stroke={brand ? \"none\" : \"currentColor\"}\n strokeWidth={brand ? undefined : \"1.8\"}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden={label ? undefined : true}\n role={label ? \"img\" : undefined}\n aria-label={label}\n className={className}\n >\n <path d={d} />\n </svg>\n );\n}\n"
139
145
  },
140
146
  {
141
147
  "path": "templates/next/files/components/ui/image.jsx",
package/r/csv-import.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "title": "CsvImport",
6
6
  "description": "Import a CSV in three calm steps: drop a file (or paste text, or download a template), match each file column to a field with automatic matches and sample values (or, with no fields, name and type a new table's fields from guessed types), then review every row in an editable spreadsheet where cells that do not fit their type are marked red and fixed in place, delete rows or leave the bad ones out, and import through one async onImport that answers { ok }. Quoted commas and newlines, TSV and semicolon files, byte-order marks, 50,000 rows.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.3.2/r/spreadsheet.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.4.0/r/spreadsheet.json"
10
10
  ],
11
11
  "dependencies": [
12
12
  "papaparse@5.7.0"
@@ -56,7 +56,7 @@
56
56
  "docs": "Installed to components/makersclaw/blocks/csv-import. Import: import { CsvImport, parseCsvText, readCsvFile, headersOf, autoMatch, guessColumns, buildRows, cleanRow, templateCsv } from \"@/components/makersclaw/blocks/csv-import\". Props and an example: components/makersclaw/blocks/csv-import/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
57
57
  "meta": {
58
58
  "makersclaw": {
59
- "version": "0.3.2",
59
+ "version": "0.4.0",
60
60
  "useWhen": "People bring records in from a spreadsheet or another tool: contacts, leads, products, a price list, an old CRM export, a first table for a new app. Built on spreadsheet. Not for uploading files to keep (file-upload) or for editing records already in the app (spreadsheet, data-table)."
61
61
  }
62
62
  }