@platformatic/ui-components 0.1.0
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/LICENSE +201 -0
- package/README.md +26 -0
- package/fonts/Montserrat/Montserrat-Italic-VariableFont_wght.ttf +0 -0
- package/fonts/Montserrat/Montserrat-VariableFont_wght.ttf +0 -0
- package/fonts/Montserrat/OFL.txt +93 -0
- package/fonts/Montserrat/README.txt +81 -0
- package/fonts/Montserrat/static/Montserrat-Black.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-BlackItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Bold.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-BoldItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-ExtraBold.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-ExtraBoldItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-ExtraLight.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-ExtraLightItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Italic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Light.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-LightItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Medium.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-MediumItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Regular.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-SemiBold.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-SemiBoldItalic.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-Thin.ttf +0 -0
- package/fonts/Montserrat/static/Montserrat-ThinItalic.ttf +0 -0
- package/index.html +12 -0
- package/index.js +28 -0
- package/package.json +48 -0
- package/postcss.config.cjs +8 -0
- package/public/api-icon-closed.svg +6 -0
- package/public/api-icon.svg +9 -0
- package/public/vite.svg +1 -0
- package/src/App.jsx +12 -0
- package/src/components/Api/Endpoints.jsx +14 -0
- package/src/components/Api/Footer.jsx +24 -0
- package/src/components/Api/Footer.module.css +3 -0
- package/src/components/Api/Status.jsx +19 -0
- package/src/components/Api/Version.jsx +8 -0
- package/src/components/Api.jsx +83 -0
- package/src/components/Api.module.css +11 -0
- package/src/components/ApiDetail.jsx +106 -0
- package/src/components/BorderedBox.jsx +15 -0
- package/src/components/BorderedBox.module.css +3 -0
- package/src/components/BorderedText.jsx +8 -0
- package/src/components/BorderedText.module.css +3 -0
- package/src/components/Common.module.css +7 -0
- package/src/components/HorizontalSeparator.jsx +5 -0
- package/src/components/Main.jsx +69 -0
- package/src/components/Playground.jsx +9 -0
- package/src/components/Pr.jsx +9 -0
- package/src/components/SearchBar.jsx +25 -0
- package/src/components/SearchBar.module.css +6 -0
- package/src/components/StatValue.jsx +16 -0
- package/src/components/StatView.jsx +47 -0
- package/src/components/StatsView.module.css +10 -0
- package/src/components/TabbedWindow.jsx +34 -0
- package/src/components/TabbedWindow.module.css +10 -0
- package/src/components/Versions.jsx +9 -0
- package/src/components/VerticalSeparator.jsx +5 -0
- package/src/components/layouts/Layout.jsx +10 -0
- package/src/components/layouts/TwoColumnsLayout.jsx +9 -0
- package/src/lib/utils.js +20 -0
- package/src/main.jsx +9 -0
- package/src/stories/BorderedBox.stories.jsx +19 -0
- package/src/stories/Button.jsx +50 -0
- package/src/stories/Button.stories.jsx +40 -0
- package/src/stories/Header.jsx +57 -0
- package/src/stories/Header.stories.jsx +24 -0
- package/src/stories/Introduction.stories.mdx +211 -0
- package/src/stories/Page.jsx +69 -0
- package/src/stories/Page.stories.jsx +25 -0
- package/src/stories/assets/code-brackets.svg +1 -0
- package/src/stories/assets/colors.svg +1 -0
- package/src/stories/assets/comments.svg +1 -0
- package/src/stories/assets/direction.svg +1 -0
- package/src/stories/assets/flow.svg +1 -0
- package/src/stories/assets/plugin.svg +1 -0
- package/src/stories/assets/repo.svg +1 -0
- package/src/stories/assets/stackalt.svg +1 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +69 -0
- package/src/styles/main.css +22 -0
- package/tailwind.config.cjs +27 -0
- package/vite.config.js +15 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
+
import { faCircle } from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
export default function ApiStatus (props) {
|
|
5
|
+
const { online } = props
|
|
6
|
+
const colorClass = online ? 'text-main-green' : 'text-error-red'
|
|
7
|
+
return (
|
|
8
|
+
<div className={colorClass}>
|
|
9
|
+
<FontAwesomeIcon icon={faCircle} size='2xs' className={`text-${colorClass} mr-1`} />
|
|
10
|
+
{online && (
|
|
11
|
+
'Online'
|
|
12
|
+
)}
|
|
13
|
+
|
|
14
|
+
{!online && (
|
|
15
|
+
'Offline'
|
|
16
|
+
)}
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import ApiFooter from './Api/Footer'
|
|
4
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
5
|
+
import { faLink } from '@fortawesome/free-solid-svg-icons'
|
|
6
|
+
import ApiVersion from './Api/Version'
|
|
7
|
+
import Separator from './VerticalSeparator'
|
|
8
|
+
import ApiStatus from './Api/Status'
|
|
9
|
+
import BorderedBox from './BorderedBox'
|
|
10
|
+
import HorizontalSeparator from './HorizontalSeparator'
|
|
11
|
+
import styles from './Api.module.css'
|
|
12
|
+
import { useState } from 'react'
|
|
13
|
+
|
|
14
|
+
function ApiName (props) {
|
|
15
|
+
const { name } = props
|
|
16
|
+
return (
|
|
17
|
+
<span className='text-main-green font-semibold text-2xl'>{name}</span>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
export default function API (props) {
|
|
21
|
+
const [isOpen, setIsOpen] = useState(true)
|
|
22
|
+
const { name, version, source, lastDeploy, graphql, openapi, online } = props.data
|
|
23
|
+
const color = online ? 'green' : 'red'
|
|
24
|
+
function getApiIcon () {
|
|
25
|
+
if (isOpen) {
|
|
26
|
+
return 'api-icon.svg'
|
|
27
|
+
}
|
|
28
|
+
return 'api-icon-closed.svg'
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
<BorderedBox
|
|
32
|
+
color={color}
|
|
33
|
+
>
|
|
34
|
+
{isOpen && (
|
|
35
|
+
<>
|
|
36
|
+
<div className={styles.main}>
|
|
37
|
+
<div className='flex w-full'>
|
|
38
|
+
<img className='w-9 mr-5' src={getApiIcon()} />
|
|
39
|
+
<div className={styles.data}>
|
|
40
|
+
<div className='flex items-center justify-between'>
|
|
41
|
+
<div className='flex items-center justify-between gap-4'>
|
|
42
|
+
<ApiName name={name} />
|
|
43
|
+
<span>
|
|
44
|
+
<a href='#' className='border p-2 rounded-full'>
|
|
45
|
+
<FontAwesomeIcon color='white' icon={faLink} />
|
|
46
|
+
</a>
|
|
47
|
+
</span>
|
|
48
|
+
<ApiVersion version={version} />
|
|
49
|
+
</div>
|
|
50
|
+
<div className={styles.status}>
|
|
51
|
+
<ApiStatus online={online} />
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className='flex gap-2 items-center'>
|
|
57
|
+
<span>Generated with: {source}</span>
|
|
58
|
+
<Separator />
|
|
59
|
+
<span>Last deployed: {lastDeploy}</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
</div>
|
|
66
|
+
<HorizontalSeparator />
|
|
67
|
+
<ApiFooter
|
|
68
|
+
graphql={graphql}
|
|
69
|
+
openapi={openapi}
|
|
70
|
+
/>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
{!isOpen && (
|
|
74
|
+
<div className='flex items-center'>
|
|
75
|
+
<img className='w-9 mr-5' src={getApiIcon()} />
|
|
76
|
+
<ApiName name={name} />
|
|
77
|
+
<span onClick={() => { setIsOpen(!isOpen) }} className='ml-4 hover:cursor-pointer'>Click here to set up your api</span>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
)}
|
|
81
|
+
</BorderedBox>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import Endpoints from './Api/Endpoints'
|
|
4
|
+
import BorderedBox from './BorderedBox'
|
|
5
|
+
import BorderedText from './BorderedText'
|
|
6
|
+
import HorizontalSeparator from './HorizontalSeparator'
|
|
7
|
+
import TwoColumnsLayout from './layouts/TwoColumnsLayout'
|
|
8
|
+
import StatsView from './StatView'
|
|
9
|
+
import VerticalSeparator from './VerticalSeparator'
|
|
10
|
+
|
|
11
|
+
export default function ApiDetails (props) {
|
|
12
|
+
const apiData = props.data
|
|
13
|
+
const { url, repository, version, openapi, graphql, source, lastDeploy } = apiData
|
|
14
|
+
const requestStats = {
|
|
15
|
+
mainStat: {
|
|
16
|
+
value: 12,
|
|
17
|
+
label: 'Total',
|
|
18
|
+
color: 'green'
|
|
19
|
+
},
|
|
20
|
+
otherStats: [
|
|
21
|
+
{
|
|
22
|
+
value: 8,
|
|
23
|
+
label: 'per minute',
|
|
24
|
+
color: 'green'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: 0.5,
|
|
28
|
+
label: 'per second',
|
|
29
|
+
color: 'green'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const latencyStats = {
|
|
35
|
+
mainStat: {
|
|
36
|
+
value: 150,
|
|
37
|
+
label: 'ms',
|
|
38
|
+
color: 'green',
|
|
39
|
+
pre: 'P90'
|
|
40
|
+
},
|
|
41
|
+
otherStats: [
|
|
42
|
+
{
|
|
43
|
+
value: 600,
|
|
44
|
+
label: 'ms',
|
|
45
|
+
color: 'green',
|
|
46
|
+
pre: 'P99'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: 4.42,
|
|
50
|
+
label: 's',
|
|
51
|
+
color: 'green',
|
|
52
|
+
pre: 'P95'
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<BorderedBox classes='api-data'>
|
|
59
|
+
<div>
|
|
60
|
+
Preview/Staging URL: <a href={url}>{url}</a>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
Repository: <a href={`https://github.com/${repository}`}>{repository}</a>
|
|
64
|
+
</div>
|
|
65
|
+
<div>
|
|
66
|
+
Version: <BorderedText text={`v${version}`} />
|
|
67
|
+
</div>
|
|
68
|
+
<HorizontalSeparator />
|
|
69
|
+
<div className='flex gap-2 items-center'>
|
|
70
|
+
<span>Generated with: {source}</span>
|
|
71
|
+
<VerticalSeparator />
|
|
72
|
+
<span>Last deployed: {lastDeploy}</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div className='flex items-center'>
|
|
75
|
+
<span className='mr-2'>Endpoints: </span><Endpoints graphql={graphql} openapi={openapi} />
|
|
76
|
+
</div>
|
|
77
|
+
</BorderedBox>
|
|
78
|
+
<TwoColumnsLayout>
|
|
79
|
+
<BorderedBox>
|
|
80
|
+
<StatsView
|
|
81
|
+
title='Requests'
|
|
82
|
+
tooltip='Number of requests'
|
|
83
|
+
stats={requestStats}
|
|
84
|
+
/>
|
|
85
|
+
</BorderedBox>
|
|
86
|
+
<BorderedBox>
|
|
87
|
+
<StatsView
|
|
88
|
+
title='Latency'
|
|
89
|
+
tooltip='Request Latency'
|
|
90
|
+
stats={latencyStats}
|
|
91
|
+
/>
|
|
92
|
+
</BorderedBox>
|
|
93
|
+
</TwoColumnsLayout>
|
|
94
|
+
|
|
95
|
+
<TwoColumnsLayout>
|
|
96
|
+
<BorderedBox>
|
|
97
|
+
Connected Clients
|
|
98
|
+
</BorderedBox>
|
|
99
|
+
<BorderedBox>
|
|
100
|
+
Failure Rate
|
|
101
|
+
</BorderedBox>
|
|
102
|
+
</TwoColumnsLayout>
|
|
103
|
+
</>
|
|
104
|
+
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { getColor } from '../lib/utils'
|
|
4
|
+
import styles from './BorderedBox.module.css'
|
|
5
|
+
import commonStyles from './Common.module.css'
|
|
6
|
+
export default function BorderedBox (props) {
|
|
7
|
+
const { classes, color, children } = props
|
|
8
|
+
const borderColor = getColor('border', color)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className={`${styles.borderedBox} ${commonStyles.bordered} ${classes} ${borderColor}`}>
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import SearchBar from './SearchBar'
|
|
4
|
+
import Api from './Api'
|
|
5
|
+
import ApiDetails from './ApiDetail'
|
|
6
|
+
import TabbedWindow from './TabbedWindow'
|
|
7
|
+
import Prs from './Pr'
|
|
8
|
+
import Playground from './Playground'
|
|
9
|
+
import Versions from './Versions'
|
|
10
|
+
import { useState } from 'react'
|
|
11
|
+
export default function Main () {
|
|
12
|
+
const [currentSearch, setCurrentSearch] = useState('')
|
|
13
|
+
const apis = [
|
|
14
|
+
{
|
|
15
|
+
id: 1,
|
|
16
|
+
name: 'api-online',
|
|
17
|
+
version: '1.2.3',
|
|
18
|
+
source: 'Platformatic DB',
|
|
19
|
+
lastDeploy: '2022-10-10',
|
|
20
|
+
graphql: true,
|
|
21
|
+
openapi: true,
|
|
22
|
+
online: true,
|
|
23
|
+
url: 'https://example.com',
|
|
24
|
+
repository: 'platformatic/example'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 2,
|
|
28
|
+
name: 'api-offline',
|
|
29
|
+
version: '1.2.3',
|
|
30
|
+
source: 'Platformatic DB',
|
|
31
|
+
lastDeploy: '2022-10-10',
|
|
32
|
+
graphql: true,
|
|
33
|
+
openapi: true,
|
|
34
|
+
online: false
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
const tabs = [
|
|
38
|
+
{
|
|
39
|
+
label: 'Overview',
|
|
40
|
+
component: () => <ApiDetails data={apis[0]} />
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: 'PRs',
|
|
44
|
+
component: () => <Prs />
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Playground',
|
|
48
|
+
component: () => <Playground />
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Versions',
|
|
52
|
+
component: () => <Versions />
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
]
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<h1 className='text-white text-2xl font-bold'>My APIs</h1>
|
|
59
|
+
<SearchBar action={(value) => setCurrentSearch(value)} />
|
|
60
|
+
{currentSearch !== '' && (
|
|
61
|
+
<span className='text-main-green'>You searched for {currentSearch}</span>
|
|
62
|
+
)}
|
|
63
|
+
<TabbedWindow
|
|
64
|
+
tabs={tabs}
|
|
65
|
+
/>
|
|
66
|
+
{apis.map((api) => { return (<Api key={api.id} data={api} />) })}
|
|
67
|
+
</>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
+
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
import { useRef } from 'react'
|
|
5
|
+
import styles from './SearchBar.module.css'
|
|
6
|
+
import commonStyles from './Common.module.css'
|
|
7
|
+
export default function SearchBar (props) {
|
|
8
|
+
const inputRef = useRef()
|
|
9
|
+
const { action } = props
|
|
10
|
+
|
|
11
|
+
function handleSearch () {
|
|
12
|
+
const value = inputRef.current.value
|
|
13
|
+
return action(value)
|
|
14
|
+
}
|
|
15
|
+
return (
|
|
16
|
+
<div className={`${styles.input} ${commonStyles.padded}`}>
|
|
17
|
+
<input type='text' placeholder='Search' className='grow' ref={inputRef} />
|
|
18
|
+
<button onClick={handleSearch}>
|
|
19
|
+
<FontAwesomeIcon color='white' icon={faSearch} />
|
|
20
|
+
</button>
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import { Fragment } from 'react'
|
|
3
|
+
import { getColor } from '../lib/utils'
|
|
4
|
+
export default function StatValue (props) {
|
|
5
|
+
const { value, color, label, pre } = props
|
|
6
|
+
return (
|
|
7
|
+
<>
|
|
8
|
+
{pre && <span>{pre}</span>}
|
|
9
|
+
<span className={`text-5xl ${getColor('text', color)}`}>
|
|
10
|
+
{value}
|
|
11
|
+
</span>
|
|
12
|
+
<span>{label}</span>
|
|
13
|
+
</>
|
|
14
|
+
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
4
|
+
import { faCircleInfo } from '@fortawesome/free-solid-svg-icons'
|
|
5
|
+
import styles from './StatsView.module.css'
|
|
6
|
+
import HorizontalSeparator from './HorizontalSeparator'
|
|
7
|
+
import VerticalSeparator from './VerticalSeparator'
|
|
8
|
+
import StatValue from './StatValue'
|
|
9
|
+
|
|
10
|
+
export default function StatsView (props) {
|
|
11
|
+
const { title, stats } = props
|
|
12
|
+
const mainStat = stats.mainStat
|
|
13
|
+
const otherStats = stats.otherStats
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<div className={styles['stats-header']}>
|
|
17
|
+
<span className='text-xl'>{title}</span>
|
|
18
|
+
<FontAwesomeIcon icon={faCircleInfo} className='text-white' />
|
|
19
|
+
</div>
|
|
20
|
+
<div className={styles['stats-content']}>
|
|
21
|
+
{mainStat && (
|
|
22
|
+
<div className={styles['stats-main-stat']}>
|
|
23
|
+
<StatValue pre={mainStat.pre} label={mainStat.label} color={mainStat.color} value={mainStat.value} />
|
|
24
|
+
</div>
|
|
25
|
+
)}
|
|
26
|
+
{otherStats && (
|
|
27
|
+
<>
|
|
28
|
+
<HorizontalSeparator />
|
|
29
|
+
<div className='flex items-center justify-around'>
|
|
30
|
+
<div className={styles['stats-main-stat']}>
|
|
31
|
+
<StatValue pre={otherStats[0].pre} label={otherStats[0].label} color={otherStats[0].color} value={otherStats[0].value} />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<VerticalSeparator />
|
|
35
|
+
<div className={styles['stats-main-stat']}>
|
|
36
|
+
<StatValue pre={otherStats[1].pre} label={otherStats[1].label} color={otherStats[1].color} value={otherStats[1].value} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</>
|
|
41
|
+
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
</>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
3
|
+
import styles from './TabbedWindow.module.css'
|
|
4
|
+
|
|
5
|
+
export default function TabbedWindow (props) {
|
|
6
|
+
const [selected, setSelected] = useState(0)
|
|
7
|
+
const { tabs } = props
|
|
8
|
+
const headers = []
|
|
9
|
+
const components = []
|
|
10
|
+
tabs.forEach((tab) => {
|
|
11
|
+
headers.push(tab.label)
|
|
12
|
+
components.push(tab.component)
|
|
13
|
+
})
|
|
14
|
+
let currentComponent = components[selected]
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
currentComponent = components[selected]
|
|
18
|
+
}, [selected])
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles['tabbed-container']}>
|
|
22
|
+
<div className={styles['tabs-header']}>
|
|
23
|
+
{headers.map((header, index) => {
|
|
24
|
+
return (
|
|
25
|
+
<span onClick={() => setSelected(index)} key={index} className={selected === index ? styles['selected-tab'] : ''}>
|
|
26
|
+
{header}
|
|
27
|
+
</span>
|
|
28
|
+
)
|
|
29
|
+
})}
|
|
30
|
+
</div>
|
|
31
|
+
<div className={styles['tabs-content']}>{currentComponent()}</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
.tabbed-container {
|
|
2
|
+
@apply bg-main-dark-blue;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.tabs-header {
|
|
6
|
+
@apply flex justify-between text-white uppercase hover:cursor-pointer mb-4 tracking-[.25rem] h-[4rem];
|
|
7
|
+
}
|
|
8
|
+
.selected-tab {
|
|
9
|
+
@apply underline text-main-green underline-offset-8 font-bold;
|
|
10
|
+
}
|
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function getColor (type, color) {
|
|
4
|
+
let convertedColor
|
|
5
|
+
switch (color) {
|
|
6
|
+
case 'green':
|
|
7
|
+
convertedColor = 'main-green'
|
|
8
|
+
break
|
|
9
|
+
case 'red':
|
|
10
|
+
convertedColor = 'error-red'
|
|
11
|
+
break
|
|
12
|
+
default:
|
|
13
|
+
convertedColor = 'main-green'
|
|
14
|
+
break
|
|
15
|
+
}
|
|
16
|
+
return `${type}-${convertedColor}`
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
getColor
|
|
20
|
+
}
|
package/src/main.jsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import BorderedBox from '../components/BorderedBox'
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Example/BorderedBox',
|
|
5
|
+
component: BorderedBox,
|
|
6
|
+
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
|
7
|
+
argTypes: {
|
|
8
|
+
color: { control: 'color' }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
|
13
|
+
const Template = (args) => <BorderedBox {...args}>Hello Platformatic</BorderedBox>
|
|
14
|
+
|
|
15
|
+
export const Green = Template.bind({})
|
|
16
|
+
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
|
17
|
+
Green.args = {
|
|
18
|
+
color: 'green'
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import './button.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Primary UI component for user interaction
|
|
7
|
+
*/
|
|
8
|
+
export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
|
|
9
|
+
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
|
10
|
+
return (
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
|
14
|
+
style={backgroundColor && { backgroundColor }}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
{label}
|
|
18
|
+
</button>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Button.propTypes = {
|
|
23
|
+
/**
|
|
24
|
+
* Is this the principal call to action on the page?
|
|
25
|
+
*/
|
|
26
|
+
primary: PropTypes.bool,
|
|
27
|
+
/**
|
|
28
|
+
* What background color to use
|
|
29
|
+
*/
|
|
30
|
+
backgroundColor: PropTypes.string,
|
|
31
|
+
/**
|
|
32
|
+
* How large should the button be?
|
|
33
|
+
*/
|
|
34
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
|
35
|
+
/**
|
|
36
|
+
* Button contents
|
|
37
|
+
*/
|
|
38
|
+
label: PropTypes.string.isRequired,
|
|
39
|
+
/**
|
|
40
|
+
* Optional click handler
|
|
41
|
+
*/
|
|
42
|
+
onClick: PropTypes.func,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
Button.defaultProps = {
|
|
46
|
+
backgroundColor: null,
|
|
47
|
+
primary: false,
|
|
48
|
+
size: 'medium',
|
|
49
|
+
onClick: undefined,
|
|
50
|
+
};
|