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 ( -
-
- - Stanford University - -
-
- ) -} 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) => + ) +} -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 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 };