Merge branch 'main' into rd.create-page-wrappers
This commit is contained in:
commit
367845f7af
|
@ -32,3 +32,4 @@ dist-ssr
|
|||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
/.tamagui/
|
||||
/storybook-static/
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
import type { StorybookConfig } from '@storybook/react-vite'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
framework: '@storybook/react-vite',
|
||||
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-onboarding",
|
||||
"@storybook/addon-interactions",
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'storybook-addon-designs',
|
||||
'storybook-dark-mode',
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
autodocs: 'tag',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
}
|
||||
|
||||
export default config
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<script>
|
||||
window.global = window
|
||||
</script>
|
|
@ -1,15 +0,0 @@
|
|||
import type { Preview } from "@storybook/react";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react'
|
||||
import type { Preview } from '@storybook/react'
|
||||
import { TamaguiProvider } from '@tamagui/web'
|
||||
import { Provider as StatusProvider } from '@status-im/components'
|
||||
import '../src/index.css'
|
||||
import appConfig from '../tamagui.config'
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
// layout: 'centered',
|
||||
},
|
||||
decorators: [
|
||||
Story => {
|
||||
return (
|
||||
<TamaguiProvider config={appConfig}>
|
||||
<StatusProvider>
|
||||
<Story />
|
||||
</StatusProvider>
|
||||
</TamaguiProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default preview
|
18
src/App.css
18
src/App.css
|
@ -8,7 +8,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
span {
|
||||
/* span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
} */
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.my-1 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.py-05 {
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
import { TamaguiProvider } from 'tamagui'
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
||||
import { Provider as StatusProvider } from '@status-im/components'
|
||||
|
||||
import './App.css'
|
||||
import config from '../tamagui.config'
|
||||
import LandingPage from './components/LayoutComponent/LandingPage'
|
||||
import LandingPage from './components/LandingPage'
|
||||
import DeviceHealthCheck from './pages/DeviceHealthCheck/DeviceHealthCheck'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <LandingPage />,
|
||||
},
|
||||
{
|
||||
path: '/device-health-check',
|
||||
element: <DeviceHealthCheck />,
|
||||
},
|
||||
])
|
||||
|
||||
function App() {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M7.75006 14.5L12.2503 9.99994L7.75 5.50002" stroke="#09101C" stroke-width="1.2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 201 B |
|
@ -0,0 +1,21 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import BreadcrumbBar from './BreadcrumbBar'
|
||||
|
||||
const meta = {
|
||||
title: 'General/BreadcrumbBar',
|
||||
component: BreadcrumbBar,
|
||||
parameters: {
|
||||
layout: 'top',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof BreadcrumbBar>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
breadcrumbList: ['Nodes', 'Nimbus', 'Connect Device'],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import './breadcrumbbar.css'
|
||||
|
||||
type BreadcrumbBarProps = {
|
||||
breadcrumbList: string[]
|
||||
}
|
||||
|
||||
function BreadcrumbBar({ breadcrumbList }: BreadcrumbBarProps) {
|
||||
return (
|
||||
<nav className="breadcrumb-bar-nav">
|
||||
<ul className="breadcrumb-bar-ul">
|
||||
{breadcrumbList.map(item => (
|
||||
<li className="breadcrumb-bar-li">{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default BreadcrumbBar
|
|
@ -0,0 +1,18 @@
|
|||
function ConnectIcon() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="20/connection">
|
||||
<path
|
||||
id="body"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.99978 1.8999C8.21669 1.8999 6.48343 2.48826 5.06881 3.57374C3.6542 4.65921 2.63728 6.18114 2.17578 7.90347L3.33489 8.21405C3.72802 6.74688 4.59428 5.45043 5.79933 4.52576C7.00437 3.6011 8.48086 3.0999 9.99978 3.0999C11.5187 3.0999 12.9952 3.6011 14.2002 4.52577C15.4053 5.45043 16.2715 6.74688 16.6647 8.21405L17.8238 7.90347C17.3623 6.18114 16.3454 4.65922 14.9307 3.57374C13.5161 2.48827 11.7829 1.8999 9.99978 1.8999ZM9.99988 4.9C8.87719 4.9 7.78588 5.27045 6.8952 5.9539C6.00451 6.63734 5.36423 7.59559 5.07366 8.68002L6.23277 8.9906C6.45497 8.16133 6.9446 7.42856 7.62571 6.90592C8.30682 6.38329 9.14136 6.1 9.99988 6.1C10.8584 6.1 11.6929 6.38329 12.374 6.90592C13.0552 7.42856 13.5448 8.16134 13.767 8.99061L14.9261 8.68002C14.6355 7.59559 13.9952 6.63735 13.1046 5.9539C12.2139 5.27045 11.1226 4.9 9.99988 4.9ZM9.99988 10.1C8.95054 10.1 8.09988 10.9507 8.09988 12C8.09988 13.0493 8.95054 13.9 9.99988 13.9C11.0492 13.9 11.8999 13.0493 11.8999 12C11.8999 10.9507 11.0492 10.1 9.99988 10.1ZM6.89988 12C6.89988 10.2879 8.2878 8.9 9.99988 8.9C11.712 8.9 13.0999 10.2879 13.0999 12C13.0999 13.5068 12.0248 14.7625 10.5999 15.042V17.5H9.39988V15.042C7.97494 14.7625 6.89988 13.5068 6.89988 12Z"
|
||||
fill="#1B273D"
|
||||
fillOpacity="0.7"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectIcon
|
|
@ -0,0 +1,117 @@
|
|||
import { useState } from 'react'
|
||||
import BreadcrumbBar from './BreadcrumbBar'
|
||||
import { Button as StatusButton, Tag, Text, Avatar, Checkbox } from '@status-im/components'
|
||||
import { Label, Separator, XStack, YStack } from 'tamagui'
|
||||
import LayoutComponent from './LayoutComponent'
|
||||
import NimbusLogo from './NimbusLogo'
|
||||
import Titles from './Titles'
|
||||
import NodeIcon from './NodeIcon'
|
||||
import ConnectIcon from './ConnectIcon'
|
||||
import PairIcon from './PairIcon'
|
||||
import CreateIcon from './CreateIcon'
|
||||
import LabelInputField from './LabelInputField'
|
||||
|
||||
function ContentPage() {
|
||||
return (
|
||||
<LayoutComponent
|
||||
breadcrumbBar={<BreadcrumbBar breadcrumbList={['Nodes', 'Nimbus', 'Connect Device']} />}
|
||||
content={<Content />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const [autoConnectChecked, setAutoConnectChecked] = useState(false)
|
||||
const [portChecked, setPortChecked] = useState(false)
|
||||
return (
|
||||
<div className="container-inner connection-page">
|
||||
<header>
|
||||
<NimbusLogo />
|
||||
<XStack space={'$2'} alignItems="center">
|
||||
<Tag icon={ConnectIcon} label="Connect" size={32} selected />
|
||||
<Tag icon={PairIcon} label="Pair" size={32} />
|
||||
<Tag icon={CreateIcon} label="Create" size={32} />
|
||||
</XStack>
|
||||
</header>
|
||||
<article className="content">
|
||||
<section className="mb-1">
|
||||
<Titles
|
||||
title="Connect Device"
|
||||
subtitle="Configure your device to connect to the Nimbus Node Manager"
|
||||
/>
|
||||
</section>
|
||||
<section className="mb-1">
|
||||
<XStack
|
||||
width={'100%'}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
// media query
|
||||
$lg={{
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<XStack width={'40%'}>
|
||||
<LabelInputField labelText="Beacon Address" placeholderText="something" />
|
||||
</XStack>
|
||||
<XStack width={'25%'}>
|
||||
<LabelInputField labelText="Beacon Node Port" placeholderText="5052" />
|
||||
</XStack>
|
||||
<XStack width={'25%'}>
|
||||
<LabelInputField labelText="Client Validator Port" placeholderText="5052" />
|
||||
</XStack>
|
||||
<YStack width={20}>
|
||||
<Checkbox
|
||||
id="port-checkbox"
|
||||
variant="outline"
|
||||
selected={portChecked}
|
||||
onCheckedChange={v => setPortChecked(v)}
|
||||
/>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack width={'100%'} alignItems="center">
|
||||
<LabelInputField labelText="API Token" placeholderText="****_*****_*****" />
|
||||
</XStack>
|
||||
</section>
|
||||
<section className="mb-1">
|
||||
<YStack>
|
||||
<Text size={13} weight="regular" color={'#647084'}>
|
||||
Device Avatar
|
||||
</Text>
|
||||
<XStack my={10}>
|
||||
<Avatar type="user" size={80} name="Device Avatar" />
|
||||
</XStack>
|
||||
<XStack space>
|
||||
<LabelInputField labelText="Device Name" placeholderText="Stake and chips" />
|
||||
<LabelInputField labelText="Device Color" placeholderText="#011892" />
|
||||
</XStack>
|
||||
</YStack>
|
||||
</section>
|
||||
<Separator alignSelf="stretch" borderColor={'#F0F2F5'} />
|
||||
<section className="my-1">
|
||||
<YStack>
|
||||
<Text size={19} weight="semibold">
|
||||
Settings
|
||||
</Text>
|
||||
</YStack>
|
||||
<XStack my={8} space={'$2'}>
|
||||
<Checkbox
|
||||
id="auto-connect"
|
||||
selected={autoConnectChecked}
|
||||
onCheckedChange={v => setAutoConnectChecked(v)}
|
||||
variant="outline"
|
||||
/>
|
||||
<Label htmlFor="auto-connect">
|
||||
<Text size={15} weight="regular">
|
||||
Auto Connect Device
|
||||
</Text>
|
||||
</Label>
|
||||
</XStack>
|
||||
<Separator alignSelf="stretch" borderColor={'#F0F2F5'} />
|
||||
</section>
|
||||
<StatusButton icon={<NodeIcon />}>Connect Device</StatusButton>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ContentPage
|
|
@ -0,0 +1,16 @@
|
|||
function CreateIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<circle cx="10" cy="10" r="7.5" stroke="#1B273D" strokeOpacity="0.7" strokeWidth="1.2" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.6 9.4V6.5H9.4V9.4L6.5 9.4V10.6L9.4 10.6V13.5H10.6V10.6L13.5 10.6L13.5 9.4L10.6 9.4Z"
|
||||
fill="#1B273D"
|
||||
fillOpacity="0.7"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateIcon
|
|
@ -0,0 +1,33 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeviceCPULoad from './DeviceCPULoad'
|
||||
|
||||
const meta = {
|
||||
title: 'Device Health/DeviceCPULoad',
|
||||
component: DeviceCPULoad,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DeviceCPULoad>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const GoodStats: Story = {
|
||||
args: {
|
||||
load: [12, 123, 4,32,40],
|
||||
},
|
||||
}
|
||||
|
||||
export const BadStats: Story = {
|
||||
args: {
|
||||
load: [12, 34, 81, 90, 91],
|
||||
},
|
||||
}
|
||||
|
||||
export const EmptyStats: Story = {
|
||||
args: {
|
||||
load: [],
|
||||
},
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import StandartLineChart from './StandardLineChart'
|
||||
import ShadowBox from './ShadowBox'
|
||||
import IconText from './IconText'
|
||||
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow, Text } from '@status-im/components'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
|
@ -34,7 +34,16 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
|
|||
const message = currentLoad < 80 ? 'Good' : 'Poor'
|
||||
|
||||
return (
|
||||
<ShadowBox boxStyle={{ width: '284px', height: '136px' }}>
|
||||
<Shadow
|
||||
variant="$2"
|
||||
style={{
|
||||
width: '284px',
|
||||
height: '136px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
justifyContent="space-between"
|
||||
|
@ -57,13 +66,20 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
|
|||
</XStack>
|
||||
<Separator borderColor={'#e3e3e3'} />
|
||||
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
|
||||
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
|
||||
<IconText
|
||||
icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}
|
||||
weight={'semibold'}
|
||||
>
|
||||
{message}
|
||||
</IconText>
|
||||
{/* <Text color={'#E95460'}>This is additional text</Text> */}
|
||||
{message === 'Poor' && (
|
||||
<Text size={13} color="#E95460">
|
||||
{((currentLoad / 80) * 100).toFixed(0)}% Utilization
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</ShadowBox>
|
||||
</Shadow>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeviceMemoryHealth from './DeviceMemoryHealth'
|
||||
|
||||
const meta = {
|
||||
title: 'Device Health/DeviceMemoryHealth',
|
||||
component: DeviceMemoryHealth,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DeviceMemoryHealth>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const GoodStats: Story = {
|
||||
args: {
|
||||
currentMemory: [25, 31, 5, 14, 20],
|
||||
maxMemory: 64,
|
||||
},
|
||||
}
|
||||
|
||||
export const BadStats: Story = {
|
||||
args: {
|
||||
currentMemory: [25, 31, 5, 14, 80],
|
||||
maxMemory: 64,
|
||||
},
|
||||
}
|
||||
export const EmptyStats: Story = {
|
||||
args: {
|
||||
currentMemory: [],
|
||||
maxMemory:0,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import StandartLineChart from './StandardLineChart'
|
||||
|
||||
import IconText from './IconText'
|
||||
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow as ShadowBox, Text } from '@status-im/components'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
type ChartData = {
|
||||
id: string
|
||||
color: string
|
||||
data: DataPoint[]
|
||||
maxValue?: number
|
||||
}
|
||||
|
||||
type DeviceMemoryHealthProps = {
|
||||
currentMemory: number[]
|
||||
maxMemory: number
|
||||
}
|
||||
const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProps) => {
|
||||
const chartData: ChartData[] = [
|
||||
{
|
||||
id: 'cpu',
|
||||
color: '#8DC6BC',
|
||||
data: currentMemory.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
maxValue: maxMemory,
|
||||
},
|
||||
]
|
||||
const currentLoad =
|
||||
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
|
||||
|
||||
const message = currentLoad < maxMemory ? 'Good' : 'Poor'
|
||||
|
||||
return (
|
||||
<ShadowBox
|
||||
variant="$2"
|
||||
style={{
|
||||
width: '284px',
|
||||
height: '136px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
justifyContent="space-between"
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<StandartLineChart data={chartData} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
|
||||
Memory
|
||||
</Paragraph>
|
||||
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
|
||||
{currentLoad} GB
|
||||
</Paragraph>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Separator borderColor={'#e3e3e3'} />
|
||||
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
|
||||
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
|
||||
{message}
|
||||
</IconText>
|
||||
{message === 'Poor' && (
|
||||
<Text size={13} color="#E95460">
|
||||
{((currentLoad / maxMemory || 0) * 100).toFixed(0)}% Utilization
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</ShadowBox>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeviceMemoryHealth
|
|
@ -0,0 +1,36 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeviceNetworkHealth from './DeviceNetworkHealth'
|
||||
|
||||
const meta = {
|
||||
title: 'Device Health/DeviceNetworkHealth',
|
||||
component: DeviceNetworkHealth,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DeviceNetworkHealth>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const GoodStats: Story = {
|
||||
args: {
|
||||
uploadRate: [1, 4, 23, 61, 34],
|
||||
downloadRate: [20, 3, 40, 56, 32],
|
||||
},
|
||||
}
|
||||
|
||||
export const BadStats: Story = {
|
||||
args: {
|
||||
uploadRate: [1, 4, 23, 55],
|
||||
downloadRate: [20, 3, 40, 56, 80],
|
||||
},
|
||||
}
|
||||
|
||||
export const NoStats: Story = {
|
||||
args: {
|
||||
uploadRate: [],
|
||||
downloadRate: [],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import StandartLineChart from './StandardLineChart'
|
||||
import IconText from './IconText'
|
||||
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow as ShadowBox, Text } from '@status-im/components'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
type ChartData = {
|
||||
id: string
|
||||
color: string
|
||||
data: DataPoint[]
|
||||
}
|
||||
|
||||
type DeviceNetworkHealthProps = {
|
||||
uploadRate: number[]
|
||||
downloadRate: number[]
|
||||
}
|
||||
const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthProps) => {
|
||||
const chartData: ChartData[] = [
|
||||
{
|
||||
id: 'uploadRate',
|
||||
color: '#8DC6BC',
|
||||
data: uploadRate.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
},
|
||||
{
|
||||
id: 'downloadRate',
|
||||
color: '#D92344',
|
||||
data: downloadRate.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
},
|
||||
]
|
||||
const currentLoad =
|
||||
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
|
||||
|
||||
const message = currentLoad > 60 ? 'Good' : 'Poor'
|
||||
|
||||
return (
|
||||
<ShadowBox
|
||||
variant="$2"
|
||||
style={{
|
||||
width: '284px',
|
||||
height: '136px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
justifyContent="space-between"
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
position: 'relative', // Make XStack a positioning context
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<StandartLineChart data={chartData} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
|
||||
Network
|
||||
</Paragraph>
|
||||
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
|
||||
{currentLoad} GB
|
||||
</Paragraph>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Separator borderColor={'#e3e3e3'} />
|
||||
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
|
||||
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
|
||||
{message}
|
||||
</IconText>
|
||||
{message === 'Poor' && (
|
||||
<Text size={13} color="#E95460">
|
||||
{((currentLoad / 60) * 100).toFixed(0)}% Utilization
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</ShadowBox>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeviceNetworkHealth
|
|
@ -0,0 +1,36 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeviceStorageHealth from './DeviceStorageHealth'
|
||||
|
||||
const meta = {
|
||||
title: 'Device Health/DeviceStorageHealth',
|
||||
component: DeviceStorageHealth,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DeviceStorageHealth>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const GoodStats: Story = {
|
||||
args: {
|
||||
storage: 10,
|
||||
maxStorage: 20,
|
||||
},
|
||||
}
|
||||
|
||||
export const BadStats: Story = {
|
||||
args: {
|
||||
storage: 20,
|
||||
maxStorage: 20,
|
||||
},
|
||||
}
|
||||
|
||||
export const NoStats: Story = {
|
||||
args: {
|
||||
storage: 0,
|
||||
maxStorage: 0,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import IconText from './IconText'
|
||||
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
|
||||
import StandardGauge from './StandardGauge'
|
||||
import { Shadow, Text } from '@status-im/components'
|
||||
interface DeviceStorageHealthProps {
|
||||
storage: number
|
||||
maxStorage: number
|
||||
}
|
||||
const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxStorage }) => {
|
||||
const message = storage < maxStorage ? 'Good' : 'Poor'
|
||||
const free = maxStorage - storage
|
||||
const utilization = (storage / (maxStorage || 1)) * 100
|
||||
|
||||
const data = (free: number) => {
|
||||
return [
|
||||
{
|
||||
id: 'storage',
|
||||
label: 'Used',
|
||||
value: storage,
|
||||
color: '#E95460',
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
label: 'Free',
|
||||
value: free,
|
||||
color: '#E7EAEE',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<Shadow
|
||||
variant="$2"
|
||||
style={{
|
||||
width: '284px',
|
||||
height: '136px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
justifyContent="space-between"
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '44px',
|
||||
width: '75px',
|
||||
height: '75px',
|
||||
}}
|
||||
>
|
||||
<StandardGauge data={data(free)} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
|
||||
Storage
|
||||
</Paragraph>
|
||||
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
|
||||
{storage} GB
|
||||
</Paragraph>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Separator borderColor={'#e3e3e3'} />
|
||||
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
|
||||
<IconText
|
||||
icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}
|
||||
weight={'semibold'}
|
||||
>
|
||||
{message}
|
||||
</IconText>
|
||||
{message === 'Poor' && (
|
||||
<Text size={13} color="#E95460">
|
||||
{utilization.toFixed(0)}% Utilization
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Shadow>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeviceStorageHealth
|
|
@ -1,35 +1,27 @@
|
|||
import { Text } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
export type TextElement = {
|
||||
text: string
|
||||
bold?: boolean
|
||||
italic?: boolean
|
||||
weight?: 'regular' | 'medium' | 'semibold'
|
||||
}
|
||||
|
||||
type FormattedTextProps = {
|
||||
textElements: TextElement[]
|
||||
color?: string
|
||||
fontSize?: string
|
||||
size: 27 | 19 | 15 | 13 | 11
|
||||
}
|
||||
|
||||
const FormattedText = ({ textElements, color, fontSize }: FormattedTextProps) => {
|
||||
const calculateStyle = (textElement: TextElement) => {
|
||||
const isBold = textElement.bold ?? false
|
||||
const isItalic = textElement.italic ?? false
|
||||
|
||||
return { fontWeight: isBold ? 'bold' : '', fontStyle: isItalic ? 'italic' : '' }
|
||||
}
|
||||
|
||||
const FormattedText = ({ textElements, color, size }: FormattedTextProps) => {
|
||||
return (
|
||||
<Text color={color} fontSize={fontSize}>
|
||||
{textElements.map((textElement, index) => {
|
||||
return (
|
||||
<span style={calculateStyle(textElement)} key={index}>
|
||||
{textElement.text}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</Text>
|
||||
<>
|
||||
{textElements.map((textElement, index) => (
|
||||
<Text key={index} size={size} color={color} weight={textElement.weight}>
|
||||
{textElement.text}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import HealthInfoSection from './HealthInfoSection'
|
||||
|
||||
const meta = {
|
||||
title: 'Device Health/HealthInfoSection',
|
||||
component: HealthInfoSection,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof HealthInfoSection>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const AllGoodStats: Story = {
|
||||
args: {
|
||||
usedStorage: 79 * 1024 * 1024 * 1024,
|
||||
maxStorage: 100 * 1024 * 1024 * 1024,
|
||||
usedRamMemory: 32 * 1024 * 1024 * 1024,
|
||||
maxRamMemory: 64 * 1024 * 1024 * 1024,
|
||||
cpuClockRate: 2.4,
|
||||
networkLatency: 50,
|
||||
},
|
||||
}
|
||||
|
||||
export const StorageBad: Story = {
|
||||
args: {
|
||||
usedStorage: 80 * 1024 * 1024 * 1024,
|
||||
maxStorage: 100 * 1024 * 1024 * 1024,
|
||||
usedRamMemory: 32 * 1024 * 1024 * 1024,
|
||||
maxRamMemory: 64 * 1024 * 1024 * 1024,
|
||||
cpuClockRate: 2.5,
|
||||
networkLatency: 50,
|
||||
},
|
||||
}
|
||||
|
||||
export const CpuBad: Story = {
|
||||
args: {
|
||||
usedStorage: 79 * 1024 * 1024 * 1024,
|
||||
maxStorage: 100 * 1024 * 1024 * 1024,
|
||||
usedRamMemory: 32 * 1024 * 1024 * 1024,
|
||||
maxRamMemory: 64 * 1024 * 1024 * 1024,
|
||||
cpuClockRate: 2.3,
|
||||
networkLatency: 50,
|
||||
},
|
||||
}
|
||||
|
||||
export const RamBad: Story = {
|
||||
args: {
|
||||
usedStorage: 79 * 1024 * 1024 * 1024,
|
||||
maxStorage: 100 * 1024 * 1024 * 1024,
|
||||
usedRamMemory: 56 * 1024 * 1024 * 1024,
|
||||
maxRamMemory: 64 * 1024 * 1024 * 1024,
|
||||
cpuClockRate: 2.4,
|
||||
networkLatency: 50,
|
||||
},
|
||||
}
|
||||
|
||||
export const LatencyBad: Story = {
|
||||
args: {
|
||||
usedStorage: 79 * 1024 * 1024 * 1024,
|
||||
maxStorage: 100 * 1024 * 1024 * 1024,
|
||||
usedRamMemory: 32 * 1024 * 1024 * 1024,
|
||||
maxRamMemory: 64 * 1024 * 1024 * 1024,
|
||||
cpuClockRate: 2.4,
|
||||
networkLatency: 101,
|
||||
},
|
||||
}
|
|
@ -26,11 +26,11 @@ const HealthInfoSection = (props: HealthInfoSectionProps) => {
|
|||
|
||||
const usedStoragePercentage = (usedStorage / maxStorage) * 100
|
||||
const usedRamMemoryPercentage = (usedRamMemory / maxRamMemory) * 100
|
||||
const cpuClockRatePercentage = cpuClockRate > 2.4 ? 100 : 0
|
||||
const cpuClockRatePercentage = cpuClockRate < 2.4 ? 100 : 0
|
||||
const networkLatencyPercentage = networkLatency > 100 ? 100 : 0
|
||||
|
||||
return (
|
||||
<YStack space={'$2'}>
|
||||
<YStack space={'$3'}>
|
||||
<StatusIconText
|
||||
percentage={usedStoragePercentage}
|
||||
threshold={80}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { Image } from 'tamagui'
|
||||
import { Image } from '@status-im/components'
|
||||
|
||||
export type IconProps = {
|
||||
source: string
|
||||
src: string
|
||||
width?: number
|
||||
height?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Icon = ({ source, width = 16, height = 16, ...props }: IconProps) => {
|
||||
return <Image {...props} source={{ uri: source }} width={width} height={height} />
|
||||
const Icon = ({ src, width = 16, height = 16 }: IconProps) => {
|
||||
return <Image src={src} source={{ uri: src }} width={width} height={height} />
|
||||
}
|
||||
|
||||
export default Icon
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import Icon from './Icon'
|
||||
import ReactButton from './ReactButton'
|
||||
|
||||
type IconButtonProps = {
|
||||
children: string
|
||||
icon: string
|
||||
style?: unknown
|
||||
size?: string
|
||||
fontSize?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const IconButton = ({ icon, children, ...props }: IconButtonProps) => {
|
||||
return (
|
||||
<ReactButton {...props} icon={<Icon source={icon} />}>
|
||||
{children}
|
||||
</ReactButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconButton
|
|
@ -1,12 +1,14 @@
|
|||
import { Paragraph, XStack } from 'tamagui'
|
||||
import { XStack } from 'tamagui'
|
||||
import Icon from './Icon'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
type IconTextProps = {
|
||||
icon: string
|
||||
children: string
|
||||
weight?: 'regular' | 'medium' | 'semibold'
|
||||
}
|
||||
|
||||
const IconText = ({ icon, children, ...props }: IconTextProps) => {
|
||||
const IconText = ({ icon, children, weight }: IconTextProps) => {
|
||||
return (
|
||||
<XStack
|
||||
style={{
|
||||
|
@ -14,10 +16,10 @@ const IconText = ({ icon, children, ...props }: IconTextProps) => {
|
|||
}}
|
||||
space={'$2'}
|
||||
>
|
||||
<Icon source={icon} />
|
||||
<Paragraph {...props} color={'#000000'} fontWeight={'bold'}>
|
||||
<Icon src={icon} />
|
||||
<Text size={13} color={'#000000'} weight={weight}>
|
||||
{children}
|
||||
</Paragraph>
|
||||
</Text>
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { XStack } from 'tamagui'
|
||||
|
||||
import Icon from './Icon'
|
||||
import FormattedText, { TextElement } from './FormattedText'
|
||||
|
||||
type InformationBoxProps = {
|
||||
icon: string
|
||||
textElements: TextElement[]
|
||||
}
|
||||
|
||||
const InformationBox = ({ icon, textElements }: InformationBoxProps) => {
|
||||
return (
|
||||
<XStack
|
||||
style={{
|
||||
border: '2px solid #E7EAEE',
|
||||
borderRadius: '12px',
|
||||
padding: '11px 16px',
|
||||
maxWidth: '590px',
|
||||
alignItems: 'start',
|
||||
}}
|
||||
space={'$2'}
|
||||
>
|
||||
<Icon source={icon} width={12} height={12} />
|
||||
<FormattedText textElements={textElements} color={'#647084'} fontSize={'$3'} />
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default InformationBox
|
|
@ -0,0 +1,20 @@
|
|||
import { Input as StatusInput, Text } from '@status-im/components'
|
||||
import { Label } from 'tamagui'
|
||||
|
||||
type LabelInputProps = {
|
||||
labelText: string
|
||||
placeholderText: string
|
||||
}
|
||||
|
||||
function LabelInputField({ labelText, placeholderText }: LabelInputProps) {
|
||||
return (
|
||||
<Label flexDirection="column" alignItems="flex-start" my={10} width={'100%'}>
|
||||
<Text size={13} weight="regular" color={'#647084'}>
|
||||
{labelText}
|
||||
</Text>
|
||||
<StatusInput placeholder={placeholderText} width={'100%'} />
|
||||
</Label>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabelInputField
|
|
@ -0,0 +1,20 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import LandingPage from './LandingPage'
|
||||
|
||||
const meta = {
|
||||
title: 'Pages/LandingPage',
|
||||
component: LandingPage,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof LandingPage>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Page: Story = {
|
||||
args: {},
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
import LayoutComponent from './LayoutComponent'
|
||||
import './LandingPage.css'
|
||||
import QuickStartBar from '../QuickStartBar/QuickStartBar'
|
||||
import DeviceCPULoad from '../DeviceCPULoad'
|
||||
import NodesLogo from '../NodesLogo'
|
||||
import QuickStartBar from './QuickStartBar'
|
||||
|
||||
function LandingPage() {
|
||||
return (
|
||||
<>
|
||||
<LayoutComponent content={<Content />} />
|
||||
<LayoutComponent
|
||||
content={<Content />}
|
||||
rightImageSrc="src/assets/bg-img/landing-page-bg.png"
|
||||
/>
|
||||
<QuickStartBar />
|
||||
</>
|
||||
)
|
||||
|
@ -17,7 +18,14 @@ function Content() {
|
|||
return (
|
||||
<div className="container-inner landing-page">
|
||||
<header>
|
||||
<NodesLogo />
|
||||
<div>
|
||||
<div>
|
||||
<img src="src/assets/nodes-app-icon.png" alt="" />
|
||||
</div>
|
||||
<p className="logo-title">
|
||||
nodes<span className="beta">BETA</span>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
<article className="content">
|
||||
<div className="avatar">
|
||||
|
@ -30,8 +38,8 @@ function Content() {
|
|||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M23 44C34.598 44 44 34.598 44 23V21C44 9.40202 34.598 2.24162e-07 23 5.00679e-07L21 5.48363e-07C9.40202 8.2488e-07 0 9.40171 0 20.9997C0 21.6637 0 22.3312 0 22.9999C0 34.5979 9.40202 44 21 44H23Z"
|
||||
fill="url(#pattern0)"
|
||||
/>
|
||||
|
@ -67,13 +75,13 @@ function Content() {
|
|||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0_760_1602)">
|
||||
<g clipPath="url(#clip0_760_1602)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.9999 2.60002C3.67442 2.60002 2.5999 3.67454 2.5999 5.00002C2.5999 6.32551 3.67442 7.40002 4.9999 7.40002C6.32539 7.40002 7.3999 6.32551 7.3999 5.00002C7.3999 3.67454 6.32539 2.60002 4.9999 2.60002ZM1.3999 5.00002C1.3999 3.0118 3.01168 1.40002 4.9999 1.40002C6.98813 1.40002 8.5999 3.0118 8.5999 5.00002C8.5999 5.77749 8.35345 6.4974 7.9344 7.08587L9.48712 8.63859C10.4478 7.86383 11.6697 7.4 13 7.4C16.0928 7.4 18.6 9.90721 18.6 13C18.6 16.0928 16.0928 18.6 13 18.6C9.9072 18.6 7.4 16.0928 7.4 13C7.4 11.6697 7.86383 10.4478 8.63859 9.48712L7.08589 7.93442C6.49739 8.35353 5.77743 8.60002 4.9999 8.60002C3.01168 8.60002 1.3999 6.98825 1.3999 5.00002ZM8.6 13C8.6 10.5699 10.5699 8.6 13 8.6C15.4301 8.6 17.4 10.5699 17.4 13C17.4 15.4301 15.4301 17.4 13 17.4C10.5699 17.4 8.6 15.4301 8.6 13Z"
|
||||
fill="white"
|
||||
fill-opacity="0.7"
|
||||
fillOpacity="0.7"
|
||||
/>
|
||||
<path
|
||||
d="M7.9344 7.08587L7.52711 6.79585C7.3855 6.99472 7.40821 7.26679 7.58085 7.43943L7.9344 7.08587ZM9.48712 8.63859L9.13357 8.99215C9.31425 9.17283 9.6021 9.18821 9.801 9.0278L9.48712 8.63859ZM8.63859 9.48712L9.0278 9.801C9.18821 9.6021 9.17283 9.31425 8.99215 9.13357L8.63859 9.48712ZM7.08589 7.93442L7.43945 7.58087C7.26681 7.40823 6.99472 7.38552 6.79585 7.52715L7.08589 7.93442ZM3.0999 5.00002C3.0999 3.95068 3.95056 3.10002 4.9999 3.10002V2.10002C3.39828 2.10002 2.0999 3.3984 2.0999 5.00002H3.0999ZM4.9999 6.90002C3.95056 6.90002 3.0999 6.04937 3.0999 5.00002H2.0999C2.0999 6.60165 3.39828 7.90002 4.9999 7.90002V6.90002ZM6.8999 5.00002C6.8999 6.04937 6.04924 6.90002 4.9999 6.90002V7.90002C6.60153 7.90002 7.8999 6.60165 7.8999 5.00002H6.8999ZM4.9999 3.10002C6.04924 3.10002 6.8999 3.95068 6.8999 5.00002H7.8999C7.8999 3.3984 6.60153 2.10002 4.9999 2.10002V3.10002ZM4.9999 0.900024C2.73553 0.900024 0.899902 2.73566 0.899902 5.00002H1.8999C1.8999 3.28794 3.28782 1.90002 4.9999 1.90002V0.900024ZM9.0999 5.00002C9.0999 2.73566 7.26427 0.900024 4.9999 0.900024V1.90002C6.71198 1.90002 8.0999 3.28794 8.0999 5.00002H9.0999ZM8.34169 7.3759C8.81905 6.70553 9.0999 5.88483 9.0999 5.00002H8.0999C8.0999 5.67015 7.88784 6.28926 7.52711 6.79585L8.34169 7.3759ZM9.84068 8.28504L8.28795 6.73232L7.58085 7.43943L9.13357 8.99215L9.84068 8.28504ZM9.801 9.0278C10.676 8.32213 11.788 7.9 13 7.9V6.9C11.5514 6.9 10.2196 7.40554 9.17324 8.24939L9.801 9.0278ZM13 7.9C15.8167 7.9 18.1 10.1833 18.1 13H19.1C19.1 9.63106 16.3689 6.9 13 6.9V7.9ZM18.1 13C18.1 15.8167 15.8167 18.1 13 18.1V19.1C16.3689 19.1 19.1 16.3689 19.1 13H18.1ZM13 18.1C10.1833 18.1 7.9 15.8167 7.9 13H6.9C6.9 16.3689 9.63106 19.1 13 19.1V18.1ZM7.9 13C7.9 11.788 8.32213 10.676 9.0278 9.801L8.24939 9.17324C7.40554 10.2196 6.9 11.5514 6.9 13H7.9ZM6.73234 8.28798L8.28504 9.84068L8.99215 9.13357L7.43945 7.58087L6.73234 8.28798ZM4.9999 9.10002C5.88478 9.10002 6.70554 8.81913 7.37594 8.3417L6.79585 7.52715C6.28924 7.88793 5.67008 8.10002 4.9999 8.10002V9.10002ZM0.899902 5.00002C0.899902 7.26439 2.73553 9.10002 4.9999 9.10002V8.10002C3.28782 8.10002 1.8999 6.71211 1.8999 5.00002H0.899902ZM13 8.1C10.2938 8.1 8.1 10.2938 8.1 13H9.1C9.1 10.8461 10.8461 9.1 13 9.1V8.1ZM17.9 13C17.9 10.2938 15.7062 8.1 13 8.1V9.1C15.1539 9.1 16.9 10.8461 16.9 13H17.9ZM13 17.9C15.7062 17.9 17.9 15.7062 17.9 13H16.9C16.9 15.1539 15.1539 16.9 13 16.9V17.9ZM8.1 13C8.1 15.7062 10.2938 17.9 13 17.9V16.9C10.8461 16.9 9.1 15.1539 9.1 13H8.1Z"
|
||||
|
@ -90,8 +98,8 @@ function Content() {
|
|||
Discover Nodes
|
||||
</button>
|
||||
</article>
|
||||
<DeviceCPULoad load={[13,32,24,1,49,90,13,32,24,1,49,90]}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingPage
|
|
@ -4,6 +4,7 @@ import './layout.css'
|
|||
type LeftProps = {
|
||||
breadcrumbBar?: ReactNode
|
||||
content: ReactNode
|
||||
rightImageSrc?: string
|
||||
}
|
||||
function LayoutComponent(props: LeftProps) {
|
||||
return (
|
||||
|
@ -12,18 +13,16 @@ function LayoutComponent(props: LeftProps) {
|
|||
{props.breadcrumbBar}
|
||||
<div className="container">{props.content}</div>
|
||||
</section>
|
||||
<LayoutRight />
|
||||
<LayoutRight rightImageSrc={props.rightImageSrc} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LayoutRight() {
|
||||
function LayoutRight({ rightImageSrc }: { rightImageSrc?: string }) {
|
||||
return (
|
||||
<section className="layout-right">
|
||||
<div className="image-container">
|
||||
<img src="src/assets/bg-img/landing-page-bg.png" alt="" />
|
||||
{/* <img src="src/assets/bg-img/day-night-bg.png" alt="" /> */}
|
||||
{/* <img src="src/assets/bg-img/key-lock-bg.png" alt="" /> */}
|
||||
<img src={rightImageSrc} alt="background" />
|
||||
</div>
|
||||
</section>
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import NimbusLogo from './NimbusLogo'
|
||||
|
||||
const meta = {
|
||||
title: 'General/NimbusLogo',
|
||||
component: NimbusLogo,
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof NimbusLogo>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const ExampleNimbusLogo: Story = {}
|
|
@ -1,6 +1,7 @@
|
|||
import { Text, XStack } from 'tamagui'
|
||||
import { XStack } from 'tamagui'
|
||||
import Icon from './Icon'
|
||||
import Tag from './Tag'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
const NimbusLogo = () => {
|
||||
return (
|
||||
|
@ -11,8 +12,10 @@ const NimbusLogo = () => {
|
|||
}}
|
||||
space={'$2'}
|
||||
>
|
||||
<Icon source={'/icons/marks.png'} width={55} height={60} />
|
||||
<Text style={{ fontWeight: '650', fontSize: '24px' }}>Nimbus</Text>
|
||||
<Icon src={'/icons/marks.png'} width={55} height={60} />
|
||||
<Text size={27} weight={'medium'}>
|
||||
Nimbus
|
||||
</Text>
|
||||
<Tag text="BETA" />
|
||||
</XStack>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
function NodeIcon() {
|
||||
return (
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<g clipPath="url(#clip0_760_1602)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.9999 2.60002C3.67442 2.60002 2.5999 3.67454 2.5999 5.00002C2.5999 6.32551 3.67442 7.40002 4.9999 7.40002C6.32539 7.40002 7.3999 6.32551 7.3999 5.00002C7.3999 3.67454 6.32539 2.60002 4.9999 2.60002ZM1.3999 5.00002C1.3999 3.0118 3.01168 1.40002 4.9999 1.40002C6.98813 1.40002 8.5999 3.0118 8.5999 5.00002C8.5999 5.77749 8.35345 6.4974 7.9344 7.08587L9.48712 8.63859C10.4478 7.86383 11.6697 7.4 13 7.4C16.0928 7.4 18.6 9.90721 18.6 13C18.6 16.0928 16.0928 18.6 13 18.6C9.9072 18.6 7.4 16.0928 7.4 13C7.4 11.6697 7.86383 10.4478 8.63859 9.48712L7.08589 7.93442C6.49739 8.35353 5.77743 8.60002 4.9999 8.60002C3.01168 8.60002 1.3999 6.98825 1.3999 5.00002ZM8.6 13C8.6 10.5699 10.5699 8.6 13 8.6C15.4301 8.6 17.4 10.5699 17.4 13C17.4 15.4301 15.4301 17.4 13 17.4C10.5699 17.4 8.6 15.4301 8.6 13Z"
|
||||
fill="white"
|
||||
fillOpacity="0.7"
|
||||
/>
|
||||
<path
|
||||
d="M7.9344 7.08587L7.52711 6.79585C7.3855 6.99472 7.40821 7.26679 7.58085 7.43943L7.9344 7.08587ZM9.48712 8.63859L9.13357 8.99215C9.31425 9.17283 9.6021 9.18821 9.801 9.0278L9.48712 8.63859ZM8.63859 9.48712L9.0278 9.801C9.18821 9.6021 9.17283 9.31425 8.99215 9.13357L8.63859 9.48712ZM7.08589 7.93442L7.43945 7.58087C7.26681 7.40823 6.99472 7.38552 6.79585 7.52715L7.08589 7.93442ZM3.0999 5.00002C3.0999 3.95068 3.95056 3.10002 4.9999 3.10002V2.10002C3.39828 2.10002 2.0999 3.3984 2.0999 5.00002H3.0999ZM4.9999 6.90002C3.95056 6.90002 3.0999 6.04937 3.0999 5.00002H2.0999C2.0999 6.60165 3.39828 7.90002 4.9999 7.90002V6.90002ZM6.8999 5.00002C6.8999 6.04937 6.04924 6.90002 4.9999 6.90002V7.90002C6.60153 7.90002 7.8999 6.60165 7.8999 5.00002H6.8999ZM4.9999 3.10002C6.04924 3.10002 6.8999 3.95068 6.8999 5.00002H7.8999C7.8999 3.3984 6.60153 2.10002 4.9999 2.10002V3.10002ZM4.9999 0.900024C2.73553 0.900024 0.899902 2.73566 0.899902 5.00002H1.8999C1.8999 3.28794 3.28782 1.90002 4.9999 1.90002V0.900024ZM9.0999 5.00002C9.0999 2.73566 7.26427 0.900024 4.9999 0.900024V1.90002C6.71198 1.90002 8.0999 3.28794 8.0999 5.00002H9.0999ZM8.34169 7.3759C8.81905 6.70553 9.0999 5.88483 9.0999 5.00002H8.0999C8.0999 5.67015 7.88784 6.28926 7.52711 6.79585L8.34169 7.3759ZM9.84068 8.28504L8.28795 6.73232L7.58085 7.43943L9.13357 8.99215L9.84068 8.28504ZM9.801 9.0278C10.676 8.32213 11.788 7.9 13 7.9V6.9C11.5514 6.9 10.2196 7.40554 9.17324 8.24939L9.801 9.0278ZM13 7.9C15.8167 7.9 18.1 10.1833 18.1 13H19.1C19.1 9.63106 16.3689 6.9 13 6.9V7.9ZM18.1 13C18.1 15.8167 15.8167 18.1 13 18.1V19.1C16.3689 19.1 19.1 16.3689 19.1 13H18.1ZM13 18.1C10.1833 18.1 7.9 15.8167 7.9 13H6.9C6.9 16.3689 9.63106 19.1 13 19.1V18.1ZM7.9 13C7.9 11.788 8.32213 10.676 9.0278 9.801L8.24939 9.17324C7.40554 10.2196 6.9 11.5514 6.9 13H7.9ZM6.73234 8.28798L8.28504 9.84068L8.99215 9.13357L7.43945 7.58087L6.73234 8.28798ZM4.9999 9.10002C5.88478 9.10002 6.70554 8.81913 7.37594 8.3417L6.79585 7.52715C6.28924 7.88793 5.67008 8.10002 4.9999 8.10002V9.10002ZM0.899902 5.00002C0.899902 7.26439 2.73553 9.10002 4.9999 9.10002V8.10002C3.28782 8.10002 1.8999 6.71211 1.8999 5.00002H0.899902ZM13 8.1C10.2938 8.1 8.1 10.2938 8.1 13H9.1C9.1 10.8461 10.8461 9.1 13 9.1V8.1ZM17.9 13C17.9 10.2938 15.7062 8.1 13 8.1V9.1C15.1539 9.1 16.9 10.8461 16.9 13H17.9ZM13 17.9C15.7062 17.9 17.9 15.7062 17.9 13H16.9C16.9 15.1539 15.1539 16.9 13 16.9V17.9ZM8.1 13C8.1 15.7062 10.2938 17.9 13 17.9V16.9C10.8461 16.9 9.1 15.1539 9.1 13H8.1Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_760_1602">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default NodeIcon
|
|
@ -1,6 +1,7 @@
|
|||
import { Text, XStack } from 'tamagui'
|
||||
import { XStack } from 'tamagui'
|
||||
import Icon from './Icon'
|
||||
import Tag from './Tag'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
const NodesLogo = () => {
|
||||
return (
|
||||
|
@ -11,8 +12,10 @@ const NodesLogo = () => {
|
|||
}}
|
||||
space={'$2'}
|
||||
>
|
||||
<Icon source={'src/assets/nodes-app-icon.png'} width={32} height={32} />
|
||||
<Text style={{ fontWeight: '700', fontSize: '28px' }}>nodes</Text>
|
||||
<Icon src={'src/assets/nodes-app-icon.png'} width={32} height={32} />
|
||||
<Text size={27} weight={'semibold'}>
|
||||
nodes
|
||||
</Text>
|
||||
<Tag text="BETA" />
|
||||
</XStack>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
function PairIcon() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.4517 10.8951L16.952 6.89508L17.2978 6.49996L16.952 6.10484L13.4517 2.10489L12.5486 2.89514L15.7032 6.49997L12.5486 10.1049L13.4517 10.8951ZM6.5487 9.10488L3.0484 13.1049L2.70264 13.5001L3.0484 13.8952L6.5487 17.8951L7.45176 17.1049L4.29722 13.5L7.45176 9.89512L6.5487 9.10488Z"
|
||||
fill="#1B273D"
|
||||
fillOpacity="0.7"
|
||||
/>
|
||||
<rect
|
||||
x="4"
|
||||
y="7.5"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="rotate(-90 4 7.5)"
|
||||
fill="url(#paint0_linear_760_2959)"
|
||||
/>
|
||||
<g mask="url(#mask0_760_2959)">
|
||||
<path d="M4.5 6.50006L16 6.50006" stroke="#1B273D" strokeOpacity="0.7" strokeWidth="1.2" />
|
||||
</g>
|
||||
<rect
|
||||
x="16"
|
||||
y="12.5"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="rotate(90 16 12.5)"
|
||||
fill="url(#paint1_linear_760_2959)"
|
||||
/>
|
||||
<g mask="url(#mask1_760_2959)">
|
||||
<path d="M15.5 13.4999L4 13.4999" stroke="#1B273D" strokeOpacity="0.7" strokeWidth="1.2" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_760_2959"
|
||||
x1="5"
|
||||
y1="8"
|
||||
x2="5"
|
||||
y2="13.5"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#09101C" stopOpacity="0" />
|
||||
<stop offset="1" stopColor="#09101C" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_760_2959"
|
||||
x1="17"
|
||||
y1="13"
|
||||
x2="17"
|
||||
y2="18.5"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#09101C" stopOpacity="0" />
|
||||
<stop offset="1" stopColor="#09101C" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default PairIcon
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import QuickStartBar from './QuickStartBar'
|
||||
|
||||
const meta = {
|
||||
title: 'General/QuickStartBar',
|
||||
component: QuickStartBar,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof QuickStartBar>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import './QuickStartBar.css'
|
||||
|
||||
function QuickStartBar() {
|
||||
return (
|
||||
<nav className="quick-start-bar">
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<g clipPath="url(#clip0_760_8063)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.00008 14.3334C11.4979 14.3334 14.3334 11.4978 14.3334 8.00002C14.3334 4.50222 11.4979 1.66669 8.00008 1.66669C4.50228 1.66669 1.66675 4.50222 1.66675 8.00002C1.66675 11.4978 4.50228 14.3334 8.00008 14.3334ZM8.00008 15.3334C12.0502 15.3334 15.3334 12.0501 15.3334 8.00002C15.3334 3.94993 12.0502 0.666687 8.00008 0.666687C3.94999 0.666687 0.666748 3.94993 0.666748 8.00002C0.666748 12.0501 3.94999 15.3334 8.00008 15.3334Z"
|
||||
fill="#939BA1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.82684 10.7661C4.76188 11.0129 4.98716 11.2382 5.23403 11.1733L9.74792 9.98539C9.86412 9.95481 9.95487 9.86406 9.98545 9.74786L11.1733 5.23397C11.2383 4.9871 11.013 4.76182 10.7661 4.82678L6.25224 6.01465C6.13604 6.04523 6.04529 6.13598 6.01471 6.25218L4.82684 10.7661ZM6.17202 9.58376C6.13304 9.73189 6.26821 9.86706 6.41634 9.82808L8.9293 9.16677C9.0455 9.13619 9.13625 9.04544 9.16683 8.92924L9.82814 6.41628C9.86712 6.26815 9.73195 6.13298 9.58383 6.17196L7.07086 6.83327C6.95466 6.86385 6.86391 6.9546 6.83333 7.0708L6.17202 9.58376Z"
|
||||
fill="#939BA1"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_760_8063">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</span>
|
||||
<div>
|
||||
<ul>
|
||||
<li>Learn More</li>
|
||||
<li>Nodes Community</li>
|
||||
<li>Documentation</li>
|
||||
</ul>
|
||||
<button className="inversed">Quick Start</button>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuickStartBar
|
|
@ -1,31 +0,0 @@
|
|||
import './QuickStartBar.css'
|
||||
|
||||
function QuickStartBar() {
|
||||
return (
|
||||
<nav className='quick-start-bar'>
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<g clip-path="url(#clip0_760_8063)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00008 14.3334C11.4979 14.3334 14.3334 11.4978 14.3334 8.00002C14.3334 4.50222 11.4979 1.66669 8.00008 1.66669C4.50228 1.66669 1.66675 4.50222 1.66675 8.00002C1.66675 11.4978 4.50228 14.3334 8.00008 14.3334ZM8.00008 15.3334C12.0502 15.3334 15.3334 12.0501 15.3334 8.00002C15.3334 3.94993 12.0502 0.666687 8.00008 0.666687C3.94999 0.666687 0.666748 3.94993 0.666748 8.00002C0.666748 12.0501 3.94999 15.3334 8.00008 15.3334Z" fill="#939BA1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.82684 10.7661C4.76188 11.0129 4.98716 11.2382 5.23403 11.1733L9.74792 9.98539C9.86412 9.95481 9.95487 9.86406 9.98545 9.74786L11.1733 5.23397C11.2383 4.9871 11.013 4.76182 10.7661 4.82678L6.25224 6.01465C6.13604 6.04523 6.04529 6.13598 6.01471 6.25218L4.82684 10.7661ZM6.17202 9.58376C6.13304 9.73189 6.26821 9.86706 6.41634 9.82808L8.9293 9.16677C9.0455 9.13619 9.13625 9.04544 9.16683 8.92924L9.82814 6.41628C9.86712 6.26815 9.73195 6.13298 9.58383 6.17196L7.07086 6.83327C6.95466 6.86385 6.86391 6.9546 6.83333 7.0708L6.17202 9.58376Z" fill="#939BA1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_760_8063">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</span>
|
||||
<div>
|
||||
<ul>
|
||||
<li>Learn More</li>
|
||||
<li>Nodes Community</li>
|
||||
<li>Documentation</li>
|
||||
</ul>
|
||||
<button className='inversed'>Quick Start</button>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuickStartBar
|
|
@ -1,16 +0,0 @@
|
|||
import { Button } from 'tamagui'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type ReactButtonProps = {
|
||||
children: string
|
||||
style?: unknown
|
||||
icon?: ReactNode
|
||||
size?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const ReactButton = ({ children, ...props }: ReactButtonProps) => {
|
||||
return <Button {...props}>{children}</Button>
|
||||
}
|
||||
|
||||
export default ReactButton
|
|
@ -1,24 +0,0 @@
|
|||
import { ReactNode } from 'react'
|
||||
import { Stack } from 'tamagui'
|
||||
|
||||
type ShadowBoxProps = {
|
||||
boxStyle?: React.CSSProperties
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const ShadowBox = ({boxStyle, children }: ShadowBoxProps) => {
|
||||
return (
|
||||
<Stack
|
||||
style={{
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0px 4px 20px 0px rgba(9, 16, 28, 0.08)',
|
||||
...boxStyle
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShadowBox
|
|
@ -0,0 +1,37 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import StandardGauge from './StandardGauge'
|
||||
|
||||
const meta = {
|
||||
title: 'General/StandardGauge',
|
||||
component: StandardGauge,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
decorators: [
|
||||
Story => (
|
||||
<div style={{ height: '25vh' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof StandardGauge>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const WithDataPoints: Story = {
|
||||
args: {
|
||||
data: [
|
||||
{ id: '1', color: 'red', label: 'Red', value: 42 },
|
||||
{ id: '2', color: 'blue', label: 'Blue', value: 1337 },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const WithoutDataPoints: Story = {
|
||||
args: {
|
||||
data: [],
|
||||
},
|
||||
}
|
|
@ -1,19 +1,20 @@
|
|||
import { ResponsivePie } from '@nivo/pie'
|
||||
|
||||
interface Data {
|
||||
export interface GaugeDataPoint {
|
||||
id: string
|
||||
label: string
|
||||
value: number
|
||||
color: string
|
||||
}
|
||||
|
||||
interface StandardGaugeProps {
|
||||
data: Data[]
|
||||
data: GaugeDataPoint[]
|
||||
}
|
||||
|
||||
const StandardGauge = ({ data }: StandardGaugeProps) => (
|
||||
<ResponsivePie
|
||||
data={data}
|
||||
margin={{ top: 40, right: 80, bottom: 80, left: 80 }}
|
||||
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
||||
innerRadius={0.65}
|
||||
colors={datum => datum.data.color}
|
||||
fit={false}
|
||||
|
@ -22,6 +23,8 @@ const StandardGauge = ({ data }: StandardGaugeProps) => (
|
|||
arcLinkLabelsColor={{ from: 'color' }}
|
||||
enableArcLabels={false}
|
||||
legends={[]}
|
||||
motionConfig="gentle" // Define transition style
|
||||
animate={false} // Enable animation
|
||||
/>
|
||||
)
|
||||
|
||||
|
|
|
@ -6,13 +6,18 @@ interface DataPoint {
|
|||
|
||||
interface ChartData {
|
||||
id: string
|
||||
color: string
|
||||
data: DataPoint[]
|
||||
maxValue?: number
|
||||
}
|
||||
|
||||
interface StandartLineChartProps {
|
||||
data: ChartData[]
|
||||
}
|
||||
const StandartLineChart = ({ data }: StandartLineChartProps) => {
|
||||
const maxMemory = data[0].maxValue || 'auto'
|
||||
const colors = data.map(dataset => dataset.color)
|
||||
|
||||
return (
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
|
@ -20,8 +25,8 @@ const StandartLineChart = ({ data }: StandartLineChartProps) => {
|
|||
xScale={{ type: 'linear', min: 0, max: data[0].data.length }}
|
||||
yScale={{
|
||||
type: 'linear',
|
||||
min: 'auto',
|
||||
max: 'auto',
|
||||
min: 0,
|
||||
max: maxMemory,
|
||||
stacked: true,
|
||||
reverse: false,
|
||||
}}
|
||||
|
@ -32,14 +37,11 @@ const StandartLineChart = ({ data }: StandartLineChartProps) => {
|
|||
enableGridX={false}
|
||||
enableGridY={false}
|
||||
enablePoints={false}
|
||||
pointSize={1}
|
||||
pointColor={{ theme: 'background' }}
|
||||
pointBorderWidth={2}
|
||||
pointBorderColor={{ from: 'serieColor' }}
|
||||
pointLabelYOffset={-12}
|
||||
useMesh={true}
|
||||
legends={[]}
|
||||
colors={['#8DC6BC']}
|
||||
colors={colors}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { Paragraph, styled } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
const SubTitle = styled(Paragraph, {
|
||||
name: 'SubTitle',
|
||||
accessibilityRole: 'header',
|
||||
size: '$3',
|
||||
})
|
||||
type SubTitleProps = {
|
||||
color?: string
|
||||
children: string
|
||||
}
|
||||
|
||||
const SubTitle = ({ color, children }: SubTitleProps) => {
|
||||
return (
|
||||
<Text size={15} color={color}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubTitle
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { Paragraph, styled } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
const Title = styled(Paragraph, {
|
||||
name: 'Title',
|
||||
accessibilityRole: 'header',
|
||||
size: '$9',
|
||||
})
|
||||
type TitleProps = {
|
||||
color?: string
|
||||
children: string
|
||||
}
|
||||
|
||||
const Title = ({ color, children }: TitleProps) => {
|
||||
return (
|
||||
<Text size={27} weight={'medium'} color={color}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export default Title
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import Titles from './Titles'
|
||||
|
||||
const meta = {
|
||||
title: 'General/Titles',
|
||||
component: Titles,
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof Titles>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const WelcomeTitles: Story = {
|
||||
args: {
|
||||
title: 'Welcome, John. This is your complete access to a truly decentralized Web 3.0',
|
||||
subtitle:
|
||||
'Status Nodes allows you to finally take control and ownership of the services you wish to run in a completely trustless and decentralized manner.',
|
||||
isAdvancedSettings: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const DeviceHealthCheckTitles: Story = {
|
||||
args: {
|
||||
title: 'Device Health Check',
|
||||
subtitle: 'Configure your device to start Staking on Nimbus',
|
||||
isAdvancedSettings: true,
|
||||
},
|
||||
}
|
|
@ -1,32 +1,28 @@
|
|||
import { XStack, YStack } from 'tamagui'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import Icon from './Icon'
|
||||
import Title from './Title'
|
||||
import SubTitle from './SubTitle'
|
||||
import IconButton from './IconButton'
|
||||
|
||||
type TitlesProps = {
|
||||
title: string
|
||||
subtitle: string
|
||||
isAdvancedSettings?: boolean
|
||||
}
|
||||
|
||||
const Titles = ({ title, subtitle }: TitlesProps) => {
|
||||
const Titles = ({ title, subtitle, isAdvancedSettings }: TitlesProps) => {
|
||||
return (
|
||||
<YStack>
|
||||
<XStack justifyContent="space-between">
|
||||
<YStack style={{ width: '100%' }}>
|
||||
<XStack style={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Title color={'#09101C'}>{title}</Title>
|
||||
<IconButton
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #DCE0E5',
|
||||
color: '#09101C',
|
||||
}}
|
||||
size={'$3'}
|
||||
icon={'/icons/reveal.png'}
|
||||
fontSize={'$5'}
|
||||
>
|
||||
Advanced Settings
|
||||
</IconButton>
|
||||
{isAdvancedSettings && (
|
||||
<Button size={32} variant="outline" icon={<Icon src={'/icons/reveal.png'} />}>
|
||||
Advanced Settings
|
||||
</Button>
|
||||
)}
|
||||
</XStack>
|
||||
<SubTitle color={'#09101C'}>{subtitle}</SubTitle>
|
||||
<Text size={15} weight="regular">
|
||||
{subtitle}
|
||||
</Text>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.breadcrumb-bar-nav {
|
||||
width: 100%;
|
||||
flex: 1 1 100%;
|
||||
padding: 1rem 2rem;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
.breadcrumb-bar-ul {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.breadcrumb-bar-li {
|
||||
padding: 0 1em;
|
||||
color: #647084;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
}
|
||||
.breadcrumb-bar-li::after {
|
||||
display: inline-block;
|
||||
content: url("../assets/chevron.svg");
|
||||
color: #09101C;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 100%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.breadcrumb-bar-li:last-child {
|
||||
color: #09101C;
|
||||
|
||||
}
|
||||
.breadcrumb-bar-li:last-child::after {
|
||||
display: none;
|
||||
}
|
|
@ -9,10 +9,11 @@
|
|||
.layout-left {
|
||||
flex: 0 0 55%;
|
||||
max-width: 55%;
|
||||
z-index: 2;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
justify-content: end;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
|
@ -21,13 +22,14 @@
|
|||
max-width: 70%;
|
||||
flex: 1 0 70%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* flex-wrap: wrap; */
|
||||
flex-direction: column;
|
||||
}
|
||||
header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
header > div {
|
||||
display: flex;
|
||||
|
@ -49,8 +51,12 @@ header > div {
|
|||
padding: 4px 6px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.content .title {
|
||||
font-size: 27px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
.content .subtitle {
|
||||
|
@ -75,6 +81,7 @@ header > div {
|
|||
.layout-right {
|
||||
flex: 0 0 45%;
|
||||
max-width: 45%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
|
@ -105,4 +112,4 @@ header > div {
|
|||
/* height: 100%; */
|
||||
height: 140%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
|
@ -14,6 +14,10 @@
|
|||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
#storybook-root {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
|
@ -33,8 +37,8 @@ h1 {
|
|||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
|
|
|
@ -6,5 +6,5 @@ import './index.css'
|
|||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeviceHealthCheck from './DeviceHealthCheck'
|
||||
|
||||
const meta = {
|
||||
title: 'Pages/DeviceHealthCheck',
|
||||
component: DeviceHealthCheck,
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DeviceHealthCheck>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Page: Story = {
|
||||
args: {},
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
import LayoutComponent from '../../components/LayoutComponent'
|
||||
import NimbusLogo from '../../components/NimbusLogo'
|
||||
import Titles from '../../components/Titles'
|
||||
import DeviceStorageHealth from '../../components/DeviceStorageHealth'
|
||||
import DeviceCPULoad from '../../components/DeviceCPULoad'
|
||||
import HealthInfoSection from '../../components/HealthInfoSection'
|
||||
import { Button, InformationBox } from '@status-im/components'
|
||||
import Icon from '../../components/Icon'
|
||||
import DeviceMemory from '../../components/DeviceMemoryHealth'
|
||||
import DeviceNetworkHealth from '../../components/DeviceNetworkHealth'
|
||||
|
||||
const DeviceHealthCheck = () => {
|
||||
return (
|
||||
<LayoutComponent
|
||||
content={<DeviceHealthCheckContent />}
|
||||
rightImageSrc="/background-images/eye-background.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeviceHealthCheck
|
||||
|
||||
const DeviceHealthCheckContent = () => {
|
||||
return (
|
||||
<div className="container-inner landing-page">
|
||||
<YStack
|
||||
space={'$4'}
|
||||
style={{
|
||||
justifyContent: 'end',
|
||||
alignItems: 'start',
|
||||
marginBottom: '2rem',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
>
|
||||
<NimbusLogo />
|
||||
<Titles
|
||||
title="Device Health Check"
|
||||
subtitle="Configure your device to start Staking on Nimbus"
|
||||
isAdvancedSettings={true}
|
||||
/>
|
||||
<XStack space={'$4'}>
|
||||
<DeviceStorageHealth storage={44} maxStorage={30} />
|
||||
<DeviceCPULoad load={[12, 123, 4, 90]} />
|
||||
</XStack>
|
||||
<XStack space={'$4'}>
|
||||
<DeviceMemory currentMemory={[25, 31, 5, 14, 20, 81]} maxMemory={38} />
|
||||
<DeviceNetworkHealth uploadRate={[1, 4, 23, 55]} downloadRate={[20, 3, 40, 56]} />
|
||||
</XStack>
|
||||
<HealthInfoSection
|
||||
usedStorage={120}
|
||||
maxStorage={160}
|
||||
usedRamMemory={8}
|
||||
maxRamMemory={16}
|
||||
cpuClockRate={2.5}
|
||||
networkLatency={75}
|
||||
/>
|
||||
<InformationBox
|
||||
icon={<Icon src="/icons/close.png" width={11} height={11} />}
|
||||
message="The information provided in the Nodes Health Check is meant to utilized as a guide to guage the readiness of your device, however please do your own due diligence prior to commiting any funds. Read our Health Check Disclosure for more information."
|
||||
/>
|
||||
<Stack style={{ marginTop: '1rem' }}>
|
||||
<Button>Continue</Button>
|
||||
</Stack>
|
||||
</YStack>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Button } from './Button';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
const meta = {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
import './button.css'
|
||||
|
||||
interface ButtonProps {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary?: boolean
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor?: string
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({
|
||||
primary = false,
|
||||
size = 'medium',
|
||||
backgroundColor,
|
||||
label,
|
||||
...props
|
||||
}: ButtonProps) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
||||
style={{ backgroundColor }}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -1,364 +0,0 @@
|
|||
import { Meta } from "@storybook/blocks";
|
||||
|
||||
import Github from "./assets/github.svg";
|
||||
import Discord from "./assets/discord.svg";
|
||||
import Youtube from "./assets/youtube.svg";
|
||||
import Tutorials from "./assets/tutorials.svg";
|
||||
import Styling from "./assets/styling.png";
|
||||
import Context from "./assets/context.png";
|
||||
import Assets from "./assets/assets.png";
|
||||
import Docs from "./assets/docs.png";
|
||||
import Share from "./assets/share.png";
|
||||
import FigmaPlugin from "./assets/figma-plugin.png";
|
||||
import Testing from "./assets/testing.png";
|
||||
import Accessibility from "./assets/accessibility.png";
|
||||
import Theming from "./assets/theming.png";
|
||||
import AddonLibrary from "./assets/addon-library.png";
|
||||
|
||||
export const RightArrow = () => <svg
|
||||
viewBox="0 0 14 14"
|
||||
width="8px"
|
||||
height="14px"
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
display: 'inline-block',
|
||||
shapeRendering: 'inherit',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor',
|
||||
'path fill': 'currentColor'
|
||||
}}
|
||||
>
|
||||
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
|
||||
</svg>
|
||||
|
||||
<Meta title="Configure your project" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Configure your project
|
||||
|
||||
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
|
||||
</div>
|
||||
<div className="sb-section">
|
||||
<div className="sb-section-item">
|
||||
<img
|
||||
src={Styling}
|
||||
alt="A wall of logos representing different styling technologies"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
|
||||
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/styling-and-css"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img
|
||||
src={Context}
|
||||
alt="An abstraction representing the composition of data for a component"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
|
||||
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-stories/decorators#context-for-mocking"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Assets} alt="A representation of typography and image assets" />
|
||||
<div>
|
||||
<h4 className="sb-section-item-heading">Load assets and resources</h4>
|
||||
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
|
||||
`staticDirs` configuration option to specify folders to load when
|
||||
starting Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/images-and-assets"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Do more with Storybook
|
||||
|
||||
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
|
||||
</div>
|
||||
|
||||
<div className="sb-section">
|
||||
<div className="sb-features-grid">
|
||||
<div className="sb-grid-item">
|
||||
<img src={Docs} alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated" />
|
||||
<h4 className="sb-section-item-heading">Autodocs</h4>
|
||||
<p className="sb-section-item-paragraph">Auto-generate living,
|
||||
interactive reference documentation from your components and stories.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-docs/autodocs"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Share} alt="A browser window showing a Storybook being published to a chromatic.com URL" />
|
||||
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
|
||||
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/sharing/publish-storybook#publish-storybook-with-chromatic"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={FigmaPlugin} alt="Windows showing the Storybook plugin in Figma" />
|
||||
<h4 className="sb-section-item-heading">Figma Plugin</h4>
|
||||
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
|
||||
implementation in one place.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/sharing/design-integrations#embed-storybook-in-figma-with-the-plugin"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Testing} alt="Screenshot of tests passing and failing" />
|
||||
<h4 className="sb-section-item-heading">Testing</h4>
|
||||
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
|
||||
complex.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-tests/introduction"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Accessibility} alt="Screenshot of accessibility tests passing and failing" />
|
||||
<h4 className="sb-section-item-heading">Accessibility</h4>
|
||||
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-tests/accessibility-testing"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Theming} alt="Screenshot of Storybook in light and dark mode" />
|
||||
<h4 className="sb-section-item-heading">Theming</h4>
|
||||
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/theming"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='sb-addon'>
|
||||
<div className='sb-addon-text'>
|
||||
<h4>Addons</h4>
|
||||
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/integrations/"
|
||||
target="_blank"
|
||||
>Discover all addons<RightArrow /></a>
|
||||
</div>
|
||||
<div className='sb-addon-img'>
|
||||
<img src={AddonLibrary} alt="Integrate your tools with Storybook to connect workflows." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sb-section sb-socials">
|
||||
<div className="sb-section-item">
|
||||
<img src={Github} alt="Github logo" className="sb-explore-image"/>
|
||||
Join our contributors building the future of UI development.
|
||||
|
||||
<a
|
||||
href="https://github.com/storybookjs/storybook"
|
||||
target="_blank"
|
||||
>Star on GitHub<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Discord} alt="Discord logo" className="sb-explore-image"/>
|
||||
<div>
|
||||
Get support and chat with frontend developers.
|
||||
|
||||
<a
|
||||
href="https://discord.gg/storybook"
|
||||
target="_blank"
|
||||
>Join Discord server<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Youtube} alt="Youtube logo" className="sb-explore-image"/>
|
||||
<div>
|
||||
Watch tutorials, feature previews and interviews.
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@chromaticui"
|
||||
target="_blank"
|
||||
>Watch on YouTube<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Tutorials} alt="A book" className="sb-explore-image"/>
|
||||
<p>Follow guided walkthroughs on for key workflows.</p>
|
||||
|
||||
<a
|
||||
href="https://storybook.js.org/tutorials/"
|
||||
target="_blank"
|
||||
>Discover tutorials<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.sb-section a:not(h1 a, h2 a, h3 a) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sb-section-item, .sb-grid-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-section-item-heading {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.sb-section-item-paragraph {
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-chevron {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px 20px;
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-explore-image {
|
||||
max-height: 32px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background-color: #EEF3F8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: #EEF3F8;
|
||||
height: 180px;
|
||||
margin-bottom: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 48px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.sb-addon-text h4 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
position: absolute;
|
||||
left: 345px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 650px;
|
||||
transform: rotate(-15deg);
|
||||
margin-left: 40px;
|
||||
margin-top: -72px;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.sb-addon-img {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.sb-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
height: 280px;
|
||||
align-items: flex-start;
|
||||
padding-top: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 130px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
width: 124%;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 1200px;
|
||||
transform: rotate(-12deg);
|
||||
margin-left: 0;
|
||||
margin-top: 48px;
|
||||
margin-bottom: -40px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
|
@ -1,27 +0,0 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Header } from './Header';
|
||||
|
||||
const meta = {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} satisfies Meta<typeof Header>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const LoggedIn: Story = {
|
||||
args: {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut: Story = {};
|
|
@ -1,54 +0,0 @@
|
|||
import { Button } from './Button'
|
||||
import './header.css'
|
||||
|
||||
type User = {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
user?: User
|
||||
onLogin: () => void
|
||||
onLogout: () => void
|
||||
onCreateAccount: () => void
|
||||
}
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
|
||||
<header>
|
||||
<div className="storybook-header">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
|
@ -1,29 +0,0 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { Page } from './Page';
|
||||
|
||||
const meta = {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} satisfies Meta<typeof Page>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const LoggedOut: Story = {};
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
|
||||
export const LoggedIn: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = await canvas.getByRole('button', {
|
||||
name: /Log in/i,
|
||||
});
|
||||
await userEvent.click(loginButton);
|
||||
},
|
||||
};
|
|
@ -1,73 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const Page: React.FC = () => {
|
||||
const [user, setUser] = React.useState<User>();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
|
||||
<section className="storybook-page">
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page
|
||||
data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
.storybook-button {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 700;
|
||||
border: 0;
|
||||
border-radius: 3em;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
.storybook-button--primary {
|
||||
color: white;
|
||||
background-color: #1ea7fd;
|
||||
}
|
||||
.storybook-button--secondary {
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
||||
}
|
||||
.storybook-button--small {
|
||||
font-size: 12px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.storybook-button--medium {
|
||||
font-size: 14px;
|
||||
padding: 11px 20px;
|
||||
}
|
||||
.storybook-button--large {
|
||||
font-size: 16px;
|
||||
padding: 12px 24px;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
.storybook-header {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.storybook-header svg {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-header h1 {
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
margin: 6px 0 6px 10px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-header button + button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.storybook-header .welcome {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
.storybook-page {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding: 48px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.storybook-page h2 {
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
margin: 0 0 4px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-page p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.storybook-page a {
|
||||
text-decoration: none;
|
||||
color: #1ea7fd;
|
||||
}
|
||||
|
||||
.storybook-page ul {
|
||||
padding-left: 30px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.storybook-page li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storybook-page .tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #e7fdd8;
|
||||
color: #66bf3c;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper svg {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.storybook-page .tip-wrapper svg path {
|
||||
fill: #1ea7fd;
|
||||
}
|
Loading…
Reference in New Issue