{"_id":"@adit_firdaus/may-ui","_rev":"2-ec21774d8f80106876275ecb767c9c78","name":"@adit_firdaus/may-ui","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@adit_firdaus/may-ui","version":"0.1.0","keywords":["react","design-system","components","ui","ios","apple","tailwind-free"],"author":{"name":"Adit Firdaus","email":"aditfirdaus.dml@gmail.com"},"license":"MIT","_id":"@adit_firdaus/may-ui@0.1.0","maintainers":[{"name":"adit_firdaus","email":"aditfirdaus.dml@gmail.com"}],"homepage":"https://github.com/adit-firdaus/may-ui#readme","bugs":{"url":"https://github.com/adit-firdaus/may-ui/issues"},"dist":{"shasum":"56e0a615d0dbfc392253b8fd517b8700ec25d48a","tarball":"https://registry.npmjs.org/@adit_firdaus/may-ui/-/may-ui-0.1.0.tgz","fileCount":28,"integrity":"sha512-JZ1xKrQfUb62/aRTbzuJt5oN2dD9AlI1wWciMdxR9fjkhSgEvL28KKh/jZ2LU/thUFJ/CMBOLrp8obh/eLOKrw==","signatures":[{"sig":"MEQCIAF6lwLJa9Rmq5pyQ1su08WhUR/OM3ZE8MrlV+s34N4MAiAn4NOp0qSMoaEvnohbZZqyla3xEBlgpdnGnCTF4fydFw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1369903},"main":"./dist/mayui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/mayui.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/mayui.js","require":"./dist/mayui.cjs"},"./mobile":{"types":"./dist/mobile.d.ts","import":"./dist/mobile.js","require":"./dist/mobile.cjs"},"./desktop":{"types":"./dist/desktop.d.ts","import":"./dist/desktop.js","require":"./dist/desktop.cjs"},"./examples":{"types":"./dist/examples.d.ts","import":"./dist/examples.js","require":"./dist/examples.cjs"},"./styles.css":"./dist/mayui.css","./package.json":"./package.json"},"gitHead":"0042019312c6a6408f71a598f8092c9b48799342","scripts":{"dev":"storybook dev -p 6006","build":"npm run generate && tsc --noEmit -p tsconfig.check.json && vite build","smoke":"node scripts/smoke.mjs","verify":"npm run build && npm run smoke && npm run check:tokens && npm run check:contract && npm run check:size && npm run check:coverage","examples":"vite --config examples/vite.config.ts","generate":"npm run gen:tokens && npm run gen:springs","storybook":"storybook dev -p 6006","typecheck":"tsc --noEmit -p tsconfig.check.json","check:size":"node scripts/check-size.mjs","gen:tokens":"node scripts/gen-tokens.mjs","gen:springs":"node scripts/gen-springs.mjs","check:tokens":"node scripts/check-tokens.mjs","check:contract":"node scripts/check-contract.mjs","check:coverage":"node scripts/check-coverage.mjs","examples:build":"vite build --config examples/vite.config.ts","prepublishOnly":"npm run build","build-storybook":"storybook build"},"_npmUser":{"name":"adit_firdaus","email":"aditfirdaus.dml@gmail.com"},"repository":{"url":"git+https://github.com/adit-firdaus/may-ui.git","type":"git"},"_npmVersion":"10.9.8","description":"May UI — an Apple-native React design system: iOS design language, real spring motion, zero runtime dependencies.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"22.22.3","dependencies":{"react-icons":"^5.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.4.3","react":"^18.3.1","react-dom":"^18.3.1","storybook":"^8.6.14","typescript":"^5.7.2","@types/react":"^18.3.12","easing-utils":"^1.0.0","vite-plugin-dts":"^4.4.0","@storybook/react":"^8.6.14","@types/react-dom":"^18.3.1","@vitejs/plugin-react":"^4.3.4","@storybook/addon-docs":"^8.6.14","@storybook/react-vite":"^8.6.14","@storybook/addon-essentials":"^8.6.14"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/may-ui_0.1.0_1789068849315_0.5208508515523231","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@adit_firdaus/may-ui@0.2.0","bugs":{"url":"https://github.com/adit-firdaus/may-ui/issues"},"dist":{"shasum":"b290bca63b471d840d324d4377f9c2ced44d0a8c","tarball":"https://registry.npmjs.org/@adit_firdaus/may-ui/-/may-ui-0.2.0.tgz","fileCount":28,"integrity":"sha512-ViT4NTVIlpR4GtGkgw1XlFF+zbTeL6D7+qleWmENhUxGz/kLyUe+dIIZ0c0mK3PIP3nTvnbNXFdGYJC6oTx8rg==","signatures":[{"sig":"MEQCIAuFYkNQK+TQO8dYcb1SIntLhFNinQUJByIsRlPeNOU/AiAsX8uvM+AffJp/R9w0tyL7GM6V1zx+1IDB3mpVgdDugA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDIbDQigXzGOxw1SKyxkldPS9/uiVJdhza2oynAQui8+wIgJUCqdx5fIfU0KQ8wnrxq4bI844S+kjTItOy/pM71Rk0="}],"unpackedSize":1387754},"main":"./dist/mayui.cjs","name":"@adit_firdaus/may-ui","type":"module","types":"./dist/index.d.ts","author":{"name":"Adit Firdaus","email":"aditfirdaus.dml@gmail.com"},"module":"./dist/mayui.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/mayui.js","require":"./dist/mayui.cjs"},"./mobile":{"types":"./dist/mobile.d.ts","import":"./dist/mobile.js","require":"./dist/mobile.cjs"},"./desktop":{"types":"./dist/desktop.d.ts","import":"./dist/desktop.js","require":"./dist/desktop.cjs"},"./examples":{"types":"./dist/examples.d.ts","import":"./dist/examples.js","require":"./dist/examples.cjs"},"./styles.css":"./dist/mayui.css","./package.json":"./package.json"},"gitHead":"76a1b2d368178f7a2a58bc004274ee8f593a6957","license":"MIT","scripts":{"dev":"storybook dev -p 6006","build":"npm run generate && tsc --noEmit -p tsconfig.check.json && vite build","smoke":"node scripts/smoke.mjs","verify":"npm run build && npm run smoke && npm run check:tokens && npm run check:contract && npm run check:size && npm run check:coverage","prepare":"npm run build","examples":"vite --config examples/vite.config.ts","generate":"npm run gen:tokens && npm run gen:springs","storybook":"storybook dev -p 6006","typecheck":"tsc --noEmit -p tsconfig.check.json","check:size":"node scripts/check-size.mjs","docs:build":"node scripts/build-docs.mjs","gen:tokens":"node scripts/gen-tokens.mjs","gen:springs":"node scripts/gen-springs.mjs","check:tokens":"node scripts/check-tokens.mjs","check:contract":"node scripts/check-contract.mjs","check:coverage":"node scripts/check-coverage.mjs","examples:build":"vite build --config examples/vite.config.ts","prepublishOnly":"npm run build","build-storybook":"storybook build"},"version":"0.2.0","_npmUser":{"name":"adit_firdaus","email":"aditfirdaus.dml@gmail.com"},"homepage":"https://github.com/adit-firdaus/may-ui#readme","keywords":["react","design-system","components","ui","ios","apple","tailwind-free"],"repository":{"url":"git+https://github.com/adit-firdaus/may-ui.git","type":"git"},"_npmVersion":"10.9.8","description":"May UI — an Apple-native React design system: iOS design language, real spring motion, zero runtime dependencies.","directories":{},"maintainers":[{"name":"adit_firdaus","email":"aditfirdaus.dml@gmail.com"}],"sideEffects":["**/*.css"],"_nodeVersion":"22.22.3","dependencies":{"react-icons":"^5.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.4.3","react":"^18.3.1","marked":"^18.0.12","react-dom":"^18.3.1","storybook":"^8.6.14","typescript":"^5.7.2","@types/react":"^18.3.12","easing-utils":"^1.0.0","vite-plugin-dts":"^4.4.0","@storybook/react":"^8.6.14","@types/react-dom":"^18.3.1","@vitejs/plugin-react":"^4.3.4","@storybook/addon-docs":"^8.6.14","@storybook/react-vite":"^8.6.14","@storybook/addon-essentials":"^8.6.14"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/may-ui_0.2.0_1789077219879_0.9322650254924647"}}},"time":{"created":"2026-09-10T19:34:09.145Z","modified":"2026-09-10T21:53:40.201Z","0.1.0":"2026-09-10T19:34:09.462Z","0.2.0":"2026-09-10T21:53:40.038Z"},"bugs":{"url":"https://github.com/adit-firdaus/may-ui/issues"},"author":{"name":"Adit Firdaus","email":"aditfirdaus.dml@gmail.com"},"license":"MIT","homepage":"https://github.com/adit-firdaus/may-ui#readme","keywords":["react","design-system","components","ui","ios","apple","tailwind-free"],"repository":{"url":"git+https://github.com/adit-firdaus/may-ui.git","type":"git"},"description":"May UI — an Apple-native React design system: iOS design language, real spring motion, zero runtime dependencies.","maintainers":[{"name":"adit_firdaus","email":"aditfirdaus.dml@gmail.com"}],"readme":"# May UI\n\nApple's design language as a React library — iOS 26 / macOS shapes, real spring physics,\nand a token layer you can theme.\n\n- **74 components** across three families — adaptive, desktop-only, mobile-only\n- **No borders** — surfaces separate by value, translucent fill and hairline separators\n- **Real springs** — damped-oscillator curves sampled into CSS `linear()`, no runtime JS\n- **Token-first** — every colour, space, radius, shadow and duration is a `--may-*` property\n- **Light and dark** — follows the OS by default, pinnable per subtree\n- **Typed** — bundled `.d.ts` per entry, JSDoc on every prop\n\n**[Docs](docs/)** · **[Gallery](https://adit-firdaus.github.io/may-ui/)** · **[Storybook](https://adit-firdaus.github.io/may-ui/storybook/)** · **[Contributing](CONTRIBUTING.md)**\n\n## Install\n\n```bash\nnpm install @adit_firdaus/may-ui\n```\n\n`react` and `react-dom` (18+) are peer dependencies. [`react-icons`](https://react-icons.github.io/react-icons/)\nis a dependency — May UI draws its glyphs from Ionicons (`react-icons/io5`), and it stays\nexternal so your bundler tree-shakes it per icon.\n\n## Use\n\nImport the stylesheet once at the root, wrap your tree in `MayProvider`, and mount\n`MayHost` once for imperative surfaces:\n\n```tsx\nimport '@adit_firdaus/may-ui/styles.css'\nimport { MayProvider, MayHost, Card, CardTitle, Stack, Text, Button } from '@adit_firdaus/may-ui'\n\nexport function App() {\n  return (\n    <MayProvider theme=\"system\">\n      <MayHost />\n      <Stack gap={4}>\n        <Card>\n          <CardTitle>Production deploy</CardTitle>\n          <Text tone=\"secondary\">All 14 health checks passed.</Text>\n          <Button>Promote</Button>\n        </Card>\n      </Stack>\n    </MayProvider>\n  )\n}\n```\n\n`MayProvider` renders the `.may-root` element carrying every token and the base layer.\nComponents render unstyled without it.\n\n## Three entry points\n\n| Import | What lives there |\n|---|---|\n| `@adit_firdaus/may-ui` | **Adaptive** components. One per concern, reshaping at the breakpoint. Start here. |\n| `@adit_firdaus/may-ui/desktop` | Shapes with no honest phone form |\n| `@adit_firdaus/may-ui/mobile` | Shapes with no desktop meaning |\n\nThe adaptive components genuinely reshape rather than restyle: `Sheet` rises from the\nbottom with drag-to-dismiss on phones and presents as a centred dialog on desktop,\n`ActionSheet` becomes an anchored menu, `Table` collapses into grouped list rows. Reach\ninto `@adit_firdaus/may-ui/desktop` only when you want more than the adaptive version carries.\n\nConsumers importing `@adit_firdaus/may-ui` never pull the desktop `DataTable` or the mobile gesture code.\n\n## Theming\n\n| `theme` prop | Behaviour |\n|---|---|\n| `'system'` (default) | follows `prefers-color-scheme` |\n| `'light'` | pinned light |\n| `'dark'` | pinned dark |\n\n```tsx\nconst { theme, resolvedTheme, setTheme } = useMayTheme()\n```\n\nProviders nest, so a region can be pinned dark inside an otherwise light page — the\nsemantic aliases are re-declared in every theme scope, so a scoped `data-may-theme`\nre-resolves them rather than leaving them stuck at the outer theme's values.\n\n`accent` on the provider re-points the tint.\n\n## Icons\n\nGlyphs come from Ionicons, which is drawn to Apple's own icon grid:\n\n```tsx\nimport { IoSettingsOutline, IoAdd } from 'react-icons/io5'\n\n<Button leadingIcon={<IoSettingsOutline />}>Settings</Button>\n<IconButton aria-label=\"Add\"><IoAdd /></IconButton>\n```\n\n**Do not pass `size`, `width` or `height`.** Each component sizes the glyph to its own slot\nin CSS, so one icon is correct in a Button, a Fab and a list row without being told.\nreact-icons emits `width=\"1em\"` as an *attribute*, which a stylesheet rule always beats.\nFor a genuine one-off, `style={{ width: 20, height: 20 }}` wins over everything.\n\nUse filled icons (`IoStar`) on tab bars, selected states and coloured `IconTile`s; outline\n(`IoStarOutline`) for toolbars, nav actions and list rows.\n\n## Styling your own markup\n\nThere are **no utility classes**, and the `may-*` class names are internal. Two ways, in\nthis order:\n\n**Layout primitives first** — `Box`, `Stack`, `Grid`, `Separator` take token-bound props:\n\n```tsx\n<Stack direction=\"horizontal\" gap={3} align=\"center\" justify=\"between\">\n  <Box surface=\"base\" padding={4} radius=\"card\">…</Box>\n</Stack>\n```\n\n`gap` / `padding` take steps on the 4px scale: `0 1 2 3 4 5 6 8 10 12 16 20 24`.\n\n**Then `var(--may-*)` in your own CSS:**\n\n```css\n.my-panel {\n  background: var(--may-color-surface);\n  color: var(--may-color-text);\n  border-radius: var(--may-radius-card);\n  padding: var(--may-space-4);\n  box-shadow: var(--may-shadow-sm);\n}\n```\n\nTo separate two surfaces, **never reach for a border** — use a background-value change, a\nsoft shadow, or `<Separator />` (a true 0.5px device-pixel rule).\n\n## The token layers\n\nPrimitives are raw values (`--may-blue`, `--may-gray-6`); semantic aliases point at them\n(`--may-color-text: var(--may-label)`). Style against the **semantic** layer so themes work.\n\nSurfaces `--may-color-bg` `--may-color-surface` `--may-color-surface-nested` · text\n`--may-color-text` `--may-color-text-secondary` `--may-color-text-tertiary` · fills\n`--may-color-fill` through `--may-color-fill-quaternary` · lines `--may-color-separator` ·\nstatus `--may-color-success` `--may-color-warning` `--may-color-danger` `--may-color-info`.\n\nApple's system palette is available raw: `--may-blue` `--may-green` `--may-indigo`\n`--may-orange` `--may-pink` `--may-purple` `--may-red` `--may-teal` `--may-yellow`\n`--may-mint` `--may-cyan`.\n\n`--may-color-tint` and `--may-color-primary` are **not** the same token: tint is\nbrand-as-text (a link, a selected tab label), primary is brand-as-fill (a filled button).\nCollapsing them is what makes a ported Apple palette look wrong.\n\n## Motion\n\nSprings are real damped oscillators sampled at build time into CSS `linear()` — they run\non the compositor with no runtime JS:\n\n`--may-spring-snappy` · `--may-spring-smooth` · `--may-spring-bouncy` (overshoots) ·\n`--may-spring-playful`. Durations: `--may-duration-instant|fast|settle`.\n\nThe same constants are readable from JS for gesture code:\n\n```tsx\nimport { motion } from '@adit_firdaus/may-ui'\nmotion.duration.settle  // 340\n```\n\nEverything collapses under `prefers-reduced-motion`.\n\n## Shared vocabulary\n\n- **`tone`** — `tint` · `neutral` · `success` · `warning` · `danger`\n- **`size`** — `sm` · `md` · `lg`\n\n## Forms\n\nWrap every control in a `Field`. It owns the label, help text, error message, required\nmarker and the `aria-describedby` wiring; the control picks all of it up from context:\n\n```tsx\n<Field label=\"Email\" help=\"We'll only use this to sign you in.\" error={error}>\n  <Input type=\"email\" value={value} onChange={onChange} />\n</Field>\n```\n\nTo wire a control of your own into that machinery, read the context directly:\n`useFieldControl()` returns the `id`, `aria-describedby` and invalid state to spread onto\nyour input; `useFieldContext()` exposes the whole field state for custom layouts.\n\n## Components\n\n**Adaptive** (`@adit_firdaus/may-ui`)\n\n| Group | Components |\n|---|---|\n| Foundation | `MayProvider`, `MayHost`, `useMayTheme`, `PlatformProvider`, `usePlatform`, `useIsDesktop` |\n| Layout | `Box`, `Stack`, `Grid`, `Separator`, `SafeArea`, `ScrollArea` |\n| Typography | `Heading`, `Text`, `Label`, `Kbd` |\n| Actions | `Button`, `IconButton`, `ButtonGroup`, `Fab`, `Toolbar`, `ToolbarSpacer` |\n| Forms | `Field`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `RadioGroup`, `Switch`, `Slider`, `Stepper`, `SearchField`, `SegmentedControl` |\n| Data display | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardBody`, `CardFooter`, `Badge`, `Tag`, `Avatar`, `AvatarGroup`, `IconTile`, `Table`, `List`, `ListRow`, `Descriptions`, `DescriptionItem`, `Statistic`, `EmptyState` |\n| Feedback | `Alert`, `NoticeBar`, `Progress`, `CircularProgress`, `Spinner`, `Skeleton`, `Toast`, `toast`, `useToast` |\n| Navigation | `Tabs`, `TabList`, `Tab`, `TabPanel`, `NavigationBar`, `Breadcrumb`, `Pagination`, `Steps`, `Accordion`, `AccordionItem`, `Collapsible` |\n| Overlays | `Sheet`, `Modal`, `AlertDialog`, `ActionSheet`, `Menu`, `Popover`, `Tooltip` |\n| Utility | `VisuallyHidden`, `cx`, `motion`, `initialsFrom`, `usePressFeedback`, `useReducedMotion` |\n\n**Desktop** (`@adit_firdaus/may-ui/desktop`) — `Sidebar`, `SidebarSection`, `SidebarItem`, `SidebarToggle`,\n`NavTree`, `DataTable`, `CommandPalette`, `ContextMenu`, `SplitPane`\n\n**Mobile** (`@adit_firdaus/may-ui/mobile`) — `TabBar`, `NavBar`, `SearchBar`, `PullToRefresh`,\n`SwipeAction`, `CapsuleTabs`, `Selector`, `Popup`, `FloatingBubble`\n\n## Toasts\n\nMount `MayHost` once, then call `toast` from anywhere — no hook, no provider:\n\n```tsx\nimport { toast, dismiss, dismissAll, setToastLimit } from '@adit_firdaus/may-ui'\n\ntoast('Saved')\ntoast.success('Deploy promoted')\ntoast.error('Health check failed')\n```\n\n`dismiss(id)` closes one toast, `dismissAll()` clears the queue, `useToast()` reads it\nlive, and `setToastLimit(n)` caps how many stack at once.\n\n## Example screens\n\n27 composed screens — full app shells, not isolated widgets — live under `@adit_firdaus/may-ui/examples`\nand in the gallery app:\n\n```bash\nnpm run examples\n```\n\nThey are a separate entry, so importing `@adit_firdaus/may-ui` never pulls a demo screen into your bundle.\n\n## Accessibility\n\n- `IconButton` requires an `aria-label` at the type level\n- Focus rings are never removed, only restyled, and appear on `:focus-visible`\n- Overlays trap focus, restore it on close, and close on `Escape`\n- Hover styling is gated behind `@media (hover: hover) and (pointer: fine)`, so touch\n  devices never get stuck hover\n- Everything collapses under `prefers-reduced-motion`, including animation iteration\n\n## Development\n\n```bash\nnpm run dev          # Vite playground\nnpm run storybook    # component workshop\nnpm run examples     # the example-screen gallery\nnpm run build        # library build (four entries) + types\nnpm run verify       # typecheck, smoke, tokens, contract, size, coverage\n```\n\n`npm run generate` regenerates `src/styles/tokens.css` and `src/styles/motion.css` from\n`scripts/gen-tokens.mjs` and `scripts/gen-springs.mjs`. **Never edit those two CSS files\nby hand** — they are build output.\n\n### The gates\n\n`npm run verify` enforces the design contract rather than trusting it: no `backdrop-filter`,\nno visible strokes, every `:hover` behind a pointer query, chrome unselectable and body copy\nselectable, dark re-resolving the full semantic alias set, hairlines at true device pixels,\nthe tint/primary split intact, and springs that are real `linear()` samples. It also holds\nbundle budgets per entry and fails if any exported component appears in no example.\n\n## Adding a component\n\n1. `src/components/<Name>/` with `<Name>.tsx`, `<Name>.css`, `<Name>.stories.tsx`, `index.ts`\n   (or `src/desktop/` / `src/mobile/` for a dedicated family)\n2. Import the CSS from the `.tsx` so the bundler picks it up\n3. Class names are `may-<component>` with `__element` and `--modifier` (BEM)\n4. Style from tokens only — no literal colours, spacings, radii or shadows\n5. Draw glyphs with `react-icons/io5`, and size them in CSS against `.may-<component>__icon > svg`\n   — never with a `:not([width])` guard, which react-icons defeats\n6. Re-export from the matching entry, and use it in at least one example screen or\n   `check:coverage` will fail\n\nThe tone pattern is worth copying: tone modifiers **set** CSS variables and variant modifiers\n**consume** them, so a new tone never touches variant CSS and vice versa. See `Button.css`.\n","readmeFilename":"README.md"}