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
24 changes: 24 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@
- ESLint errors about unused variables or improper types are blocking issues
- Fixing them takes seconds now, but accumulates into hours if deferred

## Adding a New Component Module

When adding a new top-level component folder (e.g. `Source/MyComponent/`), ALL of the following steps are mandatory:

1. **Create `Source/MyComponent/index.ts`** — export all public types and components.
2. **Add a namespace import and re-export in `Source/index.ts`**:
```typescript
import * as MyComponent from './MyComponent';
// ... add to the export block
export { ..., MyComponent };
```
3. **Add a subpath export entry in `Source/package.json`** under `"exports"`:
```json
"./MyComponent": {
"types": "./dist/esm/MyComponent/index.d.ts",
"require": "./dist/cjs/MyComponent/index.js",
"import": "./dist/esm/MyComponent/index.js"
}
```
4. **Create `Documentation/MyComponent/index.md`** and `Documentation/MyComponent/toc.yml`.
5. **Register in `Documentation/toc.yml`** under the appropriate category.

> The `package.json` entry is required for consumers to import directly via `@cratis/components/MyComponent`.

## Formatting

- Honor the existing code style and conventions in the project.
Expand Down
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 25
labels: []
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 25
labels: []
33 changes: 33 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Summary

Optional summary of the PR here. The GitHub release description is created from this comment so keep it nice and descriptive.
Remember to remove sections that you don't need or use.

If it does not make sense to have a summary, you can take that out as well.
You would typically keep the summary only if there is a general theme change that can be summarized. If you find
yourself saying the same thing as any of the bullet points in a slightly different way; then this section is not
needed.

### Added

- Describe the added features

### Changed

- Describe the outwards facing code change

### Fixed

- Describe the fix and the bug

### Removed

- Describe what was removed and why

### Security

- Describe the security issue and the fix

### Deprecated

- Describe the part of the code being deprecated and why
149 changes: 149 additions & 0 deletions Documentation/Toolbar/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Toolbar

The `Toolbar` component provides a canvas-style icon toolbar with support for orientations, active states, animated context switching, and fan-out sub-panels.

## Components

| Component | Description |
|---|---|
| `Toolbar` | Container that groups toolbar buttons into a pill-shaped bar |
| `ToolbarButton` | Icon button with a hover tooltip |
| `ToolbarSection` | Section within a toolbar that animates between named contexts |
| `ToolbarContext` | Named context (set of buttons) inside a `ToolbarSection` |
| `ToolbarFanOutItem` | Button that slides out a horizontal sub-panel on click |

## Basic Usage

Place `ToolbarButton` elements inside a `Toolbar`:

```tsx
import { Toolbar, ToolbarButton } from '@cratis/components';

function MyToolbar() {
return (
<Toolbar>
<ToolbarButton icon='pi pi-arrow-up-left' tooltip='Select' />
<ToolbarButton icon='pi pi-pencil' tooltip='Draw' />
<ToolbarButton icon='pi pi-stop' tooltip='Rectangle' />
</Toolbar>
);
}
```

## Orientation

The toolbar defaults to `vertical`. Pass `orientation='horizontal'` for a horizontal layout:

```tsx
<Toolbar orientation='horizontal'>
<ToolbarButton icon='pi pi-undo' tooltip='Undo' />
<ToolbarButton icon='pi pi-refresh' tooltip='Redo' />
</Toolbar>
```

## Active State

Use the `active` prop on `ToolbarButton` to highlight the selected tool:

```tsx
function DrawingToolbar() {
const [activeTool, setActiveTool] = useState('select');

return (
<Toolbar>
<ToolbarButton
icon='pi pi-arrow-up-left'
tooltip='Select'
active={activeTool === 'select'}
onClick={() => setActiveTool('select')}
/>
<ToolbarButton
icon='pi pi-pencil'
tooltip='Draw'
active={activeTool === 'draw'}
onClick={() => setActiveTool('draw')}
/>
</Toolbar>
);
}
```

## Context Switching

`ToolbarSection` and `ToolbarContext` enable smooth animated transitions between different sets of tools. When `activeContext` changes, the current buttons fade out, the section morphs to the new size, then the new buttons fade in.

```tsx
function ContextualToolbar() {
const [mode, setMode] = useState('drawing');

return (
<Toolbar>
<ToolbarButton icon='pi pi-arrow-up-left' tooltip='Select' />
<ToolbarSection activeContext={mode}>
<ToolbarContext name='drawing'>
<ToolbarButton icon='pi pi-pencil' tooltip='Draw' />
<ToolbarButton icon='pi pi-stop' tooltip='Rectangle' />
<ToolbarButton icon='pi pi-circle' tooltip='Circle' />
</ToolbarContext>
<ToolbarContext name='text'>
<ToolbarButton icon='pi pi-align-left' tooltip='Align Left' />
<ToolbarButton icon='pi pi-align-center' tooltip='Align Center' />
</ToolbarContext>
</ToolbarSection>
<ToolbarButton icon='pi pi-undo' tooltip='Undo' />
</Toolbar>
);
}
```

