mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-23 02:31:31 +00:00
Format with oxfmt
This commit is contained in:
@@ -184,7 +184,6 @@ jobs:
|
||||
fi
|
||||
npm publish "${TGZ}" --access public --tag stable
|
||||
|
||||
|
||||
- name: Pack + Publish @hanzo/gui (alias of gui)
|
||||
# Republish the umbrella tarball under the @hanzo/gui scoped name so
|
||||
# downstream consumers can import either alias. Same dist tree as
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{}
|
||||
{}
|
||||
|
||||
@@ -80,6 +80,7 @@ brew tap wix/brew && brew install applesimutils
|
||||
```
|
||||
|
||||
The `run-detox.sh` script will automatically:
|
||||
|
||||
- Run `expo prebuild` if the `ios/` directory doesn't exist
|
||||
- Build the Detox framework cache if missing (needed after Xcode updates)
|
||||
- Start Metro if not already running
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"clean:build": "hanzogui-build clean:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/animate-presence": "workspace:*",
|
||||
"@hanzogui/avatar": "workspace:*",
|
||||
"@hanzogui/button": "workspace:*",
|
||||
@@ -55,6 +56,7 @@
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/context-menu": "workspace:*",
|
||||
"@hanzogui/core": "workspace:*",
|
||||
"@hanzogui/dev-config": "workspace:*",
|
||||
"@hanzogui/get-button-sized": "workspace:*",
|
||||
"@hanzogui/image": "workspace:*",
|
||||
"@hanzogui/input": "workspace:*",
|
||||
@@ -75,10 +77,8 @@
|
||||
"@hanzogui/switch": "workspace:*",
|
||||
"@hanzogui/switch-headless": "workspace:*",
|
||||
"@hanzogui/tabs": "workspace:*",
|
||||
"@hanzogui/dev-config": "workspace:*",
|
||||
"@hanzogui/theme": "workspace:*",
|
||||
"@hanzogui/toast": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzogui/toast": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/build": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { type ThemeName, Button, Card, H2, Paragraph, Text, Theme, XStack, YStack } from '@hanzo/gui'
|
||||
import {
|
||||
type ThemeName,
|
||||
Button,
|
||||
Card,
|
||||
H2,
|
||||
Paragraph,
|
||||
Text,
|
||||
Theme,
|
||||
XStack,
|
||||
YStack,
|
||||
} from '@hanzo/gui'
|
||||
|
||||
// Proof surface for the token layer: true-black neutral ramp, one accent per
|
||||
// white-label brand (read straight from each brand's generated theme — the
|
||||
|
||||
@@ -83,12 +83,11 @@ function SizeSection({ section }: { section: Section }) {
|
||||
backgroundColor="$color5"
|
||||
{...(section === 'spaceNegative' && {
|
||||
backgroundColor: '$red5',
|
||||
size:
|
||||
-tokens[
|
||||
spaceTokensNegative.find(
|
||||
(t) => Number.parseFloat(t) === -Number.parseFloat(token)
|
||||
) ?? token
|
||||
]?.val,
|
||||
size: -tokens[
|
||||
spaceTokensNegative.find(
|
||||
(t) => Number.parseFloat(t) === -Number.parseFloat(token)
|
||||
) ?? token
|
||||
]?.val,
|
||||
})}
|
||||
{...(section === 'radius' && {
|
||||
size: allTokens.size[token]?.val as any,
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"name": "@hanzogui/desktop",
|
||||
"version": "8.0.0",
|
||||
"private": true,
|
||||
"description": "Tauri desktop proof shell — renders hanzogui on the true-black theme in a native webview.",
|
||||
"type": "module",
|
||||
"description": "Tauri desktop proof shell \u2014 renders hanzogui on the true-black theme in a native webview.",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -11,11 +11,11 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/react-native-web-lite": "workspace:*",
|
||||
"@hanzogui/tauri": "workspace:*",
|
||||
"@hanzogui/themes": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,14 @@ html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
font-family:
|
||||
Zen, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
Zen,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
sans-serif;
|
||||
scrollbar-color: #b1b1b1 transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ import { apiRoute } from '~/features/api/apiRoute'
|
||||
export default apiRoute(async (req) => {
|
||||
const { ImageResponse } = await import('@vercel/og')
|
||||
|
||||
const [sansRegular, sansBlack] = await Promise.all([
|
||||
sansRegularP,
|
||||
sansBlackP,
|
||||
])
|
||||
const [sansRegular, sansBlack] = await Promise.all([sansRegularP, sansBlackP])
|
||||
|
||||
const { searchParams } = new URL(req.url)
|
||||
|
||||
@@ -52,9 +49,7 @@ const sansRegularP = fetchAsset(
|
||||
new URL(`${getURL()}/fonts/Zen-Regular.ttf`, import.meta.url)
|
||||
)
|
||||
|
||||
const sansBlackP = fetchAsset(
|
||||
new URL(`${getURL()}/fonts/Zen-Black.ttf`, import.meta.url)
|
||||
)
|
||||
const sansBlackP = fetchAsset(new URL(`${getURL()}/fonts/Zen-Black.ttf`, import.meta.url))
|
||||
|
||||
const logo = fetchAsset(new URL(`${getURL()}/wordmark-white.png`, import.meta.url))
|
||||
|
||||
|
||||
@@ -68,4 +68,3 @@ export const ContainerXL = styled(YStack, {
|
||||
|
||||
variants,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ by: nate
|
||||
|
||||
<IntroParagraph>
|
||||
<UL>
|
||||
<LI>A style library (`@hanzogui/core`) for creating React Native + Web components.</LI>
|
||||
<LI>
|
||||
A style library (`@hanzogui/core`) for creating React Native + Web components.
|
||||
</LI>
|
||||
<LI>An optimizing compiler (`@hanzogui/static`) for incredible performance.</LI>
|
||||
<LI>A component kit (`hanzogui`) built using both of the above.</LI>
|
||||
</UL>
|
||||
|
||||
@@ -7,9 +7,7 @@ by: nate
|
||||
# - 'hanzogui-alpha'
|
||||
---
|
||||
|
||||
<Notice>
|
||||
Gui is in alpha - we'd love it if you tried it out and gave us feedback
|
||||
</Notice>
|
||||
<Notice>Gui is in alpha - we'd love it if you tried it out and gave us feedback</Notice>
|
||||
|
||||
Gui takes the next steps towards truly universal styling for React apps.
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ poster: banner-rc0.jpg
|
||||
---
|
||||
|
||||
<IntroParagraph>
|
||||
Gui is an optimizing compiler meets universal style system for React that enables
|
||||
highly performant styles for React Native and web.
|
||||
Gui is an optimizing compiler meets universal style system for React that enables highly
|
||||
performant styles for React Native and web.
|
||||
</IntroParagraph>
|
||||
|
||||
It supports the full APIs of React Native alongside helpful inline style props,
|
||||
|
||||
@@ -852,7 +852,8 @@ stay fully OSS.
|
||||
<Spacer />
|
||||
<XStack space ai="stretch" maw="100%" $sm={{ fd: 'column' }}>
|
||||
<Card flex={1} title="📚 Docs">
|
||||
Your design system and UI auto-generates a beautiful docs site, just like hanzogui.dev.
|
||||
Your design system and UI auto-generates a beautiful docs site, just like
|
||||
hanzogui.dev.
|
||||
</Card>
|
||||
<Card flex={1} title="🖼️ Studio">
|
||||
A frontend realtime editor for colors, themes, tokens, default styles and more.
|
||||
|
||||
@@ -7,9 +7,8 @@ image: /blog/version-two.jpg
|
||||
---
|
||||
|
||||
<IntroParagraph large>
|
||||
Gui brings high performing, consistent, typed inline styles to React Native and
|
||||
React Web, plus composable UI components, a novel theme system and an optimizing
|
||||
compiler.
|
||||
Gui brings high performing, consistent, typed inline styles to React Native and React
|
||||
Web, plus composable UI components, a novel theme system and an optimizing compiler.
|
||||
</IntroParagraph>
|
||||
|
||||
We're excited to announce **the first release candidate for Gui v2 is
|
||||
@@ -335,9 +334,9 @@ state uses that state's transition, and exiting uses the base transition:
|
||||
|
||||
```tsx
|
||||
<Square
|
||||
transition="1000ms" // slow exit (back to base)
|
||||
transition="1000ms" // slow exit (back to base)
|
||||
hoverStyle={{
|
||||
transition: '200ms', // fast enter
|
||||
transition: '200ms', // fast enter
|
||||
backgroundColor: 'red',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -55,13 +55,16 @@ import '@hanzogui/native/setup-zeego'
|
||||
```
|
||||
|
||||
<Notice theme="blue">
|
||||
On native, `ContextMenu.Trigger` coordinates with parent Gui pressables
|
||||
automatically. You should not need extra wrapper views or empty `onPress`
|
||||
handlers just to prevent a parent `pressStyle` from activating.
|
||||
On native, `ContextMenu.Trigger` coordinates with parent Gui pressables automatically.
|
||||
You should not need extra wrapper views or empty `onPress` handlers just to prevent a
|
||||
parent `pressStyle` from activating.
|
||||
</Notice>
|
||||
|
||||
<Notice theme="yellow">
|
||||
**Expo Router users:** This import must run _before_ `expo-router/entry`. Create an `index.js` at your project root that imports the setup first, then expo-router, and update your `package.json` main field to `"index.js"`. See the [upgrade guide](/docs/guides/how-to-upgrade#expo-router-entry-point) for details.
|
||||
**Expo Router users:** This import must run _before_ `expo-router/entry`. Create an
|
||||
`index.js` at your project root that imports the setup first, then expo-router, and
|
||||
update your `package.json` main field to `"index.js"`. See the [upgrade
|
||||
guide](/docs/guides/how-to-upgrade#expo-router-entry-point) for details.
|
||||
</Notice>
|
||||
|
||||
## Anatomy
|
||||
@@ -158,7 +161,8 @@ Contains every component for the ContextMenu.
|
||||
name: 'onOpenChange',
|
||||
type: '(open: boolean, event?: { preventDefault: () => void }) => void',
|
||||
required: false,
|
||||
description: 'Called when the menu opens or closes. Call event.preventDefault() to cancel and allow the native context menu to show instead.',
|
||||
description:
|
||||
'Called when the menu opens or closes. Call event.preventDefault() to cancel and allow the native context menu to show instead.',
|
||||
},
|
||||
{
|
||||
name: 'onOpenWillChange',
|
||||
@@ -180,7 +184,8 @@ Contains every component for the ContextMenu.
|
||||
type: 'ShiftProps | boolean',
|
||||
required: false,
|
||||
default: '{ padding: 10 }',
|
||||
description: 'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
|
||||
description:
|
||||
'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
|
||||
},
|
||||
{
|
||||
name: 'allowFlip',
|
||||
|
||||
@@ -46,7 +46,7 @@ mode like anything else:
|
||||
That works because the axis is resolved against the mode. It is worth knowing
|
||||
why, because the raw CSS does not do this for you: `flex-direction` is **inert**
|
||||
under grid, and the two engines invert the same word — flex `row` runs across,
|
||||
while grid's `grid-auto-flow: row` fills rows and therefore runs *down*. Written
|
||||
while grid's `grid-auto-flow: row` fills rows and therefore runs _down_. Written
|
||||
by hand, `display: grid` on a flex row silently lays out vertically. `XStack`
|
||||
says the axis in both vocabularies, so it stays horizontal.
|
||||
|
||||
@@ -98,7 +98,7 @@ tracks at 390px, four at 768px, eight at 1440px.
|
||||
|
||||
**This is the one case where the two engines disagree, so it is worth knowing
|
||||
exactly where.** They are pixel-identical while every row is full. They part on
|
||||
a *partial last row*: flex's `flexGrow` stretches a lone trailing item across
|
||||
a _partial last row_: flex's `flexGrow` stretches a lone trailing item across
|
||||
the whole width, while grid holds it to its track. Measured with seven items
|
||||
over three tracks, only the orphan differs, and only in width — by 328px on a
|
||||
480px container.
|
||||
@@ -213,12 +213,12 @@ and never sees the tracks. One source, both engines, no `$platform` branch.
|
||||
|
||||
### What cannot cross at all
|
||||
|
||||
| grid | why flex has no answer |
|
||||
| --- | --- |
|
||||
| named areas | flex has no second dimension |
|
||||
| `gridColumn` / `gridArea` placement | flex cannot place across rows *and* columns |
|
||||
| overlapping cells | flex cannot put two children in one cell |
|
||||
| `gridAutoFlow="dense"` | nothing to backfill in one dimension |
|
||||
| grid | why flex has no answer |
|
||||
| ----------------------------------- | ------------------------------------------- |
|
||||
| named areas | flex has no second dimension |
|
||||
| `gridColumn` / `gridArea` placement | flex cannot place across rows _and_ columns |
|
||||
| overlapping cells | flex cannot put two children in one cell |
|
||||
| `gridAutoFlow="dense"` | nothing to backfill in one dimension |
|
||||
|
||||
These are web-only compositions. Reach for them deliberately and give native its
|
||||
own arrangement.
|
||||
|
||||
@@ -3,8 +3,8 @@ title: Gui UI
|
||||
---
|
||||
|
||||
<IntroParagraph>
|
||||
Gui UI is a complete suite of components that render nicely on both React web and
|
||||
React Native and come in both styled and unstyled forms.
|
||||
Gui UI is a complete suite of components that render nicely on both React web and React
|
||||
Native and come in both styled and unstyled forms.
|
||||
</IntroParagraph>
|
||||
|
||||
You can install each component separately, or all of them at once with:
|
||||
|
||||
@@ -18,8 +18,8 @@ yarn add react-native-svg @hanzogui/lucide-icons-2
|
||||
```
|
||||
|
||||
<Notice>
|
||||
If you're using icons in a monorepo, install `react-native-svg` in the parent
|
||||
app or workspace root rather than a leaf package.
|
||||
If you're using icons in a monorepo, install `react-native-svg` in the parent app or
|
||||
workspace root rather than a leaf package.
|
||||
</Notice>
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -50,13 +50,16 @@ import '@hanzogui/native/setup-zeego'
|
||||
```
|
||||
|
||||
<Notice theme="blue">
|
||||
On native, `Menu.Trigger` coordinates with parent Gui pressables
|
||||
automatically. You should not need extra wrapper views or empty `onPress`
|
||||
handlers just to prevent a parent `pressStyle` from activating.
|
||||
On native, `Menu.Trigger` coordinates with parent Gui pressables automatically. You
|
||||
should not need extra wrapper views or empty `onPress` handlers just to prevent a parent
|
||||
`pressStyle` from activating.
|
||||
</Notice>
|
||||
|
||||
<Notice theme="yellow">
|
||||
**Expo Router users:** This import must run _before_ `expo-router/entry`. Create an `index.js` at your project root that imports the setup first, then expo-router, and update your `package.json` main field to `"index.js"`. See the [upgrade guide](/docs/guides/how-to-upgrade#expo-router-entry-point) for details.
|
||||
**Expo Router users:** This import must run _before_ `expo-router/entry`. Create an
|
||||
`index.js` at your project root that imports the setup first, then expo-router, and
|
||||
update your `package.json` main field to `"index.js"`. See the [upgrade
|
||||
guide](/docs/guides/how-to-upgrade#expo-router-entry-point) for details.
|
||||
</Notice>
|
||||
|
||||
## Anatomy
|
||||
@@ -173,7 +176,8 @@ Contains every component for the Menu.
|
||||
type: 'ShiftProps | boolean',
|
||||
required: false,
|
||||
default: '{ padding: 10 }',
|
||||
description: 'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
|
||||
description:
|
||||
'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
|
||||
},
|
||||
{
|
||||
name: 'allowFlip',
|
||||
@@ -193,7 +197,8 @@ Contains every component for the Menu.
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
required: false,
|
||||
description: 'Constrains the menu to fit within available viewport space. When enabled, use Menu.ScrollView inside Content for scrollable overflow.',
|
||||
description:
|
||||
'Constrains the menu to fit within available viewport space. When enabled, use Menu.ScrollView inside Content for scrollable overflow.',
|
||||
},
|
||||
{
|
||||
name: 'unstyled',
|
||||
@@ -782,9 +787,7 @@ A scrollable container for menu items. Use this inside `Menu.Content` when you h
|
||||
|
||||
```jsx
|
||||
<Menu.Content>
|
||||
<Menu.ScrollView>
|
||||
{/* Many menu items */}
|
||||
</Menu.ScrollView>
|
||||
<Menu.ScrollView>{/* Many menu items */}</Menu.ScrollView>
|
||||
</Menu.Content>
|
||||
```
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ npm install @hanzogui/native
|
||||
|
||||
<Notice>
|
||||
`@hanzogui/native` is a light setup package. When you use one of its optional
|
||||
integrations, make sure the matching native dependency is installed in your
|
||||
app as well, such as `react-native-gesture-handler`, `react-native-teleport`,
|
||||
`zeego`, or `burnt`. In a monorepo, install those native packages in the
|
||||
parent app or workspace root rather than a leaf package.
|
||||
integrations, make sure the matching native dependency is installed in your app as well,
|
||||
such as `react-native-gesture-handler`, `react-native-teleport`, `zeego`, or `burnt`. In
|
||||
a monorepo, install those native packages in the parent app or workspace root rather
|
||||
than a leaf package.
|
||||
</Notice>
|
||||
|
||||
## Portal
|
||||
@@ -117,9 +117,7 @@ import { Pressable } from 'react-native'
|
||||
export function AvatarLink() {
|
||||
return (
|
||||
<PressBoundary debugName="AvatarLink">
|
||||
<Pressable onPress={() => {}}>
|
||||
{/* your content */}
|
||||
</Pressable>
|
||||
<Pressable onPress={() => {}}>{/* your content */}</Pressable>
|
||||
</PressBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ export default () => (
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph numberOfLines={3} maxWidth={200}>
|
||||
This text will be clamped to three lines. Any overflow beyond the third
|
||||
line will be hidden. This is useful for card descriptions or preview text
|
||||
where you want to show a limited amount of content.
|
||||
This text will be clamped to three lines. Any overflow beyond the third line will be
|
||||
hidden. This is useful for card descriptions or preview text where you want to show
|
||||
a limited amount of content.
|
||||
</Paragraph>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -62,9 +62,15 @@ function App() {
|
||||
## Anatomy
|
||||
|
||||
```tsx
|
||||
<Toast> {/* Root - context and position */}
|
||||
<Toast.Viewport> {/* Container with portal */}
|
||||
<Toast.Item> {/* Individual toast with stacking/drag */}
|
||||
<Toast>
|
||||
{' '}
|
||||
{/* Root - context and position */}
|
||||
<Toast.Viewport>
|
||||
{' '}
|
||||
{/* Container with portal */}
|
||||
<Toast.Item>
|
||||
{' '}
|
||||
{/* Individual toast with stacking/drag */}
|
||||
<Toast.Title />
|
||||
<Toast.Description />
|
||||
<Toast.Close />
|
||||
@@ -100,7 +106,7 @@ toast('Message', {
|
||||
// Dismiss
|
||||
const id = toast('Hello')
|
||||
toast.dismiss(id) // dismiss specific
|
||||
toast.dismiss() // dismiss all
|
||||
toast.dismiss() // dismiss all
|
||||
```
|
||||
|
||||
### toast.promise()
|
||||
@@ -252,6 +258,7 @@ Multiple toasts stack visually. Hover over the stack to expand and see all toast
|
||||
### Swipe to Dismiss
|
||||
|
||||
Toasts can be swiped away. The direction is auto-detected based on position:
|
||||
|
||||
- **Web:** Left/right positioned toasts swipe toward their edge, center toasts swipe horizontally
|
||||
- **Native:** All positions swipe vertically (up/down) to avoid conflicting with iOS/Android navigation back gesture
|
||||
|
||||
@@ -295,9 +302,7 @@ Each part can be styled independently:
|
||||
<Toast.Title color="$blue12" fontWeight="bold">
|
||||
{t.title}
|
||||
</Toast.Title>
|
||||
<Toast.Description color="$blue11">
|
||||
{t.description}
|
||||
</Toast.Description>
|
||||
<Toast.Description color="$blue11">{t.description}</Toast.Description>
|
||||
</Toast.Item>
|
||||
```
|
||||
|
||||
|
||||
@@ -214,7 +214,11 @@ list items or buttons, while the heavier `Tooltip` and `Tooltip.Content` live
|
||||
at the root. It also lets you style all your tooltips consistently in one place.
|
||||
|
||||
<Notice>
|
||||
When using a global scoped tooltip, you should set `zIndex` to ensure tooltips appear above dialogs and other portaled content. This is because the global tooltip's portal is mounted outside the stacking context of other portals. Inline tooltips (rendered alongside their trigger) automatically participate in z-index stacking and don't need this.
|
||||
When using a global scoped tooltip, you should set `zIndex` to ensure tooltips appear
|
||||
above dialogs and other portaled content. This is because the global tooltip's portal is
|
||||
mounted outside the stacking context of other portals. Inline tooltips (rendered
|
||||
alongside their trigger) automatically participate in z-index stacking and don't need
|
||||
this.
|
||||
</Notice>
|
||||
|
||||
```tsx fileName=_layout.tsx
|
||||
|
||||
@@ -26,26 +26,24 @@ Only set `zIndex` when you need to override the automatic stacking. Common cases
|
||||
|
||||
```tsx
|
||||
// Only if you need to override automatic stacking
|
||||
<Dialog zIndex={200000}>
|
||||
...
|
||||
</Dialog>
|
||||
<Dialog zIndex={200000}>...</Dialog>
|
||||
```
|
||||
|
||||
## Component Defaults
|
||||
|
||||
All overlay components use the automatic stacking system. Here's where to set `zIndex` if needed:
|
||||
|
||||
| Component | Where to set zIndex |
|
||||
|-----------|---------------------|
|
||||
| [Dialog](/ui/dialog) | `<Dialog zIndex={...}>` |
|
||||
| [AlertDialog](/ui/alert-dialog) | `<AlertDialog zIndex={...}>` |
|
||||
| [Sheet](/ui/sheet) | `<Sheet zIndex={...}>` |
|
||||
| [Select](/ui/select) | `<Select zIndex={...}>` |
|
||||
| [Popover](/ui/popover) | `<Popover zIndex={...}>` |
|
||||
| [Tooltip](/ui/tooltip) | `<Tooltip zIndex={...}>` |
|
||||
| [Menu](/ui/menu) | `<Menu.Portal zIndex={...}>` |
|
||||
| Component | Where to set zIndex |
|
||||
| ------------------------------- | ----------------------------------- |
|
||||
| [Dialog](/ui/dialog) | `<Dialog zIndex={...}>` |
|
||||
| [AlertDialog](/ui/alert-dialog) | `<AlertDialog zIndex={...}>` |
|
||||
| [Sheet](/ui/sheet) | `<Sheet zIndex={...}>` |
|
||||
| [Select](/ui/select) | `<Select zIndex={...}>` |
|
||||
| [Popover](/ui/popover) | `<Popover zIndex={...}>` |
|
||||
| [Tooltip](/ui/tooltip) | `<Tooltip zIndex={...}>` |
|
||||
| [Menu](/ui/menu) | `<Menu.Portal zIndex={...}>` |
|
||||
| [ContextMenu](/ui/context-menu) | `<ContextMenu.Portal zIndex={...}>` |
|
||||
| [Toast](/ui/toast) | Via `ToastViewport` |
|
||||
| [Toast](/ui/toast) | Via `ToastViewport` |
|
||||
|
||||
## Stacking Hierarchy
|
||||
|
||||
@@ -68,9 +66,7 @@ Nested floating elements automatically stack above their parent:
|
||||
<Tooltip.Trigger>
|
||||
<Button>Hover me</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
I appear above the dialog!
|
||||
</Tooltip.Content>
|
||||
<Tooltip.Content>I appear above the dialog!</Tooltip.Content>
|
||||
</Tooltip>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
@@ -85,6 +81,7 @@ The stacking system uses two mechanisms:
|
||||
### Horizontal Stacking (Siblings)
|
||||
|
||||
When multiple overlays open at the same level, each gets an incrementing z-index:
|
||||
|
||||
- First dialog: 105001
|
||||
- Second dialog: 105002
|
||||
- Third dialog: 105003
|
||||
@@ -92,6 +89,7 @@ When multiple overlays open at the same level, each gets an incrementing z-index
|
||||
### Vertical Stacking (Nested)
|
||||
|
||||
When content opens inside another portal, it reads the parent's z-index from context and adds 1:
|
||||
|
||||
- Dialog opens at 105001
|
||||
- Popover inside dialog: 105002
|
||||
- Tooltip inside that popover: 105003
|
||||
|
||||
@@ -233,9 +233,9 @@ export default () => (
|
||||
<Square
|
||||
size={100}
|
||||
bg="$color5"
|
||||
transition="1000ms" // slow exit (returning to base)
|
||||
transition="1000ms" // slow exit (returning to base)
|
||||
hoverStyle={{
|
||||
transition: '200ms', // fast enter (going to hover)
|
||||
transition: '200ms', // fast enter (going to hover)
|
||||
bg: '$color10',
|
||||
}}
|
||||
/>
|
||||
@@ -243,6 +243,7 @@ export default () => (
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- **Hover enter**: Uses the 200ms transition (snaps quickly to hover state)
|
||||
- **Hover exit**: Uses the 1000ms base transition (fades slowly back)
|
||||
|
||||
|
||||
@@ -807,11 +807,7 @@ You can pass a `settings` object to `createGui`:
|
||||
|
||||
```tsx
|
||||
type AllowedValueSetting =
|
||||
| boolean
|
||||
| 'strict'
|
||||
| 'somewhat-strict'
|
||||
| 'strict-web'
|
||||
| 'somewhat-strict-web'
|
||||
boolean | 'strict' | 'somewhat-strict' | 'strict-web' | 'somewhat-strict-web'
|
||||
|
||||
type AllowedStyleValuesSetting =
|
||||
| AllowedValueSetting
|
||||
|
||||
@@ -4,8 +4,8 @@ description: Extend and build custom and optimizable components
|
||||
---
|
||||
|
||||
<Notice theme="green">
|
||||
If you're looking for a full list of style properties accepted by Gui, see the
|
||||
[Styles page](/docs/intro/styles).
|
||||
If you're looking for a full list of style properties accepted by Gui, see the [Styles
|
||||
page](/docs/intro/styles).
|
||||
</Notice>
|
||||
|
||||
Create a new component by extending an existing one:
|
||||
|
||||
@@ -36,8 +36,8 @@ export default () => (
|
||||
```
|
||||
|
||||
<Notice theme="blue">
|
||||
One really important and useful thing to note about Gui style properties: the order
|
||||
is important! [Read more here](/docs/intro/styles#order-is-important)
|
||||
One really important and useful thing to note about Gui style properties: the order is
|
||||
important! [Read more here](/docs/intro/styles#order-is-important)
|
||||
</Notice>
|
||||
|
||||
### With styled()
|
||||
|
||||
@@ -4,8 +4,8 @@ description: Command-line tools for building, checking, and managing Gui project
|
||||
---
|
||||
|
||||
<IntroParagraph>
|
||||
The Gui CLI provides a suite of command-line tools for optimizing components,
|
||||
managing dependencies, adding fonts and icons, and more.
|
||||
The Gui CLI provides a suite of command-line tools for optimizing components, managing
|
||||
dependencies, adding fonts and icons, and more.
|
||||
</IntroParagraph>
|
||||
|
||||
<Notice theme="gray">
|
||||
@@ -351,4 +351,3 @@ export default {
|
||||
The check command is strict about version consistency. If you have a specific
|
||||
reason for version mismatches (like testing), you can document them in your
|
||||
README.
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ module.exports = function (api) {
|
||||
api.cache(true)
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [
|
||||
'@hanzogui/babel-plugin',
|
||||
],
|
||||
plugins: ['@hanzogui/babel-plugin'],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -191,8 +191,8 @@ themes: process.env.VITE_ENVIRONMENT === 'client'
|
||||
|
||||
<Notice theme="blue">
|
||||
Once your v5 config is set up, run `npx hanzogui generate-prompt` and commit the output
|
||||
to your repo. This helps AI assistants understand your Gui config and can speed up
|
||||
the rest of the migration.
|
||||
to your repo. This helps AI assistants understand your Gui config and can speed up the
|
||||
rest of the migration.
|
||||
</Notice>
|
||||
|
||||
## 3. Prop Renames
|
||||
|
||||
@@ -136,8 +136,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
```
|
||||
|
||||
<Notice theme="blue">
|
||||
With React 19, Gui automatically injects runtime styles via style tags. The
|
||||
`outputCSS` file handles themes and tokens that are generated at build time.
|
||||
With React 19, Gui automatically injects runtime styles via style tags. The `outputCSS`
|
||||
file handles themes and tokens that are generated at build time.
|
||||
</Notice>
|
||||
|
||||
Run `npx hanzogui build` once to generate the initial CSS file, then commit it.
|
||||
@@ -422,8 +422,8 @@ export default class Document extends NextDocument {
|
||||
```
|
||||
|
||||
<Notice theme="blue">
|
||||
Gui automatically injects styles at runtime. You can optionally generate a static
|
||||
CSS file - see the [Static CSS Output](#static-css-output) section.
|
||||
Gui automatically injects styles at runtime. You can optionally generate a static CSS
|
||||
file - see the [Static CSS Output](#static-css-output) section.
|
||||
</Notice>
|
||||
|
||||
### pages/\_app.tsx
|
||||
@@ -570,7 +570,8 @@ You can also have the plugin generate CSS during your Next.js build:
|
||||
const hanzoguiPlugin = withGui({
|
||||
config: './hanzogui.config.ts',
|
||||
components: ['@hanzo/gui'],
|
||||
outputCSS: process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
outputCSS:
|
||||
process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
// faster dev mode, keeps debugging helpers:
|
||||
disableExtraction: process.env.NODE_ENV === 'development',
|
||||
})
|
||||
@@ -784,7 +785,8 @@ CLI or the Next.js plugin:
|
||||
const hanzoguiPlugin = withGui({
|
||||
config: './hanzogui.config.ts',
|
||||
components: ['@hanzo/gui'],
|
||||
outputCSS: process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
outputCSS:
|
||||
process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
// faster dev mode, keeps debugging helpers:
|
||||
disableExtraction: process.env.NODE_ENV === 'development',
|
||||
})
|
||||
@@ -797,7 +799,7 @@ import '../public/hanzogui.generated.css'
|
||||
```
|
||||
|
||||
<Notice theme="green">
|
||||
With React 19, Gui automatically injects runtime styles via style tags on the
|
||||
server. The `outputCSS` file handles themes and tokens generated at build time, so you
|
||||
don't need any `getCSS()` calls in your provider.
|
||||
With React 19, Gui automatically injects runtime styles via style tags on the server.
|
||||
The `outputCSS` file handles themes and tokens generated at build time, so you don't
|
||||
need any `getCSS()` calls in your provider.
|
||||
</Notice>
|
||||
|
||||
@@ -140,7 +140,8 @@ For better performance, use the `outputCSS` option in the Gui Vite plugin:
|
||||
hanzoguiPlugin({
|
||||
config: './hanzogui.config.ts',
|
||||
components: ['@hanzo/gui'],
|
||||
outputCSS: process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
outputCSS:
|
||||
process.env.NODE_ENV === 'production' ? './public/hanzogui.generated.css' : null,
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ demoName: ThemeBuilder
|
||||
</HeroContainer>
|
||||
|
||||
<IntroParagraph>
|
||||
Gui themes start simple, but can do some pretty powerful things. To make them easier
|
||||
to generate, we've built a few helpers. You can always just skip themes, or add a single
|
||||
Gui themes start simple, but can do some pretty powerful things. To make them easier to
|
||||
generate, we've built a few helpers. You can always just skip themes, or add a single
|
||||
basic theme if you prefer. This guide is for users wanting to generate a more complex
|
||||
suite of themes.
|
||||
</IntroParagraph>
|
||||
|
||||
@@ -5,8 +5,8 @@ description: Color palettes and customization options
|
||||
|
||||
<Notice theme="blue">
|
||||
This is the Gui documentation site's [own color
|
||||
palette](https://github.com/hanzoai/gui/blob/main/pkgs/core/themes/src/tokens.tsx).
|
||||
You can customize your own using
|
||||
palette](https://github.com/hanzoai/gui/blob/main/pkgs/core/themes/src/tokens.tsx). You
|
||||
can customize your own using
|
||||
[@hanzogui/create-theme](https://github.com/hanzoai/gui/blob/738011d99e1dec6002a916edf424b75bb6cc0336/packages/themes/src/themes.tsx).
|
||||
See [@hanzogui/colors](https://github.com/hanzoai/gui/tree/main/pkgs/core/colors/src)
|
||||
for Radix colors, or BYO color palette.
|
||||
|
||||
@@ -4,8 +4,8 @@ description: Adding the compiler to your apps
|
||||
---
|
||||
|
||||
<IntroParagraph>
|
||||
The Gui Compiler significantly improves performance of both web and native
|
||||
applications through partial analysis and view flattening.
|
||||
The Gui Compiler significantly improves performance of both web and native applications
|
||||
through partial analysis and view flattening.
|
||||
</IntroParagraph>
|
||||
|
||||
See the [Benchmarks](/docs/intro/benchmarks) or a more
|
||||
@@ -181,7 +181,8 @@ Some notes on the options:
|
||||
See the [Vite guide](/docs/guides/vite) for more complete setup.
|
||||
|
||||
<Notice theme="yellow">
|
||||
`@hanzogui/vite-plugin` is ESM-only. Your project must have `"type": "module"` in its `package.json` (or use `.mjs`/`.mts` config files).
|
||||
`@hanzogui/vite-plugin` is ESM-only. Your project must have `"type": "module"` in its
|
||||
`package.json` (or use `.mjs`/`.mts` config files).
|
||||
</Notice>
|
||||
|
||||
Add `@hanzogui/vite-plugin` and update your `vite.config.ts`. If you have a
|
||||
|
||||
@@ -4,8 +4,8 @@ description: Get Gui set up, step by step
|
||||
---
|
||||
|
||||
<IntroParagraph>
|
||||
Gui is easy to set up as we've invested into "0-config" installs for all bundlers.
|
||||
That said, it's easy to get lost if you start configuring too many things too early.
|
||||
Gui is easy to set up as we've invested into "0-config" installs for all bundlers. That
|
||||
said, it's easy to get lost if you start configuring too many things too early.
|
||||
</IntroParagraph>
|
||||
|
||||
Use `npm create hanzogui@latest` to pick one of our starter templates.
|
||||
@@ -43,18 +43,19 @@ The `hanzogui` package is a superset of core, so anywhere the docs reference
|
||||
`react-native` types in the parent project for proper prop typing and
|
||||
autocomplete. You can either install `react-native` directly:
|
||||
|
||||
```bash
|
||||
yarn add react-native
|
||||
```
|
||||
```bash
|
||||
yarn add react-native
|
||||
```
|
||||
|
||||
Or, if you only want the types:
|
||||
Or, if you only want the types:
|
||||
|
||||
```bash
|
||||
yarn add [email protected] @types/react-native
|
||||
```
|
||||
```bash
|
||||
yarn add [email protected] @types/react-native
|
||||
```
|
||||
|
||||
Gui's web runtime is self-contained. In a monorepo, install either option
|
||||
in the parent app or workspace root rather than a leaf package.
|
||||
|
||||
Gui's web runtime is self-contained. In a monorepo, install either option
|
||||
in the parent app or workspace root rather than a leaf package.
|
||||
</Notice>
|
||||
|
||||
Next, you'll want to set up your [configuration](/docs/core/configuration) and
|
||||
@@ -96,7 +97,11 @@ declare module '@hanzogui/core' {
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return <GuiProvider config={config} defaultTheme="light">{/* your app here */}</GuiProvider>
|
||||
return (
|
||||
<GuiProvider config={config} defaultTheme="light">
|
||||
{/* your app here */}
|
||||
</GuiProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -295,6 +295,7 @@ The `border` shorthand now works cross-platform:
|
||||
```
|
||||
|
||||
On web, the CSS shorthand is passed through directly. On native, it expands to individual properties:
|
||||
|
||||
- `borderWidth` → `borderTopWidth`, `borderRightWidth`, `borderBottomWidth`, `borderLeftWidth`
|
||||
- `borderStyle` → `borderStyle`
|
||||
- `borderColor` → `borderTopColor`, `borderRightColor`, `borderBottomColor`, `borderLeftColor`
|
||||
|
||||
@@ -37,4 +37,3 @@ export function apiRoute(handler: Endpoint) {
|
||||
}
|
||||
}) satisfies Endpoint
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,12 @@ describe('the conversion preserves what rendered before', () => {
|
||||
/>
|
||||
</>
|
||||
)
|
||||
same(find('was'), find('now'), ['display', 'gridTemplateColumns', 'rowGap', 'columnGap'])
|
||||
same(find('was'), find('now'), [
|
||||
'display',
|
||||
'gridTemplateColumns',
|
||||
'rowGap',
|
||||
'columnGap',
|
||||
])
|
||||
// the old form needed an inline style to say grid at all; the new one does not
|
||||
expect(find('now').getAttribute('style')).toBeNull()
|
||||
expect(leaked(find('now'))).toEqual([])
|
||||
|
||||
@@ -195,7 +195,6 @@ export const HeaderContents = React.memo((props: HeaderProps) => {
|
||||
</XStack>
|
||||
</XStack>
|
||||
</Link>
|
||||
|
||||
</XStack>
|
||||
|
||||
<View flex={1} />
|
||||
@@ -588,11 +587,7 @@ const HeaderLinksPopoverContent = React.memo((props: { active: ID | '' }) => {
|
||||
const getDocsSectionFromPath = (pathName: string): 'core' | 'ui' | null => {
|
||||
if (!pathName || pathName === '/' || pathName === '') return null
|
||||
if (pathName.startsWith('/ui/')) return 'ui'
|
||||
if (
|
||||
pathName.startsWith('/docs') ||
|
||||
pathName.startsWith('/blog')
|
||||
)
|
||||
return 'core'
|
||||
if (pathName.startsWith('/docs') || pathName.startsWith('/blog')) return 'core'
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -742,7 +737,6 @@ const HeaderMenuMoreContents = () => {
|
||||
</HeadAnchor>
|
||||
</Link>
|
||||
|
||||
|
||||
<Link asChild href="/blog">
|
||||
<HeadAnchor menu>Blog</HeadAnchor>
|
||||
</Link>
|
||||
|
||||
@@ -53,11 +53,7 @@ export const TooltipLabelLarge = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
asChild
|
||||
delayNavigate
|
||||
href={href as Href}
|
||||
>
|
||||
<Link asChild delayNavigate href={href as Href}>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -96,7 +96,6 @@ const HeroContents = memo(function HeroContents() {
|
||||
mx: '$-4',
|
||||
}}
|
||||
>
|
||||
|
||||
<YStack items="center" gap="$4">
|
||||
<HeroH1>
|
||||
<Span position="relative" color="$accent1">
|
||||
|
||||
@@ -51,13 +51,15 @@ export const SearchProvider = memo(({ children }: any) => {
|
||||
createPortal(
|
||||
<DocSearchModal
|
||||
placeholder="Search docs..."
|
||||
indices={[{
|
||||
name: INDEX,
|
||||
searchParameters: {
|
||||
facetFilters: [],
|
||||
distinct: 1,
|
||||
indices={[
|
||||
{
|
||||
name: INDEX,
|
||||
searchParameters: {
|
||||
facetFilters: [],
|
||||
distinct: 1,
|
||||
},
|
||||
},
|
||||
}]}
|
||||
]}
|
||||
initialQuery={initialQuery || ''}
|
||||
initialScrollY={window.scrollY}
|
||||
onClose={onClose}
|
||||
|
||||
@@ -71,8 +71,7 @@ const components = [
|
||||
<UserDropdown />
|
||||
</Panel>
|
||||
</>,
|
||||
<>
|
||||
</>,
|
||||
<></>,
|
||||
<>
|
||||
<Panel key="" fileToCopyName="Login">
|
||||
<LoginScreen />
|
||||
|
||||
@@ -32,9 +32,7 @@ type ThemeHistoryData = {
|
||||
}
|
||||
accent: {
|
||||
name: 'accent'
|
||||
anchors: Array<{
|
||||
/* 同上 */
|
||||
}>
|
||||
anchors: Array<{/* 同上 */}>
|
||||
}
|
||||
}
|
||||
schemes: {
|
||||
|
||||
@@ -39,8 +39,7 @@ const Contents = ({ themeNameBase }: { themeNameBase: string }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<XStack position="absolute" t="$4" l={30} z={1000} gap="$4" scale={0.75}>
|
||||
</XStack>
|
||||
<XStack position="absolute" t="$4" l={30} z={1000} gap="$4" scale={0.75}></XStack>
|
||||
|
||||
<XStack position="absolute" t="$4" r="$4" z={1000}>
|
||||
<XStack
|
||||
|
||||
@@ -100,6 +100,7 @@ Themes are organized hierarchically and can be combined:
|
||||
### Theme Usage
|
||||
|
||||
Themes are combined hierarchically. For example, `light_blue_alt1_Button` combines:
|
||||
|
||||
- Base: `light`
|
||||
- Color: `blue`
|
||||
- Variant: `alt1`
|
||||
@@ -318,7 +319,7 @@ Media queries can be used as style props or with the `useMedia` hook:
|
||||
|
||||
```tsx
|
||||
// As style props (prefix with $)
|
||||
<View width="100%" $gtLarge={{ width: "50%" }} />
|
||||
;<View width="100%" $gtLarge={{ width: '50%' }} />
|
||||
|
||||
// Using the useMedia hook
|
||||
const media = useMedia()
|
||||
@@ -450,4 +451,3 @@ The following components are available:
|
||||
- YGroup
|
||||
- YStack
|
||||
- ZStack
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ import { startTransition, useCallback, useState } from 'react'
|
||||
export const useTransitionState = ((val) => {
|
||||
const [a, b] = useState(val)
|
||||
// @ts-ignore
|
||||
const wrapped = useCallback((...args) => {
|
||||
startTransition(() => {
|
||||
// @ts-ignore
|
||||
b(...args)
|
||||
})
|
||||
}, [b])
|
||||
const wrapped = useCallback(
|
||||
(...args) => {
|
||||
startTransition(() => {
|
||||
// @ts-ignore
|
||||
b(...args)
|
||||
})
|
||||
},
|
||||
[b]
|
||||
)
|
||||
return [a, wrapped]
|
||||
}) as typeof useState
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
"@docsearch/css": "^5.0.4",
|
||||
"@docsearch/react": "^5.0.4",
|
||||
"@github/mini-throttle": "^2.1.1",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/assert": "workspace:*",
|
||||
"@hanzogui/demos": "workspace:*",
|
||||
"@hanzogui/dev-config": "workspace:*",
|
||||
"@hanzogui/get-token": "workspace:*",
|
||||
"@hanzogui/logo": "workspace:*",
|
||||
"@hanzogui/lucide-icons-2": "workspace:*",
|
||||
"@hanzogui/use-store": "workspace:*",
|
||||
"@hookform/resolvers": "^5.9.1",
|
||||
"@leeoniya/ufuzzy": "^1.0.19",
|
||||
"@ngneat/falso": "^8.0.2",
|
||||
@@ -32,13 +40,6 @@
|
||||
"@react-three/fiber": "^9.7.0",
|
||||
"@rehookify/datepicker": "^6.6.8",
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"@hanzogui/assert": "workspace:*",
|
||||
"@hanzogui/demos": "workspace:*",
|
||||
"@hanzogui/get-token": "workspace:*",
|
||||
"@hanzogui/logo": "workspace:*",
|
||||
"@hanzogui/lucide-icons-2": "workspace:*",
|
||||
"@hanzogui/dev-config": "workspace:*",
|
||||
"@hanzogui/use-store": "workspace:*",
|
||||
"@tanstack/react-table": "^9.2.4",
|
||||
"@ts-react/form": "^1.8.3",
|
||||
"@vercel/og": "^1.0.2",
|
||||
@@ -71,11 +72,10 @@
|
||||
"react-native-worklets": "^0.12.1",
|
||||
"refractor": "^5.0.0",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"unified": "^11.0.5",
|
||||
"sharp": "^0.35.4",
|
||||
"swr": "^2.5.1",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"three": "^0.185.1",
|
||||
"unified": "^11.0.5",
|
||||
"valibot": "^1.4.2",
|
||||
"victory": "^37.3.6",
|
||||
"vite": "^8.2.2",
|
||||
|
||||
@@ -11,7 +11,6 @@ if (!import.meta.dirname) {
|
||||
throw new Error(`Not on Node 22`)
|
||||
}
|
||||
|
||||
|
||||
// use createRequire instead of import.meta.resolve for bun compatibility in vite config
|
||||
const require = createRequire(import.meta.url)
|
||||
const resolve = (path: string) => {
|
||||
@@ -98,7 +97,6 @@ export default {
|
||||
replacement: pathResolve(import.meta.dirname, '.'),
|
||||
},
|
||||
|
||||
|
||||
// Standard string-based aliases
|
||||
{
|
||||
find: 'react-native-svg',
|
||||
@@ -127,13 +125,7 @@ export default {
|
||||
},
|
||||
|
||||
ssr: {
|
||||
external: [
|
||||
'@vxrn/mdx-rust',
|
||||
'satteri',
|
||||
'satteri-expressive-code',
|
||||
'ws',
|
||||
'postmark',
|
||||
],
|
||||
external: ['@vxrn/mdx-rust', 'satteri', 'satteri-expressive-code', 'ws', 'postmark'],
|
||||
noExternal: true,
|
||||
},
|
||||
|
||||
@@ -167,7 +159,6 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
ssr: {
|
||||
dedupeSymlinkedModules: true,
|
||||
autoDepsOptimization: {
|
||||
@@ -192,12 +183,7 @@ export default {
|
||||
config: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'@discordjs/rest',
|
||||
'@discordjs/ws',
|
||||
'@vercel/og',
|
||||
'zlib-sync',
|
||||
],
|
||||
external: ['@discordjs/rest', '@discordjs/ws', '@vercel/og', 'zlib-sync'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"test": "./run-native-tests.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/core": "workspace:*",
|
||||
"@hanzogui/demos": "workspace:*",
|
||||
@@ -23,7 +24,6 @@
|
||||
"expo": "^57.0.18",
|
||||
"expo-font": "^57.0.2",
|
||||
"react": "19.2.8",
|
||||
"react-native": "^0.87.1",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"react-native": "^0.87.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/demos": "workspace:*",
|
||||
"@hanzogui/lucide-icons-2": "workspace:*",
|
||||
"@hanzogui/toast": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzogui/toast": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=19.2.8"
|
||||
|
||||
@@ -1 +1 @@
|
||||
{}
|
||||
{}
|
||||
|
||||
@@ -106,10 +106,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^8.0.1",
|
||||
"@expo/fingerprint": "^0.20.11",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.3",
|
||||
"@hanzogui/babel-plugin": "workspace:*",
|
||||
"@hanzogui/metro-plugin": "workspace:*",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"babel-plugin-module-resolver": "^5.0.3",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
/* The three faces the kit uses, self-hosted from @hanzo/font — one variable
|
||||
file per family rather than a weight per request. */
|
||||
@font-face{font-family:'Zen';src:url('/fonts/Zen-Variable.woff2') format('woff2-variations');font-weight:100 900;font-style:normal;font-display:swap}
|
||||
@font-face{font-family:'Zen Mono';src:url('/fonts/ZenMono-Variable.woff2') format('woff2-variations');font-weight:100 900;font-style:normal;font-display:swap}
|
||||
@font-face{font-family:'Zen Pixel Square';src:url('/fonts/ZenPixel-Square.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}
|
||||
@font-face {
|
||||
font-family: 'Zen';
|
||||
src: url('/fonts/Zen-Variable.woff2') format('woff2-variations');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Zen Mono';
|
||||
src: url('/fonts/ZenMono-Variable.woff2') format('woff2-variations');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Zen Pixel Square';
|
||||
src: url('/fonts/ZenPixel-Square.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
type ObjectType = Record<PropertyKey, unknown>
|
||||
|
||||
type PickByValue<OBJ_T, VALUE_T> = // From https://stackoverflow.com/a/55153000
|
||||
type PickByValue<
|
||||
OBJ_T,
|
||||
VALUE_T,
|
||||
> = // From https://stackoverflow.com/a/55153000
|
||||
Pick<OBJ_T, { [K in keyof OBJ_T]: OBJ_T[K] extends VALUE_T ? K : never }[keyof OBJ_T]>
|
||||
|
||||
type ObjectEntries<OBJ_T> = // From https://stackoverflow.com/a/60142095
|
||||
type ObjectEntries<
|
||||
OBJ_T,
|
||||
> = // From https://stackoverflow.com/a/60142095
|
||||
{ [K in keyof OBJ_T]: [keyof PickByValue<OBJ_T, OBJ_T[K]>, OBJ_T[K]] }[keyof OBJ_T][]
|
||||
|
||||
export const objectKeys = <O extends object>(obj: O) => Object.keys(obj) as Array<keyof O>
|
||||
@@ -20,7 +25,9 @@ type EntriesType =
|
||||
|
||||
// Existing Utils
|
||||
type DeepWritable<OBJ_T> = { -readonly [P in keyof OBJ_T]: DeepWritable<OBJ_T[P]> }
|
||||
type UnionToIntersection<UNION_T> = // From https://stackoverflow.com/a/50375286
|
||||
type UnionToIntersection<
|
||||
UNION_T,
|
||||
> = // From https://stackoverflow.com/a/50375286
|
||||
(UNION_T extends any ? (k: UNION_T) => void : never) extends (k: infer I) => void
|
||||
? I
|
||||
: never
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"android": "one run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@hanzo/font": "^1.9.3",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/animations-css": "workspace:*",
|
||||
"@hanzogui/animations-motion": "workspace:*",
|
||||
"@hanzogui/colors": "workspace:*",
|
||||
@@ -28,6 +29,7 @@
|
||||
"@hanzogui/lucide-icons-2": "workspace:*",
|
||||
"@hanzogui/react-native-media-driver": "workspace:*",
|
||||
"@hanzogui/toast": "workspace:*",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@vxrn/color-scheme": "^1.25.8",
|
||||
"expo": "^57.0.18",
|
||||
"one": "^1.25.8",
|
||||
@@ -37,14 +39,12 @@
|
||||
"react-native-safe-area-context": "^5.9.1",
|
||||
"react-native-screens": "^4.27.0",
|
||||
"react-native-svg": "^15.15.5",
|
||||
"react-native-web": "^0.21.2",
|
||||
"@hanzo/font": "^1.9.3",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"react-native-web": "^0.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@react-native-community/cli": "^20.2.0",
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@types/react": "19.2.18",
|
||||
"vite": "^8.2.2",
|
||||
"wait-port": "^1.1.0",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"test:web": "true"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/lucide-icons-2": "workspace:*",
|
||||
"@hanzogui/toast": "workspace:*",
|
||||
@@ -36,14 +37,13 @@
|
||||
"react-native-screens": "^4.27.0",
|
||||
"react-native-svg": "^15.15.5",
|
||||
"react-native-web": "^0.21.2",
|
||||
"react-native-worklets": "^0.12.1",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"react-native-worklets": "^0.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^8.0.1",
|
||||
"@expo/metro-runtime": "^57.0.14",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@hanzogui/babel-plugin": "workspace:*",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@types/react": "19.2.18",
|
||||
"serve": "^14.2.6",
|
||||
"typescript": "7.0.2"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@hanzogui/remix-starter",
|
||||
"version": "8.3.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
@@ -22,9 +23,9 @@
|
||||
"react-router": "^7.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@react-router/dev": "^7.15.1",
|
||||
"@react-router/fs-routes": "^8.3.1",
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.5",
|
||||
"typescript": "7.0.2",
|
||||
@@ -34,6 +35,5 @@
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"version": "8.3.1"
|
||||
"packageManager": "[email protected]"
|
||||
}
|
||||
|
||||
+10
-2
@@ -7,8 +7,16 @@ export default function Home() {
|
||||
const signedIn = !loading && session != null
|
||||
const who = session?.user?.email ?? session?.user?.name
|
||||
|
||||
const rooms = { href: '/rooms', title: 'Rooms', caption: 'Conversations in your org' } as const
|
||||
const wallet = { href: '/wallet', title: 'Wallet', caption: 'Balance and AI usage' } as const
|
||||
const rooms = {
|
||||
href: '/rooms',
|
||||
title: 'Rooms',
|
||||
caption: 'Conversations in your org',
|
||||
} as const
|
||||
const wallet = {
|
||||
href: '/wallet',
|
||||
title: 'Wallet',
|
||||
caption: 'Balance and AI usage',
|
||||
} as const
|
||||
const views = signedIn
|
||||
? [rooms, wallet]
|
||||
: [
|
||||
|
||||
@@ -102,7 +102,13 @@ export default function RoomScreen() {
|
||||
) : null}
|
||||
</ScrollView>
|
||||
|
||||
<XStack p="$3" gap="$2" borderTopWidth={1} borderColor="$borderColor" items="center">
|
||||
<XStack
|
||||
p="$3"
|
||||
gap="$2"
|
||||
borderTopWidth={1}
|
||||
borderColor="$borderColor"
|
||||
items="center"
|
||||
>
|
||||
<Input
|
||||
flex={1}
|
||||
value={draft}
|
||||
|
||||
@@ -18,7 +18,8 @@ export default function RoomsScreen() {
|
||||
if (token == null) return
|
||||
let live = true
|
||||
setState('loading')
|
||||
void team(token).rooms()
|
||||
void team(token)
|
||||
.rooms()
|
||||
.then((list) => {
|
||||
if (live) {
|
||||
setRooms(list)
|
||||
|
||||
@@ -17,7 +17,8 @@ export default function WalletScreen() {
|
||||
if (token == null) return
|
||||
let live = true
|
||||
setState('loading')
|
||||
void billing(token).wallet()
|
||||
void billing(token)
|
||||
.wallet()
|
||||
.then((w) => {
|
||||
if (live) {
|
||||
setWallet(w)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"name": "@hanzogui/team",
|
||||
"version": "8.3.1",
|
||||
"private": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"author": "Hanzo AI <[email protected]>",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "one dev",
|
||||
@@ -31,7 +33,5 @@
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@types/react": "19.2.18",
|
||||
"vite": "^8.2.2"
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"author": "Hanzo AI <[email protected]>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ export const team = (token: string) => {
|
||||
|
||||
/** Say something. Plain text in; the surface wraps it in the client's markup. */
|
||||
say: async (id: string, space: string, text: string): Promise<TeamMessage> =>
|
||||
(await api.postTeamRoomsByIdMessages({ id, teamMessageWrite: { space, text } })).data,
|
||||
(await api.postTeamRoomsByIdMessages({ id, teamMessageWrite: { space, text } }))
|
||||
.data,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ export interface Wallet {
|
||||
|
||||
function num(v: unknown): number | undefined {
|
||||
if (typeof v === 'number' && Number.isFinite(v)) return v
|
||||
if (typeof v === 'string' && v.trim() !== '' && Number.isFinite(Number(v))) return Number(v)
|
||||
if (typeof v === 'string' && v.trim() !== '' && Number.isFinite(Number(v)))
|
||||
return Number(v)
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -29,7 +30,9 @@ function num(v: unknown): number | undefined {
|
||||
function usd(record: Record<string, unknown>): number | undefined {
|
||||
const atto = record.balance_atto_usd ?? record.balanceAtto ?? record.atto_usd
|
||||
if (typeof atto === 'string' && /^-?\d+$/.test(atto)) return Number(BigInt(atto)) / 1e18
|
||||
const dollars = num(record.balance_usd ?? record.balanceUsd ?? record.balance ?? record.credits)
|
||||
const dollars = num(
|
||||
record.balance_usd ?? record.balanceUsd ?? record.balance ?? record.credits
|
||||
)
|
||||
if (dollars !== undefined) return dollars
|
||||
const cents = num(record.balance_cents ?? record.balanceCents)
|
||||
return cents === undefined ? undefined : cents / 100
|
||||
@@ -44,7 +47,8 @@ export function read(record: Record<string, unknown>): Wallet {
|
||||
const period = record.period ?? record.month
|
||||
if (typeof period === 'string') usage.push({ label: 'Period', value: period })
|
||||
const spent = num(record.spent_usd ?? record.spentUsd ?? record.usage_usd)
|
||||
if (spent !== undefined) usage.push({ label: 'Spent this period', value: `$${spent.toFixed(2)}` })
|
||||
if (spent !== undefined)
|
||||
usage.push({ label: 'Spent this period', value: `$${spent.toFixed(2)}` })
|
||||
const requests = num(record.requests ?? record.request_count)
|
||||
if (requests !== undefined) usage.push({ label: 'Requests', value: String(requests) })
|
||||
const seats = num(record.seats ?? record.seat_count)
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"generate-prompt": "hanzogui generate-prompt"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/cli": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzogui/config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
"prod": "NODE_ENV=production webpack --json=dist/compilation-stats.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/animations-css": "workspace:*",
|
||||
"@hanzogui/core": "workspace:*",
|
||||
"@hanzogui/create-theme": "workspace:*",
|
||||
"@hanzogui/linear-gradient": "workspace:*",
|
||||
"@hanzogui/react-native-svg": "workspace:*",
|
||||
"@hanzogui/shorthands": "workspace:*",
|
||||
"@hanzogui/react-native-web-lite": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzogui/shorthands": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@hanzogui/vite-plugin": "workspace:*",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"async-retry": "^1.3.3",
|
||||
"react-dom": "19.2.8",
|
||||
"vite": "^8.2.2",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^8.0.1",
|
||||
"@babel/preset-typescript": "^8.0.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@hanzogui/build": "workspace:*",
|
||||
"@hanzogui/loader": "workspace:*"
|
||||
"@hanzogui/loader": "workspace:*",
|
||||
"@playwright/test": "^1.62.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
"react-native-web": "^0.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@hanzogui/cli": "workspace:*",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@types/node": "26.4.0",
|
||||
"@types/react": "19.2.18",
|
||||
"vitest": "^4.1.11"
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"test:clean": "git checkout packages/app/ && git clean -fd packages/app/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzogui/cli": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/cli": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^4.1.11"
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
"watch": "hanzogui-build --skip-types --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/animations-react-native": "workspace:*",
|
||||
"@hanzogui/config": "workspace:*",
|
||||
"@hanzogui/shorthands": "workspace:*",
|
||||
"@hanzogui/themes": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@hanzogui/themes": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/build": "workspace:*"
|
||||
|
||||
@@ -30,8 +30,7 @@ export const headingFont = createZenSansFont({
|
||||
})
|
||||
|
||||
export const bodyFont = createZenSansFont(
|
||||
{
|
||||
},
|
||||
{},
|
||||
{
|
||||
sizeSize: (size) => Math.round(size * 1.1),
|
||||
sizeLineHeight: (size) => Math.round(size * 1.1 + (size > 20 ? 10 : 10)),
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
"watch": "hanzogui-build --skip-types --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@my/config": "workspace:^",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/next-theme": "workspace:*",
|
||||
"@hanzogui/toast": "workspace:*",
|
||||
"expo-constants": "^57.0.16",
|
||||
"@hanzo/gui": "workspace:*"
|
||||
"@my/config": "workspace:^",
|
||||
"expo-constants": "^57.0.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/build": "workspace:*",
|
||||
|
||||
@@ -29,6 +29,7 @@ Both leaked credentials are dead; no rotation needed. The anon key currently in
|
||||
are now enabled and cover ongoing detection.)
|
||||
|
||||
### ⚠️ A2. Verify / remove the prod `[email protected]` account
|
||||
|
||||
The test-user endpoint previously created this account in the **production**
|
||||
Supabase project with a static, source-committed password and Pro entitlements.
|
||||
The static password is now removed (see H5), but if the account was ever created
|
||||
@@ -39,16 +40,17 @@ in prod it still exists. Confirm in Supabase Auth and delete it if present.
|
||||
## What was fixed in this repo
|
||||
|
||||
### hanzogui.dev web app
|
||||
| ID | Issue | Fix |
|
||||
|----|-------|-----|
|
||||
| C1 ✅ | `GET /api/bento/code?userGithubId=` served paid Bento source with no auth (legacy v1 CLI path; trusted a client-supplied id as the user identity) | Removed the unauthenticated branch; the shipped CLI already uses the token-authenticated `/api/bento/cli/v2/code-download`. `app/api/bento/code+api.ts` |
|
||||
| C2 ✅ | `POST /api/github/app-hook` was a forgeable webhook stub (no signature verification, logged all headers) | Deleted the dead endpoint. |
|
||||
| H1 ✅ | Charge endpoints applied a client-supplied `couponId` without re-validating it applied to the product (free/near-free purchases) | Added `features/stripe/assertValidCoupon.ts` and call it in `create-subscription`, `create-v2-subscription`, `add-team-seats` before any coupon is applied. |
|
||||
| H2 ✅ | `/api/validate-coupon` was unauthenticated → coupon-enumeration oracle | Added `ensureAuth`. |
|
||||
| H3 ✅ | Team-seat invite minted unlimited active seats and accepted arbitrary `user_id` | Enforced seat capacity vs `total_seats`, dedup of active members, and invitee-exists check. `app/api/team-seat+api.ts` |
|
||||
| H4 ✅ | `/api/github/users` returned all users incl. **email** (`select('*')`) and had a PostgREST `.or()` filter injection via comma | Returns only `id, full_name, avatar_url`; name-only `ilike` (or exact-email match if `@` present, never returned); strips comma. UI no longer renders others' emails. `app/api/github/users+api.ts`, `NewAccountModal.tsx` |
|
||||
| H4b ✅ | `/api/discord/search-member` let any logged-in user enumerate the whole guild | Gated behind Pro (`ensureAccess`). |
|
||||
| H5 ✅ | Hardcoded test-user password (`test-user-password-12345`) usable to sign into prod via Supabase password auth | Generates a fresh random password per run and rotates any existing user's password. `app/api/test-user+api.ts` |
|
||||
|
||||
| ID | Issue | Fix |
|
||||
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| C1 ✅ | `GET /api/bento/code?userGithubId=` served paid Bento source with no auth (legacy v1 CLI path; trusted a client-supplied id as the user identity) | Removed the unauthenticated branch; the shipped CLI already uses the token-authenticated `/api/bento/cli/v2/code-download`. `app/api/bento/code+api.ts` |
|
||||
| C2 ✅ | `POST /api/github/app-hook` was a forgeable webhook stub (no signature verification, logged all headers) | Deleted the dead endpoint. |
|
||||
| H1 ✅ | Charge endpoints applied a client-supplied `couponId` without re-validating it applied to the product (free/near-free purchases) | Added `features/stripe/assertValidCoupon.ts` and call it in `create-subscription`, `create-v2-subscription`, `add-team-seats` before any coupon is applied. |
|
||||
| H2 ✅ | `/api/validate-coupon` was unauthenticated → coupon-enumeration oracle | Added `ensureAuth`. |
|
||||
| H3 ✅ | Team-seat invite minted unlimited active seats and accepted arbitrary `user_id` | Enforced seat capacity vs `total_seats`, dedup of active members, and invitee-exists check. `app/api/team-seat+api.ts` |
|
||||
| H4 ✅ | `/api/github/users` returned all users incl. **email** (`select('*')`) and had a PostgREST `.or()` filter injection via comma | Returns only `id, full_name, avatar_url`; name-only `ilike` (or exact-email match if `@` present, never returned); strips comma. UI no longer renders others' emails. `app/api/github/users+api.ts`, `NewAccountModal.tsx` |
|
||||
| H4b ✅ | `/api/discord/search-member` let any logged-in user enumerate the whole guild | Gated behind Pro (`ensureAccess`). |
|
||||
| H5 ✅ | Hardcoded test-user password (`test-user-password-12345`) usable to sign into prod via Supabase password auth | Generates a fresh random password per run and rotates any existing user's password. `app/api/test-user+api.ts` |
|
||||
|
||||
Confirmed already-safe during the audit: Stripe webhook signature verification,
|
||||
GitHub sponsor webhook constant-time HMAC, subscription cancel/upgrade ownership
|
||||
@@ -56,6 +58,7 @@ checks, postMessage origin checks, and no server secret inlined into the client
|
||||
bundle.
|
||||
|
||||
### CI/CD pipeline
|
||||
|
||||
- ✅ **Fork PRs no longer receive secrets.** `test-ios-native.yml` gated the
|
||||
`secrets: inherit` build (and its dependent test job) to same-repo refs, so a
|
||||
fork PR can no longer exfiltrate `KV_STORE_REDIS_REST_*`.
|
||||
@@ -70,6 +73,7 @@ bundle.
|
||||
SHAs current.
|
||||
|
||||
### Secrets hygiene / scanning
|
||||
|
||||
- ✅ `.gitignore`: fixed `.env.test.localp` typo and added key-material globs
|
||||
(`*.pem`, `*.key`, `*.p12`, `*.p8`, `*.keystore`, …).
|
||||
- Secret scanning is handled by GitHub native secret scanning + push protection
|
||||
@@ -77,6 +81,7 @@ bundle.
|
||||
Action plus full-tree CLI scans weren't worth the cost over native scanning.
|
||||
|
||||
### GitHub project configuration (applied live)
|
||||
|
||||
- ✅ Secret scanning + **push protection** enabled (would have blocked the A1 leak).
|
||||
- ✅ Dependabot vulnerability alerts + automated security updates enabled.
|
||||
- ✅ Private vulnerability reporting enabled; `SECURITY.md` added.
|
||||
@@ -84,6 +89,7 @@ bundle.
|
||||
`main` (org admins retain direct-push via bypass).
|
||||
|
||||
### npm publish
|
||||
|
||||
- ✅ Confirmed no published package ships an install/postinstall hook, the
|
||||
lockfile is committed, and the interactive release flow supports 2FA/OTP.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Image "types of property ''aria-hidden'' are incompatible." improve compat with real img
|
||||
|
||||
|
||||
vite 8 monorepo fix:
|
||||
|
||||
- added `hanzogui-monorepo-exports-fix` plugin to `@hanzogui/vite-plugin`
|
||||
- vite 8 (rolldown) resolves workspace subpath imports to filesystem dirs instead of package.json exports
|
||||
- see https://github.com/vitejs/vite/issues/11676 and https://github.com/vitejs/vite/issues/20390
|
||||
|
||||
+9
-9
@@ -195,6 +195,9 @@
|
||||
"yaml": "2.8.3"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"agent": {
|
||||
"memory": true
|
||||
},
|
||||
"manypkg": {
|
||||
"workspaceProtocol": "require",
|
||||
"ignoredRules": [
|
||||
@@ -202,6 +205,12 @@
|
||||
"UNSORTED_DEPENDENCIES"
|
||||
]
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]"
|
||||
},
|
||||
"upgradeSets": {
|
||||
"takeout": [
|
||||
"@take-out/*",
|
||||
@@ -223,14 +232,5 @@
|
||||
"better-auth",
|
||||
"@better-auth/*"
|
||||
]
|
||||
},
|
||||
"agent": {
|
||||
"memory": true
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]",
|
||||
"[email protected]": "patches/[email protected]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "bento-get",
|
||||
"version": "8.3.1",
|
||||
"license": "MIT",
|
||||
"_comment_private": "npm's `bento-get` belongs to upstream (nwienert), not us \u2014 publishing it returns 403. The name survived the rebrand because it carries no `tamagui` string to rewrite, so it is the one publishable package here we do not own. It stays in the tree to keep merges with upstream clean, and is private so the release never reaches for it.",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"bento-get": "dist/esm/cli.js"
|
||||
},
|
||||
@@ -66,5 +65,6 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"_comment_private": "npm's `bento-get` belongs to upstream (nwienert), not us — publishing it returns 403. The name survived the rebrand because it carries no `tamagui` string to rewrite, so it is the one publishable package here we do not own. It stays in the tree to keep merges with upstream clean, and is private so the release never reaches for it."
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ That's the whole integration. `Booker` fetches the public event type, renders a
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Purpose |
|
||||
| ---------------- | ----------------------------------------------------------------------- |
|
||||
| `Booker` | Full scheduling flow: event → month → times → details → confirmation. |
|
||||
| `CalendarEmbed` | Compact card for a marketing hero — soonest availability inline. |
|
||||
| `Confirmation` | Booking confirmation card; pass a `booking` or a `uid` to fetch one. |
|
||||
| `EventHeader`, `MonthCalendar`, `TimeSlots`, `BookingForm` | Composable sub-views for a custom flow. |
|
||||
| Component | Purpose |
|
||||
| ---------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `Booker` | Full scheduling flow: event → month → times → details → confirmation. |
|
||||
| `CalendarEmbed` | Compact card for a marketing hero — soonest availability inline. |
|
||||
| `Confirmation` | Booking confirmation card; pass a `booking` or a `uid` to fetch one. |
|
||||
| `EventHeader`, `MonthCalendar`, `TimeSlots`, `BookingForm` | Composable sub-views for a custom flow. |
|
||||
|
||||
### Props
|
||||
|
||||
|
||||
+16
-16
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hanzo/booker",
|
||||
"version": "8.3.1",
|
||||
"description": "Universal (web + native) scheduling UI for the Hanzo Calendar \u2014 a Booker, Confirmation, and compact CalendarEmbed built with hanzogui primitives.",
|
||||
"description": "Universal (web + native) scheduling UI for the Hanzo Calendar — a Booker, Confirmation, and compact CalendarEmbed built with hanzogui primitives.",
|
||||
"license": "MIT",
|
||||
"author": "Hanzo AI <[email protected]>",
|
||||
"repository": {
|
||||
@@ -10,6 +10,12 @@
|
||||
"directory": "pkgs/calendar"
|
||||
},
|
||||
"source": "src/index.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"types",
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"main": "dist/cjs/index.js",
|
||||
@@ -27,18 +33,21 @@
|
||||
"default": "./dist/esm/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"types",
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "hanzogui-build --skip-native",
|
||||
"watch": "hanzogui-build --watch",
|
||||
"clean": "hanzogui-build clean",
|
||||
"clean:build": "hanzogui-build clean:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"@hanzogui/build": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-native": "^0.87.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@hanzo/gui": ">=8.3.1",
|
||||
"react": ">=19.2.8",
|
||||
@@ -48,14 +57,5 @@
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/build": "workspace:*",
|
||||
"@hanzo/gui": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-native": "^0.87.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// Orchestration hook — the one booking state machine both shells consume.
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { DEFAULT_API_URL, type Booking, type BookingResponses, type Slot } from './client.ts'
|
||||
import {
|
||||
DEFAULT_API_URL,
|
||||
type Booking,
|
||||
type BookingResponses,
|
||||
type Slot,
|
||||
} from './client.ts'
|
||||
import {
|
||||
useAvailableSlots,
|
||||
useCreateBooking,
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/cjs/index.js"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -32,13 +40,5 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hanzogui/build": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/cjs/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": false,
|
||||
"declarationDir": "dist/cjs/.types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
"declarationMap": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.stories.*"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/__tests__/**", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user