themelia-react-ui 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -1
- package/README.md +39 -6
- package/dist/{accordion-BRWh3BM1.js → accordion-B-5Focs8.js} +2 -2
- package/dist/{accordion-BRWh3BM1.js.map → accordion-B-5Focs8.js.map} +1 -1
- package/dist/{affordance-geometry.generated-DXl-Zb3Z.js → affordance-geometry.generated-D8Jcfb0G.js} +10 -1
- package/dist/affordance-geometry.generated-D8Jcfb0G.js.map +1 -0
- package/dist/{ai-artifact-DkOArd3-.js → ai-artifact-B5tPpXnS.js} +5 -5
- package/dist/{ai-artifact-DkOArd3-.js.map → ai-artifact-B5tPpXnS.js.map} +1 -1
- package/dist/{app-shell-BH_bGcE2.js → app-shell-at8Kt4l_.js} +3 -3
- package/dist/{app-shell-BH_bGcE2.js.map → app-shell-at8Kt4l_.js.map} +1 -1
- package/dist/{attachment-DsLgNHXt.js → attachment-BO2R47wn.js} +2 -2
- package/dist/{attachment-DsLgNHXt.js.map → attachment-BO2R47wn.js.map} +1 -1
- package/dist/{auth-BDB1V_2q.js → auth-OFwWSAap.js} +4 -4
- package/dist/{auth-BDB1V_2q.js.map → auth-OFwWSAap.js.map} +1 -1
- package/dist/{avatar-CsnWaYmt.js → avatar-Bpzty83c.js} +73 -68
- package/dist/avatar-Bpzty83c.js.map +1 -0
- package/dist/base/accordion.js +1 -1
- package/dist/base/attachment.js +1 -1
- package/dist/base/avatar.js +1 -1
- package/dist/base/cards.js +2 -2
- package/dist/base/cascader.js +6 -6
- package/dist/base/choice-inputs.js +3 -2
- package/dist/base/code-block.js +1 -1
- package/dist/base/combobox.js +1 -1
- package/dist/base/command.js +1 -1
- package/dist/base/date-pickers.js +2 -2
- package/dist/base/display.js +1 -1
- package/dist/base/feedback.js +4 -3
- package/dist/base/forms-numeric.js +1 -1
- package/dist/base/forms.js +2 -2
- package/dist/base/inline-edit.js +236 -0
- package/dist/base/inline-edit.js.map +1 -0
- package/dist/base/media-gallery.js +1 -1
- package/dist/base/navigation.js +3 -3
- package/dist/base/popover-menu.js +1 -1
- package/dist/base/popover.js +1 -1
- package/dist/base/repeaters.js +4 -4
- package/dist/base/sheet.js +1 -1
- package/dist/base/sidebar.js +1 -1
- package/dist/base/signature-pad.js +3 -3
- package/dist/base/skeleton.js +1 -1
- package/dist/base/spinner.js +1 -1
- package/dist/base/structure.js +1 -1
- package/dist/base/text-inputs.js +2 -2
- package/dist/base/tree.js +1 -1
- package/dist/base/upload-queue.js +1 -1
- package/dist/base/upload.js +3 -3
- package/dist/base/value-inputs.js +1 -1
- package/dist/blocks/admin.js +6 -6
- package/dist/blocks/ai.js +6 -6
- package/dist/blocks/analytics/usage-card.js +3 -0
- package/dist/blocks/analytics.js +361 -360
- package/dist/blocks/analytics.js.map +1 -1
- package/dist/blocks/collaboration.js +1 -1
- package/dist/blocks/commerce/receipt.js +1 -1
- package/dist/blocks/commerce.js +15 -15
- package/dist/blocks/data-display.js +1 -1
- package/dist/blocks/onboarding.js +3 -3
- package/dist/blocks/planning.js +311 -0
- package/dist/blocks/planning.js.map +1 -0
- package/dist/blocks/timelines.js +1 -1
- package/dist/{cards-BMB0laLG.js → cards-Cum56XOF.js} +3 -3
- package/dist/{cards-BMB0laLG.js.map → cards-Cum56XOF.js.map} +1 -1
- package/dist/checkbox-BavDRjF7.js +34 -0
- package/dist/checkbox-BavDRjF7.js.map +1 -0
- package/dist/{choice-inputs-CavA-SGV.js → choice-inputs-CC7XqPQO.js} +166 -191
- package/dist/choice-inputs-CC7XqPQO.js.map +1 -0
- package/dist/{code-block-DIlqzoiD.js → code-block-xHKbAJDj.js} +2 -2
- package/dist/{code-block-DIlqzoiD.js.map → code-block-xHKbAJDj.js.map} +1 -1
- package/dist/{combobox-HDftpzxt.js → combobox-DEbdr-3u.js} +2 -2
- package/dist/{combobox-HDftpzxt.js.map → combobox-DEbdr-3u.js.map} +1 -1
- package/dist/{combobox-CHD750do.js → combobox-xv3yJbzl.js} +7 -7
- package/dist/{combobox-CHD750do.js.map → combobox-xv3yJbzl.js.map} +1 -1
- package/dist/{command-D0kWBZfd.js → command-C0xe7u9B.js} +2 -2
- package/dist/{command-D0kWBZfd.js.map → command-C0xe7u9B.js.map} +1 -1
- package/dist/{comments-CvYeglMS.js → comments-DviPLnwy.js} +8 -8
- package/dist/{comments-CvYeglMS.js.map → comments-DviPLnwy.js.map} +1 -1
- package/dist/components/base/cards/smart-card.types.d.ts +2 -0
- package/dist/components/base/inline-edit/index.d.ts +2 -0
- package/dist/components/base/inline-edit/inline-edit.d.ts +81 -0
- package/dist/components/base/inline-edit/inline-edit.recipes.d.ts +6 -0
- package/dist/components/base/structure/structure.recipes.d.ts +6 -0
- package/dist/components/blocks/analytics/index.d.ts +1 -0
- package/dist/components/blocks/analytics/usage-card/index.d.ts +3 -0
- package/dist/components/blocks/analytics/usage-card/types.d.ts +52 -0
- package/dist/components/blocks/analytics/usage-card/usage-card.d.ts +6 -0
- package/dist/components/blocks/analytics/usage-card/usage-card.data.d.ts +19 -0
- package/dist/components/blocks/analytics/usage-card/usage-card.strings.d.ts +22 -0
- package/dist/components/blocks/planning/index.d.ts +3 -0
- package/dist/components/blocks/planning/task-card.d.ts +3 -0
- package/dist/components/blocks/planning/task-card.strings.d.ts +15 -0
- package/dist/components/blocks/planning/task-card.types.d.ts +66 -0
- package/dist/{containers-CPNyNo1_.js → containers-6s-ucRQQ.js} +3 -2
- package/dist/containers-6s-ucRQQ.js.map +1 -0
- package/dist/{data-display-b3d1TfL4.js → data-display-cWIRdeaf.js} +8 -8
- package/dist/{data-display-b3d1TfL4.js.map → data-display-cWIRdeaf.js.map} +1 -1
- package/dist/{date-picker-DI6kPNof.js → date-picker-CA6ogMQB.js} +5 -5
- package/dist/{date-picker-DI6kPNof.js.map → date-picker-CA6ogMQB.js.map} +1 -1
- package/dist/{date-pickers-ByP6Cv2z.js → date-pickers-BPIKLdsD.js} +4 -4
- package/dist/{date-pickers-ByP6Cv2z.js.map → date-pickers-BPIKLdsD.js.map} +1 -1
- package/dist/{display-ByI5tkqy.js → display-CFM5myut.js} +3 -3
- package/dist/{display-ByI5tkqy.js.map → display-CFM5myut.js.map} +1 -1
- package/dist/features/activities.js +4 -4
- package/dist/features/ai-chat.js +4 -4
- package/dist/features/async-preview.js +4 -4
- package/dist/features/combobox.js +1 -1
- package/dist/features/comments.js +1 -1
- package/dist/features/data-view.js +5 -5
- package/dist/features/email-composer.js +11 -11
- package/dist/features/event-calendar.js +2 -2
- package/dist/features/event-calendar.js.map +1 -1
- package/dist/features/filters.js +1 -1
- package/dist/features/global-search.js +6 -6
- package/dist/features/map.js +3 -3
- package/dist/features/media-library.js +8 -8
- package/dist/features/mentions.js +1 -1
- package/dist/features/overlays.js +1 -1
- package/dist/features/products.js +43 -42
- package/dist/features/products.js.map +1 -1
- package/dist/features/questionnaire.js +107 -106
- package/dist/features/questionnaire.js.map +1 -1
- package/dist/features/resource-assignment.js +5 -5
- package/dist/features/resource.js +7 -7
- package/dist/features/rich-text-editor.js +1 -1
- package/dist/features/schema-form.js +11 -11
- package/dist/features/schema-form.js.map +1 -1
- package/dist/features/suggestions.js +2 -2
- package/dist/features/sync.js +2 -2
- package/dist/features/table.js +1 -1
- package/dist/features/theme-tweaker.js +11 -11
- package/dist/feedback-DWUkgEjR.js +233 -0
- package/dist/feedback-DWUkgEjR.js.map +1 -0
- package/dist/{field-affordance-recipes-CHdUtn2Z.js → field-affordance-recipes-IjzPrCTw.js} +2 -2
- package/dist/{field-affordance-recipes-CHdUtn2Z.js.map → field-affordance-recipes-IjzPrCTw.js.map} +1 -1
- package/dist/{filters-qtrcv1hf.js → filters-BhsUtsxW.js} +9 -9
- package/dist/{filters-qtrcv1hf.js.map → filters-BhsUtsxW.js.map} +1 -1
- package/dist/{form-field-C6W-y2hR.js → form-field-Bn7ZH1rA.js} +2 -2
- package/dist/{form-field-C6W-y2hR.js.map → form-field-Bn7ZH1rA.js.map} +1 -1
- package/dist/{form-sizing-39NohSqn.js → form-sizing-BWs70_a2.js} +4 -3
- package/dist/{form-sizing-39NohSqn.js.map → form-sizing-BWs70_a2.js.map} +1 -1
- package/dist/{forms-BF-HSbff.js → forms-B7pzS25o.js} +5 -5
- package/dist/{forms-BF-HSbff.js.map → forms-B7pzS25o.js.map} +1 -1
- package/dist/{forms-numeric-Y15Bg5Nr.js → forms-numeric-BUBxELBv.js} +18 -18
- package/dist/{forms-numeric-Y15Bg5Nr.js.map → forms-numeric-BUBxELBv.js.map} +1 -1
- package/dist/{header-B8ex54cY.js → header-C59QZyxV.js} +6 -6
- package/dist/{header-B8ex54cY.js.map → header-C59QZyxV.js.map} +1 -1
- package/dist/{hooks-D6UBVzf2.js → hooks-CM1Vp9Pe.js} +2 -2
- package/dist/{hooks-D6UBVzf2.js.map → hooks-CM1Vp9Pe.js.map} +1 -1
- package/dist/{input-DMorJiTI.js → input-WwYQZJzG.js} +3 -3
- package/dist/{input-DMorJiTI.js.map → input-WwYQZJzG.js.map} +1 -1
- package/dist/layout/app-shell.js +1 -1
- package/dist/layout/auth.js +1 -1
- package/dist/layout/containers.js +1 -1
- package/dist/layout/header.js +1 -1
- package/dist/layout/hooks.js +1 -1
- package/dist/layout/navigation.js +1 -1
- package/dist/layout/page.js +1 -1
- package/dist/layout/settings.js +1 -1
- package/dist/layout/sidebar.js +2 -2
- package/dist/layout/workspace.js +1 -1
- package/dist/layout.js +10 -10
- package/dist/lib/affordance-geometry.generated.d.ts +9 -0
- package/dist/{media-gallery-Ca4YmvBi.js → media-gallery-DhDRgnVZ.js} +2 -2
- package/dist/{media-gallery-Ca4YmvBi.js.map → media-gallery-DhDRgnVZ.js.map} +1 -1
- package/dist/{mentions-CRio8mTm.js → mentions-B8Ko-fhS.js} +4 -4
- package/dist/{mentions-CRio8mTm.js.map → mentions-B8Ko-fhS.js.map} +1 -1
- package/dist/{message-B5-6LeYl.js → message-COsdEvXT.js} +2 -2
- package/dist/{message-B5-6LeYl.js.map → message-COsdEvXT.js.map} +1 -1
- package/dist/{navigation-DqEITuD2.js → navigation-BUIn2Qxx.js} +2 -2
- package/dist/{navigation-DqEITuD2.js.map → navigation-BUIn2Qxx.js.map} +1 -1
- package/dist/{navigation-BTrEb5u4.js → navigation-DBEu4POU.js} +3 -3
- package/dist/{navigation-BTrEb5u4.js.map → navigation-DBEu4POU.js.map} +1 -1
- package/dist/{navigation-row-recipes-BFpvGPGE.js → navigation-row-recipes-GVh11KiT.js} +2 -2
- package/dist/{navigation-row-recipes-BFpvGPGE.js.map → navigation-row-recipes-GVh11KiT.js.map} +1 -1
- package/dist/{overlays-DBqV0J_V.js → overlays-B11wZka6.js} +4 -4
- package/dist/{overlays-DBqV0J_V.js.map → overlays-B11wZka6.js.map} +1 -1
- package/dist/{page-BMBGjJY8.js → page-BV6ioQg3.js} +5 -5
- package/dist/{page-BMBGjJY8.js.map → page-BV6ioQg3.js.map} +1 -1
- package/dist/{page-header-BH41wCyY.js → page-header-DQRa0jug.js} +2 -2
- package/dist/{page-header-BH41wCyY.js.map → page-header-DQRa0jug.js.map} +1 -1
- package/dist/{popover-TInGXsjg.js → popover-CoLDEg19.js} +2 -2
- package/dist/{popover-TInGXsjg.js.map → popover-CoLDEg19.js.map} +1 -1
- package/dist/{popover-menu-BzqXqE72.js → popover-menu-GLIQTJTx.js} +4 -4
- package/dist/{popover-menu-BzqXqE72.js.map → popover-menu-GLIQTJTx.js.map} +1 -1
- package/dist/progress-BOe8l_FZ.js +168 -0
- package/dist/progress-BOe8l_FZ.js.map +1 -0
- package/dist/{receipt-DYWTQGEc.js → receipt-BM8xsGFy.js} +3 -3
- package/dist/{receipt-DYWTQGEc.js.map → receipt-BM8xsGFy.js.map} +1 -1
- package/dist/{rich-text-editor-BZSJchAF.js → rich-text-editor-CoVrTiSv.js} +2 -2
- package/dist/{rich-text-editor-BZSJchAF.js.map → rich-text-editor-CoVrTiSv.js.map} +1 -1
- package/dist/services/actions.js +1 -1
- package/dist/{settings-D9f_wdIC.js → settings-BnCyyPOJ.js} +3 -3
- package/dist/{settings-D9f_wdIC.js.map → settings-BnCyyPOJ.js.map} +1 -1
- package/dist/{sheet-vOUloJ4O.js → sheet-B-q914Ad.js} +2 -2
- package/dist/{sheet-vOUloJ4O.js.map → sheet-B-q914Ad.js.map} +1 -1
- package/dist/{sidebar-C3UKkbC5.js → sidebar-C_2p3Tfj.js} +6 -6
- package/dist/{sidebar-C3UKkbC5.js.map → sidebar-C_2p3Tfj.js.map} +1 -1
- package/dist/{sidebar-CNBKFDRW.js → sidebar-DPR7fmqN.js} +4 -4
- package/dist/{sidebar-CNBKFDRW.js.map → sidebar-DPR7fmqN.js.map} +1 -1
- package/dist/{skeleton-BvRhK8y-.js → skeleton-CmT1ppqb.js} +2 -2
- package/dist/{skeleton-BvRhK8y-.js.map → skeleton-CmT1ppqb.js.map} +1 -1
- package/dist/{smart-card-B5FcKODh.js → smart-card-CYLn46e6.js} +119 -118
- package/dist/smart-card-CYLn46e6.js.map +1 -0
- package/dist/{spinner-JtlFJ7Nc.js → spinner-BY9HbUEh.js} +2 -2
- package/dist/{spinner-JtlFJ7Nc.js.map → spinner-BY9HbUEh.js.map} +1 -1
- package/dist/{structure-BvW4vG4H.js → structure-MvKVq-oL.js} +13 -13
- package/dist/{structure-BvW4vG4H.js.map → structure-MvKVq-oL.js.map} +1 -1
- package/dist/{structure.recipes-DjqnBpwz.js → structure.recipes-Bqhf9oIh.js} +2 -2
- package/dist/structure.recipes-Bqhf9oIh.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/{table-ghtAQ-JJ.js → table-BS39xnlq.js} +6 -6
- package/dist/{table-ghtAQ-JJ.js.map → table-BS39xnlq.js.map} +1 -1
- package/dist/{tabs-DY09eoZg.js → tabs-B5cNMWiP.js} +2 -2
- package/dist/{tabs-DY09eoZg.js.map → tabs-B5cNMWiP.js.map} +1 -1
- package/dist/{text-inputs-BhOcedTo.js → text-inputs-Dh9XGyiS.js} +4 -4
- package/dist/{text-inputs-BhOcedTo.js.map → text-inputs-Dh9XGyiS.js.map} +1 -1
- package/dist/{timelines-CnZ2S_Gr.js → timelines-B9ICkQcV.js} +2 -2
- package/dist/{timelines-CnZ2S_Gr.js.map → timelines-B9ICkQcV.js.map} +1 -1
- package/dist/{tree-COD9BGdU.js → tree-BdWSaoFT.js} +2 -2
- package/dist/{tree-COD9BGdU.js.map → tree-BdWSaoFT.js.map} +1 -1
- package/dist/{upload-DIzw1dx1.js → upload-D8Za5x_c.js} +4 -4
- package/dist/{upload-DIzw1dx1.js.map → upload-D8Za5x_c.js.map} +1 -1
- package/dist/{upload-queue-xRJ3qDmd.js → upload-queue-hIIYBLS7.js} +3 -3
- package/dist/{upload-queue-xRJ3qDmd.js.map → upload-queue-hIIYBLS7.js.map} +1 -1
- package/dist/usage-card-DnmnJ58D.js +458 -0
- package/dist/usage-card-DnmnJ58D.js.map +1 -0
- package/dist/{value-inputs-Co1fu-xv.js → value-inputs-DX2dbxjj.js} +7 -7
- package/dist/{value-inputs-Co1fu-xv.js.map → value-inputs-DX2dbxjj.js.map} +1 -1
- package/dist/{workspace-Cbqz4FzN.js → workspace-C1PZmk8L.js} +4 -4
- package/dist/{workspace-Cbqz4FzN.js.map → workspace-C1PZmk8L.js.map} +1 -1
- package/docs/consumer-skill/SKILL.md +120 -17
- package/docs/consumer-skill/examples/detail.tsx +7 -8
- package/docs/consumer-skill/examples/manifest.json +14 -0
- package/docs/consumer-skill/examples/planning-usage-edit.tsx +29 -0
- package/docs/consumer-skill/examples/scoped-directory.tsx +77 -0
- package/docs/consumer-skill/references/composition.md +103 -0
- package/docs/consumer-skill/references/design-contract.md +4 -3
- package/docs/consumer-skill/references/discovery.md +17 -0
- package/docs/consumer-skill/references/installation.md +20 -3
- package/docs/consumer-skill/references/integration.md +12 -0
- package/docs/consumer-skill/references/public-api.json +452 -229
- package/docs/consumer-skill/references/review.md +74 -0
- package/docs/consumer-skill/references/tokens.json +1 -1
- package/docs/consumer-skill/references/workflows.md +97 -1
- package/docs/consumer-skill/scripts/check-composition.mjs +31 -0
- package/docs/consumer-skill/scripts/composition-policy.mjs +155 -0
- package/docs/consumer-skill/scripts/discovery.mjs +6 -1
- package/docs/consumer-skill/scripts/surface-policy.mjs +22 -0
- package/package.json +24 -3
- package/dist/affordance-geometry.generated-DXl-Zb3Z.js.map +0 -1
- package/dist/avatar-CsnWaYmt.js.map +0 -1
- package/dist/choice-inputs-CavA-SGV.js.map +0 -1
- package/dist/containers-CPNyNo1_.js.map +0 -1
- package/dist/feedback-a9j9J-9A.js +0 -391
- package/dist/feedback-a9j9J-9A.js.map +0 -1
- package/dist/smart-card-B5FcKODh.js.map +0 -1
- package/dist/structure.recipes-DjqnBpwz.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Composition review and checks
|
|
2
|
+
|
|
3
|
+
Use after selecting owners, or when auditing a screen. Read only the relevant
|
|
4
|
+
workflow/declarations; this is a review aid, not a new design system.
|
|
5
|
+
|
|
6
|
+
## Review the task and hierarchy
|
|
7
|
+
|
|
8
|
+
- Can the user identify the subject, scope and next action? A contextual fact
|
|
9
|
+
may be repeated for a separate purpose, but should not compete with itself.
|
|
10
|
+
- Do independent records, facts, metrics and editable fields use appropriate
|
|
11
|
+
owners? Inspect an existing feature before reconstructing its lifecycle.
|
|
12
|
+
- Do primary commands have explicit emphasis and page/collection/record scope?
|
|
13
|
+
Keep bulk actions tied to selection. Secondary/destructive importance is semantic.
|
|
14
|
+
- Do rows lead with identity and useful decision information? Preserve access
|
|
15
|
+
to hidden facts without forcing every fact into a tall identity stack.
|
|
16
|
+
- Does each gutter, inset and sibling/copy gap have one owner? Review the whole
|
|
17
|
+
parent/child chain. A token-backed utility on the wrong owner can still drift.
|
|
18
|
+
- Does the same composition remain readable with long copy, translations,
|
|
19
|
+
empty/missing values and narrow parent width? Zero and unavailable differ.
|
|
20
|
+
|
|
21
|
+
These criteria allow different product layouts. Do not impose a universal card
|
|
22
|
+
count, row-line limit, primary button count or one-column form rule.
|
|
23
|
+
|
|
24
|
+
## Optional source checker
|
|
25
|
+
|
|
26
|
+
The package ships a read-only script, not an npm bin/global CLI. It needs the
|
|
27
|
+
consumer's TypeScript development dependency and Node matching the package engine:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node node_modules/themelia-react-ui/docs/consumer-skill/scripts/check-composition.mjs resources/js/pages resources/js/components
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Pass explicit files/directories; `.tsx`, `.ts`, `.jsx` and `.js` are inspected.
|
|
34
|
+
It resolves TypeScript from the app working directory and uses its adjacent
|
|
35
|
+
versioned public index. A complete copied skill works too, after the installed
|
|
36
|
+
identity check. It neither edits files nor installs dependencies. Add this
|
|
37
|
+
command to app CI if useful; repository lint is not automatically installed.
|
|
38
|
+
|
|
39
|
+
Errors cover unknown package entrypoints/symbols and statically detected overrides
|
|
40
|
+
of protected popup insets or control geometry/typography. Deprecated migration
|
|
41
|
+
owners, nested framed subjects and repeated literal headings produce review
|
|
42
|
+
warnings; warnings alone do not fail CI. Messages identify the owner and remedy.
|
|
43
|
+
Use existing app lint/typechecking for wider language, utility and accessibility
|
|
44
|
+
coverage. The script is deliberately small and does not replace them.
|
|
45
|
+
|
|
46
|
+
Coverage is limited to direct public imports, aliases/namespaces, and same-file
|
|
47
|
+
static classes/styles/props. Dynamic expressions are reported as unresolved;
|
|
48
|
+
cross-file wrappers, external CSS, render functions and runtime values require
|
|
49
|
+
review. Do not treat an empty report as proof of complete compliance or taste.
|
|
50
|
+
|
|
51
|
+
## Rendered and interaction evidence
|
|
52
|
+
|
|
53
|
+
Source checks establish imports and known ownership violations. Separately inspect
|
|
54
|
+
actual rectangles, wrapping, shared insets, title/action alignment and information
|
|
55
|
+
weight at supported parent widths and light/dark scopes. Open relevant menus and
|
|
56
|
+
overlays; a closed trigger cannot prove portal theming or popup geometry.
|
|
57
|
+
|
|
58
|
+
Execute affected callbacks, keyboard/focus, cancellation, validation, pending
|
|
59
|
+
and failure/retry states. Use deferred requests to observe pending behavior.
|
|
60
|
+
Confirm accepted data changes and draft retention; a toast is only feedback.
|
|
61
|
+
Compare before/after at the same conditions and record unverified limits.
|
|
62
|
+
|
|
63
|
+
## Application policy
|
|
64
|
+
|
|
65
|
+
Keep a small app-owned reference linked from its agent instructions: name the
|
|
66
|
+
shell/header/gutter owners, routing/query/form adapters, scope semantics,
|
|
67
|
+
information priorities and relevant local tests. Link current representative
|
|
68
|
+
pages. Preserve permissions, transport, translations and product-specific forms.
|
|
69
|
+
Do not copy the package API manual or edit the packaged skill to encode local policy.
|
|
70
|
+
|
|
71
|
+
Package declarations define supported props; this skill defines shared composition
|
|
72
|
+
and anatomy; the app defines business meaning and page conventions. If a shared
|
|
73
|
+
owner fails in a valid composition, isolate a reproduction and report the owning
|
|
74
|
+
contract. Do not spread private selectors or copy the control to hide the issue.
|
|
@@ -8,7 +8,7 @@ The [example manifest](../examples/manifest.json) owns filenames, exported compo
|
|
|
8
8
|
|
|
9
9
|
[AccountIndex](../examples/index-table.tsx) exports `AccountIndex`, `AccountIndexProps`, `AccountRow` and `AccountIndexState`. Props are `state`, `onRetry`, `hrefFor(record)` and `onNavigate(href)`. The app drives the discriminated state: `loading`, `error` with a message, or `ready` with records. An empty ready collection renders the table's empty state. `onRetry` starts your loader; it must update app state through loading to ready/error. Stable row IDs belong to the records.
|
|
10
10
|
|
|
11
|
-
This recipe uses `themelia-react-ui/features/table`, so install `@tanstack/react-table` as an optional peer. Card frames
|
|
11
|
+
This recipe uses `themelia-react-ui/features/table`, so install `@tanstack/react-table` as an optional peer. Card frames an independent Accounts subject; the nested table uses `surface="flat"`. For a primary route collection use its own native table surface, as in the scoped directory below. A row action calls your navigation callback with your URL; `href` alone on a table action does not navigate. Supply your router callback or ordinary browser navigation. The library neither requests records nor chooses routes.
|
|
12
12
|
|
|
13
13
|
Verify loading, empty refresh, failure/retry and ready records. Open the row menu with pointer and keyboard, activate Open account, and assert the requested URL. Check narrow overflow and email-link behavior. If you need a presentational table without TanStack features, discover `themelia-react-ui/base/table` and inspect its separate declaration.
|
|
14
14
|
|
|
@@ -18,6 +18,51 @@ Verify loading, empty refresh, failure/retry and ready records. Open the row men
|
|
|
18
18
|
|
|
19
19
|
No optional peers are needed. The app supplies the loaded record and owns navigation, locale/provider defaults and permissions. Verify missing email/date/balance, a real minor-unit amount, Edit/Back callbacks, and the one-column mobile/two-column desktop composition in light/dark themes.
|
|
20
20
|
|
|
21
|
+
## Scoped directory
|
|
22
|
+
|
|
23
|
+
[ScopedDirectory](../examples/scoped-directory.tsx) demonstrates a primary route
|
|
24
|
+
collection. Page owns Contacts, the fixed business context and the explicit
|
|
25
|
+
primary New contact command. DataTable owns the surface; FilterLayout occupies
|
|
26
|
+
`topbarContent`, optional export occupies `topbarEnd`, and Open contact is a row
|
|
27
|
+
action. ResourceCell owns identity, shared small secondary Text supplies role,
|
|
28
|
+
and Email owns formatting. Other operational facts belong in the contact detail
|
|
29
|
+
reached by `onOpen`; the example does not append every fact to compact identity.
|
|
30
|
+
|
|
31
|
+
The optional `compact` prop is an app presentation decision from available parent
|
|
32
|
+
width. It keeps email reach in the identity cell and omits the comparison Email
|
|
33
|
+
column, preserving one copy and reachable row actions. This deliberately retains
|
|
34
|
+
one task-critical fact; other metadata stays in detail. Choose the threshold
|
|
35
|
+
from actual content and work-region measurements, not a universal library setting.
|
|
36
|
+
|
|
37
|
+
The app supplies `scope`, discriminated `state`, controlled `filters`, navigation
|
|
38
|
+
and loading callbacks. `onQueryChange({ scopeId, filters })` preserves fixed
|
|
39
|
+
business context even when criteria clear. It does not request records itself.
|
|
40
|
+
The recipe removes a blank `q` search criterion without normalizing unrelated
|
|
41
|
+
filter types. Native search can emit an empty string; the app owns query meaning.
|
|
42
|
+
The app updates filters/state, ignores stale responses and preserves scope/query
|
|
43
|
+
in pagination URLs. A criteria-driven business selector would be a different
|
|
44
|
+
product contract. The recipe's English labels are example copy: adapt labels,
|
|
45
|
+
criteria and component `strings` for app translation and permissions.
|
|
46
|
+
|
|
47
|
+
Strict imported declarations require the manifest's optional peers. DataTable
|
|
48
|
+
sorting is disabled because the app's server owns query order. Loading/error
|
|
49
|
+
feedback occupies the empty result region while query context remains mounted;
|
|
50
|
+
accepted records replace it. Use the public loading/preview owner if retaining
|
|
51
|
+
previous results is required by your app. Avoid stale pagination metadata during
|
|
52
|
+
loading/errors. Run the same query with loading, failure, retry and empty results.
|
|
53
|
+
|
|
54
|
+
Verify one contextual lead, primary command, query/clear scope retention,
|
|
55
|
+
row-menu navigation, server page callbacks and long-name/mobile containment.
|
|
56
|
+
Opening a record preserves access to supporting facts. Integrated queries and
|
|
57
|
+
separate query regions are both supported; choose by task and keep one owner.
|
|
58
|
+
|
|
59
|
+
Keep DataTable column/action definitions stable when unrelated parent feedback
|
|
60
|
+
changes. Changing a cell renderer's identity can remount its menu trigger and
|
|
61
|
+
lose return focus. This recipe memoizes row actions by `onOpen`; the app keeps
|
|
62
|
+
that callback stable when its meaning is unchanged. Stabilize these lifecycle
|
|
63
|
+
inputs deliberately, not every object. A real record/scope change can replace
|
|
64
|
+
definitions; the app then chooses appropriate destination focus.
|
|
65
|
+
|
|
21
66
|
## Validated editing
|
|
22
67
|
|
|
23
68
|
[AccountEdit](../examples/edit.tsx) exports `AccountEdit`, `AccountEditProps` and `AccountEditValue`. Supply `initial`, `onSave(value): Promise<AccountEditValue>` and `onCancel()`. Mount with `key={initial.id}` when switching records. The draft is local to that mount; resetting the same record's draft deliberately also requires a new key or your own controlled adaptation.
|
|
@@ -37,3 +82,54 @@ Your delete callback owns persistence and record removal. Reject failures instea
|
|
|
37
82
|
Verify open/title/description, trapped keyboard focus, Cancel/Escape without a deletion callback, pending dismissal prevention, rejected callback with an open error, retry and successful closure, exactly one backend effect, and trigger focus restoration. Check the opened error dialog in dark/mobile as well as light/desktop. Do not infer these interactions from typechecking or screenshots.
|
|
38
83
|
|
|
39
84
|
The existing [AccountComposition](../examples/composition.tsx) remains a basic Card/ActionDialog composition with `save(name): Promise<void>`. Its callback must reject on failure; the app owns persistence. The four recipes above add progressive collection/detail/edit/delete ownership without changing that example's contract.
|
|
85
|
+
|
|
86
|
+
## Task summaries, usage and inline editing
|
|
87
|
+
|
|
88
|
+
[Workspace summary](../examples/planning-usage-edit.tsx) composes three focused
|
|
89
|
+
owners. `TaskCard` from `themelia-react-ui/blocks/planning` owns the explicit Card
|
|
90
|
+
surface, completion summary, disclosure and controlled subtask proposals.
|
|
91
|
+
Update `items` after `onItemCompletedChange`; supply each item's `busy` or
|
|
92
|
+
`error` from your persistence lifecycle. Workflow status, due-date formatting,
|
|
93
|
+
assignment and Open navigation remain application data/callbacks.
|
|
94
|
+
|
|
95
|
+
Click passive header content or use the native caret (pointer, Enter or Space)
|
|
96
|
+
to request expansion. Both respect `expanded`/`onExpandedChange` and `disabled`;
|
|
97
|
+
header clicks focus the caret. Subtask and footer actions remain independent.
|
|
98
|
+
The block owns the segmented progress drawing, completion strikethrough and
|
|
99
|
+
compact checklist insets. Its progress value follows accepted `items`, including
|
|
100
|
+
undo; workflow status stays separate. Reduced motion disables the ring animation.
|
|
101
|
+
|
|
102
|
+
`UsageCard` from `themelia-react-ui/blocks/analytics/usage-card` is a standalone
|
|
103
|
+
quota/analytics composition with no optional chart dependency. Supply matching
|
|
104
|
+
`data`, `period` and `status` after `onPeriodChange`; cancel or ignore stale
|
|
105
|
+
application requests. `limit: null` means unlimited, omitted limits or values
|
|
106
|
+
mean unavailable, and zero is real data. Category percentages describe shares
|
|
107
|
+
of reported usage; bounded graphics never hide actual over-quota amounts.
|
|
108
|
+
Daily data is chronological application data with text equivalents. The block
|
|
109
|
+
owns no subscription or billing requests; `footerAction` is application content.
|
|
110
|
+
|
|
111
|
+
`InlineEdit` from `themelia-react-ui/base/inline-edit` uses our native Input or
|
|
112
|
+
Textarea. Supply `onSave(value, { signal })` for asynchronous persistence and
|
|
113
|
+
update a controlled saved value through `onValueChange`. Required/validation
|
|
114
|
+
checks apply when saving a draft. Parent forms serialize the committed value
|
|
115
|
+
through `name`; they own validation of their complete stored data. Enter saves
|
|
116
|
+
a single line, Control/Command Enter saves multiline text, and Escape cancels.
|
|
117
|
+
Cancellation invalidates late UI results and aborts the supplied signal; the
|
|
118
|
+
application owns actual transport and server effects. Read-only display stays
|
|
119
|
+
passive. Use Card at a composition boundary for a framed field section.
|
|
120
|
+
|
|
121
|
+
Display and editing share the Input frame and `size`, `invalid`, `loading`,
|
|
122
|
+
`clearable`, `onClear` and `showCharacterCount` APIs. The stable field/action
|
|
123
|
+
grid transitions width and honors reduced motion; multiline actions form a
|
|
124
|
+
centered column. Use `inputProps`/`textareaProps` for native editor attributes,
|
|
125
|
+
adornments, resizing and control-specific strings. State/value/ref/form ownership
|
|
126
|
+
stays with InlineEdit. Native constraints run on Save; uncommitted controls are
|
|
127
|
+
isolated from the parent form. External loading retains a draft while disabling
|
|
128
|
+
edits/save, and leaves cancellation available.
|
|
129
|
+
|
|
130
|
+
The field/action gap uses the existing `lg` spacing step; the action group uses
|
|
131
|
+
`xs`. Single-line actions form a horizontal pair and multiline actions a centered
|
|
132
|
+
column. Keep these library-owned controls and geometry instead of replacing
|
|
133
|
+
them with app-styled fields. Verify cancellation during persistence, retained
|
|
134
|
+
drafts after rejection, native constraints, committed FormData, controlled
|
|
135
|
+
expansion, checkbox/Open independence and async period retry in your app.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { validateIndex } from './discovery.mjs';
|
|
5
|
+
import { analyzeComposition } from './composition-policy.mjs';
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const targets = process.argv.slice(2);
|
|
9
|
+
if (!targets.length || targets.some(target => target.startsWith('-'))) throw new Error('Pass explicit source files/directories; no flags or automatic app-wide scan.');
|
|
10
|
+
const requireFromApp = createRequire(path.join(process.cwd(), 'package.json'));
|
|
11
|
+
const ts = requireFromApp('typescript');
|
|
12
|
+
const index = validateIndex(JSON.parse(fs.readFileSync(new URL('../references/public-api.json', import.meta.url), 'utf8')));
|
|
13
|
+
const files = new Set();
|
|
14
|
+
function collect(target) {
|
|
15
|
+
const info = fs.lstatSync(target);
|
|
16
|
+
if (info.isSymbolicLink()) throw new Error(`Use a real source path instead of a symlink: ${target}`);
|
|
17
|
+
if (info.isDirectory()) {
|
|
18
|
+
for (const entry of fs.readdirSync(target).sort()) if (!['node_modules', '.git', 'dist', 'build'].includes(entry)) collect(path.join(target, entry));
|
|
19
|
+
} else if (/\.[jt]sx?$/.test(target) && !/\.d\.ts$/.test(target)) files.add(path.resolve(target));
|
|
20
|
+
}
|
|
21
|
+
targets.forEach(collect);
|
|
22
|
+
if (!files.size) throw new Error('No supported source files found in the supplied paths.');
|
|
23
|
+
const reports = [...files].sort().map(filename => analyzeComposition(fs.readFileSync(filename, 'utf8'), { filename, index, ts }));
|
|
24
|
+
const findings = reports.flatMap(report => report.findings);
|
|
25
|
+
const errors = findings.filter(finding => finding.severity === 'error').length;
|
|
26
|
+
process.stdout.write(JSON.stringify({ package: index.package, version: index.version, files: files.size, errors, warnings: findings.length - errors, findings, unresolved: reports.flatMap(report => report.unresolved), limits: 'Direct public imports and same-file static JSX only; external CSS, cross-file wrappers and dynamic values require review.' }, null, 2) + '\n');
|
|
27
|
+
process.exitCode = errors ? 1 : 0;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
process.stderr.write(`check-composition: ${error.message}\nRun from the consumer app with its TypeScript development dependency installed.\n`);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { surfaceParts, surfaceForwarders, paddingClass, paddingProperty, utility } from './surface-policy.mjs';
|
|
2
|
+
|
|
3
|
+
const controlNames = /^(?:Base)?(?:Button|ButtonLink|IconButton)$|^(Input|InputGroupInput|Textarea|SelectTrigger|ComboboxTrigger)$/;
|
|
4
|
+
const controlFamilies = new Set(['buttons', 'text-inputs', 'choice-inputs', 'combobox']);
|
|
5
|
+
const fixedControlClass = /^(?:h|min-h|size|p[xytrblse]?)-|^rounded-(?:xs|sm|md|lg|xl|[2-9]xl|\[)|^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[(?:length:|[\d.]+(?:px|rem|em)))|^(?:font|leading|tracking)-/;
|
|
6
|
+
const controlStyle = /^(height|minHeight|fontSize|fontWeight|lineHeight|letterSpacing|borderRadius)$/;
|
|
7
|
+
|
|
8
|
+
/** Small static review: TypeScript is supplied by the app, never a runtime peer. */
|
|
9
|
+
export function analyzeComposition(text, { filename, index, ts }) {
|
|
10
|
+
const kind = /\.jsx?$/.test(filename) ? ts.ScriptKind.JSX : filename.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
11
|
+
const source = ts.createSourceFile(filename, text, ts.ScriptTarget.Latest, true, kind);
|
|
12
|
+
const families = new Map(index.families.map(owner => [owner.import, owner]));
|
|
13
|
+
const bindings = new Map();
|
|
14
|
+
const namespaces = new Map();
|
|
15
|
+
const declarations = new Map();
|
|
16
|
+
const findings = [];
|
|
17
|
+
const unresolved = [];
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const add = (node, rule, severity, message) => {
|
|
20
|
+
const position = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
21
|
+
const key = `${node.pos}:${rule}`;
|
|
22
|
+
if (seen.has(key)) return;
|
|
23
|
+
seen.add(key);
|
|
24
|
+
findings.push({ file: filename, line: position.line + 1, column: position.character + 1, rule, severity, message });
|
|
25
|
+
};
|
|
26
|
+
const unknown = node => {
|
|
27
|
+
const key = `${node.pos}:unresolved`;
|
|
28
|
+
if (seen.has(key)) return;
|
|
29
|
+
seen.add(key);
|
|
30
|
+
const position = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
31
|
+
unresolved.push({ file: filename, line: position.line + 1, expression: node.getText(source).slice(0, 120) });
|
|
32
|
+
};
|
|
33
|
+
const resolve = (node, visiting = new Set()) => {
|
|
34
|
+
if (!node || visiting.has(node)) return undefined;
|
|
35
|
+
visiting.add(node);
|
|
36
|
+
if (ts.isIdentifier(node)) return resolve(declarations.get(node.text), visiting);
|
|
37
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) return resolve(node.expression, visiting);
|
|
38
|
+
return node;
|
|
39
|
+
};
|
|
40
|
+
const properties = (expression, mark = true, visited = new Set()) => {
|
|
41
|
+
const node = resolve(expression);
|
|
42
|
+
if (!node || !ts.isObjectLiteralExpression(node) || visited.has(node)) {
|
|
43
|
+
if (mark && expression) unknown(expression);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
visited.add(node);
|
|
47
|
+
return node.properties.flatMap(property => {
|
|
48
|
+
if (ts.isSpreadAssignment(property)) return properties(property.expression, mark, visited);
|
|
49
|
+
const name = property.name;
|
|
50
|
+
const key = ts.isComputedPropertyName(name) ? resolve(name.expression) : name;
|
|
51
|
+
if (!key || !(ts.isIdentifier(key) || ts.isStringLiteralLike(key))) {
|
|
52
|
+
if (mark) unknown(property);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return [[key.text, ts.isShorthandPropertyAssignment(property) ? property.name : property.initializer]];
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const classStrings = (expression, visited = new Set()) => {
|
|
59
|
+
const node = resolve(expression);
|
|
60
|
+
if (!node || visited.has(node)) { if (expression) unknown(expression); return []; }
|
|
61
|
+
visited.add(node);
|
|
62
|
+
if (ts.isStringLiteralLike(node)) return [node.text];
|
|
63
|
+
if (ts.isConditionalExpression(node)) return [...classStrings(node.whenTrue, visited), ...classStrings(node.whenFalse, visited)];
|
|
64
|
+
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) return classStrings(node.right, visited);
|
|
65
|
+
if (ts.isArrayLiteralExpression(node)) return node.elements.flatMap(item => classStrings(item, visited));
|
|
66
|
+
if (ts.isCallExpression(node) && /^(cn|clsx|classNames|twMerge)$/.test(node.expression.getText(source))) return node.arguments.flatMap(item => classStrings(item, visited));
|
|
67
|
+
if ([ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.NullKeyword].includes(node.kind)) return [];
|
|
68
|
+
unknown(expression);
|
|
69
|
+
return [];
|
|
70
|
+
};
|
|
71
|
+
const literal = expression => {
|
|
72
|
+
const node = resolve(expression);
|
|
73
|
+
return node && ts.isStringLiteralLike(node) ? node.text : undefined;
|
|
74
|
+
};
|
|
75
|
+
function importOwner(node, specifier) {
|
|
76
|
+
if (specifier !== index.package && !specifier.startsWith(index.package + '/')) return;
|
|
77
|
+
if (index.stylesheets?.includes(specifier)) return;
|
|
78
|
+
const family = families.get(specifier);
|
|
79
|
+
if (!family) { add(node, 'public-import', 'error', `No published owner for ${specifier}; discover the version-matched public entrypoint.`); return; }
|
|
80
|
+
const names = ts.isImportDeclaration(node) ? node.importClause?.namedBindings : node.exportClause;
|
|
81
|
+
if (ts.isImportDeclaration(node) && node.importClause?.name && !family.symbols.includes('default')) add(node.importClause.name, 'public-symbol', 'error', `${specifier} has no default export; use its named public symbols.`);
|
|
82
|
+
const elements = names && (ts.isNamedImports(names) || ts.isNamedExports(names)) ? names.elements : [];
|
|
83
|
+
for (const item of elements) {
|
|
84
|
+
const symbol = (item.propertyName ?? item.name).text;
|
|
85
|
+
if (!family.symbols.includes(symbol)) add(item, 'public-symbol', 'error', `${specifier} does not export ${symbol}; inspect its declaration.`);
|
|
86
|
+
if (ts.isImportDeclaration(node)) bindings.set(item.name.text, { symbol, import: specifier });
|
|
87
|
+
}
|
|
88
|
+
if (names && ts.isNamespaceImport(names)) namespaces.set(names.name.text, specifier);
|
|
89
|
+
}
|
|
90
|
+
function gather(node) {
|
|
91
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) declarations.set(node.name.text, node.initializer);
|
|
92
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) importOwner(node, node.moduleSpecifier.text);
|
|
93
|
+
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteralLike(node.argument.literal)) importOwner(node, node.argument.literal.text);
|
|
94
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && ts.isStringLiteralLike(node.arguments[0])) importOwner(node, node.arguments[0].text);
|
|
95
|
+
ts.forEachChild(node, gather);
|
|
96
|
+
}
|
|
97
|
+
gather(source);
|
|
98
|
+
for (const diagnostic of source.parseDiagnostics) add(source, 'syntax', 'error', ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'));
|
|
99
|
+
const ownerFor = tag => {
|
|
100
|
+
if (bindings.has(tag)) return bindings.get(tag);
|
|
101
|
+
const [namespace, symbol] = tag.split('.');
|
|
102
|
+
const specifier = namespaces.get(namespace);
|
|
103
|
+
return specifier && symbol ? { symbol, import: specifier } : undefined;
|
|
104
|
+
};
|
|
105
|
+
const attributes = node => node.attributes.properties.flatMap(attribute => {
|
|
106
|
+
if (ts.isJsxSpreadAttribute(attribute)) return properties(attribute.expression);
|
|
107
|
+
if (!attribute.initializer) return [[attribute.name.getText(source), undefined]];
|
|
108
|
+
const expression = ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
|
|
109
|
+
return [[attribute.name.getText(source), expression]];
|
|
110
|
+
});
|
|
111
|
+
function visit(node, parents = []) {
|
|
112
|
+
if (ts.isJsxElement(node)) {
|
|
113
|
+
const element = node.openingElement;
|
|
114
|
+
const context = inspect(element, parents);
|
|
115
|
+
for (const child of node.children) visit(child, context ? [...parents, context] : parents);
|
|
116
|
+
// JSX in props is a separate region, not a child frame.
|
|
117
|
+
for (const attribute of element.attributes.properties) ts.forEachChild(attribute, child => visit(child, parents));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (ts.isJsxSelfClosingElement(node)) inspect(node, parents);
|
|
121
|
+
ts.forEachChild(node, child => visit(child, parents));
|
|
122
|
+
}
|
|
123
|
+
function inspect(node, parents) {
|
|
124
|
+
const owner = ownerFor(node.tagName.getText(source));
|
|
125
|
+
if (!owner) return;
|
|
126
|
+
if (!families.get(owner.import)?.symbols.includes(owner.symbol)) {
|
|
127
|
+
add(node, 'public-symbol', 'error', `${owner.import} does not export ${owner.symbol}.`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const props = new Map(attributes(node));
|
|
131
|
+
const family = owner.import.split('/base/')[1];
|
|
132
|
+
const anatomy = surfaceParts[family]?.test(owner.symbol);
|
|
133
|
+
const forwarder = surfaceForwarders[family] === owner.symbol;
|
|
134
|
+
const control = controlFamilies.has(family) && controlNames.test(owner.symbol);
|
|
135
|
+
for (const name of ['className', 'contentClassName']) {
|
|
136
|
+
if (!props.has(name)) continue;
|
|
137
|
+
const classes = classStrings(props.get(name)).flatMap(value => value.split(/\s+/)).map(utility);
|
|
138
|
+
if ((anatomy || forwarder && name === 'contentClassName') && classes.some(value => paddingClass.test(value))) add(node, 'surface-padding', 'error', `${owner.symbol}: use inset/type/density and public regions; padding overrides replace owned anatomy.`);
|
|
139
|
+
if (control && name === 'className' && classes.some(value => fixedControlClass.test(value))) add(node, 'control-anatomy', 'error', `${owner.symbol}: use its size/variant props; preserve shared control height, inset, corners and typography.`);
|
|
140
|
+
}
|
|
141
|
+
if (props.has('style')) for (const [name] of properties(props.get('style'))) {
|
|
142
|
+
if (anatomy && paddingProperty.test(name)) add(node, 'surface-padding', 'error', `${owner.symbol}: ${name} replaces the region inset; use its public surface props.`);
|
|
143
|
+
if (control && (paddingProperty.test(name) || controlStyle.test(name))) add(node, 'control-anatomy', 'error', `${owner.symbol}: ${name} belongs to shared control geometry/typography.`);
|
|
144
|
+
}
|
|
145
|
+
if (['SmartCard', 'ContentBlock'].includes(owner.symbol)) add(node, 'deprecated-owner', 'warning', `${owner.symbol} is a migration owner; prefer Card for new compositions.`);
|
|
146
|
+
const framed = ['Card', 'SmartCard', 'ContentBlock', 'DataTable', 'DataView'].includes(owner.symbol) && literal(props.get('surface')) !== 'flat';
|
|
147
|
+
if (framed && parents.some(parent => parent.framed)) add(node, 'nested-surface', 'warning', `${owner.symbol} is inside a framed owner. Review subject boundaries; use surface="flat" when the parent owns the frame.`);
|
|
148
|
+
const header = properties(props.get('header'), false);
|
|
149
|
+
const title = literal(props.get('title')) ?? literal(new Map(header).get('title'));
|
|
150
|
+
if (title && parents.some(parent => parent.title === title)) add(node, 'duplicate-heading', 'warning', `The heading "${title}" repeats its ancestor. Keep one route/subject title unless a distinct task warrants repetition.`);
|
|
151
|
+
return { framed, title };
|
|
152
|
+
}
|
|
153
|
+
visit(source);
|
|
154
|
+
return { findings, unresolved };
|
|
155
|
+
}
|
|
@@ -8,6 +8,7 @@ const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
|
8
8
|
export function validateIndex(index) {
|
|
9
9
|
requireFormat(index?.schemaVersion === 1 && index?.discoveryVersion === 1, 'unsupported schema/format; expected schemaVersion 1 and discoveryVersion 1');
|
|
10
10
|
requireFormat(text(index.package) && text(index.version) && Array.isArray(index.families), 'package, version and families are required');
|
|
11
|
+
if (index.stylesheets !== undefined) requireFormat(stringList(index.stylesheets) && index.stylesheets.every(file => file.startsWith(index.package + '/') && file.endsWith('.css') && !file.includes('..')), 'invalid public stylesheets');
|
|
11
12
|
const seen = new Set();
|
|
12
13
|
for (const family of index.families) {
|
|
13
14
|
requireFormat(text(family?.import) && (family.import === index.package || family.import.startsWith(`${index.package}/`)) && !/[\\\s]|(?:^|\/)\.\.(?:\/|$)/.test(family.import), 'invalid public import');
|
|
@@ -70,7 +71,11 @@ export function findComponents(index, { query, layer = null, limit = 5 }) {
|
|
|
70
71
|
// AND matching prevents a familiar term from inventing results for an
|
|
71
72
|
// otherwise unknown query. Avoidance/alternatives do not recommend owners.
|
|
72
73
|
if (!exactImport && !matchedSymbols.length && !terms.every(term => haystack.includes(term))) continue;
|
|
73
|
-
|
|
74
|
+
// A dedicated, documented owner precedes an aggregate re-export of the
|
|
75
|
+
// same symbol. Explicit import queries still select the requested family.
|
|
76
|
+
const documentedSymbol = family.guidance.some(guide => guide.sections.some(section => section.symbols.some(symbol => normalized(symbol) === needle)));
|
|
77
|
+
const exactIntent = family.aliases.some(alias => normalized(alias) === needle);
|
|
78
|
+
const score = exactImport ? 10000 : matchedSymbols.length ? 9000 + Number(documentedSymbol) : exactIntent ? 8000 : terms.reduce((sum, term) => sum + (family.guidance.some(guide => normalized(guide.title).includes(term)) ? 20 : family.tags.some(tag => normalized(tag).includes(term)) ? 10 : 1), 0);
|
|
74
79
|
const guidance = family.guidance.map(guide => ({ guide, score: terms.filter(term => guideText(guide).includes(term)).length })).sort((a, b) => b.score - a.score || compare(a.guide.title, b.guide.title)).slice(0, 3).map(({ guide }) => guide);
|
|
75
80
|
ranked.push({ score, import: family.import, declaration: family.declaration, layer: family.layer, responsibility: family.responsibility, optionalPeers: family.optionalPeers, match: exactImport ? 'import' : matchedSymbols.length ? 'symbol' : 'intent', matchedSymbols, guidance });
|
|
76
81
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// One public anatomy vocabulary, shared by repository and opt-in consumer checks.
|
|
2
|
+
export const surfaceParts = {
|
|
3
|
+
popover: /^(PopoverContent|PopoverHeader|PopoverFooter)$/,
|
|
4
|
+
command: /^Command(Input|List|Group|Item|Empty|Separator)?$/,
|
|
5
|
+
navigation: /^DropdownMenu(Content|SubContent|Label|Item|LinkItem|SubTrigger|CheckboxItem|RadioItem|Separator)$/,
|
|
6
|
+
'choice-inputs': /^SelectPopup(Content|Group|Item|Label|Separator)?$/,
|
|
7
|
+
combobox: /^Combobox(Popup|Content|List|Item|GroupLabel|Separator)$/,
|
|
8
|
+
};
|
|
9
|
+
export const surfaceForwarders = { 'choice-inputs': 'Select', 'popover-menu': 'PopoverMenu', 'action-menu': 'ActionMenu' };
|
|
10
|
+
export const paddingClass = /^(?:p[xytrblse]?|p(?:bs|be)|padding)-|^\[padding/;
|
|
11
|
+
export const paddingProperty = /^padding(?:Inline|Block)?(?:Start|End)?$|^padding(?:Top|Right|Bottom|Left)$|^padding-(?:(?:inline|block)(?:-(?:start|end))?|top|right|bottom|left)$/;
|
|
12
|
+
|
|
13
|
+
export function utility(token) {
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let start = 0;
|
|
16
|
+
for (let i = 0; i < token.length; i++) {
|
|
17
|
+
if (token[i] === '[' || token[i] === '(') depth++;
|
|
18
|
+
if (token[i] === ']' || token[i] === ')') depth--;
|
|
19
|
+
if (token[i] === ':' && depth === 0) start = i + 1;
|
|
20
|
+
}
|
|
21
|
+
return token.slice(start).replace(/^!|!$/g, '');
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "themelia-react-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A composable React UI library for modern admin applications, built on Base UI and Tailwind with shadcn-compatible themes, shared design tokens, reusable layouts, blocks and workflows.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|
|
8
8
|
},
|
|
9
|
-
"keywords": [
|
|
9
|
+
"keywords": [
|
|
10
|
+
"react",
|
|
11
|
+
"ui",
|
|
12
|
+
"admin",
|
|
13
|
+
"dashboard",
|
|
14
|
+
"base-ui",
|
|
15
|
+
"tailwindcss",
|
|
16
|
+
"shadcn",
|
|
17
|
+
"design-system"
|
|
18
|
+
],
|
|
10
19
|
"repository": {
|
|
11
20
|
"type": "git",
|
|
12
21
|
"url": "git+https://github.com/nicolasvlachos/themelia-react-ui.git"
|
|
@@ -413,6 +422,18 @@
|
|
|
413
422
|
"./features/questionnaire": {
|
|
414
423
|
"types": "./dist/components/features/questionnaire/index.d.ts",
|
|
415
424
|
"import": "./dist/features/questionnaire.js"
|
|
425
|
+
},
|
|
426
|
+
"./base/inline-edit": {
|
|
427
|
+
"types": "./dist/components/base/inline-edit/index.d.ts",
|
|
428
|
+
"import": "./dist/base/inline-edit.js"
|
|
429
|
+
},
|
|
430
|
+
"./blocks/planning": {
|
|
431
|
+
"types": "./dist/components/blocks/planning/index.d.ts",
|
|
432
|
+
"import": "./dist/blocks/planning.js"
|
|
433
|
+
},
|
|
434
|
+
"./blocks/analytics/usage-card": {
|
|
435
|
+
"types": "./dist/components/blocks/analytics/usage-card/index.d.ts",
|
|
436
|
+
"import": "./dist/blocks/analytics/usage-card.js"
|
|
416
437
|
}
|
|
417
438
|
},
|
|
418
439
|
"scripts": {
|
|
@@ -442,7 +463,7 @@
|
|
|
442
463
|
"check:maps": "node scripts/test-client-boundaries.mjs --built",
|
|
443
464
|
"build:docs": "node scripts/build-static-docs.mjs",
|
|
444
465
|
"check:static-docs": "node scripts/check-static-docs.mjs",
|
|
445
|
-
"check:agent": "node --test scripts/test-consumer-discovery.mjs scripts/test-consumer-skill-assets.mjs scripts/test-static-docs.mjs scripts/test-fixture-install.mjs && npm run check:skill",
|
|
466
|
+
"check:agent": "node --test scripts/test-consumer-discovery.mjs scripts/test-consumer-composition.mjs scripts/test-consumer-skill-assets.mjs scripts/test-static-docs.mjs scripts/test-fixture-install.mjs && npm run check:skill",
|
|
446
467
|
"check:release": "npm run check:all && npm run check:agent && npm run build:docs && npm run check:static-docs"
|
|
447
468
|
},
|
|
448
469
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"affordance-geometry.generated-DXl-Zb3Z.js","names":["affordanceGeometry","\"navigationGuideStart\"","\"clearEnd\"","\"sm\"","\"md\"","\"lg\"","\"clearPadding\"","\"togglePadding\"","\"largeIconStart\"","\"largeIconEnd\"","\"overlayClose\"","\"overlayHeader\""],"sources":["../src/lib/affordance-geometry.generated.ts"],"sourcesContent":["/* Generated by scripts/primitive-geometry.mjs; recipes, not theme roots. */\nexport const affordanceGeometry = {\n \"navigationGuideStart\": \"ms-[calc(var(--control-x)*0.8333333*var(--space-scale)+var(--icon)*0.75*var(--space-scale)/2)]\",\n \"clearEnd\": {\n \"sm\": \"end-[calc(var(--control-x)*0.8333333*var(--space-scale))]\",\n \"md\": \"end-[calc(var(--control-x)*var(--space-scale))]\",\n \"lg\": \"end-[calc(var(--control-x)*var(--space-scale))]\"\n },\n \"clearPadding\": {\n \"sm\": \"pe-[calc(var(--control-x)*0.8333333*var(--space-scale)+1.375rem*var(--density-scale)+0.375rem*var(--space-scale))]\",\n \"md\": \"pe-[calc(var(--control-x)*var(--space-scale)+1.375rem*var(--density-scale)+0.375rem*var(--space-scale))]\",\n \"lg\": \"pe-[calc(var(--control-x)*var(--space-scale)+1.375rem*var(--density-scale)+0.375rem*var(--space-scale))]\"\n },\n \"togglePadding\": \"pe-[calc(1.375rem*var(--density-scale)+0.625rem*var(--space-scale))]\",\n \"largeIconStart\": \"ps-[calc((var(--control-x)+var(--icon)*1.25+0.5rem)*var(--space-scale))]\",\n \"largeIconEnd\": \"pe-[calc((var(--control-x)+var(--icon)*1.25+0.5rem)*var(--space-scale))]\",\n \"overlayClose\": \"absolute top-[calc(var(--surface-y)*var(--space-scale))] end-[calc(var(--surface-x)*var(--space-scale))]\",\n \"overlayHeader\": \"[[data-slot$=-content]:has(>[data-overlay-close])>&]:pe-[calc(var(--surface-x)*var(--space-scale)+var(--height-action)*0.8888889*var(--density-scale)+0.5rem*var(--space-scale))]\"\n} as const;\n"],"mappings":";AACA,IAAaA,IAAqB;CAChCC,sBAAwB;CACxBC,UAAY;EACVC,IAAM;EACNC,IAAM;EACNC,IAAM;CACR;CACAC,cAAgB;EACdH,IAAM;EACNC,IAAM;EACNC,IAAM;CACR;CACAE,eAAiB;CACjBC,gBAAkB;CAClBC,cAAgB;CAChBC,cAAgB;CAChBC,eAAiB;AACnB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"avatar-CsnWaYmt.js","names":["Avatar","AvatarPrimitive","AvatarImage","AvatarFallback","AvatarBadge","AvatarGroup","AvatarGroupCount","Avatar","React","PrimitiveAvatar","PrimitiveAvatarImage","PrimitiveAvatarFallback","PrimitiveAvatarBadge","PrimitiveAvatarGroup","PrimitiveAvatarGroupCount","Avatar","resolveComponentScale","useDensityConfig","cn","Text","formatInitials","React$1","Avatar$2","props","Avatar","size","className","AvatarImage$1","AvatarFallback$1","AvatarBadge$1","AvatarGroup$1","AvatarGroupCount$1","AVATAR_SIZE_CLASSNAMES","getPrimitiveSize","Avatar$1","sizeProp","defaultScale","AvatarImage","AvatarFallback","children","AvatarBadge","AvatarGroup","AvatarGroupCount","FALLBACK_TONES","FALLBACK_TONE_CLASSNAMES","pickFallbackTone","override","seed","s","c","StackedAvatars","visible","users","max","overflow","overflowContent","overflowFormatter","initials","user","fallbackTone","index","avatarClassName","showOverflow"],"sources":["../src/components/ui/avatar.tsx","../src/components/base/avatar/avatar.tsx","../src/components/base/avatar/stacked-avatars.tsx"],"sourcesContent":["import { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\"\nimport * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nfunction Avatar({\n className,\n size = \"default\",\n ...props\n}: AvatarPrimitive.Root.Props & {\n size?: \"default\" | \"sm\" | \"lg\"\n}) {\n return (\n <AvatarPrimitive.Root\n data-slot=\"avatar\"\n data-size={size}\n className={cn(\n \"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n return (\n <AvatarPrimitive.Image\n data-slot=\"avatar-image\"\n className={cn(\n \"rounded-full aspect-square size-full object-cover\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AvatarFallback({\n className,\n ...props\n}: AvatarPrimitive.Fallback.Props) {\n return (\n <AvatarPrimitive.Fallback\n data-slot=\"avatar-fallback\"\n className={cn(\n \"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"avatar-badge\"\n className={cn(\n \"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none\",\n \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\n \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\n \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"avatar-group\"\n className={cn(\n \"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AvatarGroupCount({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"avatar-group-count\"\n className={cn(\"bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Avatar,\n AvatarImage,\n AvatarFallback,\n AvatarGroup,\n AvatarGroupCount,\n AvatarBadge,\n}\n","/**\n * Avatar — tokenized base wrapper around the shadcn/Base UI avatar primitive.\n * Public sizing uses the shared component scale.\n */\nimport * as React from 'react';\n\nimport {\n\tAvatar as PrimitiveAvatar,\n\tAvatarBadge as PrimitiveAvatarBadge,\n\tAvatarFallback as PrimitiveAvatarFallback,\n\tAvatarGroup as PrimitiveAvatarGroup,\n\tAvatarGroupCount as PrimitiveAvatarGroupCount,\n\tAvatarImage as PrimitiveAvatarImage,\n} from '../../ui/avatar';\nimport { useDensityConfig, type ComponentScale } from '../../../lib/ui-provider';\nimport { resolveComponentScale } from '../../../lib/ui-provider/scale';\nimport { Text } from '../../typography';\nimport { cn } from '../../../lib/utils';\n\nexport type AvatarSize = ComponentScale;\n\nexport interface AvatarProps\n\textends Omit<React.ComponentPropsWithoutRef<typeof PrimitiveAvatar>, 'size'> {\n\t/** Size preset. */\n\tsize?: AvatarSize;\n}\n\nexport type AvatarImageProps = React.ComponentPropsWithoutRef<typeof PrimitiveAvatarImage>;\nexport type AvatarFallbackProps = React.ComponentPropsWithoutRef<typeof PrimitiveAvatarFallback>;\nexport type AvatarBadgeProps = React.ComponentPropsWithoutRef<typeof PrimitiveAvatarBadge>;\nexport type AvatarGroupProps = React.ComponentPropsWithoutRef<typeof PrimitiveAvatarGroup>;\nexport type AvatarGroupCountProps = React.ComponentPropsWithoutRef<typeof PrimitiveAvatarGroupCount>;\n\nconst AVATAR_SIZE_CLASSNAMES: Record<AvatarSize, string> = {\n\tsm: 'size-avatar-sm',\n\tmd: 'size-avatar',\n\tlg: 'size-avatar-lg',\n};\n\nfunction getPrimitiveSize(size: AvatarSize): NonNullable<React.ComponentPropsWithoutRef<typeof PrimitiveAvatar>['size']> {\n\tif (size === 'sm') return 'sm';\n\tif (size === 'lg') return 'lg';\n\treturn 'default';\n}\n\nconst Avatar = React.forwardRef<React.ElementRef<typeof PrimitiveAvatar>, AvatarProps>(\n\t({ size: sizeProp, className, ...props }, ref) => {\n\t\tconst { defaultScale } = useDensityConfig();\n\t\tconst size = resolveComponentScale(sizeProp, defaultScale);\n\t\treturn (\n\t\t\t<PrimitiveAvatar\n\t\t\t\tref={ref}\n\t\t\t\tsize={getPrimitiveSize(size)}\n\t\t\t\tdata-scale={size}\n\t\t\t\tclassName={cn('avatar--component', AVATAR_SIZE_CLASSNAMES[size], className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nAvatar.displayName = 'Avatar';\n\nconst AvatarImage = React.forwardRef<React.ElementRef<typeof PrimitiveAvatarImage>, AvatarImageProps>(\n\t({ className, ...props }, ref) => (\n\t\t<PrimitiveAvatarImage\n\t\t\tref={ref}\n\t\t\tclassName={cn('avatar--image', className)}\n\t\t\t{...props}\n\t\t/>\n\t),\n);\nAvatarImage.displayName = 'AvatarImage';\n\nconst AvatarFallback = React.forwardRef<React.ElementRef<typeof PrimitiveAvatarFallback>, AvatarFallbackProps>(\n\t({ className, children, ...props }, ref) => (\n\t\t<PrimitiveAvatarFallback\n\t\t\tref={ref}\n\t\t\tclassName={cn('avatar--fallback text-xs font-medium leading-none', className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{typeof children === 'string' || typeof children === 'number' || typeof children === 'bigint' ? (\n\t\t\t\t<Text tag=\"span\" size=\"inherit\" weight=\"medium\" lineHeight=\"none\" className=\"text-inherit\">\n\t\t\t\t\t{children}\n\t\t\t\t</Text>\n\t\t\t) : children}\n\t\t</PrimitiveAvatarFallback>\n\t),\n);\nAvatarFallback.displayName = 'AvatarFallback';\n\nconst AvatarBadge = React.forwardRef<React.ElementRef<typeof PrimitiveAvatarBadge>, AvatarBadgeProps>(\n\t({ className, ...props }, ref) => (\n\t\t<PrimitiveAvatarBadge\n\t\t\tref={ref}\n\t\t\tclassName={cn('avatar--badge', className)}\n\t\t\t{...props}\n\t\t/>\n\t),\n);\nAvatarBadge.displayName = 'AvatarBadge';\n\nconst AvatarGroup = React.forwardRef<React.ElementRef<typeof PrimitiveAvatarGroup>, AvatarGroupProps>(\n\t({ className, ...props }, ref) => (\n\t\t<PrimitiveAvatarGroup\n\t\t\tref={ref}\n\t\t\tclassName={cn('avatar-group--component', className)}\n\t\t\t{...props}\n\t\t/>\n\t),\n);\nAvatarGroup.displayName = 'AvatarGroup';\n\nconst AvatarGroupCount = React.forwardRef<React.ElementRef<typeof PrimitiveAvatarGroupCount>, AvatarGroupCountProps>(\n\t({ className, children, ...props }, ref) => (\n\t\t<PrimitiveAvatarGroupCount\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t'avatar-group-count--component size-avatar text-xs',\n\t\t\t\t'group-has-data-[size=sm]/avatar-group:size-avatar-sm',\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{typeof children === 'string' || typeof children === 'number' || typeof children === 'bigint' ? (\n\t\t\t\t<Text tag=\"span\" size=\"inherit\" weight=\"medium\" lineHeight=\"none\" className=\"text-inherit\">\n\t\t\t\t\t{children}\n\t\t\t\t</Text>\n\t\t\t) : children}\n\t\t</PrimitiveAvatarGroupCount>\n\t),\n);\nAvatarGroupCount.displayName = 'AvatarGroupCount';\n\nexport {\n\tAvatar,\n\tAvatarImage,\n\tAvatarFallback,\n\tAvatarGroup,\n\tAvatarGroupCount,\n\tAvatarBadge,\n};\n","/**\n * StackedAvatars — overlapping avatar group with optional `+N` overflow chip,\n * configurable size, and an optional semantic `tone` for fallbacks (initials)\n * when the consumer hasn't passed `imageUrl`.\n *\n * Wraps the shadcn AvatarGroup primitive with library-default sizes and a\n * deterministic tone picker so fallbacks stay stable without raw hue utilities.\n */\nimport * as React from 'react';\nimport {\n\tAvatar,\n\tAvatarFallback,\n\tAvatarGroup,\n\tAvatarGroupCount,\n\tAvatarImage,\n} from './avatar';\nimport { formatInitials } from '../../primitives/initials-format';\nimport { Text } from '../../typography';\nimport { useDensityConfig, type ComponentScale } from '../../../lib/ui-provider';\nimport { resolveComponentScale } from '../../../lib/ui-provider/scale';\nimport { cn } from '../../../lib/utils';\nimport type { SemanticTone } from '../../../lib/component-vocabulary';\n\nexport type StackedAvatarsSize = ComponentScale;\nexport type StackedAvatarTone = SemanticTone;\n\nexport interface StackedAvatarUser {\n\t/** Stable identity for this entry. */\n\tid?: string;\n\t/** Form field name, forwarded for native submits. */\n\tname: string;\n\timageUrl?: string;\n\t/** Optional semantic tone override for this user's fallback. Numeric values map to the token set. */\n\ttone?: StackedAvatarTone | number;\n}\n\nexport interface StackedAvatarsProps {\n\tusers: StackedAvatarUser[];\n\tmax?: number;\n\t/** Size preset. */\n\tsize?: StackedAvatarsSize;\n\tclassName?: string;\n\t/** Whether to render a `+N` count when `users.length > max`. Default `true`. */\n\tshowOverflow?: boolean;\n\t/** Provide custom overflow text (e.g. \"+12 more\"). Default `+N`. */\n\toverflowFormatter?: (overflow: number) => React.ReactNode;\n\t/** Extra class names applied to each avatar root. */\n\tavatarClassName?: string;\n}\n\nconst FALLBACK_TONES: StackedAvatarTone[] = [\n\t'neutral',\n\t'primary',\n\t'secondary',\n\t'info',\n\t'success',\n];\n\nconst FALLBACK_TONE_CLASSNAMES: Record<StackedAvatarTone, string> = {\n\tneutral: 'bg-muted text-muted-foreground',\n\tprimary: 'bg-primary/10 text-primary-accent',\n\tsecondary: 'bg-secondary text-secondary-foreground',\n\tinfo: 'bg-info/10 text-info',\n\tsuccess: 'bg-success/10 text-success',\n\twarning: 'bg-warning/15 text-warning',\n\tdestructive: 'bg-destructive/10 text-destructive',\n};\n\nfunction pickFallbackTone(seed: string | number, override?: StackedAvatarTone | number) {\n\tif (typeof override === 'string') {\n\t\treturn override;\n\t}\n\tif (typeof override === 'number') {\n\t\treturn FALLBACK_TONES[Math.abs(override) % FALLBACK_TONES.length];\n\t}\n\tconst key = typeof seed === 'number' ? seed : Array.from(seed).reduce((s, c) => s + c.charCodeAt(0), 0);\n\treturn FALLBACK_TONES[key % FALLBACK_TONES.length];\n}\n\nexport function StackedAvatars({\n\tusers,\n\tmax = 4,\n\tsize: sizeProp,\n\tclassName,\n\tshowOverflow = true,\n\toverflowFormatter,\n\tavatarClassName,\n}: StackedAvatarsProps) {\n\tconst { defaultScale } = useDensityConfig();\n\tconst size = resolveComponentScale(sizeProp, defaultScale);\n\tconst visible = users.slice(0, max);\n\tconst overflow = Math.max(0, users.length - max);\n\tconst overflowContent = overflowFormatter ? overflowFormatter(overflow) : `+${overflow}`;\n\n\treturn (\n\t\t<AvatarGroup className={cn('stacked-avatars--component', className)}>\n\t\t\t{visible.map((user, index) => {\n\t\t\t\tconst initials = formatInitials(user.name, {\n\t\t\t\t\tmaxCharacters: size === 'sm' ? 1 : 2,\n\t\t\t\t\tfallback: '?',\n\t\t\t\t});\n\t\t\t\tconst fallbackTone = pickFallbackTone(user.id ?? user.name ?? index, user.tone);\n\t\t\t\treturn (\n\t\t\t\t\t<Avatar\n\t\t\t\t\t\tkey={user.id ?? `${user.name}-${index}`}\n\t\t\t\t\t\tsize={size}\n\t\t\t\t\t\tclassName={cn('ring-background', avatarClassName)}\n\t\t\t\t\t\ttitle={user.name}\n\t\t\t\t\t>\n\t\t\t\t\t\t{user.imageUrl ? (\n\t\t\t\t\t\t\t<AvatarImage src={user.imageUrl} alt={user.name} />\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t<AvatarFallback\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t'font-semibold',\n\t\t\t\t\t\t\t\tFALLBACK_TONE_CLASSNAMES[fallbackTone],\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{initials}\n\t\t\t\t\t\t</AvatarFallback>\n\t\t\t\t\t</Avatar>\n\t\t\t\t);\n\t\t\t})}\n\t\t\t{!!showOverflow && overflow > 0 && (\n\t\t\t\t<AvatarGroupCount>\n\t\t\t\t\t{typeof overflowContent === 'string' || typeof overflowContent === 'number' || typeof overflowContent === 'bigint' ? (\n\t\t\t\t\t\t<Text tag=\"span\" size=\"xs\" weight=\"semibold\">\n\t\t\t\t\t\t\t{overflowContent}\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t) : overflowContent}\n\t\t\t\t</AvatarGroupCount>\n\t\t\t)}\n\t\t</AvatarGroup>\n\t);\n}\n\nStackedAvatars.displayName = 'StackedAvatars';\n"],"mappings":"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"}
|