diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
index db6fddd..3896ed8 100644
--- a/src/components/Button/Button.stories.tsx
+++ b/src/components/Button/Button.stories.tsx
@@ -1,20 +1,61 @@
import React from 'react'
import {Story, Meta} from '@storybook/react'
-
+import {Icon} from '../Icons/Icon'
import {Button} from './Button'
export default {
title: 'Components/Button',
component: Button,
argTypes: {
- backgroundColor: {control: 'color'}
+ style: {
+ defaultValue: 'gray',
+ options: ['gray', 'primary', 'outline', 'ghost', 'link'],
+ control: {type: 'select'}
+ },
+ size: {
+ defaultValue: 'md',
+ options: ['xs', 'sm', 'md', 'lg', 'responsive'],
+ control: {type: 'select'}
+ },
+ status: {
+ defaultValue: 'normal',
+ options: ['normal', 'disabled'],
+ control: {type: 'radio'}
+ },
+ outline: {
+ defaultValue: 'square',
+ options: ['square', 'round'],
+ control: {type: 'radio'}
+ }
}
} as Meta
-const Template: Story = args =>
+const RandomIcon = ({className}) => (
+
+
+
+)
-export const Primary = Template.bind({})
-Primary.args = {
- variant: 'primary',
- children: 'Button'
+const Template: Story = args => {
+ return (
+ <>
+
+
+ Button
+
+
+ >
+ )
}
+
+export const Buttons = Template.bind({})
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 2ccd0cb..419aa9d 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -1,8 +1,74 @@
import React from 'react'
import cn from 'classnames'
-import type {ComponentProps} from 'react'
-// minor example
-export function Button(props: ComponentProps<'button'>) {
- return
+type ButtonStyles = 'gray' | 'primary' | 'outline' | 'ghost' | 'link'
+type ButtonSizes = 'xs' | 'sm' | 'md' | 'lg' | 'responsive'
+type ButtonStatus = 'normal' | 'disabled'
+type ButtonOutline = 'round' | 'square'
+
+const buttonSizes = {
+ xs: 'p-2 text-xs',
+ sm: 'p-2 text-sm',
+ md: 'py-2 px-3 text-base',
+ lg: 'py-3 px-4 text-lg'
+}
+
+export function Button({
+ style = 'gray',
+ size = 'md',
+ status = 'normal',
+ outline = 'square',
+ disabled,
+ className,
+ ...props
+}: {
+ style: ButtonStyles
+ size: ButtonSizes
+ status: ButtonStatus
+ outline: ButtonOutline
+ className: string
+ disabled: boolean
+}) {
+ const buttonSize = buttonSizes[size]
+ const fontWeight = 'font-bold'
+ const linkHover = style === 'link' ? 'inline-block hover:underline cursor-pointer' : ''
+ const radius = outline === 'square' ? 'rounded' : 'rounded-full'
+ const textColor = 'text-primary-600'
+ const backgroundColor = 'bg-transparent'
+ const borderColor = 'border-transparent'
+
+ let hover = 'hover:from-gradient-white'
+ let buttonClasses = `${textColor} ${backgroundColor} ${borderColor}`
+
+ if (style === 'gray') {
+ buttonClasses = 'text-primary-200 bg-gray-800 bg-gradient-to-r'
+ } else if (style === 'primary') {
+ buttonClasses = 'text-white bg-primary-500 bg-gradient-to-r border-primary-600'
+ } else if (style === 'outline') {
+ const border = size === 'md' || size === 'lg' ? 'border-2' : 'border'
+ buttonClasses = `${textColor} hover:text-white border-primary-500 hover:bg-primary-500 ${border}`
+ } else if (style === 'ghost') {
+ hover += ' hover:bg-primary-100'
+ }
+
+ const classes = cn(
+ 'flex items-center text-center space-x-2',
+ fontWeight,
+ buttonSize,
+ textColor,
+ borderColor,
+ backgroundColor,
+ linkHover,
+ radius,
+ hover,
+ buttonClasses,
+ disabled && 'opacity-40 cursor-not-allowed',
+ className
+ )
+
+ if (style === 'link') {
+ return
+ }
+
+ return
}
diff --git a/src/components/ProgressCircle/ProgressCircle.tsx b/src/components/ProgressCircle/ProgressCircle.tsx
new file mode 100644
index 0000000..9fc61be
--- /dev/null
+++ b/src/components/ProgressCircle/ProgressCircle.tsx
@@ -0,0 +1,65 @@
+const cleanPercentage = percentage => {
+ const isNegativeOrNaN = !Number.isFinite(+percentage) || percentage < 0 // we can set non-numbers to 0 here
+ const isTooHigh = percentage > 100
+ return isNegativeOrNaN ? 0 : isTooHigh ? 100 : +percentage
+}
+
+const Circle = ({color, percentage, stroke}) => {
+ const r = 50
+ const circ = 2 * Math.PI * r
+ const strokePct = ((100 - percentage) * circ) / 100 // where stroke will start, e.g. from 15% to 100%.
+ const circleColor = stroke ? stroke : color
+ return (
+
+ )
+}
+
+const Percent = ({percentage}) => {
+ return (
+
+ {percentage.toFixed(0)}%
+
+ )
+}
+
+export const ProgressCircle = percentage => {
+ const pct = cleanPercentage(percentage)
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/Sidebar/Sidebar.stories.tsx b/src/components/Sidebar/Sidebar.stories.tsx
new file mode 100644
index 0000000..081e03b
--- /dev/null
+++ b/src/components/Sidebar/Sidebar.stories.tsx
@@ -0,0 +1,115 @@
+import React from 'react'
+import {Story, Meta} from '@storybook/react'
+import {Sidebar} from './Sidebar'
+
+export default {
+ title: 'Components/Sidebar',
+ component: Sidebar,
+ argTypes: {
+ // style: {
+ // defaultValue: 'gray',
+ // options: ['gray', 'primary', 'outline', 'ghost', 'link'],
+ // control: {type: 'select'}
+ // },
+ // size: {
+ // defaultValue: 'md',
+ // options: ['xs', 'sm', 'md', 'lg', 'responsive'],
+ // control: {type: 'select'}
+ // },
+ // status: {
+ // defaultValue: 'normal',
+ // options: ['normal', 'disabled'],
+ // control: {type: 'radio'}
+ // },
+ // outline: {
+ // defaultValue: 'square',
+ // options: ['square', 'round'],
+ // control: {type: 'radio'}
+ // }
+ }
+} as Meta
+
+const smileyIcon = () => (
+
+
+
+)
+
+const smileyIcon24 = () => (
+
+
+
+)
+
+const simpleLinks = Array.from({length: 3}, () => ({
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ disabled: false
+}))
+
+const iconLinks = Array.from({length: 3}, () => ({
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ icon: smileyIcon
+}))
+
+const numberLinks = Array.from([1, 2, 3], n => ({
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ number: n
+}))
+
+const iconLinks24 = Array.from({length: 3}, () => ({
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ icon: smileyIcon24
+}))
+
+const avatarLinks = Array.from({length: 3}, () => ({
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ avatar:
+ 'https://avataaars.io/?avatarStyle=Circle&topType=LongHairStraight&accessoriesType=Blank&hairColor=BrownDark&facialHairType=Blank&clotheType=BlazerShirt&eyeType=Default&eyebrowType=Default&mouthType=Default&skinColor=Light'
+}))
+
+const prereqs = [
+ {
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ disabled: false
+ },
+ {
+ title: 'Text Here',
+ target: 'https://openmined.org',
+ disabled: true
+ }
+]
+
+const Template: Story = args => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const main = Template.bind({})
diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx
new file mode 100644
index 0000000..d021669
--- /dev/null
+++ b/src/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,120 @@
+import React from 'react'
+import cn from 'classnames'
+import {H5, Text} from '../Typography/Text'
+import {Button} from '../Button/Button'
+import {Icon} from '../Icons/Icon'
+import {Avatar} from '../Avatar/Avatar'
+import {ProgressCircle} from '../ProgressCircle/ProgressCircle'
+
+const messageIcon = () => (
+
+
+
+)
+
+export function Sidebar({
+ heading = '',
+ hasMessage = false,
+ borderLeft = false,
+ progressPct,
+ links,
+ prereqs,
+ className,
+
+ ...props
+}: {
+ heading: string
+ hasMessage: boolean
+ borderLeft: boolean
+ progressPct: number
+ icon: React.ReactType
+ links: []
+ prereqs: []
+
+ className: string
+ disabled: boolean
+}) {
+ const numberClasses = 'rounded-full bg-gray-800 text-gray-50 text-sm w-5 h-5 leading-snug'
+ const containerClasses = `sm:w-1/3 lg:w-1/4 ${borderLeft ? 'border-l-2 pl-6' : ''}`
+
+ const userMessage = () => {
+ return (
+ <>
+
+
+
+
+
+ Not seeing an answer to your specific question? Go to our{' '}
+
+ discussion boad
+ {' '}
+ to get extra assistance.
+
+ >
+ )
+ }
+
+ const mapPrerequisites = prereqs => {
+ return prereqs.map(prereq => (
+
+
+ {prereq.title}
+
+
+ ))
+ }
+
+ const mapLinks = links => {
+ return links.map(link => (
+
+
+ {link.avatar && }
+ {link.number && {link.number} }
+ {link.icon && }
+
+
+ {link.title}
+
+ {link.subtitle && (
+
+ {link.subtitle}
+
+ )}
+
+
+
+ ))
+ }
+
+ return (
+
+ {heading && (
+ <>
+
{heading}
+
+ >
+ )}
+
{links && mapLinks(links)}
+ {hasMessage && userMessage()}
+ {prereqs && (
+
+
+
+ Pre-Requisites
+
+
+ )}
+
{prereqs && mapPrerequisites(prereqs)}
+ {progressPct && (
+ <>
+
+ {ProgressCircle(progressPct)}
+ >
+ )}
+
+ )
+}
diff --git a/src/components/Typography/Text.tsx b/src/components/Typography/Text.tsx
index 06cd96c..facdaa2 100644
--- a/src/components/Typography/Text.tsx
+++ b/src/components/Typography/Text.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import cn from 'classnames'
import type {PropsWithChildren} from 'react'
-type TextComponent = 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
+type TextComponent = 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'a'
type TextSizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl'
type FontWeight = 'font-medium' | 'font-normal' | 'font-black' | 'font-bold'
diff --git a/tailwind.config.js b/tailwind.config.js
index 2849da3..cf297e9 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -48,6 +48,10 @@ module.exports = {
},
dropShadow: {
'button-hover': '-4px 4px 8px --color-primary-500'
+ },
+ strokeWidth: {
+ 5: '5',
+ 6: '6'
}
}
},