mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
frontend: block past dates in appointment/invoice pickers; drop stale Settings Features
- Appointment date picker disables days before today. - Invoice issue-date picker disables past days by default, with an opt-in "Back-date" checkbox for recording older invoices (edit mode keeps past dates). Added invoices.dialog.backdate to all locales. - Removed the inert, outdated "Features" section (patient-owned storage / require signed toggles) from Settings and its now-unused i18n keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,199 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by the Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding any notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. Please also get an approval
|
||||||
|
from the project maintainers before using the Apache License.
|
||||||
|
|
||||||
|
Copyright 2025 NextUI Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
name: heroui-migration
|
||||||
|
description: "HeroUI v2 to v3 migration guide for agents. Use when migrating HeroUI v2 apps to v3, upgrading components, or accessing migration documentation. Keywords: HeroUI migration, v2 to v3, migration guide, upgrade HeroUI."
|
||||||
|
metadata:
|
||||||
|
author: heroui
|
||||||
|
version: "2.0.0"
|
||||||
|
status: preview
|
||||||
|
---
|
||||||
|
|
||||||
|
# HeroUI v2 to v3 Migration Guide
|
||||||
|
|
||||||
|
This skill helps agents migrate HeroUI v2 applications to v3. HeroUI v3 introduces breaking changes: compound components, no Provider, Tailwind v4, and removed hooks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://heroui.com/install | bash -s heroui-migration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL: Always Fetch Migration Docs Before Applying
|
||||||
|
|
||||||
|
**Do NOT assume v2 patterns work in v3.** Always fetch migration guides before implementing changes.
|
||||||
|
|
||||||
|
### Key v2 → v3 Changes
|
||||||
|
|
||||||
|
| Feature | v2 (Migrate From) | v3 (Migrate To) |
|
||||||
|
| ------------- | -------------------------- | -------------------------------------- |
|
||||||
|
| Provider | `<HeroUIProvider>` required | **No Provider needed** |
|
||||||
|
| Component API | Flat props: `<Card title="x">` | Compound: `<Card><Card.Header>` |
|
||||||
|
| Event handlers | `onClick` | `onPress` |
|
||||||
|
| Styling | `classNames` prop | `className` prop |
|
||||||
|
| Hooks | `useSwitch`, `useDisclosure`, etc. | Compound components, `useOverlayState` |
|
||||||
|
| Packages | `@heroui/system`, `@heroui/theme` | `@heroui/react`, `@heroui/styles` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessing Migration Documentation
|
||||||
|
|
||||||
|
**For migration details, examples, and step-by-step guides, always fetch documentation:**
|
||||||
|
|
||||||
|
### Using Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all available component migration guides
|
||||||
|
node scripts/list_migration_guides.mjs
|
||||||
|
|
||||||
|
# Get main migration workflow (full or incremental)
|
||||||
|
node scripts/get_migration_guide.mjs full
|
||||||
|
node scripts/get_migration_guide.mjs incremental
|
||||||
|
|
||||||
|
# Get component-specific migration guides
|
||||||
|
node scripts/get_component_migration_guides.mjs button
|
||||||
|
node scripts/get_component_migration_guides.mjs button card modal
|
||||||
|
|
||||||
|
# Get styling migration guide
|
||||||
|
node scripts/get_styling_migration_guide.mjs
|
||||||
|
|
||||||
|
# Get hooks migration guide
|
||||||
|
node scripts/get_hooks_migration_guide.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct URLs
|
||||||
|
|
||||||
|
Migration docs (preview): `https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration/{filename}`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- Full migration: `.../agent-guide-full.mdx`
|
||||||
|
- Incremental: `.../agent-guide-incremental.mdx`
|
||||||
|
- Button: `.../button.mdx`
|
||||||
|
- Styling: `.../styling.mdx`
|
||||||
|
- Hooks: `.../hooks.mdx`
|
||||||
|
|
||||||
|
Override base URL with `HEROUI_MIGRATION_DOCS_BASE` when docs are merged to production.
|
||||||
|
|
||||||
|
### MCP Alternative
|
||||||
|
|
||||||
|
When using Cursor or other MCP clients, configure the Migration MCP server for tool-based access:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"heroui-migration": {
|
||||||
|
"url": "https://migration-mcp.heroui.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Strategies
|
||||||
|
|
||||||
|
### Full Migration
|
||||||
|
|
||||||
|
- Best for: Projects that can dedicate focused time; teams comfortable with temporarily broken code
|
||||||
|
- Migrate all component code first (project broken during migration)
|
||||||
|
- Switch dependencies to v3
|
||||||
|
- Complete styling migration
|
||||||
|
|
||||||
|
### Incremental Migration
|
||||||
|
|
||||||
|
- Best for: Projects that must stay functional; large codebases migrating gradually
|
||||||
|
- Set up coexistence (pnpm aliases or component packages)
|
||||||
|
- Migrate components one-by-one
|
||||||
|
- Both v2 and v3 coexist during migration
|
||||||
|
|
||||||
|
**Always fetch the agent guide before starting:** `node scripts/get_migration_guide.mjs full` or `incremental`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
1. **Fetch first**: Use scripts to get migration guides before applying changes
|
||||||
|
2. **Compound components**: v3 uses `Card.Header`, `Card.Title`, `Button` with children—not flat props
|
||||||
|
3. **No Provider**: Remove `HeroUIProvider` when migrating
|
||||||
|
4. **onPress not onClick**: All interactive components use `onPress`
|
||||||
|
5. **Workflow**: Analyze → Migrate components → Switch deps → Styling migration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Workflow Summary
|
||||||
|
|
||||||
|
1. Create migration branch
|
||||||
|
2. Analyze project (HeroUI imports, component usage)
|
||||||
|
3. Fetch main guide: `node scripts/get_migration_guide.mjs full`
|
||||||
|
4. Migrate components in batches (fetch component guides per batch)
|
||||||
|
5. Switch dependencies to v3
|
||||||
|
6. Fetch styling guide: `node scripts/get_styling_migration_guide.mjs`
|
||||||
|
7. Apply styling updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preview Mode
|
||||||
|
|
||||||
|
This skill targets the staging deployment of the `docs/migration` branch. Once docs are merged to main and live on heroui.com, set `HEROUI_MIGRATION_DOCS_BASE=https://heroui.com/docs/react/migration` or update the default in scripts.
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get migration guides for HeroUI components (v2 to v3).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_component_migration_guides.mjs button
|
||||||
|
* node get_component_migration_guides.mjs button card modal
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX migration guide content for each component
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DOCS_BASE =
|
||||||
|
process.env.HEROUI_MIGRATION_DOCS_BASE ||
|
||||||
|
"https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration";
|
||||||
|
const APP_PARAM = "app=migration-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert PascalCase or mixed case to kebab-case.
|
||||||
|
*/
|
||||||
|
function toKebabCase(name) {
|
||||||
|
return name
|
||||||
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||||
|
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDoc(filename) {
|
||||||
|
const url = `${DOCS_BASE}/${filename}?${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Migration-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_component_migration_guides.mjs <Component1> [Component2] ...");
|
||||||
|
console.error("Example: node get_component_migration_guides.mjs button card modal");
|
||||||
|
console.error("\nUse list_migration_guides.mjs to see all available components.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = args.map((c) => toKebabCase(c));
|
||||||
|
|
||||||
|
console.error(`# Fetching migration guides for: ${components.join(", ")}...`);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const component of components) {
|
||||||
|
const filename = `${component}.mdx`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fetchDoc(filename);
|
||||||
|
const title = component.charAt(0).toUpperCase() + component.slice(1).replace(/-/g, " ");
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
component,
|
||||||
|
content: `# ${title} Migration Guide\n\n**Component:** ${component}\n**Source:** ${DOCS_BASE}/${filename}\n\n---\n\n${content}`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
results.push({
|
||||||
|
component,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = results.filter((r) => r.error);
|
||||||
|
|
||||||
|
if (failed.length > 0) {
|
||||||
|
failed.forEach((r) => console.error(`# Error for ${r.component}: ${r.error}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === 1) {
|
||||||
|
const r = results[0];
|
||||||
|
|
||||||
|
if (r.content) {
|
||||||
|
console.log(r.content);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(r, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const output = results
|
||||||
|
.map((r) => (r.content ? r.content : `# ${r.component} Migration Guide\n\nError: ${r.error}`))
|
||||||
|
.join("\n\n---\n\n");
|
||||||
|
|
||||||
|
console.log(output);
|
||||||
|
if (failed.length === results.length) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get the hooks migration guide for HeroUI v2 to v3.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_hooks_migration_guide.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX hooks migration guide content
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DOCS_BASE =
|
||||||
|
process.env.HEROUI_MIGRATION_DOCS_BASE ||
|
||||||
|
"https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration";
|
||||||
|
const APP_PARAM = "app=migration-skills";
|
||||||
|
|
||||||
|
async function fetchDoc(filename) {
|
||||||
|
const url = `${DOCS_BASE}/${filename}?${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Migration-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to fetch ${filename}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const filename = "hooks.mdx";
|
||||||
|
|
||||||
|
console.error("# Fetching hooks migration guide...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fetchDoc(filename);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`# HeroUI v2 to v3 Hooks Migration Guide\n\n**Source:** ${DOCS_BASE}/${filename}\n\n---\n\n${content}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# Error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get the main migration workflow guide (full or incremental) for HeroUI v2 to v3.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_migration_guide.mjs [full|incremental]
|
||||||
|
*
|
||||||
|
* Default: full
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX migration guide content
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DOCS_BASE =
|
||||||
|
process.env.HEROUI_MIGRATION_DOCS_BASE ||
|
||||||
|
"https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration";
|
||||||
|
const APP_PARAM = "app=migration-skills";
|
||||||
|
|
||||||
|
async function fetchDoc(filename) {
|
||||||
|
const url = `${DOCS_BASE}/${filename}?${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Migration-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to fetch ${filename}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const arg = (process.argv[2] || "full").toLowerCase();
|
||||||
|
const migrationType = arg === "incremental" ? "incremental" : "full";
|
||||||
|
const filename =
|
||||||
|
migrationType === "incremental" ? "agent-guide-incremental.mdx" : "agent-guide-full.mdx";
|
||||||
|
|
||||||
|
console.error(`# Fetching ${migrationType} migration guide...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fetchDoc(filename);
|
||||||
|
const title =
|
||||||
|
migrationType === "incremental"
|
||||||
|
? "HeroUI v2 to v3 Agent Migration Guide - Incremental Migration"
|
||||||
|
: "HeroUI v2 to v3 Agent Migration Guide - Full Migration";
|
||||||
|
|
||||||
|
console.log(`# ${title}\n\n**Source:** ${DOCS_BASE}/${filename}\n\n---\n\n${content}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# Error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get the styling migration guide for HeroUI v2 to v3.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_styling_migration_guide.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX styling migration guide content
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DOCS_BASE =
|
||||||
|
process.env.HEROUI_MIGRATION_DOCS_BASE ||
|
||||||
|
"https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration";
|
||||||
|
const APP_PARAM = "app=migration-skills";
|
||||||
|
|
||||||
|
async function fetchDoc(filename) {
|
||||||
|
const url = `${DOCS_BASE}/${filename}?${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Migration-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to fetch ${filename}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const filename = "styling.mdx";
|
||||||
|
|
||||||
|
console.error("# Fetching styling migration guide...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fetchDoc(filename);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`# HeroUI v2 to v3 Styling Migration Guide\n\n**Source:** ${DOCS_BASE}/${filename}\n\n---\n\n${content}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# Error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* List all available HeroUI v2 to v3 component migration guides.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node list_migration_guides.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* List of component names that have migration guides
|
||||||
|
*
|
||||||
|
* Note: Keep in sync with migration-mcp list-migration-guides.ts when adding components.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Component migration guides available - must match migration-mcp list-migration-guides.ts
|
||||||
|
const AVAILABLE_COMPONENTS = [
|
||||||
|
"accordion",
|
||||||
|
"alert",
|
||||||
|
"autocomplete",
|
||||||
|
"avatar",
|
||||||
|
"breadcrumbs",
|
||||||
|
"button",
|
||||||
|
"button-group",
|
||||||
|
"card",
|
||||||
|
"checkbox",
|
||||||
|
"checkbox-group",
|
||||||
|
"chip",
|
||||||
|
"code",
|
||||||
|
"divider",
|
||||||
|
"dropdown",
|
||||||
|
"form",
|
||||||
|
"image",
|
||||||
|
"input",
|
||||||
|
"input-otp",
|
||||||
|
"kbd",
|
||||||
|
"link",
|
||||||
|
"listbox",
|
||||||
|
"modal",
|
||||||
|
"navbar",
|
||||||
|
"numberinput",
|
||||||
|
"popover",
|
||||||
|
"radio",
|
||||||
|
"radio-group",
|
||||||
|
"scroll-shadow",
|
||||||
|
"select",
|
||||||
|
"skeleton",
|
||||||
|
"slider",
|
||||||
|
"snippet",
|
||||||
|
"spacer",
|
||||||
|
"spinner",
|
||||||
|
"switch",
|
||||||
|
"tabs",
|
||||||
|
"toast",
|
||||||
|
"tooltip",
|
||||||
|
"user",
|
||||||
|
];
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const componentsList = AVAILABLE_COMPONENTS.map((name) => ` - ${name}`).join("\n");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`# Available Component Migration Guides\n\nFound ${AVAILABLE_COMPONENTS.length} component migration guides:\n\n${componentsList}\n\nUse get_component_migration_guides.mjs with component names to fetch specific guides.\nExample: node get_component_migration_guides.mjs button card modal`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by the Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding any notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. Please also get an approval
|
||||||
|
from the project maintainers before using the Apache License.
|
||||||
|
|
||||||
|
Copyright 2025 NextUI Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
name: heroui-native
|
||||||
|
description: "HeroUI Native component library for React Native (Tailwind v4 via Uniwind). Use when building mobile UIs with HeroUI Native — creating Buttons, Cards, TextFields, Dialogs; installing heroui-native; configuring dark/light themes; or fetching component docs. Keywords: HeroUI Native, heroui-native, React Native UI, Uniwind, mobile components."
|
||||||
|
metadata:
|
||||||
|
author: heroui
|
||||||
|
version: "2.0.1"
|
||||||
|
---
|
||||||
|
|
||||||
|
# HeroUI Native Development Guide
|
||||||
|
|
||||||
|
HeroUI Native is a component library built on **Uniwind (Tailwind CSS for React Native)** and **React Native**, providing accessible, customizable UI components for mobile applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://heroui.com/install | bash -s heroui-native
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL: Native Only - Do Not Use Web Patterns
|
||||||
|
|
||||||
|
**This guide is for HeroUI Native ONLY.** Do NOT apply HeroUI React (web) patterns — the package, styling engine, and color format all differ:
|
||||||
|
|
||||||
|
| Feature | React (Web) | Native (Mobile) |
|
||||||
|
| ------------ | -------------------- | ----------------------------------- |
|
||||||
|
| **Styling** | Tailwind CSS v4 | Uniwind (Tailwind for React Native) |
|
||||||
|
| **Colors** | oklch format | HSL format |
|
||||||
|
| **Package** | `@heroui/react` | `heroui-native` |
|
||||||
|
| **Platform** | Web browsers | iOS & Android |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// CORRECT — Native pattern
|
||||||
|
import { Button } from "heroui-native";
|
||||||
|
|
||||||
|
<Button variant="primary" onPress={() => console.log("Pressed!")}>
|
||||||
|
Click me
|
||||||
|
</Button>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always fetch Native docs before implementing.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
- Semantic variants (`primary`, `secondary`, `tertiary`) over visual descriptions
|
||||||
|
- Composition over configuration (compound components)
|
||||||
|
- Theme variables with HSL color format
|
||||||
|
- React Native StyleSheet patterns with Uniwind utilities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessing Documentation & Component Information
|
||||||
|
|
||||||
|
**For component details, examples, props, and implementation patterns, always fetch documentation:**
|
||||||
|
|
||||||
|
### Using Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all available components
|
||||||
|
node scripts/list_components.mjs
|
||||||
|
|
||||||
|
# Get component documentation (MDX)
|
||||||
|
node scripts/get_component_docs.mjs Button
|
||||||
|
node scripts/get_component_docs.mjs Button Card TextField
|
||||||
|
|
||||||
|
# Get theme variables
|
||||||
|
node scripts/get_theme.mjs
|
||||||
|
|
||||||
|
# Get non-component docs (guides, releases)
|
||||||
|
node scripts/get_docs.mjs /docs/native/getting-started/theming
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct MDX URLs
|
||||||
|
|
||||||
|
Component docs: `https://heroui.com/docs/native/components/{component-name}.mdx`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- Button: `https://heroui.com/docs/native/components/button.mdx`
|
||||||
|
- Dialog: `https://heroui.com/docs/native/components/dialog.mdx`
|
||||||
|
- TextField: `https://heroui.com/docs/native/components/text-field.mdx`
|
||||||
|
|
||||||
|
Getting started guides: `https://heroui.com/docs/native/getting-started/{topic}.mdx`
|
||||||
|
|
||||||
|
**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation Essentials
|
||||||
|
|
||||||
|
### Quick Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i heroui-native react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants
|
||||||
|
```
|
||||||
|
|
||||||
|
### Framework Setup (Expo - Recommended)
|
||||||
|
|
||||||
|
1. **Install dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-expo-app MyApp
|
||||||
|
cd MyApp
|
||||||
|
npm i heroui-native uniwind tailwindcss
|
||||||
|
npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create `global.css`:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "uniwind";
|
||||||
|
@import "heroui-native/styles";
|
||||||
|
|
||||||
|
@source "./node_modules/heroui-native/lib";
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Wrap app with providers:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
|
import { HeroUINativeProvider } from "heroui-native";
|
||||||
|
import "./global.css";
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
|
<HeroUINativeProvider>
|
||||||
|
<App />
|
||||||
|
</HeroUINativeProvider>
|
||||||
|
</GestureHandlerRootView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical Setup Requirements
|
||||||
|
|
||||||
|
1. **Uniwind is Required** - HeroUI Native uses Uniwind (Tailwind CSS for React Native)
|
||||||
|
2. **HeroUINativeProvider Required** - Wrap your app with `HeroUINativeProvider`
|
||||||
|
3. **GestureHandlerRootView Required** - Wrap with `GestureHandlerRootView` from react-native-gesture-handler
|
||||||
|
4. **Use Compound Components** - Components use compound structure (e.g., `Card.Header`, `Card.Body`)
|
||||||
|
5. **Use onPress, not onClick** - React Native uses `onPress` event handlers
|
||||||
|
6. **Platform-Specific Code** - Use `Platform.OS` for iOS/Android differences
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Patterns
|
||||||
|
|
||||||
|
HeroUI Native uses **compound component patterns**. Each component has subcomponents accessed via dot notation.
|
||||||
|
|
||||||
|
**Example - Card:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card>
|
||||||
|
<Card.Header>{/* Icons, badges */}</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>Title</Card.Title>
|
||||||
|
<Card.Description>Description</Card.Description>
|
||||||
|
</Card.Body>
|
||||||
|
<Card.Footer>{/* Actions */}</Card.Footer>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points:**
|
||||||
|
|
||||||
|
- Always use compound structure - don't flatten to props
|
||||||
|
- Subcomponents are accessed via dot notation (e.g., `Card.Header`)
|
||||||
|
- Native Card uses `Card.Body` (not `Card.Content`); Title and Description go inside Body
|
||||||
|
- **Fetch component docs for complete anatomy and examples**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic Variants
|
||||||
|
|
||||||
|
HeroUI uses semantic naming to communicate functional intent:
|
||||||
|
|
||||||
|
| Variant | Purpose | Usage |
|
||||||
|
| ------------- | --------------------------------- | -------------- |
|
||||||
|
| `primary` | Main action to move forward | 1 per context |
|
||||||
|
| `secondary` | Alternative actions | Multiple |
|
||||||
|
| `tertiary` | Dismissive actions (cancel, skip) | Sparingly |
|
||||||
|
| `danger` | Destructive actions | When needed |
|
||||||
|
| `danger-soft` | Soft destructive actions | Less prominent |
|
||||||
|
| `ghost` | Low-emphasis actions | Minimal weight |
|
||||||
|
| `outline` | Secondary actions | Bordered style |
|
||||||
|
|
||||||
|
**Don't use raw colors** - semantic variants adapt to themes and accessibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
HeroUI Native uses CSS variables via Tailwind/Uniwind for theming. Theme colors are defined in `global.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@theme {
|
||||||
|
--color-accent: hsl(260, 100%, 70%);
|
||||||
|
--color-accent-foreground: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get current theme variables:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/get_theme.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Access theme colors programmatically:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useThemeColor } from "heroui-native";
|
||||||
|
|
||||||
|
const accentColor = useThemeColor("accent");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Theme switching (Light/Dark Mode):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Uniwind, useUniwind } from "uniwind";
|
||||||
|
|
||||||
|
const { theme } = useUniwind();
|
||||||
|
Uniwind.setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
```
|
||||||
|
|
||||||
|
For detailed theming, fetch: `https://heroui.com/docs/native/getting-started/theming.mdx`
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get complete component documentation (MDX) for HeroUI Native components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_component_docs.mjs Button
|
||||||
|
* node get_component_docs.mjs Button Card TextField
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX documentation including imports, usage, variants, props, examples
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||||
|
const FALLBACK_BASE = "https://heroui.com";
|
||||||
|
const APP_PARAM = "app=native-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert PascalCase to kebab-case.
|
||||||
|
*/
|
||||||
|
function toKebabCase(name) {
|
||||||
|
return name
|
||||||
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||||
|
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI Native API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint, method = "GET", body = null) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "HeroUI-Native-Skill/1.0",
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch MDX directly from v3.heroui.com as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchFallback(component) {
|
||||||
|
const kebabName = toKebabCase(component);
|
||||||
|
const url = `${FALLBACK_BASE}/docs/native/components/${kebabName}.mdx`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return {component, error: `Failed to fetch docs for ${component}`};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
content,
|
||||||
|
contentType: "mdx",
|
||||||
|
source: "fallback",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {component, error: `Failed to fetch docs for ${component}`};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get component documentation.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_component_docs.mjs <Component1> [Component2] ...");
|
||||||
|
console.error("Example: node get_component_docs.mjs Button Card");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = args;
|
||||||
|
|
||||||
|
// Try API first - use POST /v1/components/docs for batch requests
|
||||||
|
console.error(`# Fetching Native docs for: ${components.join(", ")}...`);
|
||||||
|
const data = await fetchApi("/v1/components/docs", "POST", {components});
|
||||||
|
|
||||||
|
if (data && data.results) {
|
||||||
|
// Output results
|
||||||
|
if (data.results.length === 1) {
|
||||||
|
// Single component - output content directly for easier reading
|
||||||
|
const result = data.results[0];
|
||||||
|
|
||||||
|
if (result.content) {
|
||||||
|
console.log(result.content);
|
||||||
|
} else if (result.error) {
|
||||||
|
console.error(`# Error for ${result.component}: ${result.error}`);
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multiple components - output as JSON array
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to individual component fetches
|
||||||
|
console.error("# API failed, using fallback...");
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const component of components) {
|
||||||
|
const result = await fetchFallback(component);
|
||||||
|
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
if (results.length === 1) {
|
||||||
|
// Single component - output content directly for easier reading
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (result.content) {
|
||||||
|
console.log(result.content);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multiple components - output as JSON array
|
||||||
|
console.log(JSON.stringify(results, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get non-component HeroUI Native documentation (guides, theming, releases).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_docs.mjs /docs/native/getting-started/theming
|
||||||
|
* node get_docs.mjs /docs/native/releases/beta-12
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX documentation content
|
||||||
|
*
|
||||||
|
* Note: For component docs, use get_component_docs.mjs instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||||
|
const FALLBACK_BASE = "https://heroui.com";
|
||||||
|
const APP_PARAM = "app=native-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch documentation from HeroUI Native API.
|
||||||
|
* Uses v1 endpoint pattern: /v1/docs/:path
|
||||||
|
*/
|
||||||
|
async function fetchApi(path) {
|
||||||
|
// The v1 API expects path without /docs/ prefix
|
||||||
|
// Input: /docs/native/getting-started/theming
|
||||||
|
// API expects: native/getting-started/theming (route is /v1/docs/:path(*))
|
||||||
|
let apiPath = path.startsWith("/docs/")
|
||||||
|
? path.slice(6) // Remove /docs/ prefix
|
||||||
|
: path.startsWith("/")
|
||||||
|
? path.slice(1) // Remove leading /
|
||||||
|
: path;
|
||||||
|
|
||||||
|
const separator = "?";
|
||||||
|
const url = `${API_BASE}/v1/docs/${apiPath}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch MDX directly from v3.heroui.com as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchFallback(path) {
|
||||||
|
// Ensure path starts with /docs and ends with .mdx
|
||||||
|
let cleanPath = path.replace(/^\//, "");
|
||||||
|
|
||||||
|
if (!cleanPath.endsWith(".mdx")) {
|
||||||
|
cleanPath = `${cleanPath}.mdx`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${FALLBACK_BASE}/${cleanPath}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return {error: `HTTP ${response.status}: ${response.statusText}`, path};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
contentType: "mdx",
|
||||||
|
path,
|
||||||
|
source: "fallback",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {error: `Fetch Error: ${error.message}`, path};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get documentation for specified path.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_docs.mjs <path>");
|
||||||
|
console.error("Example: node get_docs.mjs /docs/native/getting-started/theming");
|
||||||
|
console.error();
|
||||||
|
console.error("Available paths include:");
|
||||||
|
console.error(" /docs/native/getting-started/theming");
|
||||||
|
console.error(" /docs/native/getting-started/colors");
|
||||||
|
console.error(" /docs/native/getting-started/styling");
|
||||||
|
console.error(" /docs/native/releases/beta-12");
|
||||||
|
console.error();
|
||||||
|
console.error("Note: For component docs, use get_component_docs.mjs instead.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = args[0];
|
||||||
|
|
||||||
|
// Check if user is trying to get component docs
|
||||||
|
if (path.includes("/components/")) {
|
||||||
|
console.error("# Warning: Use get_component_docs.mjs for component documentation.");
|
||||||
|
const componentName = path.split("/").pop().replace(".mdx", "");
|
||||||
|
const titleCase = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
||||||
|
|
||||||
|
console.error(`# Example: node get_component_docs.mjs ${titleCase}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Native path
|
||||||
|
if (!path.startsWith("/docs/native/")) {
|
||||||
|
console.error("# Warning: Native documentation paths should start with /docs/native/");
|
||||||
|
console.error(`# Provided path: ${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`# Fetching Native documentation for ${path}...`);
|
||||||
|
|
||||||
|
// Try API first
|
||||||
|
const data = await fetchApi(path);
|
||||||
|
|
||||||
|
if (data && data.content) {
|
||||||
|
data.source = "api";
|
||||||
|
console.log(data.content);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to direct fetch
|
||||||
|
console.error("# API failed, using fallback...");
|
||||||
|
const fallbackData = await fetchFallback(path);
|
||||||
|
|
||||||
|
if (fallbackData.content) {
|
||||||
|
console.log(fallbackData.content);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(fallbackData, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+222
@@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get theme variables and design tokens for HeroUI Native.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_theme.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* Theme variables organized by light/dark with HSL color format
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||||
|
const APP_PARAM = "app=native-skills";
|
||||||
|
|
||||||
|
// Fallback theme reference when API is unavailable
|
||||||
|
const FALLBACK_THEME = {
|
||||||
|
borderRadius: {
|
||||||
|
full: 9999,
|
||||||
|
lg: 12,
|
||||||
|
md: 8,
|
||||||
|
sm: 6,
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
colors: [
|
||||||
|
{
|
||||||
|
category: "base",
|
||||||
|
name: "--color-background",
|
||||||
|
value: "hsl(0, 0%, 14.5%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "semantic",
|
||||||
|
name: "--color-foreground",
|
||||||
|
value: "hsl(0, 0%, 98.4%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "semantic",
|
||||||
|
name: "--color-accent",
|
||||||
|
value: "hsl(264.1, 100%, 55.1%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-danger",
|
||||||
|
value: "hsl(25.3, 100%, 63.7%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-success",
|
||||||
|
value: "hsl(163.2, 100%, 76.5%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-warning",
|
||||||
|
value: "hsl(86.0, 100%, 79.5%)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
latestVersion: "beta",
|
||||||
|
light: {
|
||||||
|
colors: [
|
||||||
|
{
|
||||||
|
category: "base",
|
||||||
|
name: "--color-background",
|
||||||
|
value: "hsl(0, 0%, 100%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "semantic",
|
||||||
|
name: "--color-foreground",
|
||||||
|
value: "hsl(285.89, 5.9%, 21.03%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "semantic",
|
||||||
|
name: "--color-accent",
|
||||||
|
value: "hsl(253.83, 100%, 62.04%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-danger",
|
||||||
|
value: "hsl(25.74, 100%, 65.32%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-success",
|
||||||
|
value: "hsl(150.81, 100%, 73.29%)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "status",
|
||||||
|
name: "--color-warning",
|
||||||
|
value: "hsl(72.33, 100%, 78.19%)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
note: "This is a fallback. For complete theme variables, ensure the API is accessible.",
|
||||||
|
opacity: {
|
||||||
|
disabled: 0.4,
|
||||||
|
hover: 0.8,
|
||||||
|
pressed: 0.6,
|
||||||
|
},
|
||||||
|
source: "fallback",
|
||||||
|
theme: "default",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI Native API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format colors grouped by category.
|
||||||
|
*/
|
||||||
|
function formatColors(colors) {
|
||||||
|
const grouped = {};
|
||||||
|
|
||||||
|
for (const color of colors) {
|
||||||
|
const category = color.category || "semantic";
|
||||||
|
|
||||||
|
if (!grouped[category]) {
|
||||||
|
grouped[category] = [];
|
||||||
|
}
|
||||||
|
grouped[category].push(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
for (const [category, tokens] of Object.entries(grouped)) {
|
||||||
|
lines.push(` /* ${category.charAt(0).toUpperCase() + category.slice(1)} Colors */`);
|
||||||
|
for (const token of tokens) {
|
||||||
|
const name = token.name || "";
|
||||||
|
const value = token.value || "";
|
||||||
|
|
||||||
|
lines.push(` ${name}: ${value};`);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get theme variables.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
console.error("# Fetching Native theme variables...");
|
||||||
|
|
||||||
|
const rawData = await fetchApi("/v1/themes/variables?theme=default");
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let version;
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
console.error("# API failed, using fallback theme reference...");
|
||||||
|
data = FALLBACK_THEME;
|
||||||
|
version = FALLBACK_THEME.latestVersion || "unknown";
|
||||||
|
} else {
|
||||||
|
// Handle API response format
|
||||||
|
data = rawData;
|
||||||
|
version = rawData.latestVersion || "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output as formatted structure for readability
|
||||||
|
console.log("/* HeroUI Native Theme Variables */");
|
||||||
|
console.log(`/* Theme: ${data.theme || "default"} */`);
|
||||||
|
console.log(`/* Version: ${version} */`);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Light mode colors
|
||||||
|
if (data.light && data.light.colors) {
|
||||||
|
console.log("/* Light Mode Colors */");
|
||||||
|
console.log(formatColors(data.light.colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode colors
|
||||||
|
if (data.dark && data.dark.colors) {
|
||||||
|
console.log("/* Dark Mode Colors */");
|
||||||
|
console.log(formatColors(data.dark.colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border radius
|
||||||
|
if (data.borderRadius) {
|
||||||
|
console.log("/* Border Radius */");
|
||||||
|
for (const [key, value] of Object.entries(data.borderRadius)) {
|
||||||
|
console.log(` --radius-${key}: ${value};`);
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opacity
|
||||||
|
if (data.opacity) {
|
||||||
|
console.log("/* Opacity */");
|
||||||
|
for (const [key, value] of Object.entries(data.opacity)) {
|
||||||
|
console.log(` --opacity-${key}: ${value};`);
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also output raw JSON to stderr for programmatic use
|
||||||
|
console.error("\n# Raw JSON output:");
|
||||||
|
console.error(JSON.stringify(rawData || data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* List all available HeroUI Native components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node list_components.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* JSON with components array, latestVersion, and count
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||||
|
const APP_PARAM = "app=native-skills";
|
||||||
|
const LLMS_TXT_URL = "https://heroui.com/native/llms.txt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI Native API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`HTTP Error ${response.status}: ${response.statusText}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch component list from llms.txt fallback URL.
|
||||||
|
*/
|
||||||
|
async function fetchFallback() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(LLMS_TXT_URL, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Native-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
// Parse markdown to extract component names from pattern: - [ComponentName](url)
|
||||||
|
// Look for links under the Components section (### Components)
|
||||||
|
const components = [];
|
||||||
|
let inComponentsSection = false;
|
||||||
|
|
||||||
|
for (const line of content.split("\n")) {
|
||||||
|
// Check if we're entering the Components section (uses ### header)
|
||||||
|
if (line.trim() === "### Components") {
|
||||||
|
inComponentsSection = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're leaving the Components section (another ### header)
|
||||||
|
if (inComponentsSection && line.trim().startsWith("### ")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract component name from markdown link pattern
|
||||||
|
// Match: - [ComponentName](https://www.heroui.com/docs/native/components/component-name)
|
||||||
|
// Skip "All Components" which links to /components without a specific component
|
||||||
|
if (inComponentsSection) {
|
||||||
|
const match = line.match(
|
||||||
|
/^\s*-\s*\[([^\]]+)\]\(https:\/\/www\.heroui\.com\/docs\/native\/components\/[a-z]/,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
components.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.length > 0) {
|
||||||
|
console.error(`# Using fallback: ${LLMS_TXT_URL}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
components: components.sort(),
|
||||||
|
count: components.length,
|
||||||
|
latestVersion: "unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Fallback Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to list all available HeroUI Native components.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
let data = await fetchApi("/v1/components");
|
||||||
|
|
||||||
|
// Check if API returned valid data with components
|
||||||
|
if (!data || !data.components || data.components.length === 0) {
|
||||||
|
console.error("# API returned no components, trying fallback...");
|
||||||
|
data = await fetchFallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || !data.components || data.components.length === 0) {
|
||||||
|
console.error("Error: Failed to fetch component list from API and fallback");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output formatted JSON
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
|
// Print summary to stderr for human readability
|
||||||
|
console.error(
|
||||||
|
`\n# Found ${data.components.length} Native components (${data.latestVersion || "unknown"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by the Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding any notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. Please also get an approval
|
||||||
|
from the project maintainers before using the Apache License.
|
||||||
|
|
||||||
|
Copyright 2025 NextUI Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
---
|
||||||
|
name: heroui-react
|
||||||
|
description: "HeroUI v3 React component library (Tailwind CSS v4 + React Aria). Use when building UIs with HeroUI — creating Buttons, Modals, Forms, Cards; installing @heroui/react; configuring dark/light themes with oklch variables; or fetching component docs. Keywords: HeroUI, Hero UI, heroui, @heroui/react, @heroui/styles."
|
||||||
|
metadata:
|
||||||
|
author: heroui
|
||||||
|
version: "3.0.1"
|
||||||
|
---
|
||||||
|
|
||||||
|
# HeroUI v3 React Development Guide
|
||||||
|
|
||||||
|
HeroUI v3 is a component library built on **Tailwind CSS v4** and **React Aria Components**, providing accessible, customizable UI components for React applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://heroui.com/install | bash -s heroui-react
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL: v3 Only - Ignore v2 Knowledge
|
||||||
|
|
||||||
|
**This guide is for HeroUI v3 ONLY.** Do NOT apply v2 patterns — the provider, styling, and component API all changed:
|
||||||
|
|
||||||
|
| Feature | v2 (DO NOT USE) | v3 (USE THIS) |
|
||||||
|
| ------------- | --------------------------------- | ------------------------------------------- |
|
||||||
|
| Provider | `<HeroUIProvider>` required | **No Provider needed** |
|
||||||
|
| Animations | `framer-motion` package | CSS-based, no extra deps |
|
||||||
|
| Component API | Flat props: `<Card title="x">` | Compound: `<Card><Card.Header>` |
|
||||||
|
| Styling | Tailwind v3 + `@heroui/theme` | Tailwind v4 + `@heroui/styles` |
|
||||||
|
| Packages | `@heroui/system`, `@heroui/theme` | `@heroui/react`, `@heroui/styles` |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// DO NOT DO THIS - v2 pattern
|
||||||
|
import { HeroUIProvider } from "@heroui/react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
<HeroUIProvider>
|
||||||
|
<Card title="Product" description="A great product" />
|
||||||
|
</HeroUIProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORRECT (v3 patterns)
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// DO THIS - v3 pattern (no provider, compound components)
|
||||||
|
import { Card } from "@heroui/react";
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title>Product</Card.Title>
|
||||||
|
<Card.Description>A great product</Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
</Card>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always fetch v3 docs before implementing.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
- Semantic variants (`primary`, `secondary`, `tertiary`) over visual descriptions
|
||||||
|
- Composition over configuration (compound components)
|
||||||
|
- CSS variable-based theming with `oklch` color space
|
||||||
|
- BEM naming convention for predictable styling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessing Documentation & Component Information
|
||||||
|
|
||||||
|
**For component details, examples, props, and implementation patterns, always fetch documentation:**
|
||||||
|
|
||||||
|
### Using Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all available components
|
||||||
|
node scripts/list_components.mjs
|
||||||
|
|
||||||
|
# Get component documentation (MDX)
|
||||||
|
node scripts/get_component_docs.mjs Button
|
||||||
|
node scripts/get_component_docs.mjs Button Card TextField
|
||||||
|
|
||||||
|
# Get component source code
|
||||||
|
node scripts/get_source.mjs Button
|
||||||
|
|
||||||
|
# Get component CSS styles (BEM classes)
|
||||||
|
node scripts/get_styles.mjs Button
|
||||||
|
|
||||||
|
# Get theme variables
|
||||||
|
node scripts/get_theme.mjs
|
||||||
|
|
||||||
|
# Get non-component docs (guides, releases)
|
||||||
|
node scripts/get_docs.mjs /docs/react/getting-started/theming
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct MDX URLs
|
||||||
|
|
||||||
|
Component docs: `https://heroui.com/docs/react/components/{component-name}.mdx`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- Button: `https://heroui.com/docs/react/components/button.mdx`
|
||||||
|
- Modal: `https://heroui.com/docs/react/components/modal.mdx`
|
||||||
|
- Form: `https://heroui.com/docs/react/components/form.mdx`
|
||||||
|
|
||||||
|
Getting started guides: `https://heroui.com/docs/react/getting-started/{topic}.mdx`
|
||||||
|
|
||||||
|
**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation Essentials
|
||||||
|
|
||||||
|
### Quick Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i @heroui/styles @heroui/react tailwind-variants
|
||||||
|
```
|
||||||
|
|
||||||
|
### Framework Setup (Next.js App Router - Recommended)
|
||||||
|
|
||||||
|
1. **Install dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i @heroui/styles @heroui/react tailwind-variants tailwindcss @tailwindcss/postcss postcss
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create/update `app/globals.css`:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Tailwind CSS v4 - Must be first */
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* HeroUI v3 styles - Must be after Tailwind */
|
||||||
|
@import "@heroui/styles";
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Import in `app/layout.tsx`:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
{/* No Provider needed in HeroUI v3! */}
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Configure PostCSS (`postcss.config.mjs`):**
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical Setup Requirements
|
||||||
|
|
||||||
|
1. **Tailwind CSS v4 is MANDATORY** - HeroUI v3 will NOT work with Tailwind CSS v3
|
||||||
|
2. **Use Compound Components** - Components use compound structure (e.g., `Card.Header`, `Card.Content`)
|
||||||
|
3. **Use onPress, not onClick** - For better accessibility, use `onPress` event handlers
|
||||||
|
4. **Import Order Matters** - Always import Tailwind CSS before HeroUI styles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Patterns
|
||||||
|
|
||||||
|
All components use the **compound pattern** shown above (dot-notation subcomponents like `Card.Header`, `Card.Content`). Don't flatten to props — always compose with subcomponents. Fetch component docs for complete anatomy and examples.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic Variants
|
||||||
|
|
||||||
|
HeroUI uses semantic naming to communicate functional intent:
|
||||||
|
|
||||||
|
| Variant | Purpose | Usage |
|
||||||
|
| ----------- | --------------------------------- | -------------- |
|
||||||
|
| `primary` | Main action to move forward | 1 per context |
|
||||||
|
| `secondary` | Alternative actions | Multiple |
|
||||||
|
| `tertiary` | Dismissive actions (cancel, skip) | Sparingly |
|
||||||
|
| `danger` | Destructive actions | When needed |
|
||||||
|
| `ghost` | Low-emphasis actions | Minimal weight |
|
||||||
|
| `outline` | Secondary actions | Bordered style |
|
||||||
|
|
||||||
|
**Don't use raw colors** - semantic variants adapt to themes and accessibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
HeroUI v3 uses CSS variables with `oklch` color space:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--accent: oklch(0.6204 0.195 253.83);
|
||||||
|
--accent-foreground: var(--snow);
|
||||||
|
--background: oklch(0.9702 0 0);
|
||||||
|
--foreground: var(--eclipse);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get current theme variables:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/get_theme.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Color naming:**
|
||||||
|
|
||||||
|
- Without suffix = background (e.g., `--accent`)
|
||||||
|
- With `-foreground` = text color (e.g., `--accent-foreground`)
|
||||||
|
|
||||||
|
**Theme switching:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html class="dark" data-theme="dark"></html>
|
||||||
|
```
|
||||||
|
|
||||||
|
For detailed theming, fetch: `https://heroui.com/docs/react/getting-started/theming.mdx`
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get complete component documentation (MDX) for HeroUI v3 components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_component_docs.mjs Button
|
||||||
|
* node get_component_docs.mjs Button Card TextField
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX documentation including imports, usage, variants, props, examples
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const FALLBACK_BASE = "https://heroui.com";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert PascalCase to kebab-case.
|
||||||
|
*/
|
||||||
|
function toKebabCase(name) {
|
||||||
|
return name
|
||||||
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||||
|
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint, method = "GET", body = null) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "HeroUI-Skill/1.0",
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch MDX directly from v3.heroui.com as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchFallback(component) {
|
||||||
|
const kebabName = toKebabCase(component);
|
||||||
|
const url = `${FALLBACK_BASE}/docs/react/components/${kebabName}.mdx`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return {component, error: `Failed to fetch docs for ${component}`};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
content,
|
||||||
|
contentType: "mdx",
|
||||||
|
source: "fallback",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {component, error: `Failed to fetch docs for ${component}`};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get component documentation.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_component_docs.mjs <Component1> [Component2] ...");
|
||||||
|
console.error("Example: node get_component_docs.mjs Button Card");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = args;
|
||||||
|
|
||||||
|
// Try API first - use POST /v1/components/docs for batch requests
|
||||||
|
console.error(`# Fetching docs for: ${components.join(", ")}...`);
|
||||||
|
const data = await fetchApi("/v1/components/docs", "POST", {components});
|
||||||
|
|
||||||
|
if (data && data.results) {
|
||||||
|
// Output results
|
||||||
|
if (data.results.length === 1) {
|
||||||
|
// Single component - output content directly for easier reading
|
||||||
|
const result = data.results[0];
|
||||||
|
|
||||||
|
if (result.content) {
|
||||||
|
console.log(result.content);
|
||||||
|
} else if (result.error) {
|
||||||
|
console.error(`# Error for ${result.component}: ${result.error}`);
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multiple components - output as JSON array
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to individual component fetches
|
||||||
|
console.error("# API failed, using fallback...");
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const component of components) {
|
||||||
|
const result = await fetchFallback(component);
|
||||||
|
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
if (results.length === 1) {
|
||||||
|
// Single component - output content directly for easier reading
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (result.content) {
|
||||||
|
console.log(result.content);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multiple components - output as JSON array
|
||||||
|
console.log(JSON.stringify(results, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get non-component HeroUI documentation (guides, theming, releases).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_docs.mjs /docs/react/getting-started/theming
|
||||||
|
* node get_docs.mjs /docs/react/releases/v3-0-0-beta-3
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* MDX documentation content
|
||||||
|
*
|
||||||
|
* Note: For component docs, use get_component_docs.mjs instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const FALLBACK_BASE = "https://heroui.com";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch documentation from HeroUI API.
|
||||||
|
* Uses v1 endpoint pattern: /v1/docs/:path
|
||||||
|
*/
|
||||||
|
async function fetchApi(path) {
|
||||||
|
// The v1 API expects path without /docs/ prefix
|
||||||
|
// Input: /docs/react/getting-started/theming
|
||||||
|
// API expects: react/getting-started/theming (route is /v1/docs/:path(*))
|
||||||
|
let apiPath = path.startsWith("/docs/")
|
||||||
|
? path.slice(6) // Remove /docs/ prefix
|
||||||
|
: path.startsWith("/")
|
||||||
|
? path.slice(1) // Remove leading /
|
||||||
|
: path;
|
||||||
|
|
||||||
|
const separator = "?";
|
||||||
|
const url = `${API_BASE}/v1/docs/${apiPath}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch MDX directly from v3.heroui.com as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchFallback(path) {
|
||||||
|
// Ensure path starts with /docs and ends with .mdx
|
||||||
|
let cleanPath = path.replace(/^\//, "");
|
||||||
|
|
||||||
|
if (!cleanPath.endsWith(".mdx")) {
|
||||||
|
cleanPath = `${cleanPath}.mdx`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${FALLBACK_BASE}/${cleanPath}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return {error: `HTTP ${response.status}: ${response.statusText}`, path};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
contentType: "mdx",
|
||||||
|
path,
|
||||||
|
source: "fallback",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {error: `Fetch Error: ${error.message}`, path};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get documentation for specified path.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_docs.mjs <path>");
|
||||||
|
console.error("Example: node get_docs.mjs /docs/react/getting-started/theming");
|
||||||
|
console.error();
|
||||||
|
console.error("Available paths include:");
|
||||||
|
console.error(" /docs/react/getting-started/theming");
|
||||||
|
console.error(" /docs/react/getting-started/colors");
|
||||||
|
console.error(" /docs/react/getting-started/animations");
|
||||||
|
console.error(" /docs/react/releases/v3-0-0-beta-3");
|
||||||
|
console.error();
|
||||||
|
console.error("Note: For component docs, use get_component_docs.mjs instead.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = args[0];
|
||||||
|
|
||||||
|
// Check if user is trying to get component docs
|
||||||
|
if (path.includes("/components/")) {
|
||||||
|
console.error("# Warning: Use get_component_docs.mjs for component documentation.");
|
||||||
|
const componentName = path.split("/").pop().replace(".mdx", "");
|
||||||
|
const titleCase = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
||||||
|
|
||||||
|
console.error(`# Example: node get_component_docs.mjs ${titleCase}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`# Fetching documentation for ${path}...`);
|
||||||
|
|
||||||
|
// Try API first
|
||||||
|
const data = await fetchApi(path);
|
||||||
|
|
||||||
|
if (data && data.content) {
|
||||||
|
data.source = "api";
|
||||||
|
console.log(data.content);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to direct fetch
|
||||||
|
console.error("# API failed, using fallback...");
|
||||||
|
const fallbackData = await fetchFallback(path);
|
||||||
|
|
||||||
|
if (fallbackData.content) {
|
||||||
|
console.log(fallbackData.content);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(fallbackData, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get React/TypeScript source code implementation for HeroUI v3 components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_source.mjs Button
|
||||||
|
* node get_source.mjs Button Accordion Card
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* Full TSX source code with GitHub URL for each component
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint, method = "GET", body = null) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "HeroUI-Skill/1.0",
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch source code directly from GitHub as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchGithubFallback(component) {
|
||||||
|
// Try common patterns for component paths
|
||||||
|
const patterns = [
|
||||||
|
`packages/react/src/components/${component.toLowerCase()}/${component.toLowerCase()}.tsx`,
|
||||||
|
`packages/react/src/components/${component.toLowerCase()}/index.tsx`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of patterns) {
|
||||||
|
const url = `${GITHUB_RAW_BASE}/${path}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
filePath: path,
|
||||||
|
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
|
||||||
|
source: "fallback",
|
||||||
|
sourceCode: content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {component, error: `Failed to fetch source for ${component}`};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get source code for specified components.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_source.mjs <Component1> [Component2] ...");
|
||||||
|
console.error("Example: node get_source.mjs Button Accordion");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = args;
|
||||||
|
|
||||||
|
// Try API first
|
||||||
|
console.error(`# Fetching source code for: ${components.join(", ")}...`);
|
||||||
|
const data = await fetchApi("/v1/components/source", "POST", {components});
|
||||||
|
|
||||||
|
if (data && data.results) {
|
||||||
|
for (const result of data.results) {
|
||||||
|
result.source = "api";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
if (data.results.length === 1) {
|
||||||
|
const result = data.results[0];
|
||||||
|
|
||||||
|
if (result.sourceCode) {
|
||||||
|
console.log(`// File: ${result.filePath || "unknown"}`);
|
||||||
|
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
|
||||||
|
console.log();
|
||||||
|
console.log(result.sourceCode);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to GitHub direct fetch
|
||||||
|
console.error("# API failed, using GitHub fallback...");
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const component of components) {
|
||||||
|
const result = await fetchGithubFallback(component);
|
||||||
|
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === 1) {
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (result.sourceCode) {
|
||||||
|
console.log(`// File: ${result.filePath || "unknown"}`);
|
||||||
|
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
|
||||||
|
console.log();
|
||||||
|
console.log(result.sourceCode);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify({results}, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get CSS styles (BEM classes) for HeroUI v3 components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_styles.mjs Button
|
||||||
|
* node get_styles.mjs Button Card Chip
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* CSS file content with BEM classes and GitHub URL for each component
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint, method = "GET", body = null) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "HeroUI-Skill/1.0",
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch CSS styles directly from GitHub as fallback.
|
||||||
|
*/
|
||||||
|
async function fetchGithubFallback(component) {
|
||||||
|
// Try common patterns for style paths
|
||||||
|
const patterns = [
|
||||||
|
`packages/styles/src/components/${component.toLowerCase()}.css`,
|
||||||
|
`packages/styles/components/${component.toLowerCase()}.css`,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of patterns) {
|
||||||
|
const url = `${GITHUB_RAW_BASE}/${path}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
filePath: path,
|
||||||
|
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
|
||||||
|
source: "fallback",
|
||||||
|
stylesCode: content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {component, error: `Failed to fetch styles for ${component}`};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get CSS styles for specified components.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error("Usage: node get_styles.mjs <Component1> [Component2] ...");
|
||||||
|
console.error("Example: node get_styles.mjs Button Card");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = args;
|
||||||
|
|
||||||
|
// Try API first
|
||||||
|
console.error(`# Fetching styles for: ${components.join(", ")}...`);
|
||||||
|
const data = await fetchApi("/v1/components/styles", "POST", {components});
|
||||||
|
|
||||||
|
if (data && data.results) {
|
||||||
|
for (const result of data.results) {
|
||||||
|
result.source = "api";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
if (data.results.length === 1) {
|
||||||
|
const result = data.results[0];
|
||||||
|
|
||||||
|
if (result.stylesCode) {
|
||||||
|
console.log(`/* File: ${result.filePath || "unknown"} */`);
|
||||||
|
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
|
||||||
|
console.log();
|
||||||
|
console.log(result.stylesCode);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to GitHub direct fetch
|
||||||
|
console.error("# API failed, using GitHub fallback...");
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const component of components) {
|
||||||
|
const result = await fetchGithubFallback(component);
|
||||||
|
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === 1) {
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
if (result.stylesCode) {
|
||||||
|
console.log(`/* File: ${result.filePath || "unknown"} */`);
|
||||||
|
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
|
||||||
|
console.log();
|
||||||
|
console.log(result.stylesCode);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify({results}, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Get theme variables and design tokens for HeroUI v3.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node get_theme.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* Theme variables organized by common/light/dark with oklch color format
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
|
||||||
|
// Fallback theme reference when API is unavailable
|
||||||
|
const FALLBACK_THEME = {
|
||||||
|
common: {
|
||||||
|
base: [
|
||||||
|
{name: "--font-sans", value: "ui-sans-serif, system-ui, sans-serif"},
|
||||||
|
{name: "--font-mono", value: "ui-monospace, monospace"},
|
||||||
|
{name: "--radius-sm", value: "0.375rem"},
|
||||||
|
{name: "--radius-md", value: "0.5rem"},
|
||||||
|
{name: "--radius-lg", value: "0.75rem"},
|
||||||
|
{name: "--radius-full", value: "9999px"},
|
||||||
|
],
|
||||||
|
calculated: [{name: "--spacing-unit", value: "0.25rem"}],
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
semantic: [
|
||||||
|
{name: "--color-background", value: "oklch(14.5% 0 0)"},
|
||||||
|
{name: "--color-foreground", value: "oklch(98.4% 0 0)"},
|
||||||
|
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
|
||||||
|
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
|
||||||
|
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
|
||||||
|
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
latestVersion: "3.0.0-beta",
|
||||||
|
light: {
|
||||||
|
semantic: [
|
||||||
|
{name: "--color-background", value: "oklch(100% 0 0)"},
|
||||||
|
{name: "--color-foreground", value: "oklch(14.5% 0 0)"},
|
||||||
|
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
|
||||||
|
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
|
||||||
|
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
|
||||||
|
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
note: "This is a fallback. For complete theme variables, ensure the API is accessible.",
|
||||||
|
source: "fallback",
|
||||||
|
theme: "default",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`# API Error: HTTP ${response.status}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`# API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format theme variables for display.
|
||||||
|
*/
|
||||||
|
function formatVariables(variables) {
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
for (const variable of variables) {
|
||||||
|
const name = variable.name || "";
|
||||||
|
const value = variable.value || "";
|
||||||
|
const desc = variable.description || "";
|
||||||
|
|
||||||
|
if (desc) {
|
||||||
|
lines.push(` ${name}: ${value}; /* ${desc} */`);
|
||||||
|
} else {
|
||||||
|
lines.push(` ${name}: ${value};`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to get theme variables.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
console.error("# Fetching theme variables...");
|
||||||
|
|
||||||
|
const rawData = await fetchApi("/v1/themes/variables?theme=default");
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let version;
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
console.error("# API failed, using fallback theme reference...");
|
||||||
|
data = FALLBACK_THEME;
|
||||||
|
version = FALLBACK_THEME.latestVersion || "unknown";
|
||||||
|
} else {
|
||||||
|
// Handle API response format: { themes: [...], latestVersion: "..." }
|
||||||
|
if (rawData.themes && rawData.themes.length > 0) {
|
||||||
|
data = rawData.themes[0]; // Get first theme (default)
|
||||||
|
version = rawData.latestVersion || rawData.version || "unknown";
|
||||||
|
} else {
|
||||||
|
// Direct format
|
||||||
|
data = rawData;
|
||||||
|
version = rawData.latestVersion || "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output as formatted CSS-like structure for readability
|
||||||
|
console.log("/* HeroUI v3 Theme Variables */");
|
||||||
|
console.log(`/* Theme: ${data.theme || "default"} */`);
|
||||||
|
console.log(`/* Version: ${version} */`);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Common variables
|
||||||
|
if (data.common) {
|
||||||
|
console.log(":root {");
|
||||||
|
console.log(" /* Base Variables */");
|
||||||
|
if (data.common.base) {
|
||||||
|
console.log(formatVariables(data.common.base));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
console.log(" /* Calculated Variables */");
|
||||||
|
if (data.common.calculated) {
|
||||||
|
console.log(formatVariables(data.common.calculated));
|
||||||
|
}
|
||||||
|
console.log("}");
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light mode
|
||||||
|
if (data.light) {
|
||||||
|
console.log(":root, [data-theme='light'] {");
|
||||||
|
console.log(" /* Light Mode Semantic Variables */");
|
||||||
|
if (data.light.semantic) {
|
||||||
|
console.log(formatVariables(data.light.semantic));
|
||||||
|
}
|
||||||
|
console.log("}");
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode
|
||||||
|
if (data.dark) {
|
||||||
|
console.log("[data-theme='dark'] {");
|
||||||
|
console.log(" /* Dark Mode Semantic Variables */");
|
||||||
|
if (data.dark.semantic) {
|
||||||
|
console.log(formatVariables(data.dark.semantic));
|
||||||
|
}
|
||||||
|
console.log("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also output raw JSON to stderr for programmatic use
|
||||||
|
console.error("\n# Raw JSON output:");
|
||||||
|
console.error(JSON.stringify(rawData || data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* List all available HeroUI v3 components.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node list_components.mjs
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* JSON with components array, latestVersion, and count
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
|
||||||
|
const APP_PARAM = "app=react-skills";
|
||||||
|
const LLMS_TXT_URL = "https://heroui.com/react/llms.txt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from HeroUI API with app parameter for analytics.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint) {
|
||||||
|
const separator = endpoint.includes("?") ? "&" : "?";
|
||||||
|
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`HTTP Error ${response.status}: ${response.statusText}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`API Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch component list from llms.txt fallback URL.
|
||||||
|
*/
|
||||||
|
async function fetchFallback() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(LLMS_TXT_URL, {
|
||||||
|
headers: {"User-Agent": "HeroUI-Skill/1.0"},
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await response.text();
|
||||||
|
|
||||||
|
// Parse markdown to extract component names from pattern: - [ComponentName](url)
|
||||||
|
// Look for links under the Components section (### Components)
|
||||||
|
const components = [];
|
||||||
|
let inComponentsSection = false;
|
||||||
|
|
||||||
|
for (const line of content.split("\n")) {
|
||||||
|
// Check if we're entering the Components section (uses ### header)
|
||||||
|
if (line.trim() === "### Components") {
|
||||||
|
inComponentsSection = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're leaving the Components section (another ### header)
|
||||||
|
if (inComponentsSection && line.trim().startsWith("### ")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract component name from markdown link pattern
|
||||||
|
// Match: - [ComponentName](https://www.heroui.com/docs/react/components/component-name)
|
||||||
|
// Skip "All Components" which links to /components without a specific component
|
||||||
|
if (inComponentsSection) {
|
||||||
|
const match = line.match(
|
||||||
|
/^\s*-\s*\[([^\]]+)\]\(https:\/\/www\.heroui\.com\/docs\/react\/components\/[a-z]/,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
components.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.length > 0) {
|
||||||
|
console.error(`# Using fallback: ${LLMS_TXT_URL}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
components: components.sort(),
|
||||||
|
count: components.length,
|
||||||
|
latestVersion: "unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Fallback Error: ${error.message}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to list all available HeroUI v3 components.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
let data = await fetchApi("/v1/components");
|
||||||
|
|
||||||
|
// Check if API returned valid data with components
|
||||||
|
if (!data || !data.components || data.components.length === 0) {
|
||||||
|
console.error("# API returned no components, trying fallback...");
|
||||||
|
data = await fetchFallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || !data.components || data.components.length === 0) {
|
||||||
|
console.error("Error: Failed to fetch component list from API and fallback");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output formatted JSON
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
|
// Print summary to stderr for human readability
|
||||||
|
console.error(
|
||||||
|
`\n# Found ${data.components.length} components (${data.latestVersion || "unknown"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../.agents/skills/heroui-migration
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../.agents/skills/heroui-native
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../.agents/skills/heroui-react
|
||||||
@@ -6,6 +6,16 @@
|
|||||||
"shadcn@latest",
|
"shadcn@latest",
|
||||||
"mcp"
|
"mcp"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"XcodeBuildMCP": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"xcodebuildmcp@latest",
|
||||||
|
"mcp"
|
||||||
|
],
|
||||||
|
"env": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,14 @@ const TYPES = [
|
|||||||
const controlClass =
|
const controlClass =
|
||||||
"h-9 w-full rounded-3xl border border-transparent bg-input/50 px-3 text-sm text-foreground outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30";
|
"h-9 w-full rounded-3xl border border-transparent bg-input/50 px-3 text-sm text-foreground outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30";
|
||||||
|
|
||||||
|
// Local start-of-day so the calendar can disable days strictly before today
|
||||||
|
// (appointments can't be scheduled in the past).
|
||||||
|
const startOfToday = () => {
|
||||||
|
const d = new Date();
|
||||||
|
d.setHours(0, 0, 0, 0);
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
function Field({ label, children }: { label: string; children: ReactNode }) {
|
function Field({ label, children }: { label: string; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="flex flex-col gap-1.5">
|
<label className="flex flex-col gap-1.5">
|
||||||
@@ -251,6 +259,7 @@ export function AddAppointmentDialog({
|
|||||||
/>
|
/>
|
||||||
<PopoverPopup>
|
<PopoverPopup>
|
||||||
<Calendar
|
<Calendar
|
||||||
|
disabled={{ before: startOfToday() }}
|
||||||
mode="single"
|
mode="single"
|
||||||
onSelect={(d) => {
|
onSelect={(d) => {
|
||||||
if (d) {
|
if (d) {
|
||||||
|
|||||||
@@ -59,12 +59,23 @@ function Field({ label, children }: { label: string; children: ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local start-of-day, used to disable days strictly before today.
|
||||||
|
const startOfToday = () => {
|
||||||
|
const d = new Date();
|
||||||
|
d.setHours(0, 0, 0, 0);
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
function DatePicker({
|
function DatePicker({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
allowPast = true,
|
||||||
}: {
|
}: {
|
||||||
value: Date;
|
value: Date;
|
||||||
onChange: (d: Date) => void;
|
onChange: (d: Date) => void;
|
||||||
|
// When false, days before today are disabled (used for the issue date unless
|
||||||
|
// the clinician opts into back-dating an older invoice).
|
||||||
|
allowPast?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
return (
|
return (
|
||||||
@@ -87,6 +98,7 @@ function DatePicker({
|
|||||||
/>
|
/>
|
||||||
<PopoverPopup>
|
<PopoverPopup>
|
||||||
<Calendar
|
<Calendar
|
||||||
|
disabled={allowPast ? undefined : { before: startOfToday() }}
|
||||||
mode="single"
|
mode="single"
|
||||||
onSelect={(d) => {
|
onSelect={(d) => {
|
||||||
if (d) {
|
if (d) {
|
||||||
@@ -131,6 +143,9 @@ export function InvoiceFormDialog({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
const [issuedAt, setIssuedAt] = useState<Date>(() => new Date());
|
const [issuedAt, setIssuedAt] = useState<Date>(() => new Date());
|
||||||
|
// Off by default: the issue date can't be back-dated unless the clinician
|
||||||
|
// opts in (for recording an older, pre-existing invoice).
|
||||||
|
const [allowBackdate, setAllowBackdate] = useState(false);
|
||||||
const [hasDue, setHasDue] = useState(false);
|
const [hasDue, setHasDue] = useState(false);
|
||||||
const [dueAt, setDueAt] = useState<Date>(() => new Date());
|
const [dueAt, setDueAt] = useState<Date>(() => new Date());
|
||||||
const [status, setStatus] = useState<InvoiceStatus>("draft");
|
const [status, setStatus] = useState<InvoiceStatus>("draft");
|
||||||
@@ -143,6 +158,8 @@ export function InvoiceFormDialog({
|
|||||||
if (!open) return;
|
if (!open) return;
|
||||||
if (mode === "edit" && invoice) {
|
if (mode === "edit" && invoice) {
|
||||||
setIssuedAt(new Date(`${invoice.issuedAt}T00:00:00`));
|
setIssuedAt(new Date(`${invoice.issuedAt}T00:00:00`));
|
||||||
|
// Existing invoices legitimately carry past issue dates.
|
||||||
|
setAllowBackdate(true);
|
||||||
setHasDue(Boolean(invoice.dueAt));
|
setHasDue(Boolean(invoice.dueAt));
|
||||||
setDueAt(new Date(`${invoice.dueAt ?? invoice.issuedAt}T00:00:00`));
|
setDueAt(new Date(`${invoice.dueAt ?? invoice.issuedAt}T00:00:00`));
|
||||||
setStatus(invoice.status);
|
setStatus(invoice.status);
|
||||||
@@ -153,6 +170,7 @@ export function InvoiceFormDialog({
|
|||||||
} else {
|
} else {
|
||||||
setSelected(null);
|
setSelected(null);
|
||||||
setIssuedAt(new Date());
|
setIssuedAt(new Date());
|
||||||
|
setAllowBackdate(false);
|
||||||
setHasDue(false);
|
setHasDue(false);
|
||||||
setDueAt(new Date());
|
setDueAt(new Date());
|
||||||
setStatus("draft");
|
setStatus("draft");
|
||||||
@@ -310,9 +328,24 @@ export function InvoiceFormDialog({
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Field label={t("invoices.dialog.issued")}>
|
<div className="flex flex-col gap-1.5">
|
||||||
<DatePicker onChange={setIssuedAt} value={issuedAt} />
|
<span className="flex items-center justify-between gap-2 text-muted-foreground text-xs">
|
||||||
</Field>
|
{t("invoices.dialog.issued")}
|
||||||
|
<label className="flex items-center gap-1 text-[11px]">
|
||||||
|
<input
|
||||||
|
checked={allowBackdate}
|
||||||
|
onChange={(e) => setAllowBackdate(e.target.checked)}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
{t("invoices.dialog.backdate")}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
<DatePicker
|
||||||
|
allowPast={allowBackdate}
|
||||||
|
onChange={setIssuedAt}
|
||||||
|
value={issuedAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<span className="flex items-center justify-between text-muted-foreground text-xs">
|
<span className="flex items-center justify-between text-muted-foreground text-xs">
|
||||||
{t("invoices.dialog.due")}
|
{t("invoices.dialog.due")}
|
||||||
|
|||||||
@@ -293,23 +293,6 @@ export function ProfilePanel() {
|
|||||||
</div>
|
</div>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
{/* Future features — intentionally not wired to persistence yet. */}
|
|
||||||
<SettingsSection
|
|
||||||
description={t("settings.profile.featuresDescription")}
|
|
||||||
title={t("settings.profile.features")}
|
|
||||||
>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<ToggleRow
|
|
||||||
description={t("settings.profile.notif.patientStorageDesc")}
|
|
||||||
title={t("settings.profile.notif.patientStorage")}
|
|
||||||
/>
|
|
||||||
<ToggleRow
|
|
||||||
description={t("settings.profile.notif.requireSignedDesc")}
|
|
||||||
title={t("settings.profile.notif.requireSigned")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsSection>
|
|
||||||
|
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
description={t("settings.profile.dangerZoneDescription")}
|
description={t("settings.profile.dangerZoneDescription")}
|
||||||
title={t("settings.profile.dangerZone")}
|
title={t("settings.profile.dangerZone")}
|
||||||
|
|||||||
@@ -482,6 +482,7 @@
|
|||||||
"fileNumber": "ملف رقم {{number}}",
|
"fileNumber": "ملف رقم {{number}}",
|
||||||
"change": "تغيير",
|
"change": "تغيير",
|
||||||
"issued": "تاريخ الإصدار",
|
"issued": "تاريخ الإصدار",
|
||||||
|
"backdate": "تأريخ رجعي",
|
||||||
"due": "تاريخ الاستحقاق",
|
"due": "تاريخ الاستحقاق",
|
||||||
"status": "الحالة",
|
"status": "الحالة",
|
||||||
"notes": "ملاحظات",
|
"notes": "ملاحظات",
|
||||||
@@ -1846,8 +1847,6 @@
|
|||||||
"patientNotificationsDescription": "رسائل تُرسل إلى المرضى بشأن سجلاتهم ونتائجهم والموافقات المعلّقة",
|
"patientNotificationsDescription": "رسائل تُرسل إلى المرضى بشأن سجلاتهم ونتائجهم والموافقات المعلّقة",
|
||||||
"accountNotifications": "إشعارات الحساب",
|
"accountNotifications": "إشعارات الحساب",
|
||||||
"accountNotificationsDescription": "إشعارات تُرسل إليك بشأن مرضاك وفريق الرعاية",
|
"accountNotificationsDescription": "إشعارات تُرسل إليك بشأن مرضاك وفريق الرعاية",
|
||||||
"features": "الميزات",
|
|
||||||
"featuresDescription": "أدر ميزات alpha و beta لحسابك",
|
|
||||||
"dangerZone": "منطقة الخطر",
|
"dangerZone": "منطقة الخطر",
|
||||||
"dangerZoneDescription": "إجراءات لا رجعة فيها لحسابك",
|
"dangerZoneDescription": "إجراءات لا رجعة فيها لحسابك",
|
||||||
"deleteAccount": "حذف الحساب",
|
"deleteAccount": "حذف الحساب",
|
||||||
@@ -1886,11 +1885,7 @@
|
|||||||
"pendingApprovals": "الموافقات المعلّقة",
|
"pendingApprovals": "الموافقات المعلّقة",
|
||||||
"pendingApprovalsDesc": "أبلغني عند موافقة مريض على تغيير معلّق أو رفضه",
|
"pendingApprovalsDesc": "أبلغني عند موافقة مريض على تغيير معلّق أو رفضه",
|
||||||
"recordsShared": "سجلات شُوركت معي",
|
"recordsShared": "سجلات شُوركت معي",
|
||||||
"recordsSharedDesc": "أبلغني عند مشاركة مريض سجلًا معي",
|
"recordsSharedDesc": "أبلغني عند مشاركة مريض سجلًا معي"
|
||||||
"patientStorage": "تخزين مملوك للمريض (beta)",
|
|
||||||
"patientStorageDesc": "اكتب السجلات على جهاز المريض الخاص بدلًا من قاعدة بياناتك",
|
|
||||||
"requireSigned": "طلب سجلات موقّعة",
|
|
||||||
"requireSignedDesc": "طلب توقيع على كل تغيير تجريه على سجل مريض"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"careTeam": {
|
"careTeam": {
|
||||||
|
|||||||
@@ -466,6 +466,7 @@
|
|||||||
"fileNumber": "Aktennr. {{number}}",
|
"fileNumber": "Aktennr. {{number}}",
|
||||||
"change": "Ändern",
|
"change": "Ändern",
|
||||||
"issued": "Ausgestellt",
|
"issued": "Ausgestellt",
|
||||||
|
"backdate": "Rückdatieren",
|
||||||
"due": "Fälligkeitsdatum",
|
"due": "Fälligkeitsdatum",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"notes": "Notizen",
|
"notes": "Notizen",
|
||||||
@@ -1826,8 +1827,6 @@
|
|||||||
"patientNotificationsDescription": "E-Mails an Patienten über ihre Datensätze, Ergebnisse und ausstehenden Genehmigungen",
|
"patientNotificationsDescription": "E-Mails an Patienten über ihre Datensätze, Ergebnisse und ausstehenden Genehmigungen",
|
||||||
"accountNotifications": "Kontobenachrichtigungen",
|
"accountNotifications": "Kontobenachrichtigungen",
|
||||||
"accountNotificationsDescription": "Benachrichtigungen an Sie über Ihre Patienten und das Behandlungsteam",
|
"accountNotificationsDescription": "Benachrichtigungen an Sie über Ihre Patienten und das Behandlungsteam",
|
||||||
"features": "Funktionen",
|
|
||||||
"featuresDescription": "Verwalten Sie Alpha- und Beta-Funktionen für Ihr Konto",
|
|
||||||
"dangerZone": "Gefahrenzone",
|
"dangerZone": "Gefahrenzone",
|
||||||
"dangerZoneDescription": "Unumkehrbare Aktionen für Ihr Konto",
|
"dangerZoneDescription": "Unumkehrbare Aktionen für Ihr Konto",
|
||||||
"deleteAccount": "Konto löschen",
|
"deleteAccount": "Konto löschen",
|
||||||
@@ -1866,11 +1865,7 @@
|
|||||||
"pendingApprovals": "Ausstehende Genehmigungen",
|
"pendingApprovals": "Ausstehende Genehmigungen",
|
||||||
"pendingApprovalsDesc": "Benachrichtigen Sie mich, wenn ein Patient eine ausstehende Änderung genehmigt oder ablehnt",
|
"pendingApprovalsDesc": "Benachrichtigen Sie mich, wenn ein Patient eine ausstehende Änderung genehmigt oder ablehnt",
|
||||||
"recordsShared": "Mit mir geteilte Datensätze",
|
"recordsShared": "Mit mir geteilte Datensätze",
|
||||||
"recordsSharedDesc": "Benachrichtigen Sie mich, wenn ein Patient einen Datensatz mit mir teilt",
|
"recordsSharedDesc": "Benachrichtigen Sie mich, wenn ein Patient einen Datensatz mit mir teilt"
|
||||||
"patientStorage": "Patienteneigener Speicher (Beta)",
|
|
||||||
"patientStorageDesc": "Schreiben Sie Datensätze auf das eigene Gerät des Patienten statt in Ihre Datenbank",
|
|
||||||
"requireSigned": "Signierte Datensätze erforderlich",
|
|
||||||
"requireSignedDesc": "Erfordern Sie eine Signatur für jede Änderung, die Sie an einer Patientenakte vornehmen"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"careTeam": {
|
"careTeam": {
|
||||||
|
|||||||
@@ -466,6 +466,7 @@
|
|||||||
"fileNumber": "File #{{number}}",
|
"fileNumber": "File #{{number}}",
|
||||||
"change": "Change",
|
"change": "Change",
|
||||||
"issued": "Issued",
|
"issued": "Issued",
|
||||||
|
"backdate": "Back-date",
|
||||||
"due": "Due date",
|
"due": "Due date",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
@@ -1826,8 +1827,6 @@
|
|||||||
"patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals",
|
"patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals",
|
||||||
"accountNotifications": "Account notifications",
|
"accountNotifications": "Account notifications",
|
||||||
"accountNotificationsDescription": "Notifications sent to you about your patients and the care team",
|
"accountNotificationsDescription": "Notifications sent to you about your patients and the care team",
|
||||||
"features": "Features",
|
|
||||||
"featuresDescription": "Manage alpha & beta features for your account",
|
|
||||||
"dangerZone": "Danger Zone",
|
"dangerZone": "Danger Zone",
|
||||||
"dangerZoneDescription": "Irreversible actions for your account",
|
"dangerZoneDescription": "Irreversible actions for your account",
|
||||||
"deleteAccount": "Delete account",
|
"deleteAccount": "Delete account",
|
||||||
@@ -1866,11 +1865,7 @@
|
|||||||
"pendingApprovals": "Pending approvals",
|
"pendingApprovals": "Pending approvals",
|
||||||
"pendingApprovalsDesc": "Notify me when a patient approves or rejects a pending change",
|
"pendingApprovalsDesc": "Notify me when a patient approves or rejects a pending change",
|
||||||
"recordsShared": "Records shared with me",
|
"recordsShared": "Records shared with me",
|
||||||
"recordsSharedDesc": "Notify me when a patient shares a record with me",
|
"recordsSharedDesc": "Notify me when a patient shares a record with me"
|
||||||
"patientStorage": "Patient-owned storage (beta)",
|
|
||||||
"patientStorageDesc": "Write records to the patient's own device instead of your database",
|
|
||||||
"requireSigned": "Require signed records",
|
|
||||||
"requireSignedDesc": "Require a signature on every change you make to a patient record"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"careTeam": {
|
"careTeam": {
|
||||||
|
|||||||
@@ -466,6 +466,7 @@
|
|||||||
"fileNumber": "Dossier n° {{number}}",
|
"fileNumber": "Dossier n° {{number}}",
|
||||||
"change": "Changer",
|
"change": "Changer",
|
||||||
"issued": "Émise",
|
"issued": "Émise",
|
||||||
|
"backdate": "Antidater",
|
||||||
"due": "Date d'échéance",
|
"due": "Date d'échéance",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
@@ -1826,8 +1827,6 @@
|
|||||||
"patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",
|
"patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",
|
||||||
"accountNotifications": "Notifications du compte",
|
"accountNotifications": "Notifications du compte",
|
||||||
"accountNotificationsDescription": "Notifications qui vous sont envoyées à propos de vos patients et de l'équipe soignante",
|
"accountNotificationsDescription": "Notifications qui vous sont envoyées à propos de vos patients et de l'équipe soignante",
|
||||||
"features": "Fonctionnalités",
|
|
||||||
"featuresDescription": "Gérez les fonctionnalités alpha & bêta de votre compte",
|
|
||||||
"dangerZone": "Zone de danger",
|
"dangerZone": "Zone de danger",
|
||||||
"dangerZoneDescription": "Actions irréversibles pour votre compte",
|
"dangerZoneDescription": "Actions irréversibles pour votre compte",
|
||||||
"deleteAccount": "Supprimer le compte",
|
"deleteAccount": "Supprimer le compte",
|
||||||
@@ -1866,11 +1865,7 @@
|
|||||||
"pendingApprovals": "Approbations en attente",
|
"pendingApprovals": "Approbations en attente",
|
||||||
"pendingApprovalsDesc": "Me notifier lorsqu'un patient approuve ou rejette une modification en attente",
|
"pendingApprovalsDesc": "Me notifier lorsqu'un patient approuve ou rejette une modification en attente",
|
||||||
"recordsShared": "Dossiers partagés avec moi",
|
"recordsShared": "Dossiers partagés avec moi",
|
||||||
"recordsSharedDesc": "Me notifier lorsqu'un patient partage un dossier avec moi",
|
"recordsSharedDesc": "Me notifier lorsqu'un patient partage un dossier avec moi"
|
||||||
"patientStorage": "Stockage appartenant au patient (bêta)",
|
|
||||||
"patientStorageDesc": "Écrivez les dossiers sur l'appareil du patient plutôt que dans votre base de données",
|
|
||||||
"requireSigned": "Exiger des dossiers signés",
|
|
||||||
"requireSignedDesc": "Exiger une signature sur chaque modification que vous apportez à un dossier patient"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"careTeam": {
|
"careTeam": {
|
||||||
|
|||||||
@@ -466,6 +466,7 @@
|
|||||||
"fileNumber": "Faylka #{{number}}",
|
"fileNumber": "Faylka #{{number}}",
|
||||||
"change": "Beddel",
|
"change": "Beddel",
|
||||||
"issued": "La bixiyay",
|
"issued": "La bixiyay",
|
||||||
|
"backdate": "Taariikh hore",
|
||||||
"due": "Taariikhda la sugayo",
|
"due": "Taariikhda la sugayo",
|
||||||
"status": "Xaalad",
|
"status": "Xaalad",
|
||||||
"notes": "Qoraallo",
|
"notes": "Qoraallo",
|
||||||
@@ -1826,8 +1827,6 @@
|
|||||||
"patientNotificationsDescription": "Iimaylada loo diro bukaannada oo ku saabsan diiwaannadooda, natiijooyinka, iyo ansixinta la sugayo",
|
"patientNotificationsDescription": "Iimaylada loo diro bukaannada oo ku saabsan diiwaannadooda, natiijooyinka, iyo ansixinta la sugayo",
|
||||||
"accountNotifications": "Ogeysiisyada akoonka",
|
"accountNotifications": "Ogeysiisyada akoonka",
|
||||||
"accountNotificationsDescription": "Ogeysiisyada laguu soo diro oo ku saabsan bukaannadaada iyo kooxda daryeelka",
|
"accountNotificationsDescription": "Ogeysiisyada laguu soo diro oo ku saabsan bukaannadaada iyo kooxda daryeelka",
|
||||||
"features": "Astaamaha",
|
|
||||||
"featuresDescription": "Maaree astaamaha alpha & beta ee akoonkaaga",
|
|
||||||
"dangerZone": "Aagga khatarta",
|
"dangerZone": "Aagga khatarta",
|
||||||
"dangerZoneDescription": "Ficillo aan dib loo celin karin oo akoonkaaga",
|
"dangerZoneDescription": "Ficillo aan dib loo celin karin oo akoonkaaga",
|
||||||
"deleteAccount": "Tirtir akoonka",
|
"deleteAccount": "Tirtir akoonka",
|
||||||
@@ -1866,11 +1865,7 @@
|
|||||||
"pendingApprovals": "Ansixinta la sugayo",
|
"pendingApprovals": "Ansixinta la sugayo",
|
||||||
"pendingApprovalsDesc": "I ogeysii marka bukaan ansixiyo ama diido isbeddel la sugayo",
|
"pendingApprovalsDesc": "I ogeysii marka bukaan ansixiyo ama diido isbeddel la sugayo",
|
||||||
"recordsShared": "Diiwaanno la ila wadaagay",
|
"recordsShared": "Diiwaanno la ila wadaagay",
|
||||||
"recordsSharedDesc": "I ogeysii marka bukaan diiwaan ila wadaago",
|
"recordsSharedDesc": "I ogeysii marka bukaan diiwaan ila wadaago"
|
||||||
"patientStorage": "Kaydka bukaanka (beta)",
|
|
||||||
"patientStorageDesc": "Ku qor diiwaannada qalabka gaarka ah ee bukaanka halkii xogtaada",
|
|
||||||
"requireSigned": "U baahan diiwaanno la saxiixay",
|
|
||||||
"requireSignedDesc": "U baahan saxiix isbeddel kasta oo aad ku sameyso diiwaanka bukaanka"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"careTeam": {
|
"careTeam": {
|
||||||
|
|||||||
Generated
+797
-101
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,24 @@
|
|||||||
"skillPath": "better-auth/emailAndPassword/SKILL.md",
|
"skillPath": "better-auth/emailAndPassword/SKILL.md",
|
||||||
"computedHash": "747d7f73bd9e2ec5317b8ef88dc70f97df716d00e3d02de4051feebaa48b062d"
|
"computedHash": "747d7f73bd9e2ec5317b8ef88dc70f97df716d00e3d02de4051feebaa48b062d"
|
||||||
},
|
},
|
||||||
|
"heroui-migration": {
|
||||||
|
"source": "heroui-inc/heroui",
|
||||||
|
"sourceType": "github",
|
||||||
|
"skillPath": "skills/heroui-migration/SKILL.md",
|
||||||
|
"computedHash": "9696b5cf0009120bfe03e00cc94c0e2defc1f5af981fc5bb10b1eeffa53e34cf"
|
||||||
|
},
|
||||||
|
"heroui-native": {
|
||||||
|
"source": "heroui-inc/heroui",
|
||||||
|
"sourceType": "github",
|
||||||
|
"skillPath": "skills/heroui-native/SKILL.md",
|
||||||
|
"computedHash": "d2fc03a13d03678d40799726a144ba72ba1cb08126176ddbf0a0461c901aef29"
|
||||||
|
},
|
||||||
|
"heroui-react": {
|
||||||
|
"source": "heroui-inc/heroui",
|
||||||
|
"sourceType": "github",
|
||||||
|
"skillPath": "skills/heroui-react/SKILL.md",
|
||||||
|
"computedHash": "c9e1bb41fbc3cee782faae08bd465ae05cd685a6ed7cc3392464fdf29b3ab467"
|
||||||
|
},
|
||||||
"organization-best-practices": {
|
"organization-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
|
|||||||
Reference in New Issue
Block a user