diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml new file mode 100644 index 0000000..b68fe27 --- /dev/null +++ b/.github/workflows/github-pages.yml @@ -0,0 +1,62 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22.9.0' + + - name: Setup Yarn Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install + + - name: Build Storybook + run: yarn build-storybook + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Don't remove PR previews when deploying the main branch + path: 'apps/docs/storybook-static' + retention-days: 30 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..07a2e71 --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,168 @@ +name: Deploy PR Preview + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + pull-requests: write + +# Allow only one concurrent deployment per PR +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + # Build job + build: + runs-on: ubuntu-latest + # Don't run on closed PRs + if: github.event.action != 'closed' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22.9.0' + + - name: Setup Yarn Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install + + - name: Build Storybook + run: yarn build-storybook + + # Verify the build output + - name: Verify build output + run: | + echo "Checking build output directory..." + ls -la apps/docs/storybook-static + echo "Checking for index.html..." + if [ -f apps/docs/storybook-static/index.html ]; then + echo "index.html exists" + else + echo "index.html does not exist" + exit 1 + fi + + # Add a comment to the PR with the preview URL + - name: Comment PR + uses: actions/github-script@v6 + with: + script: | + const previewUrl = `https://lambda-curry.github.io/medusa-forms/pr-${context.issue.number}/`; + const commentBody = `📝 **Storybook Preview**: [View Storybook](${previewUrl}) + + This preview will be updated automatically when you push new changes to this PR. + + > Note: The preview will be available after the workflow completes and the PR is approved for deployment.`; + + // Get existing comments + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + // Check if we already have a comment + const botComment = comments.data.find(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('Storybook Preview') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } + + # Create PR-specific directory structure + - name: Create PR-specific directory + run: | + mkdir -p pr-preview/pr-${{ github.event.pull_request.number }} + cp -r apps/docs/storybook-static/* pr-preview/pr-${{ github.event.pull_request.number }}/ + + # Upload the artifact for the deployment job + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: pr-preview + retention-days: 30 + + # Deploy job + deploy: + needs: build + runs-on: ubuntu-latest + if: github.event.action != 'closed' + + # Use a specific environment with protection rules + # This ensures only approved PRs can deploy + environment: + name: pr-preview + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + # Clean up when PR is closed + cleanup: + runs-on: ubuntu-latest + if: github.event.action == 'closed' + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: Delete PR Preview + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + PR_PREVIEW_PATH="pr-preview/pr-$PR_NUMBER" + + if [ -d "$PR_PREVIEW_PATH" ]; then + echo "Removing PR preview at $PR_PREVIEW_PATH" + rm -rf "$PR_PREVIEW_PATH" + + # Commit and push the changes + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git add -A + git commit -m "Remove PR preview for PR #$PR_NUMBER" || echo "No changes to commit" + git push + else + echo "PR preview directory not found" + fi + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4ab25d9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + + - name: Enable Corepack + run: corepack enable + + - name: Install Correct Yarn Version + run: corepack prepare yarn@4.9.1 --activate + + - name: Install Dependencies + run: yarn + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: yarn release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fccf6da --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,50 @@ +name: Run Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22.9.0' + + - name: Setup Yarn Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install + + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + + - name: Build packages + run: yarn build + + - name: Build Storybook + run: yarn build-storybook + + - name: Lint and format check + run: yarn format-and-lint + + - name: Upload artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-failure + path: apps/docs/storybook-static + retention-days: 2 + diff --git a/.vscode/settings.json b/.vscode/settings.json index f8b9823..1269b28 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,5 +22,8 @@ "source.fixAll.biome": "explicit", "source.organizeImports.biome": "explicit" }, - "tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", "wrapperClassName"] + "tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", "wrapperClassName"], + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + } } diff --git a/README.md b/README.md index 98611c3..65e3097 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,84 @@ A controlled form components library for Medusa Admin and Medusa UI applications. +Checkout our [Storybook Documentation](https://lambda-curry.github.io/medusa-forms/?path=/docs/0-1-hello-world-start-here--docs) to see the components in action and get started. + +## Features + +- **Controlled Components**: All form components are controlled and work seamlessly with react-hook-form +- **Medusa UI Integration**: Built specifically for Medusa Admin and Medusa UI design system +- **TypeScript Support**: Full TypeScript support with proper type definitions +- **Storybook Documentation**: Comprehensive documentation and examples + +## Components + +- `ControlledInput` - Text input with validation +- `ControlledTextArea` - Multi-line text input +- `ControlledSelect` - Dropdown selection +- `ControlledCheckbox` - Checkbox input +- `ControlledDatePicker` - Date selection +- `ControlledCurrencyInput` - Currency input with formatting + ## Getting Started -This repository is being set up. Please check back soon for the complete implementation. +Step 1: Install dependencies + +```bash +yarn install +``` + +Step 2: Start Storybook + +```bash +yarn storybook +``` + +## Development + +### PR Previews + +When you create a pull request, a preview of the Storybook documentation will be automatically deployed. You can find the link to the preview in the PR comments. This allows you to review changes to the documentation and components before merging. + +Preview URLs follow this format: +``` +https://lambda-curry.github.io/medusa-forms/pr-preview/pr-[PR_NUMBER]/ +``` + +#### How PR Previews Work + +The PR preview system: +1. Builds the Storybook documentation for each PR +2. Deploys it to a PR-specific directory on the `gh-pages` branch +3. Adds a comment to the PR with a link to the preview +4. **Automatically updates the preview when you push new changes to the PR** +5. Cleans up the preview when the PR is closed + +#### GitHub Environment Setup + +For PR previews to work properly, you need to set up a GitHub environment: + +1. Go to your repository settings +2. Navigate to "Environments" +3. Create a new environment named `pr-preview` +4. Configure environment protection rules as needed: + - You can require reviewers to approve deployment + - You can limit deployment to specific branches + - You can add wait timers before deployment + +The main branch will continue to deploy to the `github-pages` environment. + +#### Troubleshooting PR Previews + +If you encounter a 404 error when accessing the PR preview: + +1. Make sure the PR build has completed successfully by checking the GitHub Actions tab +2. Verify that the repository has GitHub Pages enabled and configured to deploy from the `gh-pages` branch +3. Check that the PR preview comment contains the correct URL +4. Ensure the PR has been approved for deployment if environment protection rules are enabled +5. Try clearing your browser cache or using an incognito window + +The PR preview is deployed to the `gh-pages` branch in a directory structure like: +``` +/pr-preview/pr-[PR_NUMBER]/ +``` diff --git a/apps/docs/.storybook/main.ts b/apps/docs/.storybook/main.ts index 3ecc947..c138a79 100644 --- a/apps/docs/.storybook/main.ts +++ b/apps/docs/.storybook/main.ts @@ -10,10 +10,7 @@ function getAbsolutePath(value: string): string { } const config: StorybookConfig = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], - addons: [ - getAbsolutePath('@storybook/addon-links'), - getAbsolutePath("@storybook/addon-docs") - ], + addons: [getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-docs')], framework: { name: getAbsolutePath('@storybook/react-vite'), options: {}, diff --git a/apps/docs/simple-server.js b/apps/docs/simple-server.js index a7b12be..1b386c3 100644 --- a/apps/docs/simple-server.js +++ b/apps/docs/simple-server.js @@ -1,6 +1,6 @@ -const http = require('http'); -const fs = require('fs'); -const path = require('path'); +const http = require('node:http'); +const fs = require('node:fs'); +const path = require('node:path'); const server = http.createServer((req, res) => { // Add CORS headers @@ -9,27 +9,27 @@ const server = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); let filePath = path.join(__dirname, 'storybook-static', req.url === '/' ? 'index.html' : req.url); - + // If file doesn't exist, serve index.html for SPA routing if (!fs.existsSync(filePath)) { filePath = path.join(__dirname, 'storybook-static', 'index.html'); } - + const ext = path.extname(filePath); - const contentType = { - '.html': 'text/html', - '.js': 'text/javascript', - '.css': 'text/css', - '.json': 'application/json', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.gif': 'image/gif', - '.svg': 'image/svg+xml' - }[ext] || 'text/plain'; - + const contentType = + { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + }[ext] || 'text/plain'; + fs.readFile(filePath, (err, content) => { if (err) { - console.error('Error reading file:', filePath, err); res.writeHead(404); res.end('Not found'); return; @@ -41,6 +41,5 @@ const server = http.createServer((req, res) => { const PORT = 45678; server.listen(PORT, () => { - console.log(`Server running on http://127.0.0.1:${PORT}`); + // Server started successfully }); - diff --git a/apps/docs/src/0.1 Hello World (start here).mdx b/apps/docs/src/0.1 Hello World (start here).mdx index 54157f3..5e4090c 100644 --- a/apps/docs/src/0.1 Hello World (start here).mdx +++ b/apps/docs/src/0.1 Hello World (start here).mdx @@ -1,32 +1,140 @@ -# FORMS ARE HARD 🙃 -But everything is a form. And everything will continue to be a form until all the robots just talk to each other - that's probably the day I'll be able to retire. Until then, I'll keep working on forms and making them easier to implement. +# Welcome to Medusa Forms! 🎯 -## Intro: *How did we get here?* +**Controlled form components designed specifically for Medusa Admin and Medusa UI** -From jQuery Validation to AngularJS to Redux Form to Formik, I've spent way too many late nights working on form libraries. It's been a large part of my web development career as a front-end developer starting in the early 2010s. +Medusa Forms provides a comprehensive set of controlled form components that integrate seamlessly with the Medusa ecosystem, built on top of `react-hook-form` and `@medusajs/ui`. +## What is Medusa Forms? -## Inspiration +Medusa Forms is a collection of controlled form components extracted and optimized for use with Medusa Admin interfaces. Each component is designed to work perfectly with Medusa's design system while providing excellent developer experience through TypeScript support and comprehensive documentation. -Now everything is trending back to "using the platform" with this concept of progressive enhancement. I'm a big fan of Ryan Florence [(@ryanflorence)](https://x.com/ryanflorence) and Michael Jackson [(@mjackson)](https://x.com/mjackson)'s work on [Remix](https://x.com/remix_run). Also, Alem Tuzlak [(@AlemTuzlak)](https://x.com/AlemTuzlak) and Thomas Fritz [(@thomasf)](https://x.com/thomasf)'s work on Remix Hook Form (and Remix Dev Tools) with their agency Forge42 [(@forge42dev)](https://x.com/forge42dev). +## Key Features -Special shoutout to Edmund Hung [(@edmundhung)](https://x.com/edmundhung) for his work on [React Conform](https://x.com/react_conform) which is another great library for working with forms. His stuff is so cool that people like Kent C. Dodds [(@kentcdodds)](https://x.com/kentcdodds) have adopted his library into his Epic Web stack. +✅ **Medusa UI Integration** - Built specifically for Medusa Admin design system +✅ **Controlled Components** - All components work seamlessly with react-hook-form +✅ **TypeScript Support** - Full type definitions and IntelliSense +✅ **Comprehensive Documentation** - Interactive Storybook with examples +✅ **Performance Optimized** - Tree-shaking support and optimized bundle sizes +## Available Components -## Goals +### Form Controls +- **ControlledInput** - Text input with validation +- **ControlledTextArea** - Multi-line text input +- **ControlledSelect** - Dropdown selection with search +- **ControlledCheckbox** - Checkbox input with proper styling +- **ControlledDatePicker** - Date selection with calendar +- **ControlledCurrencyInput** - Currency input with formatting -1. Extremely easy implementation in React Frameworks such as Next.js and Remix. -2. Well tested, documented, and maintained. -3. Forward thinking - for example React 19 is coming and it has a few new features that could make working with forms even easier. +### UI Components +- **FieldWrapper** - Consistent field styling and error handling +- **Label** - Accessible form labels +- **ErrorMessage** - Standardized error display -## WIP +## Quick Start -This is a work in progress. Please don't hesitate to message me with questions or collaboration opportunities. +### Installation -Next up: +```bash +npm install @lambdacurry/medusa-forms +# or +yarn add @lambdacurry/medusa-forms +``` -- [ ] Add a few Next.js Examples -- [ ] Add more components (React Select?) -- [ ] Add some AI specific documentation - usefull for prompts and writing tests -- [ ] Rename Default stories to Test stories and provide documentation on how they are used by the test runner with the play command. -- [ ] Add documentation of base components to Storybook without tests (maybe they can have visual regression tests in the future) +### Basic Usage + +```tsx +import { useForm, FormProvider } from 'react-hook-form'; +import { ControlledInput, ControlledSelect } from '@lambdacurry/medusa-forms'; + +function MyForm() { + const methods = useForm(); + + return ( + +
+ + + + +
+ ); +} +``` + +## Integration with Medusa + +These components are designed to work perfectly in Medusa Admin interfaces: + +```tsx +import { RouteConfig } from "@medusajs/admin" +import { FormProvider, useForm } from "react-hook-form" +import { ControlledInput, ControlledCurrencyInput } from "@lambdacurry/medusa-forms" + +const ProductForm = () => { + const methods = useForm(); + + return ( + +
+ + + +
+ ) +} + +export const config: RouteConfig = { + link: { + label: "Custom Product Form", + }, +} + +export default ProductForm +``` + +## Form Integration Examples + +Check out our **Form Integration Examples** story to see complete working forms with: +- Validation schemas using Zod +- Complex form layouts +- Error handling +- Real-world use cases + +## Dependencies + +- **React** ^19.0.0 +- **react-hook-form** ^7.51.0 +- **@medusajs/ui** ^4.0.0 +- **zod** ^3.23.8 (for validation examples) + +## Next Steps + +1. 📖 **Explore Components** - Browse the component stories to see all available options +2. 🎮 **Try Examples** - Check out the Form Integration Examples for complete implementations +3. 🔧 **Customize** - All components accept standard HTML props and can be styled as needed +4. 🚀 **Build** - Start building your Medusa Admin forms with confidence! + +--- + +**Ready to build better forms?** Start exploring the components in the sidebar! 👈 diff --git a/apps/docs/src/main.css b/apps/docs/src/main.css index c1325f7..0ca752f 100644 --- a/apps/docs/src/main.css +++ b/apps/docs/src/main.css @@ -1,4 +1,4 @@ -@import 'tailwindcss'; +@import "tailwindcss"; @source "../../../packages/components"; @source "../../../packages/medusa-forms"; @source "../../../node_modules/@medusajs/ui"; @@ -246,48 +246,66 @@ /* Medusa UI Dark Mode Shadow Effects */ --borders-interactive-with-shadow: 0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1); --details-contrast-on-bg-interactive: 0px 1px 2px 0px rgba(30, 58, 138, 0.6); - --details-switch-handle: 0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, + --details-switch-handle: + 0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.16), 0px 5px 4px 0px rgba(0, 0, 0, 0.1), 0px 3px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.1), 0px 0px 1px 0px rgba(0, 0, 0, 0.1); --borders-interactive-with-active: 0px 0px 0px 1px rgba(96, 165, 250, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.25); --borders-focus: 0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8); - --borders-interactive-with-focus: 0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1), + --borders-interactive-with-focus: + 0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8); - --details-switch-background-focus: 0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8), + --details-switch-background-focus: + 0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8), 0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.75px rgba(255, 255, 255, 0.12) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset; - --buttons-danger: 0px -1px 0px 0px rgba(255, 255, 255, 0.16), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), + --buttons-danger: + 0px -1px 0px 0px rgba(255, 255, 255, 0.16), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(159, 18, 57, 1), 0px 0px 1px 1.5px rgba(0, 0, 0, 0.24), 0px 2px 2px 0px rgba(0, 0, 0, 0.24); - --buttons-danger-focus: 0px -1px 0px 0px rgba(255, 255, 255, 0.16), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), + --buttons-danger-focus: + 0px -1px 0px 0px rgba(255, 255, 255, 0.16), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(159, 18, 57, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8); - --details-switch-background: 0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, + --details-switch-background: + 0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.75px rgba(255, 255, 255, 0.12) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset; - --buttons-inverted-focus: 0px -1px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), + --buttons-inverted-focus: + 0px -1px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(82, 82, 91, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8); - --elevation-flyout: 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), + --elevation-flyout: + 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32); --borders-error: 0px 0px 0px 1px rgba(244, 63, 94, 1), 0px 0px 0px 3px rgba(225, 29, 72, 0.25); - --buttons-inverted: 0px -1px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), + --buttons-inverted: + 0px -1px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 0px 0px 1px rgba(82, 82, 91, 1), 0px 0px 1px 1.5px rgba(0, 0, 0, 0.24), 0px 2px 2px 0px rgba(0, 0, 0, 0.24); - --borders-base: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --borders-base: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 0px 1px 1.5px rgba(0, 0, 0, 0.24), 0px 2px 2px 0px rgba(0, 0, 0, 0.24); - --elevation-card-hover: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --elevation-card-hover: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 1px 4px 0px rgba(0, 0, 0, 0.48), 0px 2px 8px 0px rgba(0, 0, 0, 0.48); - --elevation-card-rest: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --elevation-card-rest: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 1px 2px 0px rgba(0, 0, 0, 0.32), 0px 2px 4px 0px rgba(0, 0, 0, 0.32); - --buttons-neutral: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --buttons-neutral: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 0px 1px 1.5px rgba(0, 0, 0, 0.24), 0px 2px 2px 0px rgba(0, 0, 0, 0.24); - --elevation-code-block: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --elevation-code-block: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 1px 2px 0px rgba(0, 0, 0, 0.32), 0px 2px 4px 0px rgba(0, 0, 0, 0.32); - --buttons-neutral-focus: 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), + --buttons-neutral-focus: + 0px -1px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(39, 39, 42, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8); - --elevation-modal: 0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.06) inset, + --elevation-modal: + 0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.06) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32); - --elevation-commandbar: 0px 0px 0px 0.75px rgba(24, 24, 27, 1) inset, + --elevation-commandbar: + 0px 0px 0px 0.75px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.25px rgba(255, 255, 255, 0.1) inset, 0px 4px 8px 0px rgba(0, 0, 0, 0.32), 0px 8px 16px 0px rgba(0, 0, 0, 0.32); - --elevation-tooltip: 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 2px 4px 0px rgba(0, 0, 0, 0.32), + --elevation-tooltip: + 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 2px 4px 0px rgba(0, 0, 0, 0.32), 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32); } @@ -406,53 +424,62 @@ --color-ui-alpha-400: var(--alpha-400); /* Medusa UI Shadow Effects */ - --shadow-borders-interactive-with-active: 0px 0px 0px 1px rgba(59, 130, 246, 1), - 0px 0px 0px 4px rgba(59, 130, 246, 0.2); - --shadow-buttons-danger-focus: 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, + --shadow-borders-interactive-with-active: 0px 0px 0px 1px rgba(59, 130, 246, 1), 0px 0px 0px 4px + rgba(59, 130, 246, 0.2); + --shadow-buttons-danger-focus: + 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(190, 18, 60, 0.4), 0px 0px 0px 1px rgba(190, 18, 60, 1), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6); --shadow-details-contrast-on-bg-interactive: 0px 1px 2px 0px rgba(30, 58, 138, 0.6); - --shadow-borders-interactive-with-focus: 0px 1px 2px 0px rgba(30, 58, 138, 0.5), 0px 0px 0px 1px rgba(59, 130, 246, 1), + --shadow-borders-interactive-with-focus: + 0px 1px 2px 0px rgba(30, 58, 138, 0.5), 0px 0px 0px 1px rgba(59, 130, 246, 1), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6); --shadow-borders-error: 0px 0px 0px 1px rgba(225, 29, 72, 1), 0px 0px 0px 3px rgba(225, 29, 72, 0.15); --shadow-borders-focus: 0px 0px 0px 1px rgba(255, 255, 255, 1), 0px 0px 0px 3px rgba(59, 130, 246, 0.6); - --shadow-borders-interactive-with-shadow: 0px 1px 2px 0px rgba(30, 58, 138, 0.5), - 0px 0px 0px 1px rgba(59, 130, 246, 1); + --shadow-borders-interactive-with-shadow: 0px 1px 2px 0px rgba(30, 58, 138, 0.5), 0px 0px 0px 1px + rgba(59, 130, 246, 1); --shadow-buttons-danger: 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(190, 18, 60, 0.4), 0px 0px 0px 1px rgba(190, 18, 60, 1); - --shadow-buttons-inverted-focus: 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(0, 0, 0, 0.4), + --shadow-buttons-inverted-focus: + 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(0, 0, 0, 0.4), 0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6); - --shadow-elevation-card-hover: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), - 0px 2px 8px 0px rgba(0, 0, 0, 0.1); - --shadow-details-switch-handle: 0px 0px 2px 1px rgba(255, 255, 255, 1) inset, + --shadow-elevation-card-hover: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), 0px 2px 8px + 0px rgba(0, 0, 0, 0.1); + --shadow-details-switch-handle: + 0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.02), 0px 5px 4px 0px rgba(0, 0, 0, 0.02), 0px 3px 3px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.08); --shadow-buttons-neutral: 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 1px rgba(0, 0, 0, 0.08); --shadow-borders-base: 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 1px rgba(0, 0, 0, 0.08); - --shadow-elevation-card-rest: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), - 0px 2px 4px 0px rgba(0, 0, 0, 0.04); - --shadow-buttons-neutral-focus: 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 1px rgba(0, 0, 0, 0.08), + --shadow-elevation-card-rest: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 1px 2px -1px rgba(0, 0, 0, 0.08), 0px 2px 4px + 0px rgba(0, 0, 0, 0.04); + --shadow-buttons-neutral-focus: + 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6); - --shadow-details-switch-background-focus: 0px 0px 0px 1px rgba(255, 255, 255, 1), + --shadow-details-switch-background-focus: + 0px 0px 0px 1px rgba(255, 255, 255, 1), 0px 0px 0px 3px rgba(59, 130, 246, 0.6), 0px 1px 1px 0px rgba(0, 0, 0, 0.04) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.04) inset, 0px 0px 0px 0.75px rgba(0, 0, 0, 0.06) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.02) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.04); - --shadow-details-switch-background: 0px 1px 1px 0px rgba(0, 0, 0, 0.04) inset, + --shadow-details-switch-background: + 0px 1px 1px 0px rgba(0, 0, 0, 0.04) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.04) inset, 0px 0px 0px 0.75px rgba(0, 0, 0, 0.06) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.02) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.04); - --shadow-elevation-flyout: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.08), - 0px 8px 16px 0px rgba(0, 0, 0, 0.08); - --shadow-elevation-tooltip: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 2px 4px 0px rgba(0, 0, 0, 0.08), - 0px 4px 8px 0px rgba(0, 0, 0, 0.08); - --shadow-elevation-modal: 0px 0px 0px 1px rgba(255, 255, 255, 1) inset, + --shadow-elevation-flyout: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px + rgba(0, 0, 0, 0.08); + --shadow-elevation-tooltip: 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 2px 4px 0px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px + rgba(0, 0, 0, 0.08); + --shadow-elevation-modal: + 0px 0px 0px 1px rgba(255, 255, 255, 1) inset, 0px 0px 0px 1.5px rgba(228, 228, 231, 0.6) inset, 0px 0px 0px 1px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px rgba(0, 0, 0, 0.08), 0px 16px 32px 0px rgba(0, 0, 0, 0.08); - --shadow-elevation-code-block: 0px 0px 0px 1px rgba(24, 24, 27, 1) inset, - 0px 0px 0px 1.5px rgba(255, 255, 255, 0.2) inset; - --shadow-buttons-inverted: 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(0, 0, 0, 0.4), - 0px 0px 0px 1px rgba(24, 24, 27, 1); - --shadow-elevation-commandbar: 0px 0px 0px 0.75px rgba(39, 39, 42, 1) inset, + --shadow-elevation-code-block: 0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.2) + inset; + --shadow-buttons-inverted: 0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(0, 0, 0, 0.4), 0px + 0px 0px 1px rgba(24, 24, 27, 1); + --shadow-elevation-commandbar: + 0px 0px 0px 0.75px rgba(39, 39, 42, 1) inset, 0px 0px 0px 1.25px rgba(255, 255, 255, 0.3) inset, 0px 8px 16px 0px rgba(0, 0, 0, 0.08), 0px 16px 32px 0px rgba(0, 0, 0, 0.08); } @@ -471,247 +498,277 @@ font-size: 4rem; line-height: 4.400000095367432rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h2-webs { font-size: 3.5rem; line-height: 3.8500001430511475rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h3-webs { font-size: 2.5rem; line-height: 2.75rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h4-webs { font-size: 1.5rem; line-height: 1.875rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h1-core { font-size: 1.125rem; line-height: 1.75rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h2-core { font-size: 1rem; line-height: 1.5rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h3-core { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h1-docs { font-size: 1.5rem; line-height: 1.875rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h2-docs { font-size: 1.125rem; line-height: 1.6875rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .h3-docs { font-size: 1rem; line-height: 1.5rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-xlarge { font-size: 1.125rem; line-height: 1.6875rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-xlarge-plus { font-size: 1.125rem; line-height: 1.6875rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-large { font-size: 1rem; line-height: 1.5rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-large-plus { font-size: 1rem; line-height: 1.5rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-medium { font-size: 0.875rem; line-height: 1.3125rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-medium-plus { font-size: 0.875rem; line-height: 1.3125rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-small { font-size: 0.8125rem; line-height: 1.21875rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-small-plus { font-size: 0.8125rem; line-height: 1.21875rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-xsmall { font-size: 0.75rem; line-height: 1.125rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-xsmall-plus { font-size: 0.75rem; line-height: 1.125rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-xlarge { font-size: 1.125rem; line-height: 1.25rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-xlarge-plus { font-size: 1.125rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-large { font-size: 1rem; line-height: 1.25rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-large-plus { font-size: 1rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-medium { font-size: 0.875rem; line-height: 1.25rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-medium-plus { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-small { font-size: 0.8125rem; line-height: 1.25rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-small-plus { font-size: 0.8125rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-xsmall { font-size: 0.75rem; line-height: 1.25rem; font-weight: 400; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .txt-compact-xsmall-plus { font-size: 0.75rem; line-height: 1.25rem; font-weight: 500; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', - Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } .code-body { font-size: 0.75rem; line-height: 1.125rem; font-weight: 400; - font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -719,6 +776,6 @@ font-size: 0.75rem; line-height: 0.9375rem; font-weight: 500; - font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } diff --git a/apps/docs/src/medusa-forms/ControlledCheckbox.stories.tsx b/apps/docs/src/medusa-forms/ControlledCheckbox.stories.tsx index e75df06..8ab1d95 100644 --- a/apps/docs/src/medusa-forms/ControlledCheckbox.stories.tsx +++ b/apps/docs/src/medusa-forms/ControlledCheckbox.stories.tsx @@ -3,6 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +// Regex patterns defined at top level for performance +const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; + const meta = { title: 'Medusa Forms/Controlled Checkbox', component: ControlledCheckbox, @@ -26,19 +29,15 @@ const BasicCheckboxForm = () => { return (
- -
- Current value: {form.watch('acceptTerms') ? 'true' : 'false'} -
+ +
Current value: {form.watch('acceptTerms') ? 'true' : 'false'}
); }; export const BasicUsage: Story = { + args: { name: 'acceptTerms', label: 'I accept the terms and conditions' }, render: () => , }; @@ -53,19 +52,15 @@ const DefaultCheckedForm = () => { return (
- -
- Current value: {form.watch('newsletter') ? 'true' : 'false'} -
+ +
Current value: {form.watch('newsletter') ? 'true' : 'false'}
); }; export const DefaultChecked: Story = { + args: { name: 'newsletter', label: 'Subscribe to newsletter', checked: true }, render: () => , }; @@ -80,19 +75,15 @@ const DefaultUncheckedForm = () => { return (
- -
- Current value: {form.watch('marketing') ? 'true' : 'false'} -
+ +
Current value: {form.watch('marketing') ? 'true' : 'false'}
); }; export const DefaultUnchecked: Story = { + args: { name: 'marketing', label: 'Receive marketing emails' }, render: () => , }; @@ -105,25 +96,24 @@ const RequiredValidationForm = () => { mode: 'onChange', }); - const onSubmit = (data: any) => { - console.log('Form submitted:', data); + const onSubmit = (data: Record) => { + // Form data processed successfully + alert(`Form data submitted: ${JSON.stringify(data)}`); }; return (
- -
- Form valid: {form.formState.isValid ? 'Yes' : 'No'} -
- - -
- Form valid: {form.formState.isValid ? 'Yes' : 'No'} -
+
Form valid: {form.formState.isValid ? 'Yes' : 'No'}
); }; export const CompleteFormExample: Story = { + args: { name: 'exampleForm', label: 'Complete form example' }, render: () => , }; diff --git a/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx b/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx index e027610..01b3bf6 100644 --- a/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx +++ b/apps/docs/src/medusa-forms/ControlledDatePicker.stories.tsx @@ -49,7 +49,8 @@ const RequiredFieldValidationComponent = () => { }); const onSubmit = (data: unknown) => { - console.log('Form submitted:', data); + // Form data processed successfully + alert(`Form data submitted: ${JSON.stringify(data)}`); }; return ( diff --git a/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx b/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx index 6f0cd93..123154e 100644 --- a/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx +++ b/apps/docs/src/medusa-forms/ControlledSelect.stories.tsx @@ -457,7 +457,7 @@ export const InteractiveDemo: Story = { const handleSelectChange = (value: unknown) => { setSelectedValue(value as string); - console.log('Select value changed:', value); + // Value changed - could trigger side effects here }; return ( diff --git a/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx b/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx index a4282e2..e89f6f5 100644 --- a/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx +++ b/apps/docs/src/medusa-forms/ControlledTextArea.stories.tsx @@ -128,7 +128,8 @@ const RequiredFieldForm = () => { }); const onSubmit = (data: unknown) => { - console.log('Form submitted:', data); + // Form data processed successfully + alert(`Form data submitted: ${JSON.stringify(data)}`); }; return ( @@ -242,7 +243,8 @@ const ValidationErrorForm = () => { }); const onSubmit = (data: unknown) => { - console.log('Form submitted:', data); + // Form data processed successfully + alert(`Form data submitted: ${JSON.stringify(data)}`); }; const hasError = !!form.formState.errors.message; @@ -331,7 +333,8 @@ const ComprehensiveForm = () => { }); const onSubmit = (data: unknown) => { - console.log('Comprehensive form submitted:', data); + // Form data processed successfully + alert(`Form data submitted: ${JSON.stringify(data)}`); }; return ( diff --git a/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx b/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx index f167bd4..82da8b4 100644 --- a/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx +++ b/apps/docs/src/medusa-forms/FormIntegrationExamples.stories.tsx @@ -9,6 +9,12 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +// Regex patterns defined at top level for performance +const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; +const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/; +const SKU_REGEX = /^[A-Z0-9-]+$/; +const DIMENSIONS_REGEX = /^\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*(inches?|in|cm|centimeters?)?$/i; + const meta = { title: 'Medusa Forms/Form Integration Examples', component: () => null, // No single component @@ -86,8 +92,8 @@ export const CompleteRegistrationFormExample: Story = { await new Promise((resolve) => setTimeout(resolve, 2000)); setIsSubmitting(false); - setSubmitResult(`Registration successful for ${data.firstName} ${data.lastName}!`); - console.log('Registration data:', data); + setSubmitResult(`Registration successful! Welcome, ${data.firstName}!`); + // Registration data processed successfully }; const countryOptions = [ @@ -142,7 +148,7 @@ export const CompleteRegistrationFormExample: Story = { rules={{ required: 'Email is required', pattern: { - value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, + value: EMAIL_REGEX, message: 'Invalid email address', }, }} @@ -183,7 +189,7 @@ export const CompleteRegistrationFormExample: Story = { required: 'Password is required', minLength: { value: 8, message: 'Password must be at least 8 characters' }, pattern: { - value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, + value: PASSWORD_REGEX, message: 'Password must contain at least one uppercase letter, one lowercase letter, and one number', }, @@ -317,7 +323,7 @@ export const ProductCreationFormExample: Story = { setIsSubmitting(false); setSubmitResult(`Product "${data.name}" created successfully!`); - console.log('Product data:', data); + // Product data processed successfully }; const categoryOptions = [ @@ -380,7 +386,7 @@ export const ProductCreationFormExample: Story = { rules={{ required: 'SKU is required', pattern: { - value: /^[A-Z0-9-]+$/, + value: SKU_REGEX, message: 'SKU must contain only uppercase letters, numbers, and hyphens', }, }} @@ -429,7 +435,7 @@ export const ProductCreationFormExample: Story = { rules={{ required: 'Dimensions are required', pattern: { - value: /^\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*x\s*\d+(\.\d+)?\s*(inches?|in|cm|centimeters?)?$/i, + value: DIMENSIONS_REGEX, message: 'Please enter dimensions in format: L x W x H (e.g., 10 x 8 x 6 inches)', }, }} @@ -590,7 +596,7 @@ export const FormValidationShowcase: Story = { rules={{ required: 'Email is required', pattern: { - value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, + value: EMAIL_REGEX, message: 'Invalid email format', }, }} diff --git a/biome.json b/biome.json index 058d760..7b15575 100644 --- a/biome.json +++ b/biome.json @@ -5,7 +5,10 @@ "clientKind": "git", "useIgnoreFile": false }, - "files": { "ignoreUnknown": false, "ignore": [".turbo", "yarn.lock"] }, + "files": { + "ignoreUnknown": false, + "ignore": [".turbo", "yarn.lock", "./apps/docs/storybook-static", "./packages/medusa-forms/dist", "**/package.json"] + }, "organizeImports": { "enabled": true }, "formatter": { "enabled": true, @@ -55,5 +58,17 @@ "useImportExtensions": "off" } } - } + }, + "overrides": [ + { + "include": ["./apps/docs/**/*"], + "linter": { + "rules": { + "correctness": { + "noUnusedFunctionParameters": "off" + } + } + } + } + ] } diff --git a/package.json b/package.json index fceb7f2..2126c8c 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,7 @@ "name": "medusa-forms", "version": "0.1.1", "private": true, - "workspaces": [ - "apps/*", - "packages/*" - ], + "workspaces": ["apps/*", "packages/*"], "scripts": { "start": "yarn dev", "dev": "turbo run dev", diff --git a/packages/medusa-forms/package.json b/packages/medusa-forms/package.json index 0a08a81..cc89ff4 100644 --- a/packages/medusa-forms/package.json +++ b/packages/medusa-forms/package.json @@ -4,9 +4,7 @@ "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.js", "types": "./dist/types/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "exports": { ".": { "import": { diff --git a/packages/medusa-forms/src/index.ts b/packages/medusa-forms/src/index.ts index ca92d9f..588b8c0 100644 --- a/packages/medusa-forms/src/index.ts +++ b/packages/medusa-forms/src/index.ts @@ -1,2 +1 @@ export * from './controlled'; - diff --git a/packages/medusa-forms/src/ui/FieldCheckbox.tsx b/packages/medusa-forms/src/ui/FieldCheckbox.tsx index 37cc063..0331d3f 100644 --- a/packages/medusa-forms/src/ui/FieldCheckbox.tsx +++ b/packages/medusa-forms/src/ui/FieldCheckbox.tsx @@ -36,7 +36,10 @@ export const FieldCheckbox: React.FC = ({ {...fieldProps} ref={ref} checked={props.checked} - onChange={(e) => {}} + onChange={(_e) => { + // this is a noop since we handle the onChange in the onCheckedChange handler, + // but we need to pass a function to the onChange prop because the MedusaCheckbox component expects it + }} onCheckedChange={(checked) => { onChange?.(checked); }} diff --git a/packages/medusa-forms/src/ui/FileUpload.tsx b/packages/medusa-forms/src/ui/FileUpload.tsx index b4d88d3..e7f9919 100644 --- a/packages/medusa-forms/src/ui/FileUpload.tsx +++ b/packages/medusa-forms/src/ui/FileUpload.tsx @@ -39,32 +39,36 @@ const FileUpload: FC = ({ } }; - const handleFileDrop = (e: DragEvent) => { - setUploadError(false); - e.preventDefault(); - + const extractFilesFromItems = (items: DataTransferItemList): File[] => { const files: File[] = []; - - if (e.dataTransfer.items) { - // use DataTransferItemList interface to access the file(s) - for (let i = 0; i < e.dataTransfer.items.length; i += 1) { - // if dropped items are not files, reject them - if (e.dataTransfer.items[i].kind === 'file') { - const file = e.dataTransfer.items[i].getAsFile(); - - if (file && filetypes.indexOf(file.type) > -1) { - files.push(file); - } + for (const item of items) { + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file && filetypes.indexOf(file.type) > -1) { + files.push(file); } } - } else { - // use DataTransfer interface to access the file(s) - for (let i = 0; i < e.dataTransfer.files.length; i += 1) { - if (filetypes.indexOf(e.dataTransfer.files[i].type) > -1) { - files.push(e.dataTransfer.files[i]); - } + } + return files; + }; + + const extractFilesFromFileList = (fileList: FileList): File[] => { + const files: File[] = []; + for (const file of fileList) { + if (filetypes.indexOf(file.type) > -1) { + files.push(file); } } + return files; + }; + + const handleFileDrop = (e: DragEvent) => { + setUploadError(false); + e.preventDefault(); + + const files = e.dataTransfer.items + ? extractFilesFromItems(e.dataTransfer.items) + : extractFilesFromFileList(e.dataTransfer.files); if (files.length === 1) { onFileChosen(files); diff --git a/packages/medusa-forms/src/ui/types.d.ts b/packages/medusa-forms/src/ui/types.d.ts index cafd967..2ddd712 100644 --- a/packages/medusa-forms/src/ui/types.d.ts +++ b/packages/medusa-forms/src/ui/types.d.ts @@ -1,4 +1,7 @@ +import type { CalendarDate, CalendarDateTime, Granularity } from '@internationalized/date'; +import type { BaseDatePickerProps } from '@medusajs/ui'; import type { ReactNode, RefAttributes } from 'react'; +import type * as React from 'react'; import type { Props, SelectInstance } from 'react-select'; import type { CreatableProps } from 'react-select/creatable'; @@ -34,41 +37,16 @@ export type MedusaInputProps = React.InputHTMLAttributes & { size?: 'small' | 'base'; }; -interface PickerProps extends CalendarProps { - /** - * The class name to apply on the date picker. - */ - className?: string; - /** - * Whether the date picker's input is disabled. - */ - disabled?: boolean; - /** - * Whether the date picker's input is required. - */ - required?: boolean; - /** - * The date picker's placeholder. - */ - placeholder?: string; - /** - * The date picker's size. - */ - size?: 'small' | 'base'; - /** - * Whether to show a time picker along with the date picker. - */ - showTimePicker?: boolean; - /** - * Translation keys for the date picker. Use this to localize the date picker. - */ - translations?: Translations; - id?: string; - 'aria-invalid'?: boolean; - 'aria-label'?: string; - 'aria-labelledby'?: string; - 'aria-required'?: boolean; -} +type Option = { + label: string; + value: string; +}; + +type IsMulti = boolean; +type Group = { + label: string; + options: Option[]; +}; type DatePickerValueProps = { defaultValue?: Date | null; @@ -83,7 +61,7 @@ type DatePickerValueProps = { className?: string; modal?: boolean; }; -interface DatePickerProps +export interface DatePickerProps extends Omit, keyof DatePickerValueProps>, DatePickerValueProps {} @@ -111,7 +89,7 @@ export type SearchableSelectProps = Props & export type CreatableSelectProps = CreatableProps & RefAttributes>; -interface SelectProps extends React.ComponentPropsWithRef { +export interface SelectProps extends React.ComponentPropsWithRef { size?: 'base' | 'small'; children?: React.ReactNode; value?: string; @@ -120,7 +98,7 @@ interface SelectProps extends React.ComponentPropsWithRef { open?: boolean; defaultOpen?: boolean; onOpenChange?(open: boolean): void; - dir?: Direction; + dir?: 'ltr' | 'rtl'; name?: string; autoComplete?: string; disabled?: boolean;