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
4 changes: 2 additions & 2 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pull request workflow
name: npm test

on:
pull_request:
Expand All @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20.9.0'
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: '**/yarn.lock'
- name: Install dependencies
Expand Down
35 changes: 35 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"importOrder": [
".*styles.css$",
"",
"dayjs",
"^react$",
"^next$",
"^next/.*$",
"<BUILTIN_MODULES>",
"<THIRD_PARTY_MODULES>",
"^@mantine/(.*)$",
"^@mantinex/(.*)$",
"^@mantine-tests/(.*)$",
"^@docs/(.*)$",
"^@/.*$",
"^../(?!.*\\.css$).*$",
"^./(?!.*\\.css$).*$",
"\\.module\\.css$",
"(?<!\\.module)\\.css$"
],
"sortPackageJson": false,
"ignorePatterns": [
"*.d.ts",
"*.mdx",
"*.md",
"packages/*/*/styles.css",
"packages/*/*/styles.layer.css",
"packages/*/*/styles/*.css",
"docs/.next",
"docs/out"
]
}
35 changes: 0 additions & 35 deletions .prettierrc.mjs

This file was deleted.

17 changes: 7 additions & 10 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { dirname, join } from 'path';
import type { StorybookConfig } from '@storybook/react-vite';

function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}

const config: StorybookConfig = {
core: {
disableWhatsNewNotifications: true,
disableTelemetry: true,
enableCrashReports: false,
},
stories: ['../package/src/**/*.story.@(js|jsx|mjs|ts|tsx)'],
addons: [getAbsolutePath('@storybook/addon-essentials'), getAbsolutePath('storybook-dark-mode')],
addons: ['@storybook/addon-themes'],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: false,
},
};

export default config;
54 changes: 21 additions & 33 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
import type { Preview } from '@storybook/react';

import '@mantine/core/styles.css';

import React, { useEffect } from 'react';
import { addons } from '@storybook/preview-api';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import { MantineProvider, useMantineColorScheme } from '@mantine/core';

const channel = addons.getChannel();

function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
const { setColorScheme } = useMantineColorScheme();
const handleColorScheme = (value: boolean) => setColorScheme(value ? 'dark' : 'light');
import { MantineProvider } from '@mantine/core';

useEffect(() => {
channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
}, [channel]);

return <>{children}</>;
}

export const decorators = [
(renderStory: any) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
(renderStory: any) => <MantineProvider>{renderStory()}</MantineProvider>,
];
export const parameters = {
layout: 'padded',
};

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Mantine color scheme',
defaultValue: 'light',
toolbar: {
icon: 'mirror',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
},
},
};

export default preview;
export const decorators = [
(renderStory: any, context: any) => {
const scheme = (context.globals.theme || 'light') as 'light' | 'dark';
return <MantineProvider forceColorScheme={scheme}>{renderStory()}</MantineProvider>;
},
];
942 changes: 0 additions & 942 deletions .yarn/releases/yarn-4.12.0.cjs

This file was deleted.

940 changes: 940 additions & 0 deletions .yarn/releases/yarn-4.13.0.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.12.0.cjs
yarnPath: .yarn/releases/yarn-4.13.0.cjs
15 changes: 11 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
|---------|---------|
| `yarn build` | Build the npm package via Rollup |
| `yarn dev` | Start the Next.js docs dev server (port 9281) |
| `yarn test` | Full test suite (syncpack + prettier + typecheck + lint + jest) |
| `yarn test` | Full test suite (syncpack + oxfmt + typecheck + lint + jest) |
| `yarn jest` | Run only Jest unit tests |
| `yarn docgen` | Generate component API docs (docgen.json) |
| `yarn docs:build` | Build the Next.js docs site for production |
| `yarn docs:deploy` | Build and deploy docs to GitHub Pages |
| `yarn lint` | Run ESLint |
| `yarn prettier:write` | Format all files with Prettier |
| `yarn lint` | Run oxlint + Stylelint |
| `yarn format:write` | Format all files with oxfmt |
| `yarn storybook` | Start Storybook dev server |
| `yarn clean` | Remove build artifacts |
| `yarn release:patch` | Bump patch version and deploy docs |
Expand Down Expand Up @@ -102,8 +102,15 @@ Viewport and percentage values are resolved to default pixel values during SSR a
### Mantine Styles API
The component uses Mantine's full Styles API (`getStyles`, `classNames`, `styles`, `unstyled`, `vars`, `varsResolver`) via `factory()` pattern with `useProps`, `useStyles`, and `createVarsResolver`.

### ScrollArea Control
`withScrollArea` (default: `true`) — when `false`, the internal `ScrollArea` is replaced with a plain `Box` with `overflow: hidden`. Content that overflows is clipped. Useful when the consumer provides their own scrolling mechanism.

### Controls Position & Order
- `controlsPosition` (`'left'` | `'right'`, default: `'left'`) — macOS-style (left) or Windows-style (right) button placement
- `controlsOrder` (`('close' | 'collapse' | 'tools')[]`) — custom button ordering. When not set, defaults to `['close', 'collapse', 'tools']` for left and `['tools', 'collapse', 'close']` for right (automatically reversed).

