@julianoczkowski/create-trimble-app 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/bin/create-trimble-app.js +9 -0
- package/package.json +67 -0
- package/src/cli.js +134 -0
- package/src/frameworks.js +28 -0
- package/src/scaffold.js +209 -0
- package/src/utils/file.js +47 -0
- package/src/utils/git.js +140 -0
- package/src/utils/install.js +25 -0
- package/src/utils/logger.js +124 -0
- package/templates/angular/.cursor/commands/remove-dev-content.md +394 -0
- package/templates/angular/.cursor/mcp.json +13 -0
- package/templates/angular/.cursor/rules/modus-angular-20.mdc +82 -0
- package/templates/angular/.cursor/rules/modus-angular-accordion-state-management-short.mdc +45 -0
- package/templates/angular/.cursor/rules/modus-angular-accordion-state-management.mdc +322 -0
- package/templates/angular/.cursor/rules/modus-angular-best-practices.mdc +472 -0
- package/templates/angular/.cursor/rules/modus-angular-border-usage-short.mdc +48 -0
- package/templates/angular/.cursor/rules/modus-angular-border-usage.mdc +286 -0
- package/templates/angular/.cursor/rules/modus-angular-button-group-usage-short.mdc +47 -0
- package/templates/angular/.cursor/rules/modus-angular-button-group-usage.mdc +263 -0
- package/templates/angular/.cursor/rules/modus-angular-checkbox-value-inversion-short.mdc +36 -0
- package/templates/angular/.cursor/rules/modus-angular-checkbox-value-inversion.mdc +92 -0
- package/templates/angular/.cursor/rules/modus-angular-chrome-devtools-testing-short.mdc +34 -0
- package/templates/angular/.cursor/rules/modus-angular-chrome-devtools-testing.mdc +185 -0
- package/templates/angular/.cursor/rules/modus-angular-color-usage-short.mdc +56 -0
- package/templates/angular/.cursor/rules/modus-angular-color-usage.mdc +208 -0
- package/templates/angular/.cursor/rules/modus-angular-components-reference.mdc +114 -0
- package/templates/angular/.cursor/rules/modus-angular-design-system.mdc +273 -0
- package/templates/angular/.cursor/rules/modus-angular-development-workflow-short.mdc +43 -0
- package/templates/angular/.cursor/rules/modus-angular-development-workflow.mdc +145 -0
- package/templates/angular/.cursor/rules/modus-angular-essentials.mdc +272 -0
- package/templates/angular/.cursor/rules/modus-angular-forms-validation-short.mdc +56 -0
- package/templates/angular/.cursor/rules/modus-angular-forms-validation.mdc +124 -0
- package/templates/angular/.cursor/rules/modus-angular-icon-names.mdc +70 -0
- package/templates/angular/.cursor/rules/modus-angular-icons-short.mdc +40 -0
- package/templates/angular/.cursor/rules/modus-angular-icons.mdc +137 -0
- package/templates/angular/.cursor/rules/modus-angular-implementation-guides-short.mdc +36 -0
- package/templates/angular/.cursor/rules/modus-angular-implementation-guides.mdc +301 -0
- package/templates/angular/.cursor/rules/modus-angular-integration-short.mdc +60 -0
- package/templates/angular/.cursor/rules/modus-angular-integration.mdc +1096 -0
- package/templates/angular/.cursor/rules/modus-angular-master.mdc +164 -0
- package/templates/angular/.cursor/rules/modus-angular-modal-usage-short.mdc +51 -0
- package/templates/angular/.cursor/rules/modus-angular-modal-usage.mdc +115 -0
- package/templates/angular/.cursor/rules/modus-angular-navbar-usage-short.mdc +49 -0
- package/templates/angular/.cursor/rules/modus-angular-navbar-usage.mdc +146 -0
- package/templates/angular/.cursor/rules/modus-angular-no-emojis.mdc +66 -0
- package/templates/angular/.cursor/rules/modus-angular-opacity-utilities-short.mdc +43 -0
- package/templates/angular/.cursor/rules/modus-angular-opacity-utilities.mdc +160 -0
- package/templates/angular/.cursor/rules/modus-angular-select-vs-dropdown-menu-short.mdc +51 -0
- package/templates/angular/.cursor/rules/modus-angular-select-vs-dropdown-menu.mdc +83 -0
- package/templates/angular/.cursor/rules/modus-angular-semantic-html-short.mdc +36 -0
- package/templates/angular/.cursor/rules/modus-angular-semantic-html.mdc +81 -0
- package/templates/angular/.cursor/rules/modus-angular-side-navigation-usage-short.mdc +50 -0
- package/templates/angular/.cursor/rules/modus-angular-side-navigation-usage.mdc +136 -0
- package/templates/angular/.cursor/rules/modus-angular-table-usage-short.mdc +52 -0
- package/templates/angular/.cursor/rules/modus-angular-table-usage.mdc +151 -0
- package/templates/angular/.cursor/rules/modus-angular-tailwind-usage-short.mdc +44 -0
- package/templates/angular/.cursor/rules/modus-angular-tailwind-usage.mdc +242 -0
- package/templates/angular/.cursor/rules/modus-angular-themes-short.mdc +54 -0
- package/templates/angular/.cursor/rules/modus-angular-themes.mdc +222 -0
- package/templates/angular/.cursor/rules/modus-angular-utility-panel-usage-short.mdc +52 -0
- package/templates/angular/.cursor/rules/modus-angular-utility-panel-usage.mdc +130 -0
- package/templates/angular/.cursor/rules/ux/gestalt-laws-detailed.mdc +514 -0
- package/templates/angular/.cursor/rules/ux/ux-ui-foundations.mdc +235 -0
- package/templates/angular/.cursor/skills/run-lint-checks/SKILL.md +169 -0
- package/templates/angular/.editorconfig +17 -0
- package/templates/angular/.github/dependabot.yml +19 -0
- package/templates/angular/.github/workflows/a11y-check.yml +135 -0
- package/templates/angular/.github/workflows/ci.yml +44 -0
- package/templates/angular/.husky/pre-commit +32 -0
- package/templates/angular/.postcssrc.json +5 -0
- package/templates/angular/.vscode/extensions.json +4 -0
- package/templates/angular/.vscode/launch.json +20 -0
- package/templates/angular/.vscode/tasks.json +42 -0
- package/templates/angular/CLAUDE.md +148 -0
- package/templates/angular/README.md +92 -0
- package/templates/angular/amplify.yml +25 -0
- package/templates/angular/angular.json +106 -0
- package/templates/angular/data/modusIcons.ts +861 -0
- package/templates/angular/package-lock.json +11030 -0
- package/templates/angular/package.json +66 -0
- package/templates/angular/public/angular-icon.svg +12 -0
- package/templates/angular/public/favicon.ico +0 -0
- package/templates/angular/public/modus-icons.css +49 -0
- package/templates/angular/public/vite.svg +1 -0
- package/templates/angular/scripts/README.md +410 -0
- package/templates/angular/scripts/check-border-violations.js +352 -0
- package/templates/angular/scripts/check-icon-names.js +402 -0
- package/templates/angular/scripts/check-inline-styles.js +292 -0
- package/templates/angular/scripts/check-modus-colors.js +282 -0
- package/templates/angular/scripts/check-modus-icons.js +263 -0
- package/templates/angular/scripts/check-opacity-utilities.js +426 -0
- package/templates/angular/scripts/check-semantic-html.js +452 -0
- package/templates/angular/scripts/check-typescript.js +109 -0
- package/templates/angular/src/app/app.config.ts +29 -0
- package/templates/angular/src/app/app.css +0 -0
- package/templates/angular/src/app/app.html +4 -0
- package/templates/angular/src/app/app.routes.ts +351 -0
- package/templates/angular/src/app/app.spec.ts +27 -0
- package/templates/angular/src/app/app.ts +47 -0
- package/templates/angular/src/app/components/README.md +77 -0
- package/templates/angular/src/app/components/index.ts +53 -0
- package/templates/angular/src/app/components/modus-accordion.component.ts +50 -0
- package/templates/angular/src/app/components/modus-alert.component.ts +133 -0
- package/templates/angular/src/app/components/modus-autocomplete.component.ts +262 -0
- package/templates/angular/src/app/components/modus-avatar.component.ts +75 -0
- package/templates/angular/src/app/components/modus-badge.component.ts +84 -0
- package/templates/angular/src/app/components/modus-breadcrumbs.component.ts +65 -0
- package/templates/angular/src/app/components/modus-button-group.component.ts +82 -0
- package/templates/angular/src/app/components/modus-button.component.ts +292 -0
- package/templates/angular/src/app/components/modus-card.component.ts +73 -0
- package/templates/angular/src/app/components/modus-checkbox.component.ts +117 -0
- package/templates/angular/src/app/components/modus-chip.component.ts +97 -0
- package/templates/angular/src/app/components/modus-collapse.component.ts +118 -0
- package/templates/angular/src/app/components/modus-date.component.ts +165 -0
- package/templates/angular/src/app/components/modus-dropdown-menu.component.ts +108 -0
- package/templates/angular/src/app/components/modus-file-dropzone.component.ts +121 -0
- package/templates/angular/src/app/components/modus-handle.component.ts +96 -0
- package/templates/angular/src/app/components/modus-icon.component.ts +81 -0
- package/templates/angular/src/app/components/modus-input-feedback.component.ts +54 -0
- package/templates/angular/src/app/components/modus-input-label.component.ts +62 -0
- package/templates/angular/src/app/components/modus-loader.component.ts +48 -0
- package/templates/angular/src/app/components/modus-logo.component.ts +115 -0
- package/templates/angular/src/app/components/modus-menu-item.component.ts +116 -0
- package/templates/angular/src/app/components/modus-menu.component.ts +58 -0
- package/templates/angular/src/app/components/modus-modal.component.ts +70 -0
- package/templates/angular/src/app/components/modus-navbar.component.ts +303 -0
- package/templates/angular/src/app/components/modus-number-input.component.ts +174 -0
- package/templates/angular/src/app/components/modus-pagination.component.ts +74 -0
- package/templates/angular/src/app/components/modus-panel.component.ts +61 -0
- package/templates/angular/src/app/components/modus-progress.component.ts +62 -0
- package/templates/angular/src/app/components/modus-radio.component.ts +102 -0
- package/templates/angular/src/app/components/modus-rating.component.ts +80 -0
- package/templates/angular/src/app/components/modus-select.component.ts +131 -0
- package/templates/angular/src/app/components/modus-side-navigation.component.ts +90 -0
- package/templates/angular/src/app/components/modus-skeleton.component.ts +54 -0
- package/templates/angular/src/app/components/modus-slider.component.ts +132 -0
- package/templates/angular/src/app/components/modus-stepper.component.ts +65 -0
- package/templates/angular/src/app/components/modus-switch.component.ts +118 -0
- package/templates/angular/src/app/components/modus-table.component.ts +204 -0
- package/templates/angular/src/app/components/modus-tabs.component.ts +82 -0
- package/templates/angular/src/app/components/modus-text-input.component.ts +221 -0
- package/templates/angular/src/app/components/modus-textarea.component.ts +168 -0
- package/templates/angular/src/app/components/modus-theme-switcher.component.ts +45 -0
- package/templates/angular/src/app/components/modus-time-input.component.ts +172 -0
- package/templates/angular/src/app/components/modus-toast.component.ts +63 -0
- package/templates/angular/src/app/components/modus-toolbar.component.ts +43 -0
- package/templates/angular/src/app/components/modus-tooltip.component.ts +83 -0
- package/templates/angular/src/app/components/modus-typography.component.ts +79 -0
- package/templates/angular/src/app/components/modus-utility-panel.component.ts +275 -0
- package/templates/angular/src/app/components/theme-demo.component.ts +1242 -0
- package/templates/angular/src/app/data/component-demos.ts +360 -0
- package/templates/angular/src/app/data/modus-icons.ts +806 -0
- package/templates/angular/src/app/demos/accordion/accordion-demo.component.ts +212 -0
- package/templates/angular/src/app/demos/alert/alert-demo.component.ts +108 -0
- package/templates/angular/src/app/demos/autocomplete/autocomplete-demo.component.ts +174 -0
- package/templates/angular/src/app/demos/avatar/avatar-demo.component.ts +149 -0
- package/templates/angular/src/app/demos/badge/badge-demo.component.ts +148 -0
- package/templates/angular/src/app/demos/breadcrumbs/breadcrumbs-demo.component.ts +96 -0
- package/templates/angular/src/app/demos/button/button-demo.component.ts +256 -0
- package/templates/angular/src/app/demos/button-group/button-group-demo.component.ts +215 -0
- package/templates/angular/src/app/demos/card/card-demo.component.ts +180 -0
- package/templates/angular/src/app/demos/checkbox/checkbox-demo.component.ts +71 -0
- package/templates/angular/src/app/demos/chip/chip-demo.component.ts +183 -0
- package/templates/angular/src/app/demos/date/date-demo.component.ts +193 -0
- package/templates/angular/src/app/demos/dropdown/dropdown-demo.component.ts +196 -0
- package/templates/angular/src/app/demos/file-dropzone/file-dropzone-demo.component.ts +176 -0
- package/templates/angular/src/app/demos/handle/handle-demo.component.ts +265 -0
- package/templates/angular/src/app/demos/icon/icon-demo.component.ts +65 -0
- package/templates/angular/src/app/demos/input-feedback/input-feedback-demo.component.ts +189 -0
- package/templates/angular/src/app/demos/input-label/input-label-demo.component.ts +330 -0
- package/templates/angular/src/app/demos/loader/loader-demo.component.ts +143 -0
- package/templates/angular/src/app/demos/logo/logo-demo.component.ts +191 -0
- package/templates/angular/src/app/demos/menu/menu-demo.component.ts +72 -0
- package/templates/angular/src/app/demos/modal/modal-demo.component.ts +278 -0
- package/templates/angular/src/app/demos/navbar/navbar-demo.component.ts +135 -0
- package/templates/angular/src/app/demos/number-input/number-input-demo.component.ts +175 -0
- package/templates/angular/src/app/demos/pagination/pagination-demo.component.ts +134 -0
- package/templates/angular/src/app/demos/panel/panel-demo.component.ts +235 -0
- package/templates/angular/src/app/demos/progress/progress-demo.component.ts +169 -0
- package/templates/angular/src/app/demos/radio/radio-demo.component.ts +161 -0
- package/templates/angular/src/app/demos/rating/rating-demo.component.ts +97 -0
- package/templates/angular/src/app/demos/select/select-demo.component.ts +107 -0
- package/templates/angular/src/app/demos/shared/demo-example-clean.component.ts +41 -0
- package/templates/angular/src/app/demos/shared/demo-example.component.ts +42 -0
- package/templates/angular/src/app/demos/shared/demo-page.component.ts +97 -0
- package/templates/angular/src/app/demos/shared/index.ts +3 -0
- package/templates/angular/src/app/demos/side-navigation/side-navigation-demo.component.ts +524 -0
- package/templates/angular/src/app/demos/skeleton/skeleton-demo.component.ts +112 -0
- package/templates/angular/src/app/demos/slider/slider-demo.component.ts +76 -0
- package/templates/angular/src/app/demos/stepper/stepper-demo.component.ts +79 -0
- package/templates/angular/src/app/demos/switch/switch-demo.component.ts +113 -0
- package/templates/angular/src/app/demos/table/table-demo.component.ts +405 -0
- package/templates/angular/src/app/demos/tabs/tabs-demo.component.ts +318 -0
- package/templates/angular/src/app/demos/text-input/text-input-demo.component.ts +160 -0
- package/templates/angular/src/app/demos/textarea/textarea-demo.component.ts +95 -0
- package/templates/angular/src/app/demos/theme-switcher/theme-switcher-demo.component.ts +38 -0
- package/templates/angular/src/app/demos/time-input/time-input-demo.component.ts +130 -0
- package/templates/angular/src/app/demos/toast/toast-demo.component.ts +258 -0
- package/templates/angular/src/app/demos/toolbar/toolbar-demo.component.ts +54 -0
- package/templates/angular/src/app/demos/tooltip/tooltip-demo.component.ts +163 -0
- package/templates/angular/src/app/demos/utility-panel/utility-panel-demo.component.ts +197 -0
- package/templates/angular/src/app/dev/dev-config.ts +119 -0
- package/templates/angular/src/app/dev/dev-panel/dev-panel.component.ts +215 -0
- package/templates/angular/src/app/dev/dev-panel.service.ts +63 -0
- package/templates/angular/src/app/dev/index.ts +8 -0
- package/templates/angular/src/app/dev/theme-switcher-dropdown/theme-switcher-dropdown.component.ts +134 -0
- package/templates/angular/src/app/dev-pages/color-palette/color-palette.component.ts +229 -0
- package/templates/angular/src/app/dev-pages/components-gallery/components-gallery.component.ts +486 -0
- package/templates/angular/src/app/dev-pages/icons/icons.component.ts +149 -0
- package/templates/angular/src/app/pages/home/home.component.ts +251 -0
- package/templates/angular/src/app/services/README.md +57 -0
- package/templates/angular/src/app/services/theme.service.ts +163 -0
- package/templates/angular/src/environments/environment.development.ts +8 -0
- package/templates/angular/src/environments/environment.ts +8 -0
- package/templates/angular/src/index.html +14 -0
- package/templates/angular/src/main.ts +6 -0
- package/templates/angular/src/styles.css +1328 -0
- package/templates/angular/tsconfig.app.json +15 -0
- package/templates/angular/tsconfig.json +35 -0
- package/templates/angular/tsconfig.spec.json +14 -0
- package/templates/config.json +23 -0
- package/templates/react/.cursor/commands/remove-dev-content.md +311 -0
- package/templates/react/.cursor/mcp.json +13 -0
- package/templates/react/.cursor/rules/README.md +240 -0
- package/templates/react/.cursor/rules/border-usage-guidelines-short.mdc +22 -0
- package/templates/react/.cursor/rules/border-usage-guidelines.mdc +380 -0
- package/templates/react/.cursor/rules/chrome-devtools-testing-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/development-workflow-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/development-workflow-react.mdc +292 -0
- package/templates/react/.cursor/rules/implementation-guides-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/implementation-guides-react.mdc +446 -0
- package/templates/react/.cursor/rules/modus-accordion-state-management-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-accordion-state-management-react.mdc +445 -0
- package/templates/react/.cursor/rules/modus-button-group-usage-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-button-group-usage-react.mdc +117 -0
- package/templates/react/.cursor/rules/modus-checkbox-value-inversion-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-checkbox-value-inversion-react.mdc +492 -0
- package/templates/react/.cursor/rules/modus-color-usage-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-color-usage-react.mdc +420 -0
- package/templates/react/.cursor/rules/modus-components-reference.mdc +366 -0
- package/templates/react/.cursor/rules/modus-icon-names.mdc +63 -0
- package/templates/react/.cursor/rules/modus-icons-react-short.mdc +24 -0
- package/templates/react/.cursor/rules/modus-icons-react.mdc +402 -0
- package/templates/react/.cursor/rules/modus-modal-implementation-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-modal-implementation-react.mdc +831 -0
- package/templates/react/.cursor/rules/modus-navbar-side-navigation-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-navbar-side-navigation-react.mdc +247 -0
- package/templates/react/.cursor/rules/modus-no-emojis-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-opacity-utilities-react-short.mdc +70 -0
- package/templates/react/.cursor/rules/modus-opacity-utilities-react.mdc +208 -0
- package/templates/react/.cursor/rules/modus-react-best-practices-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-react-best-practices.mdc +508 -0
- package/templates/react/.cursor/rules/modus-react-essentials.mdc +209 -0
- package/templates/react/.cursor/rules/modus-react-integration-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-react-integration.mdc +509 -0
- package/templates/react/.cursor/rules/modus-react-key-warnings-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-react-key-warnings.mdc +805 -0
- package/templates/react/.cursor/rules/modus-react-master.mdc +160 -0
- package/templates/react/.cursor/rules/modus-select-vs-dropdown-menu-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-select-vs-dropdown-menu-react.mdc +442 -0
- package/templates/react/.cursor/rules/modus-semantic-html-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-semantic-html-react.mdc +427 -0
- package/templates/react/.cursor/rules/modus-tailwind-usage-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-tailwind-usage-react.mdc +642 -0
- package/templates/react/.cursor/rules/modus-themes-react-short.mdc +23 -0
- package/templates/react/.cursor/rules/modus-themes-react.mdc +506 -0
- package/templates/react/.cursor/rules/ux/gestalt-laws-detailed.mdc +456 -0
- package/templates/react/.cursor/rules/ux/ux-ui-foundations.mdc +211 -0
- package/templates/react/.cursor/skills/create-modus-form-component/SKILL.md +518 -0
- package/templates/react/.cursor/skills/create-modus-wrapper-component/SKILL.md +252 -0
- package/templates/react/.cursor/skills/fix-modus-component-event-issues/SKILL.md +345 -0
- package/templates/react/.cursor/skills/handle-modus-checkbox-value-bug/SKILL.md +202 -0
- package/templates/react/.cursor/skills/implement-modus-modal-with-refs/SKILL.md +386 -0
- package/templates/react/.cursor/skills/integrate-modus-icons/SKILL.md +300 -0
- package/templates/react/.cursor/skills/run-lint-checks/SKILL.md +235 -0
- package/templates/react/.cursor/skills/set-up-modus-event-listeners/SKILL.md +284 -0
- package/templates/react/.cursor/skills/style-modus-components-with-tailwind/SKILL.md +382 -0
- package/templates/react/.env.development +3 -0
- package/templates/react/.env.production +3 -0
- package/templates/react/.github/CODEOWNERS +28 -0
- package/templates/react/.github/ISSUE_TEMPLATE/bug_report.yml +176 -0
- package/templates/react/.github/ISSUE_TEMPLATE/config.yml +20 -0
- package/templates/react/.github/ISSUE_TEMPLATE/documentation.yml +115 -0
- package/templates/react/.github/ISSUE_TEMPLATE/feature_request.yml +171 -0
- package/templates/react/.github/ISSUE_TEMPLATE/question.yml +139 -0
- package/templates/react/.github/copilot-instructions.md +80 -0
- package/templates/react/.github/instructions/components.instructions.md +82 -0
- package/templates/react/.github/instructions/demos.instructions.md +82 -0
- package/templates/react/.github/instructions/pages.instructions.md +76 -0
- package/templates/react/.github/instructions/styles.instructions.md +77 -0
- package/templates/react/.github/instructions/typescript.instructions.md +101 -0
- package/templates/react/.github/pull_request_template.md +188 -0
- package/templates/react/.github/workflows/ci.yml +43 -0
- package/templates/react/.github/workflows/claude-code-review.yml +44 -0
- package/templates/react/.github/workflows/claude.yml +50 -0
- package/templates/react/.husky/pre-commit +28 -0
- package/templates/react/.vscode/extensions.json +8 -0
- package/templates/react/CLAUDE.md +119 -0
- package/templates/react/CODE_OF_CONDUCT.md +79 -0
- package/templates/react/CONTRIBUTING.md +65 -0
- package/templates/react/LICENSE +21 -0
- package/templates/react/README.md +728 -0
- package/templates/react/SECURITY.md +50 -0
- package/templates/react/eslint.config.js +23 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package-lock.json +5209 -0
- package/templates/react/package.json +49 -0
- package/templates/react/postcss.config.js +6 -0
- package/templates/react/public/react.svg +1 -0
- package/templates/react/public/vite.svg +1 -0
- package/templates/react/readme_assets/getting_started_header.png +0 -0
- package/templates/react/readme_assets/hero.png +0 -0
- package/templates/react/readme_assets/modus_comp.png +0 -0
- package/templates/react/readme_assets/modus_figma_mcp.png +0 -0
- package/templates/react/readme_assets/teaser_comp.gif +0 -0
- package/templates/react/scripts/README.md +343 -0
- package/templates/react/scripts/check-border-violations.js +483 -0
- package/templates/react/scripts/check-icon-names.js +486 -0
- package/templates/react/scripts/check-inline-styles.js +364 -0
- package/templates/react/scripts/check-modus-colors.js +247 -0
- package/templates/react/scripts/check-modus-icons.js +256 -0
- package/templates/react/scripts/check-opacity-utilities.js +481 -0
- package/templates/react/scripts/check-semantic-html.js +476 -0
- package/templates/react/scripts/check-typescript.js +109 -0
- package/templates/react/src/App.css +42 -0
- package/templates/react/src/App.tsx +54 -0
- package/templates/react/src/assets/react.svg +1 -0
- package/templates/react/src/components/DemoExample.tsx +61 -0
- package/templates/react/src/components/DemoPage.tsx +81 -0
- package/templates/react/src/components/ModusAccordion.tsx +89 -0
- package/templates/react/src/components/ModusAlert.tsx +85 -0
- package/templates/react/src/components/ModusAutocomplete.tsx +207 -0
- package/templates/react/src/components/ModusAvatar.tsx +50 -0
- package/templates/react/src/components/ModusBadge.tsx +82 -0
- package/templates/react/src/components/ModusBreadcrumbs.tsx +75 -0
- package/templates/react/src/components/ModusButton.tsx +244 -0
- package/templates/react/src/components/ModusButtonGroup.tsx +91 -0
- package/templates/react/src/components/ModusCard.tsx +70 -0
- package/templates/react/src/components/ModusCheckbox.tsx +168 -0
- package/templates/react/src/components/ModusChip.tsx +93 -0
- package/templates/react/src/components/ModusDate.tsx +154 -0
- package/templates/react/src/components/ModusDropdownMenu.tsx +148 -0
- package/templates/react/src/components/ModusFileDropzone.tsx +140 -0
- package/templates/react/src/components/ModusHandle.tsx +101 -0
- package/templates/react/src/components/ModusIcon.tsx +49 -0
- package/templates/react/src/components/ModusInputFeedback.tsx +52 -0
- package/templates/react/src/components/ModusInputLabel.tsx +50 -0
- package/templates/react/src/components/ModusLoader.tsx +42 -0
- package/templates/react/src/components/ModusLogo.tsx +102 -0
- package/templates/react/src/components/ModusMenu.tsx +119 -0
- package/templates/react/src/components/ModusMenuItem.tsx +86 -0
- package/templates/react/src/components/ModusModal.tsx +145 -0
- package/templates/react/src/components/ModusNavbar.tsx +504 -0
- package/templates/react/src/components/ModusNumberInput.tsx +230 -0
- package/templates/react/src/components/ModusPagination.tsx +94 -0
- package/templates/react/src/components/ModusPanel.tsx +92 -0
- package/templates/react/src/components/ModusProgress.tsx +70 -0
- package/templates/react/src/components/ModusProvider.tsx +18 -0
- package/templates/react/src/components/ModusRadio.tsx +114 -0
- package/templates/react/src/components/ModusRating.tsx +108 -0
- package/templates/react/src/components/ModusSelect.tsx +171 -0
- package/templates/react/src/components/ModusSideNavigation.tsx +149 -0
- package/templates/react/src/components/ModusSkeleton.tsx +42 -0
- package/templates/react/src/components/ModusSlider.tsx +128 -0
- package/templates/react/src/components/ModusStepper.tsx +85 -0
- package/templates/react/src/components/ModusSwitch.tsx +130 -0
- package/templates/react/src/components/ModusTable.tsx +309 -0
- package/templates/react/src/components/ModusTabs.tsx +114 -0
- package/templates/react/src/components/ModusTextInput.tsx +179 -0
- package/templates/react/src/components/ModusTextarea.tsx +164 -0
- package/templates/react/src/components/ModusThemeSwitcher.tsx +58 -0
- package/templates/react/src/components/ModusTimeInput.tsx +176 -0
- package/templates/react/src/components/ModusToast.tsx +207 -0
- package/templates/react/src/components/ModusToolbar.tsx +70 -0
- package/templates/react/src/components/ModusTooltip.tsx +97 -0
- package/templates/react/src/components/ModusUtilityPanel.tsx +198 -0
- package/templates/react/src/components/ThemeSwitcherDropdown.tsx +117 -0
- package/templates/react/src/components/ThemeToggleSimple.tsx +157 -0
- package/templates/react/src/config/routes.ts +196 -0
- package/templates/react/src/contexts/ThemeContext.tsx +81 -0
- package/templates/react/src/contexts/ThemeContextData.tsx +89 -0
- package/templates/react/src/data/modusIcons.ts +865 -0
- package/templates/react/src/demos/accordion-demo/page.tsx +236 -0
- package/templates/react/src/demos/alert-demo/page.tsx +94 -0
- package/templates/react/src/demos/autocomplete-demo/page.tsx +166 -0
- package/templates/react/src/demos/avatar-demo/page.tsx +135 -0
- package/templates/react/src/demos/badge-demo/page.tsx +174 -0
- package/templates/react/src/demos/breadcrumbs-demo/page.tsx +88 -0
- package/templates/react/src/demos/button-demo/page.tsx +261 -0
- package/templates/react/src/demos/button-group-demo/page.tsx +231 -0
- package/templates/react/src/demos/card-demo/page.tsx +241 -0
- package/templates/react/src/demos/checkbox-demo/page.tsx +79 -0
- package/templates/react/src/demos/chip-demo/page.tsx +197 -0
- package/templates/react/src/demos/date-demo/page.tsx +179 -0
- package/templates/react/src/demos/dropdown-demo/page.tsx +150 -0
- package/templates/react/src/demos/file-dropzone-demo/page.tsx +186 -0
- package/templates/react/src/demos/handle-demo/page.tsx +313 -0
- package/templates/react/src/demos/icon-demo/page.tsx +72 -0
- package/templates/react/src/demos/input-feedback-demo/page.tsx +202 -0
- package/templates/react/src/demos/input-label-demo/page.tsx +392 -0
- package/templates/react/src/demos/loader-demo/page.tsx +138 -0
- package/templates/react/src/demos/logo-demo/page.tsx +292 -0
- package/templates/react/src/demos/menu-demo/page.tsx +70 -0
- package/templates/react/src/demos/modal-demo/page.tsx +332 -0
- package/templates/react/src/demos/navbar-demo/page.tsx +141 -0
- package/templates/react/src/demos/number-input-demo/page.tsx +180 -0
- package/templates/react/src/demos/pagination-demo/page.tsx +147 -0
- package/templates/react/src/demos/panel-demo/page.tsx +376 -0
- package/templates/react/src/demos/progress-demo/page.tsx +185 -0
- package/templates/react/src/demos/radio-demo/page.tsx +242 -0
- package/templates/react/src/demos/rating-demo/page.tsx +97 -0
- package/templates/react/src/demos/select-demo/page.tsx +111 -0
- package/templates/react/src/demos/side-navigation-demo/page.tsx +775 -0
- package/templates/react/src/demos/skeleton-demo/page.tsx +107 -0
- package/templates/react/src/demos/slider-demo/page.tsx +78 -0
- package/templates/react/src/demos/stepper-demo/page.tsx +86 -0
- package/templates/react/src/demos/switch-demo/page.tsx +146 -0
- package/templates/react/src/demos/table-demo/page.tsx +489 -0
- package/templates/react/src/demos/tabs-demo/page.tsx +187 -0
- package/templates/react/src/demos/text-input-demo/page.tsx +151 -0
- package/templates/react/src/demos/textarea-demo/page.tsx +73 -0
- package/templates/react/src/demos/theme-switcher-demo/page.tsx +26 -0
- package/templates/react/src/demos/time-input-demo/page.tsx +148 -0
- package/templates/react/src/demos/toast-demo/page.tsx +302 -0
- package/templates/react/src/demos/toolbar-demo/page.tsx +49 -0
- package/templates/react/src/demos/tooltip-demo/page.tsx +209 -0
- package/templates/react/src/demos/typography-test/page.tsx +28 -0
- package/templates/react/src/demos/utility-panel-demo/page.tsx +197 -0
- package/templates/react/src/dev/DevPanel.tsx +219 -0
- package/templates/react/src/dev/DevPanelContext.ts +14 -0
- package/templates/react/src/dev/DevPanelProvider.tsx +63 -0
- package/templates/react/src/dev/DevRoutes.tsx +98 -0
- package/templates/react/src/dev/config.ts +127 -0
- package/templates/react/src/dev/index.ts +8 -0
- package/templates/react/src/dev/useDevPanel.ts +17 -0
- package/templates/react/src/dev-pages/ColorPalettePage.tsx +347 -0
- package/templates/react/src/dev-pages/ComponentsGalleryPage.tsx +489 -0
- package/templates/react/src/dev-pages/IconsPage.tsx +137 -0
- package/templates/react/src/dev-pages/index.ts +3 -0
- package/templates/react/src/hooks/useTheme.ts +15 -0
- package/templates/react/src/index.css +635 -0
- package/templates/react/src/main.tsx +14 -0
- package/templates/react/src/pages/HomePage.tsx +283 -0
- package/templates/react/src/vite-env.d.ts +9 -0
- package/templates/react/tailwind.config.js +58 -0
- package/templates/react/tsconfig.app.json +27 -0
- package/templates/react/tsconfig.json +7 -0
- package/templates/react/tsconfig.node.json +25 -0
- package/templates/react/vite.config.ts +18 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Critical implementation patterns for ModusWcModal in React
|
|
3
|
+
globs: ["**/components/ModusModal*.tsx", "**/demos/**/page.tsx"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ModusWcModal Implementation in React
|
|
8
|
+
|
|
9
|
+
## 🚨 **CRITICAL: Modal Method Access Issue**
|
|
10
|
+
|
|
11
|
+
**Problem**: The `ModusWcModal` component's `showModal()` and `close()` methods are not directly available on the component reference in React.
|
|
12
|
+
|
|
13
|
+
**Root Cause**: Modus Web Components use shadow DOM, and the modal methods are on the inner native `<dialog>` element, not the component itself.
|
|
14
|
+
|
|
15
|
+
**Official Documentation**: According to the official Modus documentation, modals are controlled with direct method calls (`showModal()` and `close()`) on the modal element, not through React state management.
|
|
16
|
+
|
|
17
|
+
## ❌ **Common Anti-Patterns**
|
|
18
|
+
|
|
19
|
+
### ❌ **Direct Method Access (Won't Work)**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// ❌ WRONG: Direct method access on component
|
|
23
|
+
function ModalComponent() {
|
|
24
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
25
|
+
|
|
26
|
+
const openModal = () => {
|
|
27
|
+
if (modalRef.current) {
|
|
28
|
+
modalRef.current.showModal(); // ❌ This won't work
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const closeModal = () => {
|
|
33
|
+
if (modalRef.current) {
|
|
34
|
+
modalRef.current.close(); // ❌ This won't work
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<button onClick={openModal}>Open Modal</button>
|
|
41
|
+
<ModusWcModal ref={modalRef}>
|
|
42
|
+
<div slot="header">Modal Header</div>
|
|
43
|
+
<div slot="body">Modal Body</div>
|
|
44
|
+
<div slot="footer">
|
|
45
|
+
<button onClick={closeModal}>Close</button>
|
|
46
|
+
</div>
|
|
47
|
+
</ModusWcModal>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### ❌ **Trying to Control Modal State from React**
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// ❌ WRONG: Trying to control modal state from React
|
|
57
|
+
function ModalComponent() {
|
|
58
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ModusWcModal
|
|
62
|
+
open={isOpen} // ❌ This won't work as expected
|
|
63
|
+
onClose={() => setIsOpen(false)}
|
|
64
|
+
>
|
|
65
|
+
<div slot="header">Modal Header</div>
|
|
66
|
+
<div slot="body">Modal Body</div>
|
|
67
|
+
</ModusWcModal>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### ❌ **Using useEffect to Control Modal State (Critical Anti-Pattern)**
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// ❌ WRONG: Using useEffect to control modal state
|
|
76
|
+
function ModusModal({ isOpen, onClose, ...props }) {
|
|
77
|
+
const modalRef = useRef<HTMLModusWcModalElement>(null);
|
|
78
|
+
|
|
79
|
+
// ❌ CRITICAL ANTI-PATTERN: Don't control modal state from React
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const modal = modalRef.current;
|
|
82
|
+
if (modal) {
|
|
83
|
+
if (isOpen) {
|
|
84
|
+
const dialogElement = modal.querySelector("dialog");
|
|
85
|
+
if (dialogElement) {
|
|
86
|
+
dialogElement.showModal();
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
const dialogElement = modal.querySelector("dialog");
|
|
90
|
+
if (dialogElement) {
|
|
91
|
+
dialogElement.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, [isOpen]); // ❌ This violates the official pattern
|
|
96
|
+
|
|
97
|
+
return <ModusWcModal ref={modalRef} {...props} />;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Why This is Wrong**: The official Modus documentation states that modals should be controlled by direct method calls, not React state. This approach creates unnecessary re-renders and goes against the documented pattern.
|
|
102
|
+
|
|
103
|
+
## ✅ **Correct Patterns**
|
|
104
|
+
|
|
105
|
+
### ✅ **Recommended: React Wrapper Component with forwardRef**
|
|
106
|
+
|
|
107
|
+
The most maintainable approach is to create a React wrapper component that handles the dialog element access internally:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// ✅ CORRECT: React wrapper with forwardRef
|
|
111
|
+
import { ModusWcModal } from "@trimble-oss/moduswebcomponents-react";
|
|
112
|
+
import { useRef, forwardRef, useImperativeHandle } from "react";
|
|
113
|
+
|
|
114
|
+
export interface ModusModalRef {
|
|
115
|
+
openModal: () => void;
|
|
116
|
+
closeModal: () => void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const ModusModal = forwardRef<ModusModalRef, ModusModalProps>(
|
|
120
|
+
(
|
|
121
|
+
{
|
|
122
|
+
modalId,
|
|
123
|
+
ariaLabel,
|
|
124
|
+
backdrop = "default",
|
|
125
|
+
position = "center",
|
|
126
|
+
fullscreen = false,
|
|
127
|
+
showFullscreenToggle = false,
|
|
128
|
+
showClose = true,
|
|
129
|
+
customClass,
|
|
130
|
+
header,
|
|
131
|
+
children,
|
|
132
|
+
footer,
|
|
133
|
+
onClose,
|
|
134
|
+
className,
|
|
135
|
+
},
|
|
136
|
+
ref
|
|
137
|
+
) => {
|
|
138
|
+
const modalRef = useRef<HTMLModusWcModalElement>(null);
|
|
139
|
+
|
|
140
|
+
const openModal = () => {
|
|
141
|
+
if (modalRef.current) {
|
|
142
|
+
const dialog = modalRef.current.querySelector(
|
|
143
|
+
"dialog"
|
|
144
|
+
) as HTMLDialogElement;
|
|
145
|
+
if (dialog) {
|
|
146
|
+
dialog.showModal();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const closeModal = () => {
|
|
152
|
+
if (modalRef.current) {
|
|
153
|
+
const dialog = modalRef.current.querySelector(
|
|
154
|
+
"dialog"
|
|
155
|
+
) as HTMLDialogElement;
|
|
156
|
+
if (dialog) {
|
|
157
|
+
dialog.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
useImperativeHandle(ref, () => ({
|
|
163
|
+
openModal,
|
|
164
|
+
closeModal,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
// Handle modal events
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const modal = modalRef.current;
|
|
170
|
+
if (modal) {
|
|
171
|
+
const handleClose = () => {
|
|
172
|
+
onClose?.();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const dialogElement = modal.querySelector("dialog");
|
|
176
|
+
if (dialogElement) {
|
|
177
|
+
dialogElement.addEventListener("close", handleClose);
|
|
178
|
+
return () => {
|
|
179
|
+
dialogElement.removeEventListener("close", handleClose);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, [onClose]);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<ModusWcModal
|
|
187
|
+
ref={modalRef}
|
|
188
|
+
modal-id={modalId}
|
|
189
|
+
aria-label={ariaLabel}
|
|
190
|
+
backdrop={backdrop}
|
|
191
|
+
position={position}
|
|
192
|
+
fullscreen={fullscreen}
|
|
193
|
+
show-fullscreen-toggle={showFullscreenToggle}
|
|
194
|
+
show-close={showClose}
|
|
195
|
+
custom-class={customClass || className}
|
|
196
|
+
>
|
|
197
|
+
{header && <div slot="header">{header}</div>}
|
|
198
|
+
<div slot="content">{children}</div>
|
|
199
|
+
{footer && <div slot="footer">{footer}</div>}
|
|
200
|
+
</ModusWcModal>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Usage in parent component
|
|
206
|
+
function ParentComponent() {
|
|
207
|
+
const modalRef = useRef<ModusModalRef>(null);
|
|
208
|
+
|
|
209
|
+
const openModal = () => {
|
|
210
|
+
modalRef.current?.openModal();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const closeModal = () => {
|
|
214
|
+
modalRef.current?.closeModal();
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div>
|
|
219
|
+
<button onClick={openModal}>Open Modal</button>
|
|
220
|
+
<ModusModal
|
|
221
|
+
ref={modalRef}
|
|
222
|
+
modalId="my-modal"
|
|
223
|
+
onClose={() => console.log("Modal closed")}
|
|
224
|
+
header={<div>Modal Title</div>}
|
|
225
|
+
footer={<div>Modal Actions</div>}
|
|
226
|
+
>
|
|
227
|
+
<div>Modal Content</div>
|
|
228
|
+
</ModusModal>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### ✅ **Access Inner Dialog Element (Direct Approach)**
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
// ✅ CORRECT: Access inner dialog element
|
|
238
|
+
function ModalComponent() {
|
|
239
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
240
|
+
|
|
241
|
+
const openModal = () => {
|
|
242
|
+
if (modalRef.current) {
|
|
243
|
+
// ✅ CORRECT: Access inner dialog element
|
|
244
|
+
const dialog = modalRef.current.querySelector(
|
|
245
|
+
"dialog"
|
|
246
|
+
) as HTMLDialogElement;
|
|
247
|
+
if (dialog) {
|
|
248
|
+
dialog.showModal();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const closeModal = () => {
|
|
254
|
+
if (modalRef.current) {
|
|
255
|
+
// ✅ CORRECT: Access inner dialog element
|
|
256
|
+
const dialog = modalRef.current.querySelector(
|
|
257
|
+
"dialog"
|
|
258
|
+
) as HTMLDialogElement;
|
|
259
|
+
if (dialog) {
|
|
260
|
+
dialog.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<div>
|
|
267
|
+
<button onClick={openModal}>Open Modal</button>
|
|
268
|
+
<ModusWcModal ref={modalRef}>
|
|
269
|
+
<div slot="header">Modal Header</div>
|
|
270
|
+
<div slot="body">Modal Body</div>
|
|
271
|
+
<div slot="footer">
|
|
272
|
+
<button onClick={closeModal}>Close</button>
|
|
273
|
+
</div>
|
|
274
|
+
</ModusWcModal>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### ✅ **Reusable Modal Hook**
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// ✅ CORRECT: Reusable modal hook
|
|
284
|
+
function useModal() {
|
|
285
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
286
|
+
|
|
287
|
+
const openModal = () => {
|
|
288
|
+
if (modalRef.current) {
|
|
289
|
+
const dialog = modalRef.current.querySelector(
|
|
290
|
+
"dialog"
|
|
291
|
+
) as HTMLDialogElement;
|
|
292
|
+
if (dialog) {
|
|
293
|
+
dialog.showModal();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const closeModal = () => {
|
|
299
|
+
if (modalRef.current) {
|
|
300
|
+
const dialog = modalRef.current.querySelector(
|
|
301
|
+
"dialog"
|
|
302
|
+
) as HTMLDialogElement;
|
|
303
|
+
if (dialog) {
|
|
304
|
+
dialog.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
modalRef,
|
|
311
|
+
openModal,
|
|
312
|
+
closeModal,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ✅ CORRECT: Use the hook
|
|
317
|
+
function ModalComponent() {
|
|
318
|
+
const { modalRef, openModal, closeModal } = useModal();
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<div>
|
|
322
|
+
<button onClick={openModal}>Open Modal</button>
|
|
323
|
+
<ModusWcModal ref={modalRef}>
|
|
324
|
+
<div slot="header">Modal Header</div>
|
|
325
|
+
<div slot="body">Modal Body</div>
|
|
326
|
+
<div slot="footer">
|
|
327
|
+
<button onClick={closeModal}>Close</button>
|
|
328
|
+
</div>
|
|
329
|
+
</ModusWcModal>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### ✅ **Modal with React State Tracking**
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
// ✅ CORRECT: Track modal state without controlling it
|
|
339
|
+
function ModalComponent() {
|
|
340
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
341
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
342
|
+
|
|
343
|
+
const openModal = () => {
|
|
344
|
+
if (modalRef.current) {
|
|
345
|
+
const dialog = modalRef.current.querySelector(
|
|
346
|
+
"dialog"
|
|
347
|
+
) as HTMLDialogElement;
|
|
348
|
+
if (dialog) {
|
|
349
|
+
dialog.showModal();
|
|
350
|
+
setIsModalOpen(true); // ✅ CORRECT: Track state for UI updates
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const closeModal = () => {
|
|
356
|
+
if (modalRef.current) {
|
|
357
|
+
const dialog = modalRef.current.querySelector(
|
|
358
|
+
"dialog"
|
|
359
|
+
) as HTMLDialogElement;
|
|
360
|
+
if (dialog) {
|
|
361
|
+
dialog.close();
|
|
362
|
+
setIsModalOpen(false); // ✅ CORRECT: Track state for UI updates
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// ✅ CORRECT: Listen to modal events
|
|
368
|
+
const handleModalClose = () => {
|
|
369
|
+
setIsModalOpen(false);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<div>
|
|
374
|
+
<button onClick={openModal}>Open Modal</button>
|
|
375
|
+
{isModalOpen && <div>Modal is open</div>}
|
|
376
|
+
|
|
377
|
+
<ModusWcModal ref={modalRef} onClose={handleModalClose}>
|
|
378
|
+
<div slot="header">Modal Header</div>
|
|
379
|
+
<div slot="body">Modal Body</div>
|
|
380
|
+
<div slot="footer">
|
|
381
|
+
<button onClick={closeModal}>Close</button>
|
|
382
|
+
</div>
|
|
383
|
+
</ModusWcModal>
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## 🎯 **Advanced Modal Patterns**
|
|
390
|
+
|
|
391
|
+
### ✅ **Modal with Form Handling**
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
// ✅ CORRECT: Modal with form handling
|
|
395
|
+
function FormModal() {
|
|
396
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
397
|
+
const [formData, setFormData] = useState({ name: "", email: "" });
|
|
398
|
+
|
|
399
|
+
const openModal = () => {
|
|
400
|
+
if (modalRef.current) {
|
|
401
|
+
const dialog = modalRef.current.querySelector(
|
|
402
|
+
"dialog"
|
|
403
|
+
) as HTMLDialogElement;
|
|
404
|
+
if (dialog) {
|
|
405
|
+
dialog.showModal();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const closeModal = () => {
|
|
411
|
+
if (modalRef.current) {
|
|
412
|
+
const dialog = modalRef.current.querySelector(
|
|
413
|
+
"dialog"
|
|
414
|
+
) as HTMLDialogElement;
|
|
415
|
+
if (dialog) {
|
|
416
|
+
dialog.close();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
422
|
+
e.preventDefault();
|
|
423
|
+
console.log("Form data:", formData);
|
|
424
|
+
closeModal();
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<div>
|
|
429
|
+
<button onClick={openModal}>Open Form Modal</button>
|
|
430
|
+
|
|
431
|
+
<ModusWcModal ref={modalRef}>
|
|
432
|
+
<div slot="header">User Form</div>
|
|
433
|
+
<div slot="body">
|
|
434
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
435
|
+
<div>
|
|
436
|
+
<label className="block text-sm font-medium text-foreground mb-1">
|
|
437
|
+
Name
|
|
438
|
+
</label>
|
|
439
|
+
<input
|
|
440
|
+
type="text"
|
|
441
|
+
value={formData.name}
|
|
442
|
+
onChange={(e) =>
|
|
443
|
+
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
444
|
+
}
|
|
445
|
+
className="w-full px-3 py-2 border border-input rounded bg-input text-foreground"
|
|
446
|
+
required
|
|
447
|
+
/>
|
|
448
|
+
</div>
|
|
449
|
+
<div>
|
|
450
|
+
<label className="block text-sm font-medium text-foreground mb-1">
|
|
451
|
+
Email
|
|
452
|
+
</label>
|
|
453
|
+
<input
|
|
454
|
+
type="email"
|
|
455
|
+
value={formData.email}
|
|
456
|
+
onChange={(e) =>
|
|
457
|
+
setFormData((prev) => ({ ...prev, email: e.target.value }))
|
|
458
|
+
}
|
|
459
|
+
className="w-full px-3 py-2 border border-input rounded bg-input text-foreground"
|
|
460
|
+
required
|
|
461
|
+
/>
|
|
462
|
+
</div>
|
|
463
|
+
</form>
|
|
464
|
+
</div>
|
|
465
|
+
<div slot="footer" className="flex justify-end gap-2">
|
|
466
|
+
<button
|
|
467
|
+
type="button"
|
|
468
|
+
onClick={closeModal}
|
|
469
|
+
className="px-4 py-2 border border-border rounded text-foreground hover:bg-muted"
|
|
470
|
+
>
|
|
471
|
+
Cancel
|
|
472
|
+
</button>
|
|
473
|
+
<button
|
|
474
|
+
type="submit"
|
|
475
|
+
onClick={handleSubmit}
|
|
476
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded hover:bg-primary/90"
|
|
477
|
+
>
|
|
478
|
+
Save
|
|
479
|
+
</button>
|
|
480
|
+
</div>
|
|
481
|
+
</ModusWcModal>
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### ✅ **Modal with Confirmation**
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
// ✅ CORRECT: Confirmation modal
|
|
491
|
+
function ConfirmationModal({
|
|
492
|
+
isOpen,
|
|
493
|
+
onConfirm,
|
|
494
|
+
onCancel,
|
|
495
|
+
title,
|
|
496
|
+
message,
|
|
497
|
+
}: ConfirmationModalProps) {
|
|
498
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
499
|
+
|
|
500
|
+
useEffect(() => {
|
|
501
|
+
if (isOpen && modalRef.current) {
|
|
502
|
+
const dialog = modalRef.current.querySelector(
|
|
503
|
+
"dialog"
|
|
504
|
+
) as HTMLDialogElement;
|
|
505
|
+
if (dialog) {
|
|
506
|
+
dialog.showModal();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}, [isOpen]);
|
|
510
|
+
|
|
511
|
+
const handleConfirm = () => {
|
|
512
|
+
onConfirm();
|
|
513
|
+
if (modalRef.current) {
|
|
514
|
+
const dialog = modalRef.current.querySelector(
|
|
515
|
+
"dialog"
|
|
516
|
+
) as HTMLDialogElement;
|
|
517
|
+
if (dialog) {
|
|
518
|
+
dialog.close();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const handleCancel = () => {
|
|
524
|
+
onCancel();
|
|
525
|
+
if (modalRef.current) {
|
|
526
|
+
const dialog = modalRef.current.querySelector(
|
|
527
|
+
"dialog"
|
|
528
|
+
) as HTMLDialogElement;
|
|
529
|
+
if (dialog) {
|
|
530
|
+
dialog.close();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
return (
|
|
536
|
+
<ModusWcModal ref={modalRef}>
|
|
537
|
+
<div slot="header">{title}</div>
|
|
538
|
+
<div slot="body">{message}</div>
|
|
539
|
+
<div slot="footer" className="flex justify-end gap-2">
|
|
540
|
+
<button
|
|
541
|
+
onClick={handleCancel}
|
|
542
|
+
className="px-4 py-2 border border-border rounded text-foreground hover:bg-muted"
|
|
543
|
+
>
|
|
544
|
+
Cancel
|
|
545
|
+
</button>
|
|
546
|
+
<button
|
|
547
|
+
onClick={handleConfirm}
|
|
548
|
+
className="px-4 py-2 bg-destructive text-destructive-foreground rounded hover:bg-destructive/90"
|
|
549
|
+
>
|
|
550
|
+
Confirm
|
|
551
|
+
</button>
|
|
552
|
+
</div>
|
|
553
|
+
</ModusWcModal>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### ✅ **Modal with Backdrop Click**
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
// ✅ CORRECT: Modal with backdrop click handling
|
|
562
|
+
function ModalWithBackdrop() {
|
|
563
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
564
|
+
|
|
565
|
+
const openModal = () => {
|
|
566
|
+
if (modalRef.current) {
|
|
567
|
+
const dialog = modalRef.current.querySelector(
|
|
568
|
+
"dialog"
|
|
569
|
+
) as HTMLDialogElement;
|
|
570
|
+
if (dialog) {
|
|
571
|
+
dialog.showModal();
|
|
572
|
+
|
|
573
|
+
// ✅ CORRECT: Handle backdrop click
|
|
574
|
+
dialog.addEventListener("click", (e) => {
|
|
575
|
+
if (e.target === dialog) {
|
|
576
|
+
dialog.close();
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const closeModal = () => {
|
|
584
|
+
if (modalRef.current) {
|
|
585
|
+
const dialog = modalRef.current.querySelector(
|
|
586
|
+
"dialog"
|
|
587
|
+
) as HTMLDialogElement;
|
|
588
|
+
if (dialog) {
|
|
589
|
+
dialog.close();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
return (
|
|
595
|
+
<div>
|
|
596
|
+
<button onClick={openModal}>Open Modal</button>
|
|
597
|
+
<ModusWcModal ref={modalRef}>
|
|
598
|
+
<div slot="header">Modal Header</div>
|
|
599
|
+
<div slot="body">Click outside to close</div>
|
|
600
|
+
<div slot="footer">
|
|
601
|
+
<button onClick={closeModal}>Close</button>
|
|
602
|
+
</div>
|
|
603
|
+
</ModusWcModal>
|
|
604
|
+
</div>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## 🚫 **What NOT to Do**
|
|
610
|
+
|
|
611
|
+
### ❌ **Don't Try to Control Modal State from React**
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
// ❌ WRONG: Don't try to control modal state
|
|
615
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
616
|
+
|
|
617
|
+
<ModusWcModal open={isOpen}> // ❌ This won't work
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### ❌ **Don't Use Direct Method Access**
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
// ❌ WRONG: Don't use direct method access
|
|
624
|
+
modalRef.current.showModal(); // ❌ This won't work
|
|
625
|
+
modalRef.current.close(); // ❌ This won't work
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### ❌ **Don't Ignore the Dialog Element**
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
// ❌ WRONG: Don't ignore the dialog element
|
|
632
|
+
const openModal = () => {
|
|
633
|
+
// This won't work
|
|
634
|
+
modalRef.current.showModal();
|
|
635
|
+
};
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
## 📚 **Official Modus Documentation Compliance**
|
|
639
|
+
|
|
640
|
+
### **Key Findings from Official Documentation:**
|
|
641
|
+
|
|
642
|
+
1. **No React State Control**: The official documentation shows modals controlled by direct method calls, not React state
|
|
643
|
+
2. **Direct Method Access**: Use `element.showModal()` and `element.close()` directly
|
|
644
|
+
3. **Proper Slot Structure**: Use `header`, `content`, and `footer` slots correctly
|
|
645
|
+
4. **Event Handling**: Listen to native dialog events, not React synthetic events
|
|
646
|
+
5. **No Built-in React Integration**: Modus Web Components are framework-agnostic
|
|
647
|
+
|
|
648
|
+
### **Documentation Quote:**
|
|
649
|
+
|
|
650
|
+
> "Opening / closing: call `element.showModal()` to open and `element.close()` to dismiss; both return immediately and trigger `<dialog>` events."
|
|
651
|
+
|
|
652
|
+
## 🎯 **Key Takeaways**
|
|
653
|
+
|
|
654
|
+
1. **✅ Use forwardRef Pattern**: Create React wrapper components with `forwardRef` and `useImperativeHandle`
|
|
655
|
+
2. **✅ Access Inner Dialog**: Use `querySelector('dialog')` to access the inner dialog element
|
|
656
|
+
3. **✅ Use Dialog Methods**: Call `showModal()` and `close()` on the dialog element
|
|
657
|
+
4. **❌ Don't Control from React State**: Never use `useEffect` to control modal visibility
|
|
658
|
+
5. **✅ Track State Separately**: Use React state only for UI updates, not modal control
|
|
659
|
+
6. **✅ Handle Events Properly**: Listen to modal events for state synchronization
|
|
660
|
+
7. **✅ Follow Official Pattern**: Align with Modus documentation for best practices
|
|
661
|
+
|
|
662
|
+
## 🔧 **Testing Modal Implementation**
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
// ✅ CORRECT: Test modal functionality
|
|
666
|
+
function ModalTest() {
|
|
667
|
+
const modalRef = useRef<ModusWcModal>(null);
|
|
668
|
+
const [testResults, setTestResults] = useState<string[]>([]);
|
|
669
|
+
|
|
670
|
+
const testModal = () => {
|
|
671
|
+
if (modalRef.current) {
|
|
672
|
+
const dialog = modalRef.current.querySelector(
|
|
673
|
+
"dialog"
|
|
674
|
+
) as HTMLDialogElement;
|
|
675
|
+
if (dialog) {
|
|
676
|
+
dialog.showModal();
|
|
677
|
+
setTestResults((prev) => [...prev, "Modal opened successfully"]);
|
|
678
|
+
|
|
679
|
+
setTimeout(() => {
|
|
680
|
+
dialog.close();
|
|
681
|
+
setTestResults((prev) => [...prev, "Modal closed successfully"]);
|
|
682
|
+
}, 1000);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
return (
|
|
688
|
+
<div>
|
|
689
|
+
<button onClick={testModal}>Test Modal</button>
|
|
690
|
+
<div>
|
|
691
|
+
<h3>Test Results:</h3>
|
|
692
|
+
{testResults.map((result, index) => (
|
|
693
|
+
<div key={index}>{result}</div>
|
|
694
|
+
))}
|
|
695
|
+
</div>
|
|
696
|
+
<ModusWcModal ref={modalRef}>
|
|
697
|
+
<div slot="header">Test Modal</div>
|
|
698
|
+
<div slot="body">This is a test modal</div>
|
|
699
|
+
<div slot="footer">
|
|
700
|
+
<button
|
|
701
|
+
onClick={() => {
|
|
702
|
+
const dialog = modalRef.current?.querySelector(
|
|
703
|
+
"dialog"
|
|
704
|
+
) as HTMLDialogElement;
|
|
705
|
+
dialog?.close();
|
|
706
|
+
}}
|
|
707
|
+
>
|
|
708
|
+
Close
|
|
709
|
+
</button>
|
|
710
|
+
</div>
|
|
711
|
+
</ModusWcModal>
|
|
712
|
+
</div>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## 🎉 **Successfully Implemented Pattern**
|
|
718
|
+
|
|
719
|
+
### **Working Implementation Example:**
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
// ✅ SUCCESSFUL: Our working implementation
|
|
723
|
+
import { ModusWcModal } from "@trimble-oss/moduswebcomponents-react";
|
|
724
|
+
import { useRef, forwardRef, useImperativeHandle } from "react";
|
|
725
|
+
|
|
726
|
+
export interface ModusModalRef {
|
|
727
|
+
openModal: () => void;
|
|
728
|
+
closeModal: () => void;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const ModusModal = forwardRef<ModusModalRef, ModusModalProps>(
|
|
732
|
+
(
|
|
733
|
+
{
|
|
734
|
+
modalId,
|
|
735
|
+
ariaLabel,
|
|
736
|
+
backdrop = "default",
|
|
737
|
+
position = "center",
|
|
738
|
+
fullscreen = false,
|
|
739
|
+
showFullscreenToggle = false,
|
|
740
|
+
showClose = true,
|
|
741
|
+
customClass,
|
|
742
|
+
header,
|
|
743
|
+
children,
|
|
744
|
+
footer,
|
|
745
|
+
onClose,
|
|
746
|
+
className,
|
|
747
|
+
},
|
|
748
|
+
ref
|
|
749
|
+
) => {
|
|
750
|
+
const modalRef = useRef<HTMLModusWcModalElement>(null);
|
|
751
|
+
|
|
752
|
+
const openModal = () => {
|
|
753
|
+
if (modalRef.current) {
|
|
754
|
+
const dialog = modalRef.current.querySelector(
|
|
755
|
+
"dialog"
|
|
756
|
+
) as HTMLDialogElement;
|
|
757
|
+
if (dialog) {
|
|
758
|
+
dialog.showModal();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const closeModal = () => {
|
|
764
|
+
if (modalRef.current) {
|
|
765
|
+
const dialog = modalRef.current.querySelector(
|
|
766
|
+
"dialog"
|
|
767
|
+
) as HTMLDialogElement;
|
|
768
|
+
if (dialog) {
|
|
769
|
+
dialog.close();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
useImperativeHandle(ref, () => ({
|
|
775
|
+
openModal,
|
|
776
|
+
closeModal,
|
|
777
|
+
}));
|
|
778
|
+
|
|
779
|
+
return (
|
|
780
|
+
<ModusWcModal
|
|
781
|
+
ref={modalRef}
|
|
782
|
+
modal-id={modalId}
|
|
783
|
+
aria-label={ariaLabel}
|
|
784
|
+
backdrop={backdrop}
|
|
785
|
+
position={position}
|
|
786
|
+
fullscreen={fullscreen}
|
|
787
|
+
show-fullscreen-toggle={showFullscreenToggle}
|
|
788
|
+
show-close={showClose}
|
|
789
|
+
custom-class={customClass || className}
|
|
790
|
+
>
|
|
791
|
+
{header && <div slot="header">{header}</div>}
|
|
792
|
+
<div slot="content">{children}</div>
|
|
793
|
+
{footer && <div slot="footer">{footer}</div>}
|
|
794
|
+
</ModusWcModal>
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
// Usage in parent component
|
|
800
|
+
function ParentComponent() {
|
|
801
|
+
const modalRef = useRef<ModusModalRef>(null);
|
|
802
|
+
|
|
803
|
+
return (
|
|
804
|
+
<div>
|
|
805
|
+
<button onClick={() => modalRef.current?.openModal()}>Open Modal</button>
|
|
806
|
+
<ModusModal
|
|
807
|
+
ref={modalRef}
|
|
808
|
+
modalId="my-modal"
|
|
809
|
+
onClose={() => console.log("Modal closed")}
|
|
810
|
+
header={<div>Modal Title</div>}
|
|
811
|
+
footer={<div>Modal Actions</div>}
|
|
812
|
+
>
|
|
813
|
+
<div>Modal Content</div>
|
|
814
|
+
</ModusModal>
|
|
815
|
+
</div>
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### **Benefits of This Pattern:**
|
|
821
|
+
|
|
822
|
+
- ✅ **Follows Official Documentation**: Aligns with Modus documentation exactly
|
|
823
|
+
- ✅ **No React State Control**: Uses direct method calls as documented
|
|
824
|
+
- ✅ **Type Safety**: Full TypeScript support with proper interfaces
|
|
825
|
+
- ✅ **Reusable**: Can be used across the application consistently
|
|
826
|
+
- ✅ **Performance**: No unnecessary re-renders from state changes
|
|
827
|
+
- ✅ **Maintainable**: Clear separation of concerns
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
**Remember: ModusWcModal methods are on the inner dialog element, not the component itself. Always use `querySelector('dialog')` to access the native dialog element and call methods on it. This ensures proper modal functionality in React applications and aligns with the official Modus documentation.**
|