Skip to content

Commit 0fe85aa

Browse files
Add TopBarToolName component for use in header bar (#12)
<!-- See https://github.com/guardian/recommendations/blob/main/pull-requests.md for recommendations on raising and reviewing pull requests. --> ## What does this change? Adds a TopBarToolName component to be used to compose a header bar. [Figma](https://www.figma.com/design/gmHnKr3tOzvnYp1FZeIl4t/Stand--Editorial-tool-Design-System--WIP%F0%9F%9A%A7-?node-id=68-4884&t=zPB8nJ4I6Ynn7MOM-0) <!-- A PR should have enough detail to be understandable far in the future. e.g what is the problem/why is the change needed, how does it solve it and any questions or points of discussion. Prefer copying information from a Trello card over linking to it; the card may not always exist and reviewers may not have access to the board. --> # Top Bar Tool Name The Tool Name component represents the identity of the application and (optionally) the current type of content on the page, this is intended to be used within a header bar. ## When to use - In the header bar of the application ## Peer dependencies - `@emotion/react` - `react` - `react-dom` - `typescript` See the `peerDependencies` section of `package.json` for compatible versions. See [custom component build](#custom-component-build) for usage without React/Emotion. See [Icon component](?path=/docs/stand-editorial-design-system-components-icon--docs#installing-icons) for more on installing icons. ## Example usage ```tsx import { ToolName } from '@guardian/stand/ToolName'; /* types, if required */ import type { ToolNameProps, ToolNameTheme } from '@guardian/stand/ToolName'; /* Tool name only */ <ToolName name="Songwriter" favicon={{ type: 'letter', letter: 'S' }} />; /* Tool name and content type */ <ToolName name="Songwriter" favicon={{ type: 'letter', letter: 'S' }} subsection="Article" subsectionIcon="menu" />; ``` ## Props | Name | Type | Required | Default | Description | | ----------------- | ----------------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------- | | `name` | `string` | Yes | N/A | Name of the tool. | | `favicon` | `FaviconProps` | Yes | N/A | Favicon of the tool | | `subsection` | `string` | No | N/A | The type of content type of the current page. | | `subsectionIcon` | `IconProps['symbol']` \| `Exclude<IconProps['children'], string>` | No | N/A | Icon that represents the content type. | | `theme` | `ToolNameTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. | | `cssOverrides` | `SerializedStyles` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. | | `className` | `string` | No | N/A | Additional class name(s) for the tool name. | ```tsx import type { ToolNameTheme } from '@guardian/stand/ToolName'; import { ToolName } from '@guardian/stand/ToolName'; import { baseSizing, semanticTypography } from '@guardian/stand'; const customTheme: Partial<ToolNameTheme> = { gap: baseSizing['size-1-px'], typography: semanticTypography['article-body-bold-italic-md'], subsection: { typography: semanticTypography['meta-compact-md'], }, }; const faviconCustomTheme = { color: { background: baseColors['cool-purple'][700], text: baseColors.orange[300], }, typography: semanticTypography['body-italic-lg'], }; const Component = () => ( <ToolName name="Songwriter" subsection="Article" subsectionIcon="menu" theme={customTheme} favicon={{ letter: 'S, theme: faviconCustomTheme }} /> ); ``` ### CSS overrides ```tsx import { ToolName } from '@guardian/stand/ToolName'; import { baseColors, baseRadius, baseSizing } from '@guardian/stand'; import { css } from '@emotion/react'; const customStyles = css` border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]}; background-color: ${baseColors.orange[700]}; color: ${baseColors['warm-purple'][300]}; border-radius: ${baseRadius['corner-4-px']}; `; const Component = () => ( <ToolName name="Songwriter" subsection="Article" cssOverrides={customStyles} /> ); ``` ## Custom Component Build If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Tool Name component using the styles defined in the `ToolNameTheme` type. You will however be responsible for any additional functionality on top of the styles, for example icon loading, accessibility, etc. ## How has this change been tested? Storybook ToDo: sandbox <!-- For example, have you deployed your branch to `CODE` and tested certain functionality or is unit testing sufficent for this change? If you'd like help testing your changes as part of the PR review process then mention that explicitly here. --> ## Images <img width="298" height="173" alt="image" src="https://github.com/user-attachments/assets/860ddf0e-10e0-47eb-88a5-6df8b4cec3a9" /> <!-- Usually only applicable to UI changes, what did it look like before and what will it look like after? --> ## Accessibility <!-- Usually only applicable to UI changes, check the boxes if you are satisfied that your changes pass these tests --> - [ ] [Tested with screen reader](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#screen-reader) - [ ] [Navigable with keyboard](https://github.com/guardian/accessibility/blob/main/people-and-technology/02-physical.md#keyboard) - [ ] [Colour contrast passed](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#contrast) - [ ] [The change doesn't use only colour to convey meaning](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#use-of-colour)
2 parents 88b1b7b + f35e589 commit 0fe85aa

File tree

16 files changed

+669
-1
lines changed

16 files changed

+669
-1
lines changed

.changeset/cute-donkeys-stand.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@guardian/stand': patch
3+
---
4+
5+
Add TopBar ToolName component

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
"import": "./dist/favicon.js",
5050
"require": "./dist/favicon.cjs"
5151
},
52+
"./TopBar": {
53+
"types": "./dist/types/TopBar.d.ts",
54+
"import": "./dist/TopBar.js",
55+
"require": "./dist/TopBar.cjs"
56+
},
5257
"./byline": {
5358
"types": "./dist/types/byline.d.ts",
5459
"import": "./dist/byline.js",
@@ -90,7 +95,8 @@
9095
"./component/button.css": "./dist/styleD/build/css/component/button.css",
9196
"./component/typography.css": "./dist/styleD/build/css/component/typography.css",
9297
"./component/icon.css": "./dist/styleD/build/css/component/icon.css",
93-
"./component/favicon.css": "./dist/styleD/build/css/component/favicon.css"
98+
"./component/favicon.css": "./dist/styleD/build/css/component/favicon.css",
99+
"./component/TopBar.css": "./dist/styleD/build/css/component/TopBar.css"
94100
},
95101
"//typesVersions": "Provides backward compatibility for TypeScript moduleResolution: node - maps subpath imports to correct type definition files. When adding new components with their own entry points, ensure to add them here.",
96102
"typesVersions": {
@@ -119,6 +125,9 @@
119125
"favicon": [
120126
"./dist/types/favicon.d.ts"
121127
],
128+
"TopBar": [
129+
"./dist/types/TopBar.d.ts"
130+
],
122131
"byline": [
123132
"./dist/types/byline.d.ts"
124133
],

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const input = {
3737
typography: 'src/typography.ts',
3838
icon: 'src/icon.ts',
3939
favicon: 'src/favicon.ts',
40+
TopBar: 'src/TopBar.ts',
4041
// editorial components
4142
byline: 'src/byline.ts',
4243
'tag-picker': 'src/tag-picker.ts',

src/TopBar.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Top Bar components entry point
3+
*
4+
* Peer dependencies required to use these components:
5+
* - `@emotion/react`
6+
* - `react`
7+
* - `react-dom`
8+
* - `typescript`
9+
*
10+
* See the `peerDependencies` section of package.json for compatible versions.
11+
*
12+
* If you only need the built CSS (./component/TopBar.css),
13+
* you don't need to install these.
14+
*/
15+
export { TopBarToolName } from './components/topbar/toolName/TopBarToolName';
16+
export type { TopBarToolNameProps } from './components/topbar/toolName/types';
17+
export type { TopBarToolNameTheme } from './components/topbar/toolName/styles';
18+
export { componentTopBar } from './styleD/build/typescript/component/TopBar';
19+
export type { ComponentTopBar } from './styleD/build/typescript/component/TopBar';

src/components/favicon/Favicon.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ See the `peerDependencies` section of `package.json` for compatible versions.
4141

4242
See [custom component build](#custom-component-build) for usage without React/Emotion.
4343

44+
See [Icon component](?path=/docs/stand-editorial-design-system-components-icon--docs#installing-icons) for more on installing icons.
45+
4446
## Example usage
4547

4648
<SandboxReact componentName={componentName} componentTsx={componentTsx} />
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { Meta, Story } from '@storybook/addon-docs/blocks';
2+
import ToolNameStories, {
3+
WithName,
4+
WithSubsection,
5+
CustomTheme,
6+
CssOverrides,
7+
} from './TopBarToolName.stories';
8+
import {
9+
SandboxReact,
10+
SandboxCss,
11+
SandboxJs,
12+
} from '../../../util/storybook/sandbox/Sandbox';
13+
import {
14+
componentCss,
15+
componentHtml,
16+
componentJs,
17+
componentName,
18+
componentTsx,
19+
} from './sandbox';
20+
21+
<Meta of={ToolNameStories} />
22+
23+
# Tool Name
24+
25+
The Top Bar Tool Name component represents the identity of the application and (optionally) the current type of content on the page, this is intended to be used within the top bar.
26+
27+
## When to use
28+
29+
- In the header/top bar/navbar of the application
30+
31+
## Peer dependencies
32+
33+
- `@emotion/react`
34+
- `react`
35+
- `react-dom`
36+
- `typescript`
37+
38+
See the `peerDependencies` section of `package.json` for compatible versions.
39+
40+
See [custom component build](#custom-component-build) for usage without React/Emotion.
41+
42+
See [Icon component](?path=/docs/stand-editorial-design-system-components-icon--docs#installing-icons) for more on installing icons.
43+
44+
## Example usage
45+
46+
<SandboxReact componentName={componentName} componentTsx={componentTsx} />
47+
48+
```tsx
49+
import { TopBarToolName } from '@guardian/stand/TopBarToolName';
50+
51+
/* types, if required */
52+
import type {
53+
TopBarToolNameProps,
54+
TopBarToolNameTheme,
55+
} from '@guardian/stand/TopBarToolName';
56+
57+
/* Tool name only */
58+
<TopBarToolName name="Songwriter" favicon={{ type: 'letter', letter: 'S' }} />;
59+
60+
/* Tool name and content type */
61+
<TopBarToolName
62+
name="Songwriter"
63+
favicon={{ type: 'letter', letter: 'S' }}
64+
subsection="Article"
65+
subsectionIcon="menu"
66+
/>;
67+
```
68+
69+
## Props
70+
71+
| Name | Type | Required | Default | Description |
72+
| ---------------- | ----------------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------- |
73+
| `name` | `string` | Yes | N/A | Name of the tool. |
74+
| `favicon` | `FaviconProps` | Yes | N/A | Favicon of the tool |
75+
| `subsection` | `string` | No | N/A | The subsection or content type of the current page. |
76+
| `subsectionIcon` | `IconProps['symbol']` \| `Exclude<IconProps['children'], string>` | No | N/A | Icon that represents the content type. |
77+
| `theme` | `TopBarToolNameTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
78+
| `cssOverrides` | `SerializedStyles` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
79+
| `className` | `string` | No | N/A | Additional class name(s) for the tool name. |
80+
81+
## Stories
82+
83+
### With name only
84+
85+
<Story of={WithName} />
86+
87+
### With content type
88+
89+
<Story of={WithSubsection} />
90+
91+
## Customisation
92+
93+
We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
94+
95+
### Custom theme
96+
97+
<Story of={CustomTheme} />
98+
99+
```tsx
100+
import type { TopBarToolNameTheme } from '@guardian/stand/TopBarToolName';
101+
import { TopBarToolName } from '@guardian/stand/TopBarToolName';
102+
import { baseSizing, semanticTypography } from '@guardian/stand';
103+
104+
const customTheme: Partial<TopBarToolNameTheme> = {
105+
gap: baseSizing['size-1-px'],
106+
typography: semanticTypography['article-body-bold-italic-md'],
107+
subsection: {
108+
typography: semanticTypography['meta-compact-md'],
109+
},
110+
};
111+
112+
const faviconCustomTheme = {
113+
color: {
114+
background: baseColors['cool-purple'][700],
115+
text: baseColors.orange[300],
116+
},
117+
typography: semanticTypography['body-italic-lg'],
118+
};
119+
120+
const Component = () => (
121+
<TopBarToolName
122+
name="Songwriter"
123+
subsection="Article"
124+
subsectionIcon="menu"
125+
theme={customTheme}
126+
favicon={{
127+
letter: 'S,
128+
theme: faviconCustomTheme
129+
}}
130+
/>
131+
);
132+
```
133+
134+
### CSS overrides
135+
136+
<Story of={CssOverrides} />
137+
138+
```tsx
139+
import { TopBarToolName } from '@guardian/stand/TopBarToolName';
140+
import { baseColors, baseRadius, baseSizing } from '@guardian/stand';
141+
import { css } from '@emotion/react';
142+
143+
const customStyles = css`
144+
border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]};
145+
background-color: ${baseColors.orange[700]};
146+
color: ${baseColors['warm-purple'][300]};
147+
border-radius: ${baseRadius['corner-4-px']};
148+
`;
149+
150+
const Component = () => (
151+
<TopBarToolName
152+
name="Songwriter"
153+
subsection="Article"
154+
cssOverrides={customStyles}
155+
/>
156+
);
157+
```
158+
159+
## Custom Component Build
160+
161+
If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Tool Name component using the styles defined in the `TopBarToolNameTheme` type.
162+
163+
You will however be responsible for any additional functionality on top of the styles, for example icon loading, accessibility, etc.
164+
165+
**`css`**
166+
167+
You can import the TopBarToolName styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
168+
169+
<SandboxCss
170+
componentName={componentName}
171+
componentHtml={componentHtml}
172+
componentCss={componentCss}
173+
174+
/>
175+
176+
**TypeScript/JavaScript**
177+
178+
Use the `componentTopBar` variable and the `componentTopBar` type to define your custom styles in TypeScript/JavaScript:
179+
180+
<SandboxJs componentName={componentName} componentJs={componentJs} />
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { css } from '@emotion/react';
2+
import type { Meta, StoryObj } from '@storybook/react-vite';
3+
import { baseColors } from '../../../styleD/build/typescript/base/colors';
4+
import { baseRadius } from '../../../styleD/build/typescript/base/radius';
5+
import { baseSizing } from '../../../styleD/build/typescript/base/sizing';
6+
import { semanticTypography } from '../../../styleD/build/typescript/semantic/typography';
7+
import { TopBarToolName } from './TopBarToolName';
8+
9+
const meta = {
10+
title: 'Stand/Tools Design System/Components/TopBar/TopBarToolName',
11+
component: TopBarToolName,
12+
parameters: {},
13+
} satisfies Meta<typeof TopBarToolName>;
14+
15+
type Story = StoryObj<typeof TopBarToolName>;
16+
17+
export const WithName = {
18+
args: {
19+
name: 'Songwriter',
20+
favicon: {
21+
letter: 'S',
22+
},
23+
},
24+
} satisfies Story;
25+
26+
export const WithSubsection = {
27+
args: {
28+
name: 'Songwriter',
29+
favicon: {
30+
letter: 'S',
31+
},
32+
subsection: 'Article',
33+
subsectionIcon: 'menu',
34+
},
35+
} satisfies Story;
36+
37+
export const CustomTheme = {
38+
args: {
39+
name: 'Songwriter',
40+
subsection: 'Article',
41+
subsectionIcon: 'menu',
42+
favicon: {
43+
letter: 'S',
44+
theme: {
45+
color: {
46+
background: baseColors['cool-purple'][700],
47+
text: baseColors.orange[300],
48+
},
49+
typography: semanticTypography['body-italic-lg'],
50+
},
51+
},
52+
theme: {
53+
gap: baseSizing['size-1-px'],
54+
typography: semanticTypography['article-body-bold-italic-md'],
55+
subsection: {
56+
typography: semanticTypography['meta-compact-md'],
57+
},
58+
},
59+
},
60+
} satisfies Story;
61+
62+
export const CssOverrides = {
63+
name: 'cssOverrides',
64+
args: {
65+
name: 'Songwriter',
66+
favicon: {
67+
letter: 'S',
68+
},
69+
cssOverrides: css`
70+
border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]};
71+
background-color: ${baseColors.orange[700]};
72+
color: ${baseColors['warm-purple'][300]};
73+
border-radius: ${baseRadius['corner-4-px']};
74+
`,
75+
},
76+
} satisfies Story;
77+
78+
export default meta;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { mergeDeep } from '../../../util/mergeDeep';
2+
import { Favicon } from '../../favicon/Favicon';
3+
import { Icon } from '../../icon/Icon';
4+
import {
5+
defaultToolNameTheme,
6+
dividerStyles,
7+
subsectionStyles,
8+
subsectionTypography,
9+
toolNameStyles,
10+
toolNameTypography,
11+
} from './styles';
12+
import type { TopBarToolNameProps } from './types';
13+
14+
export const TopBarToolName = ({
15+
name,
16+
favicon,
17+
subsection,
18+
subsectionIcon,
19+
theme = {},
20+
cssOverrides,
21+
}: TopBarToolNameProps) => {
22+
const mergedTheme = mergeDeep(defaultToolNameTheme, theme);
23+
return (
24+
<div css={[toolNameStyles(mergedTheme), cssOverrides]}>
25+
<Favicon {...favicon} />
26+
<div css={[toolNameTypography(mergedTheme)]}>{name}</div>
27+
{subsection && (
28+
<>
29+
<div css={dividerStyles(mergedTheme)}>&nbsp;</div>
30+
<div css={subsectionStyles(mergedTheme)}>
31+
{subsectionIcon && <Icon size="sm">{subsectionIcon}</Icon>}
32+
<div css={subsectionTypography(mergedTheme)}>{subsection}</div>
33+
</div>
34+
</>
35+
)}
36+
</div>
37+
);
38+
};

0 commit comments

Comments
 (0)