### WindowGroup Compound Component
`WindowGroup` provides shared context (`WindowGroupContextValue`) for managing multiple `Window` instances with predefined layouts (`WindowLayout`). Uses React context via `WindowGroup.context.ts`.
`WindowGroup` provides shared context (`WindowGroupContextValue`) for managing multiple `Window` instances with predefined layouts (`WindowLayout`). Uses standard React `createContext`/`useContext` (replaced `createOptionalContext` which was removed in Mantine 9).

## Testing
Jest with `jsdom` environment, `esbuild-jest` transform, CSS mocked via `identity-obj-proxy`. Component tests use `@testing-library/react` with a custom `renderWithMantine` helper that wraps components in `MantineProvider`.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
## Overview

This component is created on top of the [Mantine](https://mantine.dev/) library.
It requires **Mantine 9.x** and **React 19**.

[Mantine Window](https://gfazioli.github.io/mantine-window/) is a versatile UI container that brings a familiar desktop‑like window experience to React applications built with Mantine.

Expand All @@ -37,6 +38,9 @@ This component is created on top of the [Mantine](https://mantine.dev/) library.
- Full Mantine Styles API support with fine-grained classNames
- SSR-safe with hydration-compatible viewport unit resolution
- `onPositionChange` and `onSizeChange` callbacks
- `withScrollArea` prop to disable the internal ScrollArea wrapper
- `controlsPosition` prop for macOS-style (left) or Windows-style (right) button placement
- `controlsOrder` prop for custom button ordering (close, collapse, tools)

> [!note]
>
Expand Down
11 changes: 11 additions & 0 deletions docs/demos/Window.demo.configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ export const configurator: MantineDemo = {
{ type: 'boolean', prop: 'withCollapseButton', initialValue: true, libraryValue: true },
{ type: 'boolean', prop: 'withCloseButton', initialValue: true, libraryValue: true },
{ type: 'boolean', prop: 'withToolsButton', initialValue: true, libraryValue: true },
{ type: 'boolean', prop: 'withScrollArea', initialValue: true, libraryValue: true },
{
prop: 'controlsPosition',
type: 'select',
initialValue: 'left',
libraryValue: 'left',
data: [
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
],
},
{ type: 'boolean', prop: 'collapsed', initialValue: false, libraryValue: false },
{
type: 'boolean',
Expand Down
111 changes: 111 additions & 0 deletions docs/demos/Window.demo.controlsPosition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Window } from '@gfazioli/mantine-window';
import { Box, Text } from '@mantine/core';
import { MantineDemo } from '@mantinex/demo';

const code = `import { Window } from '@gfazioli/mantine-window';
import { Box, Text } from '@mantine/core';

function Demo() {
return (
<Box pos="relative" style={{ width: '100%', height: 500 }}>
{/* macOS style: close, collapse, tools on the left (default) */}
<Window
title="macOS style (default)"
opened
defaultX={20} defaultY={20}
defaultWidth={300} defaultHeight={150}
controlsPosition="left"
persistState={false}
withinPortal={false}
>
<Text size="sm">Controls on the left — macOS style</Text>
</Window>

{/* Windows style: tools, collapse, close on the right (auto-reversed) */}
<Window
title="Windows style"
opened
defaultX={350} defaultY={20}
defaultWidth={300} defaultHeight={150}
controlsPosition="right"
persistState={false}
withinPortal={false}
>
<Text size="sm">Controls on the right — Windows style</Text>
<Text size="sm" c="dimmed">Button order is automatically reversed</Text>
</Window>

{/* Custom order: tools first, then close */}
<Window
title="Custom order"
opened
defaultX={20} defaultY={220}
defaultWidth={300} defaultHeight={150}
controlsOrder={['tools', 'close', 'collapse']}
persistState={false}
withinPortal={false}
>
<Text size="sm">Custom order: tools, close, collapse</Text>
</Window>
</Box>
);
}
`;

function Demo() {
return (
<Box pos="relative" style={{ width: '100%', height: 500 }}>
<Window
title="macOS style (default)"
opened
defaultX={20}
defaultY={20}
defaultWidth={300}
defaultHeight={150}
controlsPosition="left"
persistState={false}
withinPortal={false}
>
<Text size="sm">Controls on the left — macOS style</Text>
</Window>

<Window
title="Windows style"
opened
defaultX={350}
defaultY={20}
defaultWidth={300}
defaultHeight={150}
controlsPosition="right"
persistState={false}
withinPortal={false}
>
<Text size="sm">Controls on the right — Windows style</Text>
<Text size="sm" c="dimmed">
Button order is automatically reversed
</Text>
</Window>

<Window
title="Custom order"
opened
defaultX={20}
defaultY={220}
defaultWidth={300}
defaultHeight={150}
controlsOrder={['tools', 'close', 'collapse']}
persistState={false}
withinPortal={false}
>
<Text size="sm">Custom order: tools, close, collapse</Text>
</Window>
</Box>
);
}

export const controlsPositionDemo: MantineDemo = {
type: 'code',
component: Demo,
code,
defaultExpanded: false,
};
Loading
Loading