├── LICENSE ├── CHANGELOG.md ├── types ├── keycoder.d.ts └── react-relative-portal.d.ts ├── public └── .gitignore ├── .npmignore ├── .gitattributes ├── .gitignore ├── stories ├── data │ ├── CAMPAIGNS.ts │ ├── SCOPES.ts │ ├── OPPORTUNITIES.ts │ ├── CASES.ts │ └── COMPANIES.ts ├── util.tsx ├── Tree.stories.tsx ├── BreadCrumbs.stories.tsx ├── Toggle.stories.tsx ├── SalesPath.stories.tsx ├── Pill.stories.tsx ├── Badge.stories.tsx ├── Grid.stories.tsx ├── MediaObject.stories.tsx ├── Spinner.stories.tsx ├── Datepicker.stories.tsx ├── Textarea.stories.tsx ├── Radio.stories.tsx ├── DateInput.stories.tsx ├── DropdownMenu.stories.tsx ├── Select.stories.tsx ├── Checkbox.stories.tsx ├── ButtonGroup.stories.tsx ├── Table.stories.tsx ├── Icon.stories.tsx ├── Form.stories.tsx ├── Input.stories.tsx ├── Notification.stories.tsx ├── Button.stories.tsx ├── Tabs.stories.tsx └── Picklist.stories.tsx ├── .prettierrc.js ├── src └── scripts │ ├── common.ts │ ├── typeUtils.ts │ ├── Container.tsx │ ├── Badge.tsx │ ├── Form.tsx │ ├── MediaObject.tsx │ ├── ComponentSettings.tsx │ ├── ButtonGroup.tsx │ ├── BreadCrumbs.tsx │ ├── Text.tsx │ ├── TooltipContent.tsx │ ├── hooks.ts │ ├── Spinner.tsx │ ├── index.ts │ ├── Radio.tsx │ ├── Tree.tsx │ ├── util.ts │ ├── Toggle.tsx │ ├── Checkbox.tsx │ ├── Pill.tsx │ ├── FieldSet.tsx │ ├── Textarea.tsx │ ├── Select.tsx │ ├── Notification.tsx │ ├── RadioGroup.tsx │ ├── FormElement.tsx │ ├── Popover.tsx │ ├── Modal.tsx │ ├── CheckboxGroup.tsx │ ├── TreeNode.tsx │ ├── Button.tsx │ ├── Grid.tsx │ ├── SalesPath.tsx │ └── Input.tsx ├── tsconfig.types.json ├── .storybook ├── wrapSLDS.js ├── main.js ├── preview-head.html └── preview.js ├── .editorconfig ├── babel.config.js ├── regconfig.json ├── .eslintrc.js ├── README.md ├── .circleci └── config.yml ├── package.json └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /types/keycoder.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'keycoder'; 2 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.ico 3 | assets 4 | static 5 | -------------------------------------------------------------------------------- /types/react-relative-portal.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-relative-portal'; 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | docs 4 | examples 5 | icon-builder 6 | test 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | lib/* 4 | module/* 5 | .idea 6 | 7 | coverage 8 | .reg 9 | 10 | .env 11 | .vscode -------------------------------------------------------------------------------- /stories/data/CAMPAIGNS.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | Online Seminar 3 | Event 4 | Survey 5 | ` 6 | .split('\n') 7 | .filter((n) => n); 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | jsxSingleQuote: true, 4 | arrowParens: 'always', 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /stories/data/SCOPES.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | Account 3 | Campaign 4 | Case 5 | Contract 6 | Opportunity 7 | Solution 8 | ` 9 | .split('\n') 10 | .filter((n) => n); 11 | -------------------------------------------------------------------------------- /stories/data/OPPORTUNITIES.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | New License 3 | Professional Service 4 | Additional License 5 | Hardware Renewal 6 | ` 7 | .split('\n') 8 | .filter((n) => n); 9 | -------------------------------------------------------------------------------- /src/scripts/common.ts: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | 3 | /** 4 | * 5 | */ 6 | export function createFC(componentFn: FC

, statics: T): FC