Only the section transitions — buttons outside the section are unaffected.

## Fan-Out Sub-Panel

`ToolbarFanOutItem` replaces a regular button with one that slides out a horizontal panel of additional tools when clicked. The panel closes when clicking the button again or anywhere outside it.

```tsx
<Toolbar>
<ToolbarButton icon='pi pi-arrow-up-left' tooltip='Select' />
<ToolbarFanOutItem icon='pi pi-th-large' tooltip='Shapes'>
<ToolbarButton icon='pi pi-stop' tooltip='Rectangle' />
<ToolbarButton icon='pi pi-circle' tooltip='Circle' />
<ToolbarButton icon='pi pi-minus' tooltip='Line' />
</ToolbarFanOutItem>
</Toolbar>
```

By default the panel fans out to the right. Use `fanOutDirection='left'` when the toolbar is positioned on the right side of the screen:

```tsx
<ToolbarFanOutItem icon='pi pi-th-large' tooltip='Shapes' fanOutDirection='left'>
...
</ToolbarFanOutItem>
```

## Multiple Toolbar Groups

Render multiple `Toolbar` instances to create separate groups, matching the style of canvas-based tools panels:

```tsx
<div className='flex flex-col gap-2'>
<Toolbar>
<ToolbarButton icon='pi pi-arrow-up-left' tooltip='Select' />
<ToolbarButton icon='pi pi-pencil' tooltip='Draw' />
</Toolbar>
<Toolbar>
<ToolbarButton icon='pi pi-undo' tooltip='Undo' />
<ToolbarButton icon='pi pi-refresh' tooltip='Redo' />
</Toolbar>
</div>
```

## Tooltip Position

Both `ToolbarButton` and `ToolbarFanOutItem` default to showing tooltips on the `right`. Use `tooltipPosition` to override:

```tsx
<ToolbarButton icon='pi pi-cog' tooltip='Settings' tooltipPosition='bottom' />
```

Valid values are `'top'`, `'right'`, `'bottom'`, and `'left'`.
2 changes: 2 additions & 0 deletions Documentation/Toolbar/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- name: Overview
href: index.md
2 changes: 2 additions & 0 deletions Documentation/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
href: Dialogs/toc.yml
- name: Dropdown
href: Dropdown/index.md
- name: Toolbar
href: Toolbar/toc.yml
- name: Specialized Components
items:
- name: PivotViewer
Expand Down
10 changes: 10 additions & 0 deletions Source/Common/Tooltip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Copyright (c) Cratis. All rights reserved. */
/* Licensed under the MIT license. See LICENSE file in the project root for full license information. */

/* ── Tooltip bubble ──────────────────────────────────────────────────────── */
.tooltip-bubble {
background: var(--surface-100);
color: var(--text-color);
border: 1px solid var(--surface-border);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
44 changes: 44 additions & 0 deletions Source/Common/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import React from 'react';
import './Tooltip.css';

/** Position of the tooltip relative to its trigger element. */
export type TooltipPosition = 'top' | 'right' | 'bottom' | 'left';

/** Props for the {@link Tooltip} component. */
export interface TooltipProps {
/** The text to display inside the tooltip. */
content: string;
/** Where the tooltip appears relative to the trigger (default: 'top'). */
position?: TooltipPosition;
/** The element that triggers the tooltip on hover. */
children: React.ReactNode;
}

const POSITION_CLASSES: Record<TooltipPosition, string> = {
right: 'left-full ml-2 top-1/2 -translate-y-1/2',
left: 'right-full mr-2 top-1/2 -translate-y-1/2',
top: 'bottom-full mb-2 left-1/2 -translate-x-1/2',
bottom: 'top-full mt-2 left-1/2 -translate-x-1/2',
};

/**
* A CSS-only hover tooltip wrapper. Wraps any child element and displays
* a styled floating label on hover without relying on native browser tooltips.
*/
export const Tooltip: React.FC<TooltipProps> = ({ content, position = 'top', children }) => (
<div className='relative group inline-flex'>
{children}
<div
role='tooltip'
className={`tooltip-bubble pointer-events-none absolute ${POSITION_CLASSES[position]} z-50
text-xs px-2 py-1 rounded
whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 delay-200`}
style={{ fontFamily: 'system-ui, sans-serif' }}
>
{content}
</div>
</div>
);
1 change: 1 addition & 0 deletions Source/Common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
export * from './ErrorBoundary';
export * from './Page';
export * from './FormElement';
export * from './Tooltip';
Loading
Loading