@xferops/design-guide 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/chunk-R4AEJOIP.js +161 -0
- package/dist/chunk-R4AEJOIP.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +246 -0
- package/dist/server.js.map +1 -0
- package/guides/feedback-status-states.md +107 -0
- package/guides/forms-field-input.md +124 -0
- package/guides/foundations-app-setup.md +83 -0
- package/guides/foundations-token-theming.md +125 -0
- package/guides/navigation-shell.md +124 -0
- package/guides/overlays-actions.md +134 -0
- package/guides/primitives-icons-actions.md +77 -0
- package/guides/primitives-layout-content.md +96 -0
- package/guides/table-sorting.md +156 -0
- package/package.json +42 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: forms/field-input
|
|
3
|
+
title: Field and Input Setup
|
|
4
|
+
summary: Wire Field, Input, Select, Textarea, Checkbox, Radio, and Switch into labeled form controls.
|
|
5
|
+
category: forms
|
|
6
|
+
slug: field-input
|
|
7
|
+
tags:
|
|
8
|
+
- forms
|
|
9
|
+
- field
|
|
10
|
+
- input
|
|
11
|
+
- accessibility
|
|
12
|
+
- react
|
|
13
|
+
sourcePath: apps/docs/src/App.tsx
|
|
14
|
+
relatedPaths:
|
|
15
|
+
- packages/ui/src/components/Field
|
|
16
|
+
- packages/ui/src/components/Input
|
|
17
|
+
- packages/ui/src/components/Select
|
|
18
|
+
- packages/ui/src/components/Textarea
|
|
19
|
+
- packages/ui/src/components/Checkbox
|
|
20
|
+
- packages/ui/src/components/Radio
|
|
21
|
+
- packages/ui/src/components/Switch
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Field and Input Setup
|
|
25
|
+
|
|
26
|
+
This guide covers the form control pattern used in the docs app for labeled inputs and selection controls.
|
|
27
|
+
|
|
28
|
+
## Import the Form Primitives
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import {
|
|
32
|
+
Checkbox,
|
|
33
|
+
Field,
|
|
34
|
+
Input,
|
|
35
|
+
Radio,
|
|
36
|
+
Select,
|
|
37
|
+
Stack,
|
|
38
|
+
Switch,
|
|
39
|
+
Textarea
|
|
40
|
+
} from "@xferops/ui";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Use `Field` for Labels and Support Text
|
|
44
|
+
|
|
45
|
+
Wrap text-entry controls with `Field` so labels, hint text, and error text stay aligned.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<Field
|
|
49
|
+
labelText="Organization name"
|
|
50
|
+
htmlFor="orgName"
|
|
51
|
+
hintText="Shown in invoices and account settings."
|
|
52
|
+
required
|
|
53
|
+
>
|
|
54
|
+
<Input id="orgName" placeholder="XferOps LLC" />
|
|
55
|
+
</Field>
|
|
56
|
+
|
|
57
|
+
<Field
|
|
58
|
+
labelText="Billing email"
|
|
59
|
+
htmlFor="billingEmail"
|
|
60
|
+
errorText="Enter a valid email address."
|
|
61
|
+
>
|
|
62
|
+
<Input id="billingEmail" type="email" defaultValue="finance@" />
|
|
63
|
+
</Field>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Keep Control State in React When Needed
|
|
67
|
+
|
|
68
|
+
Simple toggles and radio groups can use local state.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
const [selectedContactMethod, setSelectedContactMethod] = React.useState("email");
|
|
72
|
+
const [notificationsEnabled, setNotificationsEnabled] = React.useState(true);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Compose Selection Controls in Stacks
|
|
76
|
+
|
|
77
|
+
Use `Stack` to group related controls without introducing custom layout wrappers.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Stack gap="md">
|
|
81
|
+
<Select
|
|
82
|
+
labelText="Billing cycle"
|
|
83
|
+
options={[
|
|
84
|
+
{ label: "Monthly", value: "monthly" },
|
|
85
|
+
{ label: "Quarterly", value: "quarterly" }
|
|
86
|
+
]}
|
|
87
|
+
defaultValue="monthly"
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<Textarea
|
|
91
|
+
labelText="Internal note"
|
|
92
|
+
placeholder="Add context for the finance team"
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<Switch
|
|
96
|
+
labelText="Enable notifications"
|
|
97
|
+
checked={notificationsEnabled}
|
|
98
|
+
onChange={(event) => setNotificationsEnabled(event.currentTarget.checked)}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<Checkbox labelText="Send a copy to the account owner" defaultChecked />
|
|
102
|
+
|
|
103
|
+
<Stack direction="row" gap="md">
|
|
104
|
+
<Radio
|
|
105
|
+
name="contactMethod"
|
|
106
|
+
labelText="Email"
|
|
107
|
+
checked={selectedContactMethod === "email"}
|
|
108
|
+
onChange={() => setSelectedContactMethod("email")}
|
|
109
|
+
/>
|
|
110
|
+
<Radio
|
|
111
|
+
name="contactMethod"
|
|
112
|
+
labelText="Phone"
|
|
113
|
+
checked={selectedContactMethod === "phone"}
|
|
114
|
+
onChange={() => setSelectedContactMethod("phone")}
|
|
115
|
+
/>
|
|
116
|
+
</Stack>
|
|
117
|
+
</Stack>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Notes
|
|
121
|
+
|
|
122
|
+
- Use `Field` when a control needs label, hint, or validation messaging.
|
|
123
|
+
- Let `Switch`, `Checkbox`, and `Radio` own their accessible label through `labelText`.
|
|
124
|
+
- Keep layout concerns in `Stack` instead of adding form-specific wrappers.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: foundations/app-setup
|
|
3
|
+
title: App Setup with Themes and Base Styles
|
|
4
|
+
summary: Bootstrap an app with @xferops/tokens theme classes and the @xferops/ui base layer.
|
|
5
|
+
category: foundations
|
|
6
|
+
slug: app-setup
|
|
7
|
+
tags:
|
|
8
|
+
- foundations
|
|
9
|
+
- setup
|
|
10
|
+
- theming
|
|
11
|
+
- tokens
|
|
12
|
+
- ui
|
|
13
|
+
- vanilla-extract
|
|
14
|
+
sourcePath: apps/docs/src/main.tsx
|
|
15
|
+
relatedPaths:
|
|
16
|
+
- packages/tokens/src/index.ts
|
|
17
|
+
- packages/tokens/src/themes/baseTheme.css.ts
|
|
18
|
+
- packages/tokens/src/themes/partnerTheme.css.ts
|
|
19
|
+
- packages/tokens/src/themes/darkTheme.css.ts
|
|
20
|
+
- packages/tokens/src/themes/ventureTheme.css.ts
|
|
21
|
+
- packages/ui/src/styles/baseLayer.css.ts
|
|
22
|
+
- packages/ui/src/styles/reset.css.ts
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# App Setup with Themes and Base Styles
|
|
26
|
+
|
|
27
|
+
Use `@xferops/tokens` to provide a theme class and `@xferops/ui` to apply the shared base layer. This is the minimum setup expected before rendering UI primitives.
|
|
28
|
+
|
|
29
|
+
## Import Theme Classes and the Base Layer
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import React from "react";
|
|
33
|
+
import ReactDOM from "react-dom/client";
|
|
34
|
+
import {
|
|
35
|
+
baseThemeClass,
|
|
36
|
+
darkThemeClass,
|
|
37
|
+
partnerThemeClass,
|
|
38
|
+
ventureThemeClass
|
|
39
|
+
} from "@xferops/tokens";
|
|
40
|
+
import { uiBaseLayerClass } from "@xferops/ui";
|
|
41
|
+
import { App } from "./App";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Wrap the App in Both Classes
|
|
45
|
+
|
|
46
|
+
`uiBaseLayerClass` handles the shared reset and base typography. The theme class provides the semantic values consumed by UI components.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
function Root() {
|
|
50
|
+
const [theme, setTheme] = React.useState("base");
|
|
51
|
+
|
|
52
|
+
const themeClass =
|
|
53
|
+
theme === "base"
|
|
54
|
+
? baseThemeClass
|
|
55
|
+
: theme === "partner"
|
|
56
|
+
? partnerThemeClass
|
|
57
|
+
: theme === "dark"
|
|
58
|
+
? darkThemeClass
|
|
59
|
+
: ventureThemeClass;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={`${themeClass} ${uiBaseLayerClass}`}>
|
|
63
|
+
<App activeTheme={theme} onThemeChange={setTheme} />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Render Once at the App Boundary
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
73
|
+
<React.StrictMode>
|
|
74
|
+
<Root />
|
|
75
|
+
</React.StrictMode>
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- Always apply a theme class before rendering `@xferops/ui` components, otherwise semantic token lookups from `vars` will be unset.
|
|
82
|
+
- Apply `uiBaseLayerClass` once near the root instead of per component subtree.
|
|
83
|
+
- Keep theme switching outside shared UI components. Brand or tenant changes should happen by swapping token theme classes.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: foundations/token-theming
|
|
3
|
+
title: Token Consumption and Theme Authoring
|
|
4
|
+
summary: Use semantic token vars in styles and implement brand differences through theme classes.
|
|
5
|
+
category: foundations
|
|
6
|
+
slug: token-theming
|
|
7
|
+
tags:
|
|
8
|
+
- foundations
|
|
9
|
+
- tokens
|
|
10
|
+
- theming
|
|
11
|
+
- vanilla-extract
|
|
12
|
+
- semantic-tokens
|
|
13
|
+
sourcePath: packages/tokens/src/themes/baseTheme.css.ts
|
|
14
|
+
relatedPaths:
|
|
15
|
+
- packages/tokens/src/index.ts
|
|
16
|
+
- packages/tokens/src/themeContract.css.ts
|
|
17
|
+
- packages/tokens/src/themes/baseTheme.css.ts
|
|
18
|
+
- packages/tokens/src/themes/partnerTheme.css.ts
|
|
19
|
+
- packages/tokens/src/themes/darkTheme.css.ts
|
|
20
|
+
- packages/tokens/src/themes/ventureTheme.css.ts
|
|
21
|
+
- packages/tokens/src/primitives/colors.ts
|
|
22
|
+
- packages/tokens/src/primitives/spacing.ts
|
|
23
|
+
- packages/tokens/src/primitives/radii.ts
|
|
24
|
+
- packages/tokens/src/primitives/typography.ts
|
|
25
|
+
- packages/tokens/src/primitives/shadows.ts
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Token Consumption and Theme Authoring
|
|
29
|
+
|
|
30
|
+
`@xferops/tokens` exposes two layers:
|
|
31
|
+
|
|
32
|
+
- primitive scales such as `colors`, `spacing`, `radii`, `typography`, and `shadows`
|
|
33
|
+
- semantic theme variables through `vars`
|
|
34
|
+
|
|
35
|
+
Use primitives when authoring themes. Use `vars` when authoring component styles.
|
|
36
|
+
|
|
37
|
+
## Import the Public Token API
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
baseThemeClass,
|
|
42
|
+
colors,
|
|
43
|
+
radii,
|
|
44
|
+
shadows,
|
|
45
|
+
spacing,
|
|
46
|
+
typography,
|
|
47
|
+
vars
|
|
48
|
+
} from "@xferops/tokens";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Use `vars` Inside Component Styles
|
|
52
|
+
|
|
53
|
+
Reference semantic values from the theme contract instead of hardcoded colors or spacing.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { style } from "@vanilla-extract/css";
|
|
57
|
+
import { vars } from "@xferops/tokens";
|
|
58
|
+
|
|
59
|
+
export const section = style({
|
|
60
|
+
background: vars.color.bg.surface,
|
|
61
|
+
color: vars.color.text.default,
|
|
62
|
+
border: `1px solid ${vars.color.border.subtle}`,
|
|
63
|
+
borderRadius: vars.radius.lg,
|
|
64
|
+
padding: vars.space.lg,
|
|
65
|
+
boxShadow: vars.shadow.sm
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Use Primitive Tokens When Defining a Theme
|
|
70
|
+
|
|
71
|
+
Theme files map primitives into the semantic contract.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createTheme } from "@vanilla-extract/css";
|
|
75
|
+
import { colors, radii, shadows, spacing, typography, vars } from "@xferops/tokens";
|
|
76
|
+
|
|
77
|
+
export const customThemeClass = createTheme(vars, {
|
|
78
|
+
color: {
|
|
79
|
+
bg: {
|
|
80
|
+
page: colors.neutral[50],
|
|
81
|
+
surface: colors.neutral[0],
|
|
82
|
+
surfaceElevated: colors.neutral[0]
|
|
83
|
+
},
|
|
84
|
+
text: {
|
|
85
|
+
default: colors.neutral[900],
|
|
86
|
+
muted: colors.neutral[500],
|
|
87
|
+
inverse: colors.neutral[0],
|
|
88
|
+
danger: colors.red[600]
|
|
89
|
+
},
|
|
90
|
+
border: {
|
|
91
|
+
subtle: colors.neutral[200],
|
|
92
|
+
strong: colors.neutral[300],
|
|
93
|
+
focus: colors.blue[500]
|
|
94
|
+
},
|
|
95
|
+
action: {
|
|
96
|
+
primary: {
|
|
97
|
+
bg: colors.blue[600],
|
|
98
|
+
bgHover: colors.blue[700],
|
|
99
|
+
text: colors.neutral[0]
|
|
100
|
+
},
|
|
101
|
+
secondary: {
|
|
102
|
+
bg: colors.neutral[0],
|
|
103
|
+
bgHover: colors.neutral[100],
|
|
104
|
+
text: colors.neutral[900],
|
|
105
|
+
border: colors.neutral[300]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
space: spacing,
|
|
110
|
+
radius: radii,
|
|
111
|
+
shadow: shadows,
|
|
112
|
+
font: {
|
|
113
|
+
family: typography.fontFamily,
|
|
114
|
+
size: typography.fontSize,
|
|
115
|
+
weight: typography.fontWeight,
|
|
116
|
+
lineHeight: typography.lineHeight
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Notes
|
|
122
|
+
|
|
123
|
+
- Keep brand-specific decisions in `packages/tokens/src/themes/*`, not in `@xferops/ui` components.
|
|
124
|
+
- Prefer `vars.color.*`, `vars.space.*`, and `vars.font.*` in component styles so all themes stay compatible.
|
|
125
|
+
- Reuse the existing semantic contract unless a real cross-brand need requires adding a new token.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: navigation/shell
|
|
3
|
+
title: Navigation Shell Setup
|
|
4
|
+
summary: Assemble HeaderBar, HeaderNav, Breadcrumbs, Tabs, and Pagination into a cohesive app shell.
|
|
5
|
+
category: navigation
|
|
6
|
+
slug: shell
|
|
7
|
+
tags:
|
|
8
|
+
- navigation
|
|
9
|
+
- header
|
|
10
|
+
- breadcrumbs
|
|
11
|
+
- tabs
|
|
12
|
+
- pagination
|
|
13
|
+
- layout
|
|
14
|
+
sourcePath: apps/docs/src/App.tsx
|
|
15
|
+
relatedPaths:
|
|
16
|
+
- packages/ui/src/components/Header
|
|
17
|
+
- packages/ui/src/components/Breadcrumbs
|
|
18
|
+
- packages/ui/src/components/Tabs
|
|
19
|
+
- packages/ui/src/components/Pagination
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Navigation Shell Setup
|
|
23
|
+
|
|
24
|
+
This guide shows the navigation composition used in the docs app preview.
|
|
25
|
+
|
|
26
|
+
## Import the Navigation Primitives
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import {
|
|
30
|
+
Avatar,
|
|
31
|
+
Badge,
|
|
32
|
+
Breadcrumbs,
|
|
33
|
+
Button,
|
|
34
|
+
HeaderActions,
|
|
35
|
+
HeaderBar,
|
|
36
|
+
HeaderBrand,
|
|
37
|
+
HeaderNav,
|
|
38
|
+
HeaderNavItem,
|
|
39
|
+
HeaderNavList,
|
|
40
|
+
Pagination,
|
|
41
|
+
Stack,
|
|
42
|
+
Tabs,
|
|
43
|
+
Text
|
|
44
|
+
} from "@xferops/ui";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 1. Build the Header
|
|
48
|
+
|
|
49
|
+
Use `HeaderBar` as the top-level container, with brand content on the left, navigation in the center, and actions on the right.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<HeaderBar>
|
|
53
|
+
<HeaderBrand>
|
|
54
|
+
<Badge tone="accent">XO</Badge>
|
|
55
|
+
<Stack gap="none">
|
|
56
|
+
<Text as="span" size="sm" weight="semibold">
|
|
57
|
+
XferOps
|
|
58
|
+
</Text>
|
|
59
|
+
<Text as="span" size="sm" tone="muted">
|
|
60
|
+
Tenant console
|
|
61
|
+
</Text>
|
|
62
|
+
</Stack>
|
|
63
|
+
</HeaderBrand>
|
|
64
|
+
|
|
65
|
+
<HeaderNav aria-label="Primary">
|
|
66
|
+
<HeaderNavList>
|
|
67
|
+
<li>
|
|
68
|
+
<HeaderNavItem href="#" current>
|
|
69
|
+
Overview
|
|
70
|
+
</HeaderNavItem>
|
|
71
|
+
</li>
|
|
72
|
+
<li>
|
|
73
|
+
<HeaderNavItem href="#">Invoices</HeaderNavItem>
|
|
74
|
+
</li>
|
|
75
|
+
<li>
|
|
76
|
+
<HeaderNavItem href="#">Workflows</HeaderNavItem>
|
|
77
|
+
</li>
|
|
78
|
+
</HeaderNavList>
|
|
79
|
+
</HeaderNav>
|
|
80
|
+
|
|
81
|
+
<HeaderActions>
|
|
82
|
+
<Button size="sm" variant="secondary">
|
|
83
|
+
Contact sales
|
|
84
|
+
</Button>
|
|
85
|
+
<Avatar name="Ada Lovelace" />
|
|
86
|
+
</HeaderActions>
|
|
87
|
+
</HeaderBar>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 2. Add Secondary Wayfinding
|
|
91
|
+
|
|
92
|
+
Use `Breadcrumbs` for location context and `Tabs` for section switching inside a page.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Breadcrumbs
|
|
96
|
+
items={[
|
|
97
|
+
{ label: "Dashboard", href: "#" },
|
|
98
|
+
{ label: "Billing", href: "#" },
|
|
99
|
+
{ label: "Invoices" }
|
|
100
|
+
]}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<Tabs
|
|
104
|
+
value={activeTab}
|
|
105
|
+
onValueChange={setActiveTab}
|
|
106
|
+
items={[
|
|
107
|
+
{ value: "overview", label: "Overview", content: <Text>Overview content</Text> },
|
|
108
|
+
{ value: "activity", label: "Activity", content: <Text>Activity content</Text> },
|
|
109
|
+
{ value: "settings", label: "Settings", content: <Text>Settings content</Text> }
|
|
110
|
+
]}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 3. Add Pagination for Datasets
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Pagination totalPages={5} page={activePage} onPageChange={setActivePage} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Notes
|
|
121
|
+
|
|
122
|
+
- `HeaderNavItem` carries the current-page state with `current`.
|
|
123
|
+
- `Tabs` is controlled through `value` and `onValueChange`.
|
|
124
|
+
- `Pagination` is also controlled, so keep the page in React state.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: overlays/actions
|
|
3
|
+
title: Overlay Actions Setup
|
|
4
|
+
summary: Use Dialog, Drawer, Tooltip, and Popover for focused actions and contextual UI.
|
|
5
|
+
category: overlays
|
|
6
|
+
slug: actions
|
|
7
|
+
tags:
|
|
8
|
+
- overlays
|
|
9
|
+
- dialog
|
|
10
|
+
- drawer
|
|
11
|
+
- tooltip
|
|
12
|
+
- popover
|
|
13
|
+
- interactions
|
|
14
|
+
sourcePath: apps/docs/src/App.tsx
|
|
15
|
+
relatedPaths:
|
|
16
|
+
- packages/ui/src/components/Dialog
|
|
17
|
+
- packages/ui/src/components/Drawer
|
|
18
|
+
- packages/ui/src/components/Tooltip
|
|
19
|
+
- packages/ui/src/components/Popover
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Overlay Actions Setup
|
|
23
|
+
|
|
24
|
+
This guide follows the same overlay pattern used in the docs app.
|
|
25
|
+
|
|
26
|
+
## Import the Overlay Primitives
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import {
|
|
30
|
+
Button,
|
|
31
|
+
Dialog,
|
|
32
|
+
Drawer,
|
|
33
|
+
Field,
|
|
34
|
+
Input,
|
|
35
|
+
Popover,
|
|
36
|
+
Stack,
|
|
37
|
+
Switch,
|
|
38
|
+
Text,
|
|
39
|
+
Tooltip
|
|
40
|
+
} from "@xferops/ui";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 1. Keep Open State in React
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
|
47
|
+
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
|
|
48
|
+
const [notificationsEnabled, setNotificationsEnabled] = React.useState(true);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. Use Lightweight Triggers for Small Interactions
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Stack direction="row" gap="sm" wrap align="center">
|
|
55
|
+
<Button onClick={() => setIsDialogOpen(true)}>Open dialog</Button>
|
|
56
|
+
<Button variant="secondary" onClick={() => setIsDrawerOpen(true)}>
|
|
57
|
+
Open drawer
|
|
58
|
+
</Button>
|
|
59
|
+
<Tooltip contentText="This action syncs your workspace" side="top">
|
|
60
|
+
<Button variant="secondary">Tooltip trigger</Button>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
<Popover trigger={<Button variant="secondary">Open popover</Button>} side="bottom">
|
|
63
|
+
<Stack gap="xs">
|
|
64
|
+
<Text as="span" weight="semibold">
|
|
65
|
+
Quick actions
|
|
66
|
+
</Text>
|
|
67
|
+
<Button size="sm">Create workspace</Button>
|
|
68
|
+
<Button size="sm" variant="secondary">
|
|
69
|
+
Invite teammate
|
|
70
|
+
</Button>
|
|
71
|
+
</Stack>
|
|
72
|
+
</Popover>
|
|
73
|
+
</Stack>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 3. Use `Dialog` for Confirmation
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<Dialog
|
|
80
|
+
open={isDialogOpen}
|
|
81
|
+
onOpenChange={setIsDialogOpen}
|
|
82
|
+
titleText="Confirm publish"
|
|
83
|
+
descriptionText="Publishing will make this brand theme available to all tenants."
|
|
84
|
+
footerContent={
|
|
85
|
+
<>
|
|
86
|
+
<Button variant="secondary" onClick={() => setIsDialogOpen(false)}>
|
|
87
|
+
Cancel
|
|
88
|
+
</Button>
|
|
89
|
+
<Button onClick={() => setIsDialogOpen(false)}>Publish</Button>
|
|
90
|
+
</>
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
<Text size="sm" tone="muted">
|
|
94
|
+
You can still edit token values later, but tenant apps will pick up this version.
|
|
95
|
+
</Text>
|
|
96
|
+
</Dialog>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 4. Use `Drawer` for Side-Panel Forms
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<Drawer
|
|
103
|
+
open={isDrawerOpen}
|
|
104
|
+
onOpenChange={setIsDrawerOpen}
|
|
105
|
+
titleText="Tenant settings"
|
|
106
|
+
descriptionText="Adjust workspace-level controls."
|
|
107
|
+
side="right"
|
|
108
|
+
footerContent={
|
|
109
|
+
<>
|
|
110
|
+
<Button variant="secondary" onClick={() => setIsDrawerOpen(false)}>
|
|
111
|
+
Close
|
|
112
|
+
</Button>
|
|
113
|
+
<Button onClick={() => setIsDrawerOpen(false)}>Save changes</Button>
|
|
114
|
+
</>
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<Stack gap="md">
|
|
118
|
+
<Field labelText="Tenant name" htmlFor="drawerTenantName">
|
|
119
|
+
<Input id="drawerTenantName" defaultValue="Acme Corp" />
|
|
120
|
+
</Field>
|
|
121
|
+
<Switch
|
|
122
|
+
labelText="Enable partner override"
|
|
123
|
+
checked={notificationsEnabled}
|
|
124
|
+
onChange={(event) => setNotificationsEnabled(event.currentTarget.checked)}
|
|
125
|
+
/>
|
|
126
|
+
</Stack>
|
|
127
|
+
</Drawer>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Notes
|
|
131
|
+
|
|
132
|
+
- `Dialog` and `Drawer` are controlled through `open` and `onOpenChange`.
|
|
133
|
+
- `Tooltip` is best for non-critical supporting context.
|
|
134
|
+
- `Popover` is useful for grouped actions that should remain lightweight.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: primitives/icons-actions
|
|
3
|
+
title: Icons and IconButton
|
|
4
|
+
summary: Use the shared SVG icon primitives for inline affordances and icon-only actions.
|
|
5
|
+
category: primitives
|
|
6
|
+
slug: icons-actions
|
|
7
|
+
tags:
|
|
8
|
+
- icons
|
|
9
|
+
- svg
|
|
10
|
+
- actions
|
|
11
|
+
- button
|
|
12
|
+
- accessibility
|
|
13
|
+
sourcePath: apps/docs/src/App.tsx
|
|
14
|
+
relatedPaths:
|
|
15
|
+
- packages/ui/src/components/Icon
|
|
16
|
+
- packages/ui/src/components/IconButton
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Icons and IconButton
|
|
20
|
+
|
|
21
|
+
This guide covers the starter icon surface exposed from `@xferops/ui`.
|
|
22
|
+
|
|
23
|
+
## Import the Icon Primitives
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import {
|
|
27
|
+
Icon,
|
|
28
|
+
IconButton,
|
|
29
|
+
LogOutIcon,
|
|
30
|
+
MenuIcon,
|
|
31
|
+
SaveIcon,
|
|
32
|
+
SearchIcon
|
|
33
|
+
} from "@xferops/ui";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Use Named Icons for Common Actions
|
|
37
|
+
|
|
38
|
+
Named icons keep common affordances consistent across apps while still inheriting text color.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<Stack direction="row" gap="md" align="center">
|
|
42
|
+
<SearchIcon />
|
|
43
|
+
<SaveIcon size="lg" />
|
|
44
|
+
<LogOutIcon aria-label="Sign out" />
|
|
45
|
+
</Stack>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Use `Icon` for Custom SVG Paths
|
|
49
|
+
|
|
50
|
+
Use the base `Icon` component when a product-specific glyph is needed but you still want the shared sizing and accessibility behavior.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Icon aria-label="Workspace exit" size={18}>
|
|
54
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
55
|
+
<path d="M16 17l5-5-5-5" />
|
|
56
|
+
<path d="M21 12H9" />
|
|
57
|
+
</Icon>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Pair Icons with `IconButton` for Icon-Only Actions
|
|
61
|
+
|
|
62
|
+
Use the `icon` and `label` props for the common icon-only case. The label is mapped to the button's accessible name.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<Stack direction="row" gap="sm">
|
|
66
|
+
<IconButton icon={<SaveIcon size="sm" />} label="Save item" variant="primary" />
|
|
67
|
+
<IconButton icon={<MenuIcon size="sm" />} label="Open menu" />
|
|
68
|
+
<IconButton icon={<LogOutIcon size="sm" />} label="Sign out" size="sm" />
|
|
69
|
+
</Stack>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- Icons default to decorative output unless `title`, `aria-label`, or `aria-labelledby` is supplied.
|
|
75
|
+
- All icons use SVG and inherit color through `currentColor`.
|
|
76
|
+
- `IconButton` also supports the lower-level `children` plus `aria-label` pattern when you need custom content.
|
|
77
|
+
- Prefer named exports from `@xferops/ui` for shared actions, and reserve raw `Icon` usage for genuinely custom shapes.
|