diff --git a/.eslintrc b/.eslintrc
index 2500d3fa..2b0943a0 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -25,6 +25,7 @@
"react/jsx-handler-names": 0,
"react/jsx-fragments": 0,
"react/no-unused-prop-types": 0,
- "import/export": 0
+ "import/export": 0,
+ "semi": [1, "always"] // 1 is for warning
}
}
diff --git a/.github/workflows/run-jest-tests.yml b/.github/workflows/run-jest-tests.yml
new file mode 100644
index 00000000..d67b612f
--- /dev/null
+++ b/.github/workflows/run-jest-tests.yml
@@ -0,0 +1,22 @@
+name: "Run Jest Tests"
+
+# Trigger the workflow on merge of pull request or direct push,
+# but only for the main branch
+on:
+ pull_request :
+ types: [ opened, edited, synchronize, reopened]
+
+jobs:
+ tests:
+ name: 'Run Tests'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ - name: Set Node Version
+ uses: actions/setup-node@v1
+ with:
+ node-version: 15.x
+ - run: npm ci
+ - run: npm run test
+ - run: npm run test:coverage
diff --git a/.storybook/stories/Paragraph.stories.js b/.storybook/stories/Paragraph.stories.js
index 48233d1a..84e541ff 100644
--- a/.storybook/stories/Paragraph.stories.js
+++ b/.storybook/stories/Paragraph.stories.js
@@ -103,6 +103,12 @@ Intro.args = {
export const Big = Template.bind({})
Big.args = {
- ...Short.args,
+ ...Default.args,
className: 'su-big-paragraph'
}
+
+export const Card = Template.bind({})
+Card.args = {
+ ...Default.args,
+ className: 'su-card-paragraph'
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..1b9f2005
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,17 @@
+module.exports = {
+ "verbose": true,
+ "testPathIgnorePatterns": [
+ "/node_modules/",
+ "/docs/"
+ ],
+ "testRegex": "(/test/.*|\\.(test|spec))\\.(js|jsx)$",
+ "moduleFileExtensions": [
+ "js",
+ "jsx",
+ "json"
+ ],
+ "moduleDirectories": [
+ "node_modules",
+ "src"
+ ]
+}
diff --git a/netlify.toml b/netlify.toml
index c8b2e010..ecfb9716 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -19,4 +19,4 @@
X-Content-Type-Options = "nosniff"
Referrer-Policy = "origin-when-cross-origin"
Strict-Transport-Security = "max-age=2592000"
- Permissions-Policy = "vibrate=(), geolocation=(), midi=(), notifications=(), push=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), speaker=(), vibrate=(), fullscreen=()"
+ Permissions-Policy = "vibrate=(), geolocation=(), midi=(), notifications=(), push=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), speaker=()"
diff --git a/package-lock.json b/package-lock.json
index a224ffce..be0d108c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1982,6 +1982,7 @@
"jest-resolve": "^26.6.2",
"jest-util": "^26.6.2",
"jest-worker": "^26.6.2",
+ "node-notifier": "^8.0.0",
"slash": "^3.0.0",
"source-map": "^0.6.0",
"string-length": "^4.0.1",
@@ -6541,6 +6542,7 @@
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
+ "fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@@ -6782,6 +6784,7 @@
"integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
"dev": true,
"dependencies": {
+ "colors": "^1.1.2",
"object-assign": "^4.1.0",
"string-width": "^4.2.0"
},
@@ -8342,7 +8345,8 @@
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
- "optionator": "^0.8.1"
+ "optionator": "^0.8.1",
+ "source-map": "~0.6.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
@@ -9776,6 +9780,9 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.6"
+ },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -12822,6 +12829,7 @@
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
+ "fsevents": "^2.1.2",
"graceful-fs": "^4.2.4",
"jest-regex-util": "^26.0.0",
"jest-serializer": "^26.6.2",
@@ -14107,6 +14115,7 @@
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
+ "graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"optionalDependencies": {
@@ -14164,6 +14173,9 @@
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
"integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
"dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.9"
+ },
"optionalDependencies": {
"graceful-fs": "^4.1.9"
}
@@ -16342,6 +16354,9 @@
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"dev": true,
+ "dependencies": {
+ "clipboard": "^2.0.0"
+ },
"optionalDependencies": {
"clipboard": "^2.0.0"
}
@@ -19304,11 +19319,6 @@
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
- "bin": {
- "sshpk-conv": "bin/sshpk-conv",
- "sshpk-sign": "bin/sshpk-sign",
- "sshpk-verify": "bin/sshpk-verify"
- },
"engines": {
"node": ">=0.10.0"
}
@@ -21230,8 +21240,10 @@
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"dev": true,
"dependencies": {
+ "chokidar": "^3.4.1",
"graceful-fs": "^4.1.2",
- "neo-async": "^2.5.0"
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.1"
},
"optionalDependencies": {
"chokidar": "^3.4.1",
@@ -21328,6 +21340,7 @@
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
+ "fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
diff --git a/package.json b/package.json
index 310e4729..cc730487 100644
--- a/package.json
+++ b/package.json
@@ -13,13 +13,12 @@
"prebuild": "npm run test:gen",
"build": "build-storybook",
"lint": "eslint .",
- "test": "jest",
+ "test": "jest --no-cache",
"test:watch": "jest --watch",
- "test:coverage": "jest --coverage",
- "test:gen": "jest --json --outputFile=./.storybook/.jest-test-results.json",
+ "test:coverage": "jest --coverage --no-cache",
+ "test:gen": "jest --no-cache --json --outputFile=./.storybook/.jest-test-results.json",
"predev": "npm run test:gen",
- "dev": "start-storybook -p 6006",
- "storybook": "npm run dev"
+ "dev": "start-storybook -p 6006"
},
"repository": {
"type": "git",
@@ -74,17 +73,5 @@
"storybook-addon-designs": "^5.4.3",
"storybook-addon-pseudo-states": "^1.0.0-rc.3",
"storybook-dark-mode": "^1.0.4"
- },
- "jest": {
- "testPathIgnorePatterns": [
- "/node_modules/",
- "/docs/"
- ],
- "testRegex": "(/test/.*|\\.(test|spec))\\.(js|jsx)$",
- "moduleFileExtensions": [
- "js",
- "jsx",
- "json"
- ]
}
}
diff --git a/src/Alert/Alert.js b/src/Alert/Alert.js
index a413ef19..badb9d6a 100644
--- a/src/Alert/Alert.js
+++ b/src/Alert/Alert.js
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import propTypes from 'prop-types'
+import PropTypes from 'prop-types'
import { alertTypes, lightText, darkText } from './Alert.levers'
import { Button } from '../Button/Button'
import Icon from 'react-hero-icon'
@@ -14,7 +14,7 @@ import Icon from 'react-hero-icon'
*/
export const Alert = ({ classes = {}, children, ref, ...props }) => {
// Defaults & Variables
- const classnames = require('classnames')
+ const classnames = require('classnames/dedupe')
const levers = {}
const iconProps = { height: 24, width: 24 }
const [isDismissed, setDismissed] = useState(false)
@@ -84,12 +84,14 @@ export const Alert = ({ classes = {}, children, ref, ...props }) => {
const DefaultDismiss = (
@@ -169,78 +171,82 @@ export const Alert = ({ classes = {}, children, ref, ...props }) => {
Alert.propTypes = {
// Nodes and content.
- children: propTypes.oneOfType([
- propTypes.node,
- propTypes.element,
- propTypes.string
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.element,
+ PropTypes.string
+ ]),
+ dismissBtn: PropTypes.element,
+ icon: PropTypes.element,
+ label: PropTypes.string,
+ heading: PropTypes.string,
+ footer: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.element,
+ PropTypes.string
]),
- dismissBtn: propTypes.element,
- icon: propTypes.element,
- label: propTypes.string,
- heading: propTypes.string,
- footer: propTypes.node,
// State and Levers.
- type: propTypes.oneOf(alertTypes),
- isLargeIcon: propTypes.bool,
- isLabelTop: propTypes.bool,
- isIconTop: propTypes.bool,
- hasDismiss: propTypes.bool,
- hasIcon: propTypes.bool,
- hasLabel: propTypes.bool,
+ type: PropTypes.oneOf(alertTypes),
+ isLargeIcon: PropTypes.bool,
+ isLabelTop: PropTypes.bool,
+ isIconTop: PropTypes.bool,
+ hasDismiss: PropTypes.bool,
+ hasIcon: PropTypes.bool,
+ hasLabel: PropTypes.bool,
// The CSS Classname property
- classes: propTypes.shape(
+ classes: PropTypes.shape(
{
- wrapper: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ wrapper: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- container: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ container: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- dismissWrapper: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ dismissWrapper: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- headerWrapper: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ headerWrapper: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- headerIcon: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ headerIcon: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- label: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- bodyWrapper: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ bodyWrapper: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- bodyHeading: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ bodyHeading: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- body: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ body: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
]),
- footerWrapper: propTypes.oneOfType([
- propTypes.string,
- propTypes.object,
- propTypes.array
+ footerWrapper: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.array
])
}
)
diff --git a/src/Alert/Alert.stories.js b/src/Alert/Alert.stories.js
index fdaec28d..dfc329fd 100644
--- a/src/Alert/Alert.stories.js
+++ b/src/Alert/Alert.stories.js
@@ -20,7 +20,10 @@ export default {
description: {
component: 'For displaying a notification that keeps people informed of a status, or for displaying a validation message that alerts someone of an important piece of information.'
}
- }
+ },
+ jest: [
+ 'Alert.test.js'
+ ]
},
argTypes: {
type: {
@@ -45,17 +48,12 @@ const AlertTemplate = ({ children, ...rest }) => {
// /////////////////////////////////////////////////////////////////////////////
export const Default = AlertTemplate.bind({})
Default.args = {
- children: textMixed,
- heading: 'Alert Lorem Ipsum'
-}
-Default.parameters = {
- jest: ['Alert.test.js']
+ children: textMixed
}
export const Info = AlertTemplate.bind({})
Info.args = {
children: textMixed,
- heading: 'Alert Lorem Ipsum',
type: 'info'
}
@@ -71,8 +69,8 @@ Info.parameters = {
export const Error = AlertTemplate.bind({})
Error.args = {
children: textMixed,
- heading: 'Alert Lorem Ipsum',
- type: 'error'
+ type: 'error',
+ label: 'error'
}
Error.parameters = {
docs: {
@@ -85,8 +83,8 @@ Error.parameters = {
export const Warning = AlertTemplate.bind({})
Warning.args = {
children: textMixed,
- heading: 'Alert Lorem Ipsum',
- type: 'warning'
+ type: 'warning',
+ label: 'warning'
}
Warning.parameters = {
docs: {
@@ -99,8 +97,8 @@ Warning.parameters = {
export const Success = AlertTemplate.bind({})
Success.args = {
children: textMixed,
- heading: 'Alert Lorem Ipsum',
- type: 'success'
+ type: 'success',
+ label: 'success'
}
Success.parameters = {
docs: {
@@ -113,7 +111,6 @@ Success.parameters = {
export const LabelsOnTop = AlertTemplate.bind({})
LabelsOnTop.args = {
children: textMixed,
- heading: 'Alert Lorem Ipsum',
isIconTop: true,
isLabelTop: true
}
@@ -135,8 +132,9 @@ BigIcon.args = {
}
BigIcon.storyName = 'Big Icon + No Label'
-export const NoHeader = AlertTemplate.bind({})
-NoHeader.args = {
+export const WithHeader = AlertTemplate.bind({})
+WithHeader.args = {
+ heading: 'Alert Lorem Ipsum',
children: textMixed,
isLabelTop: true,
isLargeIcon: true
diff --git a/src/BrandBar/index.js b/src/BrandBar/index.js
deleted file mode 100644
index dc604d56..00000000
--- a/src/BrandBar/index.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-
-export const BrandBar = (props) => {
-
- // Defaults.
- const defaultClasses = {
- wrapper: 'su-brand-bar su-bg-cardinal-red',
- container: 'su-cc',
- link: 'su-logo su-text-white hover:su-text-white focus:su-text-white',
- }
-
- // Variant Sets.
- const variants = {
- bright: {
- wrapper: 'su-brand-bar su-bg-digital-red'
- },
- dark: {
- wrapper: 'su-brand-bar su-bg-black'
- },
- white: {
- wrapper: 'su-brand-bar su-bg-white su-text-black',
- link: 'su-logo su-text-black hover:su-text-black focus:su-text-black'
- }
- }
-
- // Merge with passed in props.
- let classes = Object.assign(defaultClasses, props.classes);
- classes = Object.assign(classes, variants[props.variant])
-
- return (
-
- )
-}
diff --git a/src/BrandBar/index.test.js b/src/BrandBar/index.test.js
deleted file mode 100644
index 679880d3..00000000
--- a/src/BrandBar/index.test.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { BrandBar } from '.'
-
-describe('BrandBar', () => {
- it('is truthy', () => {
- expect(BrandBar).toBeTruthy()
- })
-})
diff --git a/src/Button/Button.js b/src/Button/Button.js
index dee210a6..f7d11719 100644
--- a/src/Button/Button.js
+++ b/src/Button/Button.js
@@ -1,54 +1,108 @@
-import React from 'react'
-import propTypes from 'prop-types'
+import React from 'react';
+import PropTypes from 'prop-types';
+import { buttonVariants, buttonSizes, buttonTypes } from './Button.levers';
/**
- * Primary UI component for user interaction
+ * Button Component
+ *
+ * HTML button element
*/
-export const Button = ({ className, children, onClick, type, ref, ...props }) => {
+export const Button = ({ className, children, onClick, ref, variant, size, type, isDisabled, ...props }) => {
// Defaults & Variables.
// ---------------------------------------------------------------------------
- const classnames = require('classnames')
- const levers = {}
+ const classnames = require('classnames/dedupe');
+ const levers = {};
// Levers
// ---------------------------------------------------------------------------
+ // Props.variant
+ if (variant && buttonVariants.includes(variant)) {
+ switch (variant) {
+ case 'primary':
+ levers.variant = classnames('su-bg-digital-red su-text-white su-border-2 su-border-digital-red su-border-solid hover:su-border-black focus:su-border-black');
+ break;
+
+ case 'secondary':
+ levers.variant = classnames('su-bg-transparent hover:su-bg-transparent focus:su-bg-transparent su-text-digital-red hover:su-text-black focus:su-text-black su-border-2 su-border-digital-red su-border-solid hover:su-border-black focus:su-border-black');
+ break;
+
+ case 'none':
+ levers.variant = classnames('su-bg-transparent hover:su-bg-transparent focus:su-bg-transparent');
+ break;
+ }
+ }
+
+ // Props.size
+ if (size && buttonSizes.includes(size)) {
+ switch (size) {
+ case 'big':
+ levers.size = classnames('su-px-34 su-py-15 su-text-20 md:su-text-24');
+ break;
+
+ case 'small':
+ levers.size = classnames('su-px-19 su-py-9 su-text-16 md:su-text-18');
+ break;
+
+ case 'minimal':
+ levers.size = classnames('su-p-0');
+ break;
+
+ default:
+ levers.size = classnames('su-px-26 su-py-10 su-text-16 md:su-text-20');
+ }
+ }
+
+ // Is disabled
+ if (isDisabled) {
+ levers.disabled = classnames('su-bg-black-20 su-text-black su-border-2 su-border-black-20 su-border-solid su-pointer-events-none')
+ levers.variant = classnames(levers.variant, {'su-bg-digital-red': false, 'su-text-digital-red': false, 'su-border-digital-red': false, 'hover:su-border-black': false, 'focus:su-border-black': false, 'su-text-white': false})
+ }
+
return (
- )
-}
+ );
+};
Button.propTypes = {
// HTML Button type.
- type: propTypes.oneOf(['button', 'reset', 'submit']),
+ type: PropTypes.oneOf(buttonTypes),
+ variant: PropTypes.oneOf(buttonVariants),
+ size: PropTypes.oneOf(buttonSizes),
+ isDisabled: PropTypes.bool,
// Optional click handler
- onClick: propTypes.func,
+ onClick: PropTypes.func,
// CSS Classes.
- className: propTypes.oneOfType([
- propTypes.string,
- propTypes.array,
- propTypes.object
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object
]),
// Children
- children: propTypes.oneOfType([
- propTypes.string,
- propTypes.element,
- propTypes.node
+ children: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.element,
+ PropTypes.node
])
-}
+};
+// Default Props.
+// -----------------------------------------------------------------------------
Button.defaultProps = {
onClick: undefined,
- type: 'submit'
-}
+ type: 'button',
+ isDisabled: false,
+ ref: null
+};
diff --git a/src/Button/Button.levers.js b/src/Button/Button.levers.js
index e69de29b..745af43d 100644
--- a/src/Button/Button.levers.js
+++ b/src/Button/Button.levers.js
@@ -0,0 +1,14 @@
+/**
+ * Styles for the button.
+ */
+export const buttonVariants = ['primary', 'secondary', 'none'];
+
+/**
+ * Size of the button
+ */
+export const buttonSizes = ['big', 'small', 'minimal'];
+
+/**
+ * Type of the button HTML element
+ */
+export const buttonTypes = ['button', 'submit', 'reset'];
diff --git a/src/Button/Button.stories.js b/src/Button/Button.stories.js
index 4f5fc3f3..3279dc17 100644
--- a/src/Button/Button.stories.js
+++ b/src/Button/Button.stories.js
@@ -1,17 +1,90 @@
import React from 'react';
-
import { Button } from './Button';
+import { buttonVariants, buttonSizes, buttonTypes } from "./Button.levers";
+import DOMPurify from 'dompurify'
export default {
title: 'Simple/Button',
component: Button,
+ argTypes: {
+ variant: {
+ control: {
+ type: 'select',
+ options: buttonVariants
+ }
+ },
+ size: {
+ control: {
+ type: 'select',
+ options: buttonSizes
+ }
+ },
+ type: {
+ control: {
+ type: 'select',
+ options: buttonTypes
+ }
+ },
+ onClick: {
+ action: 'clicked'
+ }
+ },
+ parameters: {
+ jest: [
+ 'Button.test.js'
+ ]
+ }
};
-const Template = (args) => ;
+const ButtonTemplate = ({ children, ...rest }) => {
+ // We do this to mimic sending in CMS content or another React component.
+ const content =
+ return (
+
+ )
+}
-export const Default = Template.bind({});
+export const Default = ButtonTemplate.bind({});
Default.args = {
- children: 'Button',
+ children: '🦸♀️ Be BOLD 🦸♂️',
+};
+
+export const Primary = ButtonTemplate.bind({});
+Primary.args = {
+ variant: 'primary',
+ children: 'Primary Button',
};
+export const Secondary = ButtonTemplate.bind({});
+Secondary.args = {
+ variant: 'secondary',
+ children: 'Secondary Button',
+};
+
+export const Big = ButtonTemplate.bind({});
+Big.args = {
+ variant: 'primary',
+ size: 'big',
+ children: 'Big Button',
+};
+export const Small = ButtonTemplate.bind({});
+Small.args = {
+ variant: 'primary',
+ size: 'small',
+ children: 'Small Button',
+};
+
+export const Disabled = ButtonTemplate.bind({});
+Disabled.args = {
+ variant: 'primary',
+ isDisabled: true,
+ children: 'Disabled Button',
+};
+
+export const Minimal = ButtonTemplate.bind({});
+Minimal.args = {
+ variant: 'none',
+ size: 'minimal',
+ children: 'Minimal Button',
+};
diff --git a/src/Button/Button.test.js b/src/Button/Button.test.js
new file mode 100644
index 00000000..b832047f
--- /dev/null
+++ b/src/Button/Button.test.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+
+import { Button } from './Button'
+
+// Component is a component.
+describe('Button', () => {
+ // Is a component with valid syntax.
+ it('is truthy', () => {
+ expect(Button).toBeTruthy()
+ })
+
+ // Default is rendered.
+ it('renders the Button in the default state', () => {
+ render()
+ screen.getByText('Test Children') // full string match
+ })
+})
diff --git a/src/GlobalFooter/GlobalFooter.js b/src/GlobalFooter/GlobalFooter.js
new file mode 100644
index 00000000..53e3237f
--- /dev/null
+++ b/src/GlobalFooter/GlobalFooter.js
@@ -0,0 +1,145 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { GlobalFooterColors } from './GlobalFooter.levers';
+import { Logo } from '../Logo/Logo';
+import { SrOnlyText } from '../SrOnlyText/SrOnlyText';
+
+/**
+ * Stanford Global Footer Component.
+ *
+ */
+export const GlobalFooter = ({ className, ...props }) => {
+ // Defaults & Variables
+ const classnames = require('classnames/dedupe');
+ const levers = {};
+
+ // props.color
+ if (props.color && GlobalFooterColors.includes(props.color)) {
+ switch (props.color) {
+ case 'cardinal-red':
+ levers.wrapper = classnames('su-bg-cardinal-red');
+ break;
+
+ case 'digital-red':
+ levers.wrapper = classnames('su-bg-digital-red');
+ break;
+
+ case 'black':
+ levers.wrapper = classnames('su-bg-black');
+ break;
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ © Stanford University.
+ Stanford, California 94305.
+
+
+
+
+ );
+};
+
+// Prop Types.
+// -----------------------------------------------------------------------------
+GlobalFooter.propTypes = {
+ /**
+ * Which background color theme?
+ */
+ color: PropTypes.oneOf(GlobalFooterColors),
+
+ /**
+ * Custom CSS classes, e.g., to control position
+ */
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object
+ ]),
+};
+
+// Default Props.
+// -----------------------------------------------------------------------------
+GlobalFooter.defaultProps = {
+ color: 'cardinal-red'
+};
diff --git a/src/GlobalFooter/GlobalFooter.levers.js b/src/GlobalFooter/GlobalFooter.levers.js
new file mode 100644
index 00000000..2c3802fa
--- /dev/null
+++ b/src/GlobalFooter/GlobalFooter.levers.js
@@ -0,0 +1,4 @@
+/**
+ * Default colors for the color prop.
+ */
+export const GlobalFooterColors = ['cardinal-red', 'digital-red', 'black'];
diff --git a/src/GlobalFooter/GlobalFooter.stories.js b/src/GlobalFooter/GlobalFooter.stories.js
new file mode 100644
index 00000000..edbcd237
--- /dev/null
+++ b/src/GlobalFooter/GlobalFooter.stories.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import { GlobalFooter } from './GlobalFooter';
+import { Logo } from '../Logo/Logo';
+import { SrOnlyText } from "../SrOnlyText/SrOnlyText";
+import { withDesign } from 'storybook-addon-designs';
+import { GlobalFooterColors } from './GlobalFooter.levers';
+
+export default {
+ title: 'Stanford Identity/Global Footer',
+ decorators: [withDesign],
+ component: GlobalFooter,
+ subcomponents: { SrOnlyText, Logo },
+ argTypes: {
+ color: {
+ control: {
+ type: 'select',
+ options: GlobalFooterColors
+ }
+ },
+ },
+ parameters: {
+ design: {
+ type: 'figma',
+ url: 'https://www.figma.com/file/Kmd4utmJFPRMVeCFEEBQhLtx/Decanter-Design-System?node-id=1%3A23'
+ },
+ jest: [
+ 'GlobalFooter.test.js'
+ ]
+ }
+};
+
+// Set up a Global Footer Template.
+const GlobalFooterTemplate = ({ children, ...rest }) => ;
+
+export const Default = GlobalFooterTemplate.bind({});
+Default.args = {
+ color: 'cardinal-red',
+};
+Default.storyName = 'Cardinal Red'
+
+export const DigitalRed = GlobalFooterTemplate.bind({});
+DigitalRed.args = {
+ color: 'digital-red',
+};
+DigitalRed.storyName = 'Digital Red'
+
+export const Black = GlobalFooterTemplate.bind({});
+Black.args = {
+ color: 'black',
+};
diff --git a/src/GlobalFooter/GlobalFooter.test.js b/src/GlobalFooter/GlobalFooter.test.js
new file mode 100644
index 00000000..57462d22
--- /dev/null
+++ b/src/GlobalFooter/GlobalFooter.test.js
@@ -0,0 +1,14 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+
+import { GlobalFooter } from './GlobalFooter'
+
+// Component is a component.
+describe('GlobalFooter', () => {
+ // Is a component with valid syntax.
+ it('is truthy', () => {
+ expect(GlobalFooter).toBeTruthy()
+ })
+
+})
diff --git a/src/GlobalFooter/index.js b/src/GlobalFooter/index.js
deleted file mode 100644
index 7e3c8e52..00000000
--- a/src/GlobalFooter/index.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react'
-
-export const GlobalFooter = (props) => {
- // Defaults.
- const defaultClasses = {
- wrapper: 'su-global-footer'
- }
-
- // Variant Sets.
- const variants = {
- bright: {
- wrapper: 'su-global-footer su-bg-digital-red'
- },
- dark: {
- wrapper: 'su-global-footer su-bg-black'
- }
- }
-
- // Merge with passed in props.
- let classes = Object.assign(defaultClasses, props.classes)
- classes = Object.assign(classes, variants[props.variant])
-
- return (
-
-
-
-
-
-
- © Stanford University.
- Stanford, California 94305.
-
-
-
-
- )
-}
diff --git a/src/GlobalFooter/index.test.js b/src/GlobalFooter/index.test.js
deleted file mode 100644
index 708fdd18..00000000
--- a/src/GlobalFooter/index.test.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { GlobalFooter } from '.'
-
-describe('GlobalFooter', () => {
- it('is truthy', () => {
- expect(GlobalFooter).toBeTruthy()
- })
-})
diff --git a/src/Hero/index.test.js b/src/Hero/index.test.js
deleted file mode 100644
index d82d5548..00000000
--- a/src/Hero/index.test.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Hero } from '.'
-
-describe('Hero', () => {
- it('is truthy', () => {
- expect(Hero).toBeTruthy()
- })
-})
diff --git a/src/IdentityBar/IdentityBar.js b/src/IdentityBar/IdentityBar.js
new file mode 100644
index 00000000..8d0740c8
--- /dev/null
+++ b/src/IdentityBar/IdentityBar.js
@@ -0,0 +1,73 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { IdentityBarColors } from './IdentityBar.levers';
+import { Logo } from '../Logo/Logo';
+
+/**
+ * Stanford Identity Bar Component.
+ *
+ */
+export const IdentityBar = ({ className, ...props }) => {
+ const classnames = require('classnames/dedupe');
+ const levers = {};
+
+ // Levers
+ // ---------------------------------------------------------------------------
+
+ // props.color
+ if (props.color && IdentityBarColors.includes(props.color)) {
+ switch (props.color) {
+ case 'white':
+ levers.wrapper = classnames('su-bg-white');
+ levers.logo = "cardinal-red";
+ break;
+
+ case 'cardinal-red':
+ levers.wrapper = classnames('su-bg-cardinal-red');
+ levers.logo = "white";
+ break;
+
+ case 'digital-red':
+ levers.wrapper = classnames('su-bg-digital-red');
+ levers.logo = "white";
+ break;
+
+ case 'black':
+ levers.wrapper = classnames('su-bg-black');
+ levers.logo = "white";
+ break;
+ }
+ }
+
+ return (
+
+ );
+};
+
+// Prop Types.
+// -----------------------------------------------------------------------------
+IdentityBar.propTypes = {
+ /**
+ * Which background color theme?
+ */
+ color: PropTypes.oneOf(IdentityBarColors),
+
+ /**
+ * Custom CSS classes, e.g., to control position
+ */
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object
+ ]),
+};
+
+// Default Props.
+// -----------------------------------------------------------------------------
+IdentityBar.defaultProps = {
+ color: 'cardinal-red'
+};
diff --git a/src/IdentityBar/IdentityBar.levers.js b/src/IdentityBar/IdentityBar.levers.js
new file mode 100644
index 00000000..b82b1501
--- /dev/null
+++ b/src/IdentityBar/IdentityBar.levers.js
@@ -0,0 +1,4 @@
+/**
+ * Default colors for the color prop.
+ */
+export const IdentityBarColors = ['cardinal-red', 'digital-red', 'black', 'white'];
diff --git a/src/IdentityBar/IdentityBar.stories.js b/src/IdentityBar/IdentityBar.stories.js
new file mode 100644
index 00000000..220d132f
--- /dev/null
+++ b/src/IdentityBar/IdentityBar.stories.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import { IdentityBar } from './IdentityBar';
+import { Logo } from '../Logo/Logo';
+import { withDesign } from 'storybook-addon-designs';
+import { IdentityBarColors } from './IdentityBar.levers';
+
+export default {
+ title: 'Stanford Identity/Identity Bar',
+ decorators: [withDesign],
+ component: IdentityBar,
+ subcomponents: { Logo },
+ argTypes: {
+ color: {
+ control: {
+ type: 'select',
+ options: IdentityBarColors
+ }
+ },
+ },
+ parameters: {
+ design: {
+ type: 'figma',
+ url: 'https://www.figma.com/file/Kmd4utmJFPRMVeCFEEBQhLtx/Decanter-Design-System?node-id=1%3A23'
+ },
+ jest: [
+ 'IdentityBar.test.js'
+ ]
+ }
+};
+
+// Set up an Alert Template.
+const IdentityBarTemplate = (props) => ;
+
+export const Default = IdentityBarTemplate.bind({});
+Default.args = {
+ color: 'cardinal-red',
+};
+Default.storyName = 'Cardinal Red'
+
+export const DigitalRed = IdentityBarTemplate.bind({});
+DigitalRed.args = {
+ color: 'digital-red',
+};
+DigitalRed.storyName = 'Digital Red'
+
+export const Black = IdentityBarTemplate.bind({});
+Black.args = {
+ color: 'black',
+};
+
+export const White = IdentityBarTemplate.bind({});
+White.args = {
+ color: 'white',
+};
diff --git a/src/IdentityBar/IdentityBar.test.js b/src/IdentityBar/IdentityBar.test.js
new file mode 100644
index 00000000..03aa9287
--- /dev/null
+++ b/src/IdentityBar/IdentityBar.test.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+
+import { IdentityBar } from './IdentityBar'
+
+// Component is a component.
+describe('IdentityBar', () => {
+ // Is a component with valid syntax.
+ it('is truthy', () => {
+ expect(IdentityBar).toBeTruthy()
+ })
+
+ // Default is rendered.
+ it('renders the IdentityBar in the default state', () => {
+ render()
+ })
+})
diff --git a/src/Logo/Logo.js b/src/Logo/Logo.js
new file mode 100644
index 00000000..6d1fdbec
--- /dev/null
+++ b/src/Logo/Logo.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { LogoColors, LogoTypes } from './Logo.levers';
+
+/**
+ * Stanford Wordmark Logo Component.
+ *
+ */
+export const Logo = ({ className, ...props }) => {
+ const classnames = require('classnames/dedupe');
+ const levers = {};
+ let logoText;
+
+ // Levers
+ // ---------------------------------------------------------------------------
+
+ // props.color
+ if (props.color && LogoColors.includes(props.color)) {
+ switch (props.color) {
+ case 'cardinal-red':
+ levers.logo = classnames('su-text-cardinal-red');
+ break;
+
+ case 'black':
+ levers.logo = classnames('su-text-black hover:su-text-black focus:su-text-black');
+ break;
+
+ case 'white':
+ levers.logo = classnames('su-text-white hover:su-text-white focus:su-text-white');
+ break;
+ }
+ }
+
+ // props.type
+ if (props.type && LogoTypes.includes(props.type)) {
+ switch (props.type) {
+ case 'short':
+ logoText = 'Stanford';
+ break;
+
+ case 'full':
+ logoText = 'Stanford University';
+ break;
+
+ case 'stacked':
+ logoText = (<>Stanford
University>);
+ break;
+ }
+ }
+
+ return (
+
+ {logoText}
+
+ );
+};
+
+// Prop Types.
+// -----------------------------------------------------------------------------
+Logo.propTypes = {
+ color: PropTypes.oneOf(LogoColors),
+ type: PropTypes.oneOf(LogoTypes),
+
+ /**
+ * Custom CSS classes, e.g., to change font size
+ */
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object
+ ]),
+};
+
+// Default Props.
+// -----------------------------------------------------------------------------
+Logo.defaultProps = {
+ color: 'cardinal-red',
+ type: 'short'
+};
diff --git a/src/Logo/Logo.levers.js b/src/Logo/Logo.levers.js
new file mode 100644
index 00000000..49815b5e
--- /dev/null
+++ b/src/Logo/Logo.levers.js
@@ -0,0 +1,9 @@
+/**
+ * Default colors for the color prop.
+ */
+export const LogoColors = ['cardinal-red', 'black', 'white'];
+
+/**
+ * The three different logo types.
+ */
+export const LogoTypes = ['short', 'full', 'stacked'];
diff --git a/src/Logo/Logo.stories.js b/src/Logo/Logo.stories.js
new file mode 100644
index 00000000..9b90cb53
--- /dev/null
+++ b/src/Logo/Logo.stories.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { Logo } from './Logo';
+import { LogoColors, LogoTypes } from "./Logo.levers";
+
+export default {
+ title: 'Stanford Identity/Logo',
+ component: Logo,
+ argTypes: {
+ color: {
+ control: {
+ type: 'select',
+ options: LogoColors
+ }
+ },
+ type: {
+ control: {
+ type: 'select',
+ options: LogoTypes
+ }
+ },
+ },
+ parameters: {
+ jest: [
+ 'Logo.test.js'
+ ]
+ }
+};
+
+// Set up an Alert Template.
+const LogoTemplate = (props) => ;
+
+export const Red = LogoTemplate.bind({});
+Red.args = {
+ color: 'cardinal-red',
+ type: 'short',
+ className: 'su-type-3'
+};
+Red.storyName = 'Cardinal Red'
+
+export const Black = LogoTemplate.bind({});
+Black.args = {
+ color: 'black',
+ type: 'short',
+ className: 'su-type-3'
+};
+
+export const White = LogoTemplate.bind({});
+White.args = {
+ color: 'white',
+ type: 'short',
+ className: 'su-type-3'
+};
+
+export const Full = LogoTemplate.bind({});
+Full.args = {
+ color: 'cardinal-red',
+ type: 'full',
+ className: 'su-type-3'
+};
+
+export const Stacked = LogoTemplate.bind({});
+Stacked.args = {
+ color: 'cardinal-red',
+ type: 'stacked',
+ className: 'su-type-3'
+};
diff --git a/src/Logo/Logo.test.js b/src/Logo/Logo.test.js
new file mode 100644
index 00000000..6300e3dd
--- /dev/null
+++ b/src/Logo/Logo.test.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+
+import { Logo } from './Logo'
+
+// Component is a component.
+describe('Logo', () => {
+ // Is a component with valid syntax.
+ it('is truthy', () => {
+ expect(Logo).toBeTruthy()
+ })
+
+ // Default is rendered.
+ it('renders the Logo in the default state', () => {
+ render()
+ })
+})
diff --git a/src/SrOnlyText/SrOnlyText.js b/src/SrOnlyText/SrOnlyText.js
new file mode 100644
index 00000000..f3e24030
--- /dev/null
+++ b/src/SrOnlyText/SrOnlyText.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import PropTypes from "prop-types";
+
+export const SrOnlyText = (props) => {
+ const txt = props.srText ?? '(link is external)'
+ return (
+ {txt}
+ );
+};
+
+// Prop Types.
+// -----------------------------------------------------------------------------
+SrOnlyText.propTypes = {
+ /**
+ * Text for screen reader only
+ */
+ srText: PropTypes.string
+};
+
+// Default Props.
+// -----------------------------------------------------------------------------
+SrOnlyText.defaultProps = {
+ srText: '(link is external)'
+};
diff --git a/src/SrOnlyText/SrOnlyText.stories.js b/src/SrOnlyText/SrOnlyText.stories.js
new file mode 100644
index 00000000..5fd4d2a0
--- /dev/null
+++ b/src/SrOnlyText/SrOnlyText.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { SrOnlyText } from "./SrOnlyText";
+
+export default {
+ title: 'Accessibility/Screen Reader Only Text',
+ component: SrOnlyText,
+ parameters: {
+ docs: {
+ description: {
+ component: 'For adding text for screen readers that is not visible.'
+ }
+ },
+ jest: [
+ 'SrOnlyText.test.js'
+ ]
+ }
+}
+
+// Set up the Template.
+const SrOnlyTextTemplate = ({ ...rest }) => ;
+
+export const Default = SrOnlyTextTemplate.bind({});
+Default.args = {
+ srText: 'This text is for screen readers only',
+};
+Default.storyName = 'Example'
diff --git a/src/SrOnlyText/SrOnlyText.test.js b/src/SrOnlyText/SrOnlyText.test.js
new file mode 100644
index 00000000..06f48b98
--- /dev/null
+++ b/src/SrOnlyText/SrOnlyText.test.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+
+import { SrOnlyText } from './SrOnlyText'
+
+// Component is a component.
+describe('SrOnlyText', () => {
+ // Is a component with valid syntax.
+ it('is truthy', () => {
+ expect(SrOnlyText).toBeTruthy()
+ })
+
+ // Default is rendered.
+ it('renders the SrOnlyText in the default state', () => {
+ render()
+ })
+})
diff --git a/src/StyledLink/index.test.js b/src/StyledLink/index.test.js
deleted file mode 100644
index a98ccdf8..00000000
--- a/src/StyledLink/index.test.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { StyledLink } from '.'
-
-describe('StyledLink', () => {
- it('is truthy', () => {
- expect(StyledLink).toBeTruthy()
- })
-})
diff --git a/src/index.js b/src/index.js
index 6967d116..1ead3061 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,9 @@
-import { Alert } from './Alert/Alert'
-import { BrandBar } from './BrandBar'
-import { GlobalFooter } from './GlobalFooter'
-import { Hero } from './Hero'
-import { StyledLink } from './StyledLink'
+import { Alert } from './Alert/Alert';
+import { Button } from './Button/Button';
+import { IdentityBar } from './IdentityBar/IdentityBar';
+import { GlobalFooter } from './GlobalFooter/GlobalFooter';
+import { Logo } from './Logo/Logo';
+import { SrOnlyText } from './SrOnlyText/SrOnlyText';
+import { StyledLink } from './StyledLink';
-export { Alert, BrandBar, GlobalFooter, Hero, StyledLink }
+export { Alert, Button, IdentityBar, GlobalFooter, Logo, SrOnlyText, StyledLink };