Skip to content

Commit fcd0528

Browse files
authored
Action Bar - Experimental component (#4233)
* Add new Actionbar component * Add size context to ActionBar * Add parent styles * Get the overflow somewhat working * Fix calculations * Show More button appropriately * Enhance overflow behaviour and add proper menu items * Remove selection variant * Add comment box story as example * Add logic to show divider * Add docs * Fix builds * Switch to drafts * Remove stories from docs for now * Update snapshots for exports * Create old-peas-cross.md
1 parent e1dbd6a commit fcd0528

File tree

8 files changed

+566
-4
lines changed

8 files changed

+566
-4
lines changed

.changeset/old-peas-cross.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
Add a new experimental component ActionBar

packages/react/src/UnderlineNav/UnderlineNav.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ export type UnderlineNavProps = {
3434
}
3535
// When page is loaded, we don't have ref for the more button as it is not on the DOM yet.
3636
// However, we need to calculate number of possible items when the more button present as well. So using the width of the more button as a constant.
37-
const MORE_BTN_WIDTH = 86
37+
export const MORE_BTN_WIDTH = 86
3838
// The height is needed to make sure we don't have a layout shift when the more button is the only item in the nav.
3939
const MORE_BTN_HEIGHT = 45
4040

4141
// Needed this because passing a ref using HTMLULListElement to `Box` causes a type error
42-
const NavigationList = styled.ul`
42+
export const NavigationList = styled.ul`
4343
${sx};
4444
`
4545

46-
const MoreMenuListItem = styled.li`
46+
export const MoreMenuListItem = styled.li`
4747
display: flex;
4848
align-items: center;
4949
height: ${MORE_BTN_HEIGHT}px;
@@ -113,7 +113,7 @@ const overflowEffect = (
113113
updateListAndMenu({items, menuItems}, iconsVisible)
114114
}
115115

116-
const getValidChildren = (children: React.ReactNode) => {
116+
export const getValidChildren = (children: React.ReactNode) => {
117117
return React.Children.toArray(children).filter(child => React.isValidElement(child)) as React.ReactElement[]
118118
}
119119

packages/react/src/__tests__/__snapshots__/exports.test.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ exports[`@primer/react/deprecated should not update exports without a semver cha
237237

238238
exports[`@primer/react/drafts should not update exports without a semver change 1`] = `
239239
[
240+
"type ActionBarProps",
240241
"Blankslate",
241242
"type BlankslateProps",
242243
"callbackCancelledResult",
@@ -249,6 +250,7 @@ exports[`@primer/react/drafts should not update exports without a semver change
249250
"type DataTableProps",
250251
"default",
251252
"default",
253+
"default",
252254
"Dialog",
253255
"type DialogButtonProps",
254256
"type DialogHeaderProps",
@@ -320,6 +322,7 @@ exports[`@primer/react/drafts should not update exports without a semver change
320322

321323
exports[`@primer/react/experimental should not update exports without a semver change 1`] = `
322324
[
325+
"type ActionBarProps",
323326
"Blankslate",
324327
"type BlankslateProps",
325328
"callbackCancelledResult",
@@ -332,6 +335,7 @@ exports[`@primer/react/experimental should not update exports without a semver c
332335
"type DataTableProps",
333336
"default",
334337
"default",
338+
"default",
335339
"Dialog",
336340
"type DialogButtonProps",
337341
"type DialogHeaderProps",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"id": "actionbar",
3+
"name": "ActionBar",
4+
"status": "draft",
5+
"a11yReviewed": false,
6+
"stories": [],
7+
"props": [
8+
{
9+
"name": "size",
10+
"type": "'small' | 'medium' | 'large'",
11+
"required": false,
12+
"description": "Size of the action bar"
13+
},
14+
{
15+
"name": "aria-label",
16+
"type": "string",
17+
"description": "When provided, a label is added to the action bar"
18+
},
19+
{
20+
"name": "children",
21+
"type": "React.ReactElement",
22+
"required": true
23+
}
24+
],
25+
"subcomponents": [
26+
{
27+
"name": "ActionBar.Icon",
28+
"props": [
29+
{
30+
"name": "children",
31+
"type": "React.ReactNode",
32+
"defaultValue": "",
33+
"required": true,
34+
"description": "This will be the Button description."
35+
},
36+
{
37+
"name": "icon",
38+
"type": "Component",
39+
"defaultValue": "",
40+
"description": "provide an octicon. It will be placed in the center of the button"
41+
},
42+
{
43+
"name": "aria-label",
44+
"type": "string",
45+
"defaultValue": "",
46+
"description": "Use an aria label to describe the functionality of the button. Please refer to [our guidance on alt text](https://primer.style/guides/accessibility/alternative-text-for-images) for tips on writing good alternative text."
47+
},
48+
{
49+
"name": "sx",
50+
"type": "SystemStyleObject"
51+
}
52+
]
53+
},
54+
{
55+
"name": "ActionBar.Divider",
56+
"props": []
57+
}
58+
]
59+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from 'react'
2+
import type {Meta} from '@storybook/react'
3+
import ActionBar from '.'
4+
import {
5+
BoldIcon,
6+
CodeIcon,
7+
ItalicIcon,
8+
SearchIcon,
9+
LinkIcon,
10+
FileAddedIcon,
11+
HeadingIcon,
12+
QuoteIcon,
13+
ListUnorderedIcon,
14+
ListOrderedIcon,
15+
TasklistIcon,
16+
} from '@primer/octicons-react'
17+
import {MarkdownInput} from '../MarkdownEditor/_MarkdownInput'
18+
import {ViewSwitch} from '../MarkdownEditor/_ViewSwitch'
19+
import type {MarkdownViewMode} from '../MarkdownEditor/_ViewSwitch'
20+
import {Box} from '../..'
21+
22+
export default {
23+
title: 'Drafts/Components/ActionBar',
24+
} as Meta<typeof ActionBar>
25+
26+
export const Default = () => (
27+
<ActionBar>
28+
<ActionBar.IconButton icon={BoldIcon} aria-label="Default"></ActionBar.IconButton>
29+
<ActionBar.IconButton icon={ItalicIcon} aria-label="Default"></ActionBar.IconButton>
30+
<ActionBar.IconButton icon={CodeIcon} aria-label="Default"></ActionBar.IconButton>
31+
<ActionBar.IconButton icon={LinkIcon} aria-label="Default"></ActionBar.IconButton>
32+
<ActionBar.Divider />
33+
<ActionBar.IconButton icon={FileAddedIcon} aria-label="Default"></ActionBar.IconButton>
34+
<ActionBar.IconButton icon={SearchIcon} aria-label="Default"></ActionBar.IconButton>
35+
<ActionBar.IconButton icon={FileAddedIcon} aria-label="Default"></ActionBar.IconButton>
36+
<ActionBar.IconButton icon={SearchIcon} aria-label="Default"></ActionBar.IconButton>
37+
<ActionBar.IconButton icon={FileAddedIcon} aria-label="Default"></ActionBar.IconButton>
38+
<ActionBar.IconButton icon={SearchIcon} aria-label="Default"></ActionBar.IconButton>
39+
<ActionBar.IconButton icon={FileAddedIcon} aria-label="Default"></ActionBar.IconButton>
40+
<ActionBar.IconButton icon={SearchIcon} aria-label="Default"></ActionBar.IconButton>
41+
</ActionBar>
42+
)
43+
44+
export const SmallActionBar = () => (
45+
<ActionBar size="small">
46+
<ActionBar.IconButton icon={BoldIcon} aria-label="Default"></ActionBar.IconButton>
47+
<ActionBar.IconButton icon={ItalicIcon} aria-label="Default"></ActionBar.IconButton>
48+
<ActionBar.IconButton icon={CodeIcon} aria-label="Default"></ActionBar.IconButton>
49+
<ActionBar.IconButton icon={LinkIcon} aria-label="Default"></ActionBar.IconButton>
50+
<ActionBar.Divider />
51+
<ActionBar.IconButton icon={FileAddedIcon} aria-label="Default"></ActionBar.IconButton>
52+
<ActionBar.IconButton icon={SearchIcon} aria-label="Default"></ActionBar.IconButton>
53+
</ActionBar>
54+
)
55+
56+
export const CommentBox = () => {
57+
const [view, setView] = React.useState<MarkdownViewMode>('edit')
58+
const loadPreview = React.useCallback(() => {
59+
//console.log('loadPreview')
60+
}, [])
61+
const [value, setValue] = React.useState('')
62+
return (
63+
<Box
64+
sx={{
65+
maxWidth: 800,
66+
display: 'flex',
67+
flexDirection: 'column',
68+
width: '100%',
69+
borderColor: 'border.default',
70+
borderWidth: 1,
71+
borderStyle: 'solid',
72+
borderRadius: 2,
73+
minInlineSize: 'auto',
74+
bg: 'canvas.default',
75+
color: 'fg.default',
76+
}}
77+
>
78+
<Box
79+
sx={{
80+
display: 'flex',
81+
flexDirection: 'row',
82+
backgroundColor: 'canvas.subtle',
83+
borderTopLeftRadius: 2,
84+
borderTopRightRadius: 2,
85+
justifyContent: 'space-between',
86+
}}
87+
as="header"
88+
>
89+
<Box sx={{width: '50%'}}>
90+
<ViewSwitch
91+
selectedView={view}
92+
onViewSelect={setView}
93+
//disabled={fileHandler?.uploadProgress !== undefined}
94+
onLoadPreview={loadPreview}
95+
/>
96+
</Box>
97+
<Box sx={{width: '50%'}}>
98+
<ActionBar>
99+
<ActionBar.IconButton icon={HeadingIcon} aria-label="Heading"></ActionBar.IconButton>
100+
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold"></ActionBar.IconButton>
101+
<ActionBar.IconButton icon={ItalicIcon} aria-label="Italic"></ActionBar.IconButton>
102+
<ActionBar.IconButton icon={CodeIcon} aria-label="Insert Code"></ActionBar.IconButton>
103+
<ActionBar.IconButton icon={LinkIcon} aria-label="Insert Link"></ActionBar.IconButton>
104+
<ActionBar.Divider />
105+
<ActionBar.IconButton icon={QuoteIcon} aria-label="Insert Quote"></ActionBar.IconButton>
106+
<ActionBar.IconButton icon={ListUnorderedIcon} aria-label="Unordered List"></ActionBar.IconButton>
107+
<ActionBar.IconButton icon={ListOrderedIcon} aria-label="Ordered List"></ActionBar.IconButton>
108+
<ActionBar.IconButton icon={TasklistIcon} aria-label="Task List"></ActionBar.IconButton>
109+
</ActionBar>
110+
</Box>
111+
</Box>
112+
<MarkdownInput
113+
value={value}
114+
visible={view === 'edit'}
115+
onChange={e => {
116+
setValue(e.target.value)
117+
}}
118+
id={'markdowninput'}
119+
isDraggedOver={false}
120+
minHeightLines={5}
121+
maxHeightLines={35}
122+
monospace={false}
123+
pasteUrlsAsPlainText={false}
124+
/>
125+
</Box>
126+
)
127+
}

0 commit comments

Comments
 (0)