& T { 7 | return Object.assign(componentFn, statics); 8 | } 9 | -------------------------------------------------------------------------------- /stories/data/CASES.ts: -------------------------------------------------------------------------------- 1 | export default new Array(1001) 2 | .join('_') 3 | .split('') 4 | .map((a, i) => { 5 | const padded = new Array(5).join('0') + i; 6 | return padded.substring(padded.length - 5); 7 | }); 8 | -------------------------------------------------------------------------------- /src/scripts/typeUtils.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export type Bivariant any> = { 3 | bivarianceHack(...args: Parameters): ReturnType; 4 | }['bivarianceHack']; 5 | -------------------------------------------------------------------------------- /tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.storybook/wrapSLDS.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentSettings } from '../src/scripts'; 3 | 4 | export default function wrapSLDS(options) { 5 | return story => ( 6 | 7 | {story()} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | insert_final_newline = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | typescript: { 5 | reactDocgen: "react-docgen-typescript-plugin" 6 | }, 7 | stories: ['../stories/**/*.stories.tsx'], 8 | addons: [ 9 | '@storybook/addon-docs', 10 | '@storybook/addon-actions', 11 | '@storybook/addon-controls', 12 | 'storycap', 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/env', 5 | process.env.BUILD_TARGET === 'module' 6 | ? { 7 | modules: false, 8 | } 9 | : {}, 10 | ], 11 | '@babel/react', 12 | '@babel/typescript', 13 | ], 14 | plugins: [ 15 | '@babel/plugin-transform-runtime', 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /regconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "workingDir": ".reg", 4 | "actualDir": "images", 5 | "thresholdRate": 0.001, 6 | "addIgnore": true, 7 | "ximgdiff": { 8 | "invocationType": "client" 9 | } 10 | }, 11 | "plugins": { 12 | "reg-keygen-git-hash-plugin": true, 13 | "reg-notify-github-plugin": { 14 | "clientId": "$REG_NOTIF_CLIENT_ID" 15 | }, 16 | "reg-publish-s3-plugin": { 17 | "bucketName": "$S3_BUCKET_NAME" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 12 | 17 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import 'core-js/stable'; 2 | import { withScreenshot } from 'storycap'; 3 | import wrapSLDS from './wrapSLDS'; 4 | 5 | let assetRoot; 6 | if (typeof location !== 'undefined' && location.hostname === 'mashmatrix.github.io') { 7 | assetRoot = '//mashmatrix.github.io/react-lightning-design-system/assets'; 8 | } 9 | 10 | const withSLDS = wrapSLDS({ assetRoot }); 11 | 12 | export const decorators = [ 13 | withScreenshot, 14 | withSLDS, 15 | ]; 16 | 17 | export const parameters = { 18 | // Global parameter is optional. 19 | screenshot: { 20 | // Put global screenshot parameters(e.g. viewport) 21 | }, 22 | }; -------------------------------------------------------------------------------- /src/scripts/Container.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type ContainerProps = { 8 | size: 'small' | 'medium' | 'large'; 9 | align: 'left' | 'center' | 'right'; 10 | } & HTMLAttributes; 11 | 12 | /** 13 | * 14 | */ 15 | export const Container: FC = ({ 16 | className, 17 | size, 18 | align, 19 | children, 20 | ...props 21 | }) => { 22 | const ctClassNames = classnames( 23 | className, 24 | `slds-container_${size || 'fluid'}`, 25 | align ? `slds-container_${align}` : null 26 | ); 27 | return ( 28 |

29 | {children} 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/scripts/Badge.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type BadgeProps = { 8 | type?: 'inverse' | 'lightest' | 'success' | 'warning' | 'error'; 9 | label?: string; 10 | } & HTMLAttributes; 11 | 12 | /** 13 | * 14 | */ 15 | export const Badge: FC = ({ type, label, ...props }) => { 16 | const typeClassName = /^(inverse|lightest)$/.test(type ?? '') 17 | ? `slds-badge_${type}` 18 | : null; 19 | const themeClassName = /^(success|warning|error)$/.test(type ?? '') 20 | ? `slds-theme_${type}` 21 | : null; 22 | const badgeClassNames = classnames( 23 | 'slds-badge', 24 | typeClassName, 25 | themeClassName 26 | ); 27 | return ( 28 | 29 | {label || props.children} 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/scripts/Form.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import classnames from 'classnames'; 3 | import { FormElement } from './FormElement'; 4 | 5 | /** 6 | * 7 | */ 8 | export type FormProps = { 9 | type?: 'stacked' | 'horizontal' | 'inline' | 'compound'; 10 | } & HTMLAttributes; 11 | 12 | /** 13 | * 14 | */ 15 | export const Form: FC = (props) => { 16 | const { className, type = 'stacked', children, ...rprops } = props; 17 | const formClassNames = classnames(className, `slds-form_${type}`); 18 | return ( 19 |
20 | {React.Children.map(children, (child) => { 21 | if ( 22 | React.isValidElement(child) && 23 | !(child.type as unknown as { isFormElement?: boolean }).isFormElement 24 | ) { 25 | return {child}; 26 | } 27 | return child; 28 | })} 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/scripts/MediaObject.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes, ReactNode } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type MediaObjectProps = { 8 | figureLeft?: ReactNode; 9 | figureRight?: ReactNode; 10 | centered?: boolean; 11 | children?: ReactNode; 12 | } & HTMLAttributes; 13 | 14 | /** 15 | * 16 | */ 17 | export const MediaObject: FC = (props) => { 18 | const { className, figureLeft, figureRight, centered, children, ...rprops } = 19 | props; 20 | const mediaClassNames = classnames( 21 | 'slds-media', 22 | { 'slds-media_center': centered }, 23 | className 24 | ); 25 | return ( 26 |
27 | {figureLeft ? ( 28 |
{figureLeft}
29 | ) : undefined} 30 |
{children}
31 | {figureRight ? ( 32 |
33 | {figureRight} 34 |
35 | ) : undefined} 36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/scripts/ComponentSettings.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, FC, ReactNode } from 'react'; 2 | 3 | export type ComponentSettingsProps = { 4 | assetRoot?: string; 5 | portalClassName?: string; 6 | portalStyle?: object; 7 | getActiveElement?: () => HTMLElement | null; 8 | children?: ReactNode; 9 | }; 10 | 11 | function getDocumentActiveElement() { 12 | return document.activeElement as HTMLElement | null; 13 | } 14 | 15 | export const ComponentSettingsContext = createContext< 16 | ComponentSettingsProps & 17 | Required> 18 | >({ getActiveElement: getDocumentActiveElement }); 19 | 20 | /** 21 | * 22 | */ 23 | export const ComponentSettings: FC = (props) => { 24 | const { 25 | assetRoot, 26 | portalClassName, 27 | portalStyle, 28 | getActiveElement = getDocumentActiveElement, 29 | children, 30 | } = props; 31 | return ( 32 | 35 | {children} 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/scripts/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { Children, HTMLAttributes, FC, createContext } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type ButtonGroupProps = HTMLAttributes; 8 | 9 | /** 10 | * 11 | */ 12 | export const ButtonGroupContext = createContext<{ 13 | grouped: true; 14 | isFirstInGroup: boolean; 15 | isLastInGroup: boolean; 16 | } | null>(null); 17 | 18 | /** 19 | * 20 | */ 21 | export const ButtonGroup: FC = (props) => { 22 | const { className, children, ...rprops } = props; 23 | const btnGrpClassNames = classnames(className, 'slds-button-group'); 24 | const cnt = React.Children.count(children); 25 | return ( 26 |
27 | {Children.map(children, (child, index) => ( 28 | 35 | {child} 36 | 37 | ))} 38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/scripts/BreadCrumbs.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type CrumbProps = HTMLAttributes & { 8 | href?: string; 9 | }; 10 | 11 | /** 12 | * 13 | */ 14 | export const Crumb: FC = ({ 15 | className, 16 | href, 17 | children, 18 | ...props 19 | }) => { 20 | const text = children; 21 | const cClassName = classnames('slds-breadcrumb__item', className); 22 | 23 | return ( 24 |
  • 25 | {text} 26 |
  • 27 | ); 28 | }; 29 | 30 | /** 31 | * 32 | */ 33 | export type BreadCrumbsProps = HTMLAttributes; 34 | 35 | /** 36 | * 37 | */ 38 | export const BreadCrumbs: FC = ({ 39 | className, 40 | children, 41 | ...props 42 | }) => { 43 | const oClassName = classnames( 44 | 'slds-breadcrumb', 45 | 'slds-list_horizontal', 46 | 'slds-wrap', 47 | className 48 | ); 49 | 50 | return ( 51 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/scripts/Text.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactHTML, HTMLAttributes } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | /** 5 | * 6 | */ 7 | export type TextProps = { 8 | tag?: keyof ReactHTML; 9 | category?: 'body' | 'heading' | 'title'; 10 | type?: 'small' | 'regular' | 'medium' | 'large' | 'caps'; 11 | align?: 'left' | 'center' | 'right'; 12 | truncate?: boolean; 13 | section?: boolean; 14 | } & HTMLAttributes; 15 | 16 | /** 17 | * 18 | */ 19 | export const Text: FC = ({ 20 | tag, 21 | category, 22 | type, 23 | align, 24 | truncate, 25 | section, 26 | children, 27 | className, 28 | ...props 29 | }) => { 30 | const textClassNames = classnames( 31 | type && category ? `slds-text-${category}_${type}` : undefined, 32 | category === 'title' && !type ? `slds-text-${category}` : undefined, 33 | align ? `slds-text-align_${align}` : undefined, 34 | { 35 | 'slds-truncate': truncate, 36 | 'slds-section-title_divider': section, 37 | }, 38 | className 39 | ); 40 | const Tag = tag || 'p'; 41 | return ( 42 | 43 | {children} 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/scripts/TooltipContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | ReactNode, 3 | useRef, 4 | useState, 5 | FocusEvent, 6 | useCallback, 7 | } from 'react'; 8 | import { Button } from './Button'; 9 | import { Popover } from './Popover'; 10 | 11 | /** 12 | * 13 | */ 14 | export const TooltipContent = (props: { 15 | children: ReactNode; 16 | icon?: string; 17 | }) => { 18 | const { children, icon = 'info' } = props; 19 | const [isHideTooltip, setIsHideTooltip] = useState(true); 20 | const popoverRef = useRef(null); 21 | const tooltipToggle = useCallback(() => { 22 | setIsHideTooltip((hidden) => !hidden); 23 | }, []); 24 | const onBlur = useCallback((e: FocusEvent) => { 25 | if (!popoverRef.current?.contains(e.relatedTarget)) { 26 | setIsHideTooltip(true); 27 | } 28 | }, []); 29 | return ( 30 | 31 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | , document.body); 34 | ``` 35 | 36 | See more examples in [examples](https://github.com/mashmatrix/react-lightning-design-system/tree/master/stories) directory. 37 | 38 | 39 | ## Running example stories locally 40 | 41 | This repo ships with a react storybook based story scripts. 42 | To run stories and get component examples, follow these steps: 43 | 44 | 1. run ```npm install``` 45 | 2. run ```npm run storybook``` 46 | 3. Find the stories running on [localhost:9001](http://localhost:9001). 47 | 48 | ## Snapshot testing in react storybook 49 | 50 | This repo ships with story snapshots to examine differences in rendering as a result of changes to source code. 51 | 52 | To identify render differences run ```npm run test:storyshots```. If all changes are intentional run ```npm run test:storyshots -- -u```. To learn about other run options including *interactive mode*, read 53 | [Snapshot Testing in React Storybook](https://voice.kadira.io/snapshot-testing-in-react-storybook-43b3b71cec4f) 54 | -------------------------------------------------------------------------------- /src/scripts/Pill.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | HTMLAttributes, 3 | MouseEvent, 4 | KeyboardEvent, 5 | Ref, 6 | FC, 7 | } from 'react'; 8 | import classnames from 'classnames'; 9 | import { Icon, IconCategory } from './Icon'; 10 | import { Button } from './Button'; 11 | import { useEventCallback } from './hooks'; 12 | 13 | /** 14 | * 15 | */ 16 | export type PillProps = { 17 | label?: string; 18 | title?: string; 19 | truncate?: boolean; 20 | disabled?: boolean; 21 | icon?: { 22 | category?: IconCategory; 23 | icon?: string; 24 | }; 25 | pillRef?: Ref; 26 | onRemove?: () => void; 27 | } & HTMLAttributes; 28 | 29 | /** 30 | * 31 | */ 32 | export const Pill: FC = (props) => { 33 | const { 34 | icon, 35 | disabled, 36 | label, 37 | title, 38 | truncate, 39 | className, 40 | pillRef, 41 | onClick, 42 | onRemove, 43 | } = props; 44 | const onPillRemove = useEventCallback( 45 | (e: MouseEvent | KeyboardEvent) => { 46 | e.preventDefault(); 47 | e.stopPropagation(); 48 | onRemove?.(); 49 | } 50 | ); 51 | 52 | const onKeyDown = useEventCallback((e: KeyboardEvent) => { 53 | if (e.keyCode === 8 || e.keyCode === 46) { 54 | // Bacspace / DEL 55 | onPillRemove(e); 56 | } 57 | }); 58 | 59 | const pillClassNames = classnames( 60 | 'slds-pill', 61 | { 'slds-pill_link': !disabled }, 62 | { 'slds-truncate': truncate }, 63 | className 64 | ); 65 | return ( 66 | 72 | {icon && icon.icon ? ( 73 | 74 | 75 | 76 | ) : undefined} 77 | {disabled ? ( 78 | 79 | {label} 80 | 81 | ) : ( 82 | 83 | {label} 84 | 85 | )} 86 |