Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ All notable changes to this project will be documented in this file.
collapses the per-parent-dir watchers into one recursive watch rooted at the
deepest common ancestor of the targets.

- **@overeng/effect-schema-form-aria** (pilot): migrate styling from Tailwind
CSS v4 to StyleX (`@stylexjs/stylex`). Components consume a semantic token
layer (`src/tokens.stylex.ts`, exported as `tokens`) built on StyleX vars;
raw scales (spacing, radii, font sizes) come from `tailwind-stylex`.
State-dependent styling (selection) moves to React Aria render-prop
`className` instead of Tailwind group/data variants. Consumers import the
required reset from `@overeng/effect-schema-form-aria/styles.css`; the
publication build emits that reset and compiled component rules together.
Byte-identical markup baselines were regenerated with hashed StyleX class names.

- **@overeng/effect-rpc-tanstack**: client request-id policy — `layerClient`
now validates outgoing RPC request ids at the client boundary. Non-empty
strings and non-negative, finite numbers/bigints are accepted (v4 interop);
Expand Down
8 changes: 7 additions & 1 deletion genie/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ export const catalog = defineCatalog({
// Styling
tailwindcss: '4.3.1',
'@tailwindcss/vite': '4.3.1',
'@stylexjs/stylex': '0.19.0',
'@stylexjs/unplugin': '0.19.0',
'tailwind-stylex': '0.1.1',

// Storybook
storybook: '10.4.6',
Expand Down Expand Up @@ -443,6 +446,9 @@ export const commonPnpmPolicySettings = {
typescript: '>=6.0.0',
eslint: '>=10.0.0',
vitest: '>=4.0.0',
// @stylexjs/unplugin@0.19 declares unplugin ^2 but works with v3;
// proven by the effect-schema-form-aria StyleX pilot (build + storybook).
unplugin: '>=3.0.0',
},
},
packageImportMethod: 'auto' as const,
Expand All @@ -453,7 +459,7 @@ export const commonPnpmPolicySettings = {
// The Effect 4 RC cohort moves fast; keep minimum-release-age strict
// globally but let these advance immediately during the coordinated
// migration window.
minimumReleaseAgeExclude: ['@types/node', ...effectV4Cohort],
minimumReleaseAgeExclude: ['@types/node', 'tailwind-stylex', ...effectV4Cohort],
pmOnFail: 'ignore' as const,
/** Disable until pnpm#10393 is resolved (install no-ops for workspace changes) */
optimisticRepeatInstall: false as const,
Expand Down
5 changes: 3 additions & 2 deletions genie/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ export const commonPnpmWorkspaceData = {
patchedDependencies: { ...effectUtilsWorkspacePatches },
allowUnusedPatches: true as const,
peerDependencyRules: {
// Merge the shared rules (e.g. `unplugin: '>=3.0.0'`) with repo-specific
// entries instead of shadowing them.
allowedVersions: {
// bun-ffi-structs@0.2.3 (via @myobie/pty) declares typescript ^5 but the
// repo compiles with TS 6; resolution is proven fine.
...commonPnpmPolicySettings.peerDependencyRules.allowedVersions,
typescript: '>=6.0.0',
eslint: '>=10.0.0',
vitest: '>=4.0.0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ printf '{"name":"fixture-root","private":true,"packageManager":"pnpm@11.8.0","de
"$stable_stage_path" > "$workspace/package.json"

install_fixture() {
"${pnpm_command[@]}" --dir "$workspace" install \
# The temporary fixture install is the behavior under test, not a workspace mutation.
DEVENV_TASK_PASSTHROUGH=1 "${pnpm_command[@]}" --dir "$workspace" install \
--store-dir "$workspace/.devenv/pnpm-store" \
--config.enable-global-virtual-store=false \
--config.virtual-store-dir=node_modules/.pnpm \
Expand Down
2 changes: 1 addition & 1 deletion nix/oxc-config-plugin.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let
pnpm = pinnedPnpm;
};
packageDir = "packages/@overeng/oxc-config";
pnpmDepsHash = "sha256-ziA6Qj851RhfAyy7J/G2YoIBUJjVXYMGv/rT0vdSygo=";
pnpmDepsHash = "sha256-IO0UJfp74D+FdgMf328AgtYvjLKm32x6HQmW9gNr7L0=";

srcPath =
if builtins.isAttrs src && builtins.hasAttr "outPath" src then
Expand Down
2 changes: 1 addition & 1 deletion packages/@overeng/ci-tools/nix/build.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let
workspaceRoot = src;
# Managed by the repo FOD refresh workflow — do not edit manually.
depsBuilds = {
"." = mkSharedHash "sha256-5lXB8UByHOdKM2lfYdfV3KfFdLTCShUGRZVjcSne3u8=";
"." = mkSharedHash "sha256-Pxlx66Up4rzRAs2Xp36a8+kUyxgVLiBTN/phJxexMjk=";
};
smokeTestArgs = [ "--help" ];
inherit gitRev commitTs dirty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { createDomStorybookConfig } from '@overeng/utils/node/storybook/config'

export default createDomStorybookConfig({})
export default createDomStorybookConfig({ stylex: true })
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { Preview } from '@storybook/react'
import * as stylex from '@stylexjs/stylex'
import type { ReactNode } from 'react'
import { spacing } from 'tailwind-stylex/tokens.stylex'

import '../src/styles.css'

const styles = stylex.create({
page: {
padding: spacing[4],
},
})

/** Wrapper providing design tokens and padding for all stories */
const StorybookDecorator = ({ children }: { children: ReactNode }) => (
<div className="p-4 font-sans">{children}</div>
<div {...stylex.props(styles.page)}>{children}</div>
)

const preview: Preview = {
Expand Down
29 changes: 13 additions & 16 deletions packages/@overeng/effect-schema-form-aria/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @overeng/effect-schema-form-aria

Styled React Aria implementation for `@overeng/effect-schema-form`. Provides accessible, styled form components using React Aria Components and Tailwind CSS.
Styled React Aria implementation for `@overeng/effect-schema-form`. Provides accessible, styled form components using React Aria Components and StyleX.

[**Storybook**](https://overeng-effect-utils-schema-form-ar.vercel.app) - Interactive component documentation and examples

Expand All @@ -14,6 +14,12 @@ bun add @overeng/effect-schema-form-aria @overeng/effect-schema-form react-aria-

### Quick Start

Import the package reset once in your application entry point. It preserves the form-control and box-model defaults that the components require:

```tsx
import '@overeng/effect-schema-form-aria/styles.css'
```

```tsx
import { AriaSchemaForm } from '@overeng/effect-schema-form-aria'
import { Schema } from 'effect'
Expand Down Expand Up @@ -150,23 +156,14 @@ Automatically renders as segmented control (5 or fewer options) or dropdown (mor

## Styling

Components use Tailwind CSS with semantic design tokens. Ensure your app defines these CSS variables:

```css
:root {
--color-ink: /* text color */;
--color-subtle-ink: /* secondary text */;
--color-muted-ink: /* muted text */;
--color-border: /* border color */;
--color-input: /* input background */;
--color-surface: /* surface background */;
--color-surface-raised: /* hover state */;
--color-primary: /* accent color */;
--color-accent: /* accent color */;
}
Components use StyleX with semantic variables exported as `tokens`. The package stylesheet contains only the base/reset rules required by the components; component styles are compiled from colocated `stylex.create` declarations.

```tsx
import { tokens } from '@overeng/effect-schema-form-aria'
import '@overeng/effect-schema-form-aria/styles.css'
```

Or customize the components by overriding the className props or creating your own renderers.
The default semantic color values intentionally match the previous Tailwind implementation.

## Custom Renderers

Expand Down
17 changes: 9 additions & 8 deletions packages/@overeng/effect-schema-form-aria/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 17 additions & 18 deletions packages/@overeng/effect-schema-form-aria/package.json.genie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,23 @@ const runtimeDeps = catalog.compose({
workspace: workspaceMember({ memberPath: 'packages/@overeng/effect-schema-form-aria' }),
dependencies: {
workspace: [schemaFormPkg],
external: catalog.pick('@stylexjs/stylex', 'tailwind-stylex'),
},
devDependencies: {
workspace: [utilsPkg],
external: {
...catalog.pick(
...peerDepNames,
'effect',
'react',
'@storybook/react',
'@storybook/react-vite',
'@tailwindcss/vite',
'@types/react',
'@types/react-dom',
'@vitejs/plugin-react',
'storybook',
'tailwindcss',
'typescript',
'vite',
'vitest',
),
},
external: catalog.pick(
'effect',
'@storybook/react',
'@storybook/react-vite',
'@stylexjs/unplugin',
'@types/react',
'@types/react-dom',
'@vitejs/plugin-react',
'storybook',
'typescript',
'vite',
'vitest',
),
},
peerDependencies: {
workspace: [schemaFormPkg],
Expand All @@ -48,14 +44,17 @@ export default packageJson(
...privatePackageDefaults,
exports: {
'.': exportEntry('./src/mod.ts', { environment: 'browser' }),
'./styles.css': exportEntry('./src/styles.css', { environment: 'browser' }),
},
publishConfig: {
access: 'public',
exports: {
'.': './dist/mod.js',
'./styles.css': './dist/styles.css',
},
},
scripts: {
build: 'tsc --build tsconfig.json && vite build',
storybook: 'storybook dev -p 6010',
'storybook:build': 'storybook build',
},
Expand Down
16 changes: 15 additions & 1 deletion packages/@overeng/effect-schema-form-aria/src/AriaSchemaForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as stylex from '@stylexjs/stylex'
import type { Schema } from 'effect'
import type { ReactNode } from 'react'
import { spacing } from 'tailwind-stylex/tokens.stylex'

import {
formatLiteralLabel,
Expand All @@ -10,6 +12,13 @@ import {
import { FieldGroup, FieldGroupEmpty } from './components/FieldGroup.tsx'
import { ariaRenderers } from './renderers.tsx'

const styles = stylex.create({
fieldsGrid: {
display: 'grid',
rowGap: spacing[4],
},
})

/** Props for AriaSchemaForm component */
export interface AriaSchemaFormProps<T extends Record<string, unknown>> {
/** The Effect Schema to render */
Expand Down Expand Up @@ -80,7 +89,12 @@ export const AriaSchemaForm = <T extends Record<string, unknown>>({
// Render fields
const renderedFields = (
<div
className={`grid gap-4 ${tagInfo.isTagged === false || showTagHeader === false ? className : ''}`}
className={[
stylex.props(styles.fieldsGrid).className,
tagInfo.isTagged === false || showTagHeader === false ? className : '',
]
.filter(Boolean)
.join(' ')}
>
{fields.map((field) => (
<div key={field.key}>{renderField(field)}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as stylex from '@stylexjs/stylex'
import type { ReactNode } from 'react'
import {
CheckboxButton as AriaCheckboxButton,
CheckboxField as AriaCheckboxField,
} from 'react-aria-components'
import { fontSizes, radii, spacing } from 'tailwind-stylex/tokens.stylex'

import { tokens } from '../tokens.stylex.ts'
import { FieldWrapper } from './FieldWrapper.tsx'

/** Props for BooleanField component */
Expand All @@ -22,6 +25,50 @@ export interface BooleanFieldProps {
isDisabled?: boolean | undefined
}

const styles = stylex.create({
root: {
fontSize: fontSizes.sm,
lineHeight: '1.25rem',
color: tokens.ink,
},
button: {
display: 'flex',
alignItems: 'center',
columnGap: spacing[2],
cursor: 'pointer',
},
box: {
width: '1rem',
height: '1rem',
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: radii.default,
borderWidth: 1,
borderStyle: 'solid',
borderColor: tokens.border,
backgroundColor: tokens.input,
transitionProperty: 'background-color, border-color',
transitionDuration: '150ms',
},
boxSelected: {
backgroundColor: tokens.primary,
borderColor: tokens.primary,
},
check: {
width: '0.75rem',
height: '0.75rem',
color: '#ffffff',
opacity: 0,
transitionProperty: 'opacity',
transitionDuration: '150ms',
},
checkVisible: {
opacity: 1,
},
})

/**
* Accessible checkbox field using React Aria.
*/
Expand All @@ -39,13 +86,17 @@ export const BooleanField = ({
isSelected={value}
onChange={onChange}
isDisabled={isDisabled}
className="text-sm text-ink"
{...stylex.props(styles.root)}
>
<AriaCheckboxButton className="group flex items-center gap-2 cursor-pointer">
<div className="size-4 shrink-0 rounded border border-border bg-input group-data-[selected]:bg-primary group-data-[selected]:border-primary flex items-center justify-center transition-colors">
<AriaCheckboxButton {...stylex.props(styles.button)}>
<div
className={stylex.props(styles.box, value === true && styles.boxSelected).className ?? ''}
>
<svg
viewBox="0 0 12 12"
className="size-3 text-white opacity-0 group-data-[selected]:opacity-100 transition-opacity"
className={
stylex.props(styles.check, value === true && styles.checkVisible).className ?? ''
}
aria-hidden="true"
>
<path
Expand Down
Loading
Loading