├── .npmignore ├── .prettierignore ├── CHANGELOG.md ├── .prettierrc ├── .babelrc ├── bsconfig.json ├── .editorconfig ├── src ├── components │ ├── DangerousHTML.re │ ├── Loader.re │ ├── Radio.js │ ├── CodeBlock.re │ ├── RenderIf.re │ ├── Box.re │ ├── Portal.js │ ├── Tab.js │ ├── ProgressBar.re │ ├── Margin.re │ ├── Padding.re │ ├── Breadcrumbs.js │ ├── Text.re │ ├── Flex.re │ ├── Card.re │ ├── Panel.re │ ├── Checkbox.re │ ├── Navbar.re │ ├── TruncateText.js │ ├── Button.re │ ├── Tabs.js │ ├── Table.js │ ├── Modal.re │ ├── Article.re │ ├── TextField.re │ ├── ImageCarousel.js │ ├── Select.re │ ├── TextArea.re │ ├── Collapsible.re │ ├── Dropdown.re │ ├── MegaFooter.js │ └── Alert.re ├── styles │ └── main.css └── index.js ├── .gitignore ├── LICENSE ├── rollup.config.js ├── README.md └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .babelrc 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | dist/ 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.5 2 | - Fix broken components 3 | - Fix optional props 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "singleQuote": true, 4 | "semi": false 5 | } 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "@babel/stage-0", 10 | "@babel/react" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tails-ui", 3 | "sources": [ 4 | { 5 | "dir": "src", 6 | "subdirs": true 7 | } 8 | ], 9 | "bs-dependencies": ["reason-react"], 10 | "reason": { 11 | "react-jsx": 2 12 | }, 13 | "package-specs": ["es6"], 14 | "bsc-flags": ["-bs-super-errors"], 15 | "refmt": 3 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/components/DangerousHTML.re: -------------------------------------------------------------------------------- 1 | let dangerousHtml: string => Js.t('a) = html => {"__html": html}; 2 | 3 | let component = ReasonReact.statelessComponent("DangerousHTML"); 4 | 5 | let make = (~children) => { 6 | ...component, 7 | render: _self =>
8 | }; 9 | 10 | let default = 11 | ReasonReact.wrapReasonForJs(~component, jsProps => 12 | make(~children=jsProps##children) 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Loader.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Loader"); 2 | 3 | let make = (~color="teal", _children) => { 4 | ...component, 5 | render: _self => 6 |
9 | }; 10 | 11 | let default = 12 | ReasonReact.wrapReasonForJs(~component, jsProps => 13 | make(~color=jsProps##color, [||]) 14 | ); 15 | -------------------------------------------------------------------------------- /src/components/Radio.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Radio = ({ checked, disabled, handleChange, label, ...props }) => ( 4 |
5 | 12 | {label && } 13 |
14 | ) 15 | 16 | export default Radio 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # bucklescript 14 | /lib 15 | /types 16 | .merlin 17 | .bsb.lock 18 | 19 | # misc 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | -------------------------------------------------------------------------------- /src/components/CodeBlock.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("CodeBlock"); 2 | 3 | let make = (~children) => { 4 | ...component, 5 | render: _self => 6 |
 7 |       
 8 |         (ReasonReact.arrayToElement(children))
 9 |       
10 |     
11 | }; 12 | 13 | let default = 14 | ReasonReact.wrapReasonForJs(~component, jsProps => 15 | make(~children=jsProps##children) 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/RenderIf.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("RenderIf"); 2 | 3 | let make = (~condition, ~children) => { 4 | ...component, 5 | render: _self => 6 | Js.Boolean.to_js_boolean(condition) === Js.Boolean.to_js_boolean(true) ? 7 | ReasonReact.arrayToElement(children) : ReasonReact.nullElement 8 | }; 9 | 10 | let default = 11 | ReasonReact.wrapReasonForJs(~component, jsProps => 12 | make(~condition=jsProps##condition, ~children=jsProps##children) 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Box.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Box"); 2 | 3 | let make = (~sm="full", ~md="full", ~lg="full", ~xl="full", ~children) => { 4 | ...component, 5 | render: (_) => 6 |
7 | (ReasonReact.arrayToElement(children)) 8 |
9 | }; 10 | 11 | let default = 12 | ReasonReact.wrapReasonForJs(~component, jsProps => 13 | make( 14 | ~sm=jsProps##sm, 15 | ~md=jsProps##md, 16 | ~lg=jsProps##lg, 17 | ~xl=jsProps##xl, 18 | ~children=jsProps##children 19 | ) 20 | ); 21 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | button:focus { 2 | outline: none; 3 | } 4 | 5 | textarea:focus { 6 | outline: none; 7 | } 8 | 9 | input:focus { 10 | outline: none; 11 | } 12 | 13 | select:focus { 14 | outline: none; 15 | } 16 | 17 | table { 18 | border-collapse: collapse; 19 | border-spacing: 0; 20 | } 21 | 22 | tr:nth-child(even) { 23 | background-color: #f1f5f8; 24 | } 25 | 26 | @keyframes spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | 35 | .loader { 36 | animation: spin 1s infinite linear; 37 | } 38 | 39 | .top-pin { 40 | top: 110%; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Portal.js: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react' 2 | import { createPortal } from 'react-dom' 3 | 4 | export default class Portal extends PureComponent { 5 | componentWillUnmount() { 6 | if (this.defaultNode) { 7 | document.body.removeChild(this.defaultNode) 8 | } 9 | this.defaultNode = null 10 | } 11 | 12 | render() { 13 | const { domNode } = this.props 14 | 15 | if (!domNode && !this.defaultNode) { 16 | this.defaultNode = document.createElement('div') 17 | document.body.appendChild(this.defaultNode) 18 | } 19 | 20 | return createPortal( 21 | this.props.children, 22 | this.props.domNode || this.defaultNode 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Tab.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Tab = ({ onClick, tabIndex, title, isActive, color }) => ( 4 |
  • 5 | { 7 | event.preventDefault() 8 | onClick(tabIndex) 9 | }} 10 | className={`inline-block no-underline ${ 11 | isActive ? 'border-blue' : 'border-white' 12 | } ${!isActive && 13 | 'hover:border-grey hover:bg-grey-lighter'} rounded py-1 px-3 ${isActive && 14 | `bg-${color}`} ${!isActive && `text-${color}`} text-white`} 15 | href="#" 16 | > 17 | {title} 18 | 19 |
  • 20 | ) 21 | 22 | Tab.defaultProps = { 23 | color: 'teal' 24 | } 25 | 26 | export default Tab 27 | -------------------------------------------------------------------------------- /src/components/ProgressBar.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("ProgressBar"); 2 | 3 | let make = (~color="teal", ~percent="50%", _children) => { 4 | ...component, 5 | render: _self => 6 |
    7 |
    8 |
    11 | (ReasonReact.stringToElement(percent)) 12 |
    13 |
    14 |
    15 | }; 16 | 17 | let default = 18 | ReasonReact.wrapReasonForJs(~component, jsProps => 19 | make(~color=jsProps##color, ~percent=jsProps##percent, [||]) 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/Margin.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Margin"); 2 | 3 | let make = 4 | ( 5 | ~margin=?, 6 | ~marginLeft=?, 7 | ~marginRight=?, 8 | ~marginBottom=?, 9 | ~marginTop=?, 10 | _children 11 | ) => { 12 | ...component, 13 | render: _self => 14 |
    17 | }; 18 | 19 | let default = 20 | ReasonReact.wrapReasonForJs(~component, jsProps => 21 | make( 22 | ~margin=?jsProps##margin, 23 | ~marginLeft=?jsProps##marginLeft, 24 | ~marginRight=?jsProps##marginRight, 25 | ~marginBottom=?jsProps##marginBottom, 26 | ~marginTop=?jsProps##marginTop, 27 | [||] 28 | ) 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/Padding.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Padding"); 2 | 3 | let make = 4 | ( 5 | ~padding=?, 6 | ~paddingLeft=?, 7 | ~paddingRight=?, 8 | ~paddingBottom=?, 9 | ~paddingTop=?, 10 | _children 11 | ) => { 12 | ...component, 13 | render: _self => 14 |
    17 | }; 18 | 19 | let default = 20 | ReasonReact.wrapReasonForJs(~component, jsProps => 21 | make( 22 | ~padding=?jsProps##padding, 23 | ~paddingLeft=?jsProps##paddingLeft, 24 | ~paddingRight=?jsProps##paddingRight, 25 | ~paddingBottom=?jsProps##paddingBottom, 26 | ~paddingTop=?jsProps##paddingTop, 27 | [||] 28 | ) 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/Breadcrumbs.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Breadcrumbs = ({ bgColor, crumbs, textColor }) => ( 4 | 22 | ) 23 | 24 | Breadcrumbs.defaultProps = { 25 | bgColor: 'bg-grey-light', 26 | textColor: 'text-blue' 27 | } 28 | 29 | export default Breadcrumbs 30 | -------------------------------------------------------------------------------- /src/components/Text.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Text"); 2 | 3 | let make = 4 | ( 5 | ~font="sans", 6 | ~color="grey-darker", 7 | ~size="sm", 8 | ~weight="normal", 9 | ~align="left", 10 | ~fontStyle="normal-case", 11 | ~children 12 | ) => { 13 | ...component, 14 | render: _self => 15 |
    17 | (ReasonReact.arrayToElement(children)) 18 |
    19 | }; 20 | 21 | let default = 22 | ReasonReact.wrapReasonForJs(~component, jsProps => 23 | make( 24 | ~font=jsProps##font, 25 | ~color=jsProps##color, 26 | ~size=jsProps##size, 27 | ~weight=jsProps##weight, 28 | ~align=jsProps##align, 29 | ~fontStyle=jsProps##fontStyle, 30 | ~children=jsProps##children 31 | ) 32 | ); 33 | -------------------------------------------------------------------------------- /src/components/Flex.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Flex"); 2 | 3 | let make = 4 | ( 5 | ~display="flex", 6 | ~direction="row", 7 | ~wrap="no-wrap", 8 | ~justify="start", 9 | ~align="start", 10 | ~height="full", 11 | ~width="full", 12 | ~children 13 | ) => { 14 | ...component, 15 | render: _self => 16 |
    18 | (ReasonReact.arrayToElement(children)) 19 |
    20 | }; 21 | 22 | let default = 23 | ReasonReact.wrapReasonForJs(~component, jsProps => 24 | make( 25 | ~display=jsProps##display, 26 | ~direction=jsProps##direction, 27 | ~wrap=jsProps##wrap, 28 | ~justify=jsProps##justify, 29 | ~align=jsProps##align, 30 | ~height=jsProps##height, 31 | ~width=jsProps##width, 32 | ~children=jsProps##children 33 | ) 34 | ); 35 | -------------------------------------------------------------------------------- /src/components/Card.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Card"); 2 | 3 | let make = 4 | ( 5 | ~title="Card Title", 6 | ~footer: array(ReasonReact.reactElement)=[||], 7 | ~children 8 | ) => { 9 | ...component, 10 | render: _self => 11 |
    12 |
    13 |
    14 | (ReasonReact.stringToElement(title)) 15 |
    16 | (ReasonReact.arrayToElement(children)) 17 |
    18 |
    (ReasonReact.arrayToElement(footer))
    19 |
    20 | }; 21 | 22 | let default = 23 | ReasonReact.wrapReasonForJs(~component, jsProps => 24 | make( 25 | ~title=jsProps##title, 26 | ~footer= 27 | switch (Js.Null_undefined.to_opt(jsProps##footer)) { 28 | | None => [||] 29 | | Some(footer) => footer 30 | }, 31 | ~children=jsProps##children 32 | ) 33 | ); 34 | -------------------------------------------------------------------------------- /src/components/Panel.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Panel"); 2 | 3 | let make = (~color="grey", ~title="Panel Title", ~extra=?, ~children) => { 4 | ...component, 5 | render: _self => { 6 | let extraComponent = 7 | switch extra { 8 | | None => ReasonReact.nullElement 9 | | Some(extra) => ReasonReact.arrayToElement(extra) 10 | }; 11 |
    12 |
    14 |
    15 | (ReasonReact.stringToElement(title)) 16 |
    17 |
    extraComponent
    18 |
    19 |
    (ReasonReact.arrayToElement(children))
    20 |
    ; 21 | } 22 | }; 23 | 24 | let default = 25 | ReasonReact.wrapReasonForJs(~component, jsProps => 26 | make( 27 | ~color=jsProps##color, 28 | ~title=jsProps##title, 29 | ~extra=?jsProps##extra, 30 | ~children=jsProps##children 31 | ) 32 | ); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tyler Knipfer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/Checkbox.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Checkbox"); 2 | 3 | let make = (~checked=false, ~disabled=false, ~label=?, _children) => { 4 | ...component, 5 | render: _self => { 6 | let labelComponent = 7 | switch label { 8 | | None => ReasonReact.nullElement 9 | | Some(label) => ReasonReact.stringToElement(label) 10 | }; 11 |
    12 | 17 | ( 18 | labelComponent === ReasonReact.stringToElement("") ? 19 | : 23 | ReasonReact.nullElement 24 | ) 25 |
    ; 26 | } 27 | }; 28 | 29 | let default = 30 | ReasonReact.wrapReasonForJs(~component, jsProps => 31 | make( 32 | ~checked=jsProps##checked, 33 | ~disabled=jsProps##disabled, 34 | ~label=jsProps##label, 35 | [||] 36 | ) 37 | ); 38 | -------------------------------------------------------------------------------- /src/components/Navbar.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Navbar"); 2 | 3 | let make = (~color="teal", ~brand="Navbar", ~navRight=?, ~children) => { 4 | ...component, 5 | render: _self => { 6 | let navRightComponent = 7 | switch navRight { 8 | | None => ReasonReact.nullElement 9 | | Some(navRight) => ReasonReact.arrayToElement(navRight) 10 | }; 11 |
    12 |
    13 |
    14 | (ReasonReact.stringToElement(brand)) 15 |
    16 |
    navRightComponent
    17 |
    18 |
    20 | (ReasonReact.arrayToElement(children)) 21 |
    22 |
    ; 23 | } 24 | }; 25 | 26 | let default = 27 | ReasonReact.wrapReasonForJs(~component, jsProps => 28 | make( 29 | ~color=jsProps##color, 30 | ~brand=jsProps##brand, 31 | ~navRight=?jsProps##navRight, 32 | ~children=jsProps##children 33 | ) 34 | ); 35 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import bucklescript from 'rollup-plugin-bucklescript' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | import filesize from 'rollup-plugin-filesize' 5 | import { minify } from 'uglify-es' 6 | import pkg from './package.json' 7 | import postcss from 'rollup-plugin-postcss' 8 | import replace from 'rollup-plugin-replace' 9 | import resolve from 'rollup-plugin-node-resolve' 10 | import uglify from 'rollup-plugin-uglify' 11 | 12 | export default { 13 | input: 'src/index.js', 14 | output: [ 15 | { 16 | file: pkg.main, 17 | format: 'cjs', 18 | sourcemap: true 19 | } 20 | ], 21 | external: ['react', 'react-dom', 'reason-react'], 22 | plugins: [ 23 | postcss({ 24 | extract: true 25 | }), 26 | bucklescript(), 27 | babel({ 28 | exclude: 'node_modules/**' 29 | }), 30 | resolve(), 31 | commonjs(), 32 | replace({ 33 | exclude: 'node_modules/**', 34 | 'process.env.NODE_ENV': JSON.stringify( 35 | process.env.NODE_ENV || 'development' 36 | ) 37 | }), 38 | process.env.NODE_ENV === 'production' && filesize(), 39 | process.env.NODE_ENV === 'production' && uglify({}, minify) 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/components/TruncateText.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | export default class TruncateText extends PureComponent { 4 | static defaultProps = { 5 | wordCount: 10, 6 | toggleColor: 'teal' 7 | } 8 | 9 | state = { 10 | showMore: false 11 | } 12 | 13 | toggleShowMore = () => this.setState({ showMore: !this.state.showMore }) 14 | 15 | render() { 16 | const { showMore } = this.state 17 | const { children, wordCount, toggleColor } = this.props 18 | 19 | const spliced = children 20 | .split(' ') 21 | .splice(0, wordCount) 22 | .join(' ') 23 | 24 | const truncated = 25 | spliced.split('').lastIndexOf(',') === spliced.length - 1 26 | ? spliced.slice(0, spliced.length - 1) 27 | : spliced 28 | 29 | return ( 30 | 31 | {showMore ? children : truncated} 32 | {children.split(' ').length > wordCount && ( 33 | this.toggleShowMore()} 36 | > 37 | {!showMore ? ...more : ...less} 38 | 39 | )} 40 | 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Button.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Button"); 2 | 3 | let make = 4 | ( 5 | ~htmlType="button", 6 | ~fontColor="white", 7 | ~loading=false, 8 | ~width="full", 9 | ~backgroundColor="teal", 10 | ~borderColor="teal", 11 | ~borderRadius="rounded", 12 | ~children 13 | ) => { 14 | ...component, 15 | render: _self => 16 | 25 | }; 26 | 27 | let default = 28 | ReasonReact.wrapReasonForJs(~component, jsProps => 29 | make( 30 | ~htmlType=jsProps##htmlType, 31 | ~fontColor=jsProps##fontColor, 32 | ~loading=jsProps##loading, 33 | ~width=jsProps##width, 34 | ~backgroundColor=jsProps##backgroundColor, 35 | ~borderColor=jsProps##borderColor, 36 | ~borderRadius=jsProps##borderRadius, 37 | ~children=jsProps##children 38 | ) 39 | ); 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tails-UI 2 | React UI components using **tailwindcss** 3 | 4 | ## Components: 5 | - Alert 6 | - Article 7 | - Box 8 | - Breadcrumbs 9 | - Button 10 | - Card 11 | - CodeBlock 12 | - DangerousHTML 13 | - Dropdown 14 | - Flex 15 | - ImageCarousel 16 | - Loader 17 | - MegaFooter 18 | - Modal 19 | - Navbar 20 | - Panel 21 | - Portal 22 | - ProgressBar 23 | - RenderIf 24 | - Select 25 | - Space 26 | - Tab 27 | - Table 28 | - Tabs 29 | - Text 30 | - TextArea 31 | - TextField 32 | - TruncateText 33 | 34 | ## Installation: 35 | `npm i tails-ui` 36 | 37 | ## Usage: 38 | - Make sure to import the tails-ui css file `import 'tails-ui/dist/index.css'` 39 | - Documentation site is coming soon with all the component api's 40 | ``` js 41 | import React from 'react' 42 | import { Button } from 'tails-ui' 43 | 44 | const Button = () => ( 45 | 53 | ) 54 | 55 | export default Button 56 | 57 | ``` 58 | 59 | ## Running Locally: 60 | - You can run `npm link` inside **tails-ui** and then `npm link tails-ui` inside a project you have created to test the components locally. 61 | - You can run `npm start` to start **tails-ui** in watch mode and it will compile any new components you add. 62 | - `npm test` will run jest 63 | 64 | -------------------------------------------------------------------------------- /src/components/Tabs.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | export default class Tabs extends PureComponent { 4 | static defaultProps = { 5 | position: 'start' 6 | } 7 | 8 | state = { 9 | activeTabIndex: this.props.defaultActiveTabIndex 10 | } 11 | 12 | handleTabClick = tabIndex => { 13 | this.setState({ 14 | activeTabIndex: tabIndex 15 | }) 16 | } 17 | 18 | renderChildrenWithTabsApiAsProps = () => { 19 | return React.Children.map(this.props.children, (child, index) => { 20 | return React.cloneElement(child, { 21 | onClick: this.handleTabClick, 22 | tabIndex: index, 23 | isActive: index === this.state.activeTabIndex 24 | }) 25 | }) 26 | } 27 | 28 | renderActiveTabContent = () => { 29 | const { children } = this.props 30 | const { activeTabIndex } = this.state 31 | if (children[activeTabIndex]) { 32 | return children[activeTabIndex].props.children 33 | } 34 | } 35 | 36 | render() { 37 | const { position } = this.props 38 | return ( 39 | 40 |
      41 | {this.renderChildrenWithTabsApiAsProps()} 42 |
    43 |
    {this.renderActiveTabContent()}
    44 |
    45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/Table.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | export default class Table extends PureComponent { 4 | generateHeaders = () => { 5 | const cols = this.props.cols 6 | return cols.map(colData => { 7 | return ( 8 | 9 | {colData.label} 10 | 11 | ) 12 | }) 13 | } 14 | 15 | generateRows = () => { 16 | const cols = this.props.cols 17 | const data = this.props.data 18 | return data.map(item => { 19 | var cells = cols.map((colData, index) => { 20 | return ( 21 | 22 | {item[colData.key]} 23 | 24 | ) 25 | }) 26 | return ( 27 | 28 | {cells} 29 | 30 | ) 31 | }) 32 | } 33 | 34 | render() { 35 | const headers = this.generateHeaders() 36 | const rows = this.generateRows() 37 | return ( 38 |
    39 | 40 | 41 | {headers} 42 | 43 | {rows} 44 |
    45 |
    46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Modal.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Modal"); 2 | 3 | let make = (~title="Modal Title", ~footer=?, ~children) => { 4 | ...component, 5 | render: _self => { 6 | let footerComponent = 7 | switch footer { 8 | | None => ReasonReact.nullElement 9 | | Some(footer) => ReasonReact.arrayToElement(footer) 10 | }; 11 |
    12 |
    13 |
    15 |
    16 |
    17 | (ReasonReact.stringToElement(title)) 18 |
    19 | 24 |
    25 |
    (ReasonReact.arrayToElement(children))
    26 |
    footerComponent
    27 |
    28 |
    ; 29 | } 30 | }; 31 | 32 | let default = 33 | ReasonReact.wrapReasonForJs(~component, jsProps => 34 | make( 35 | ~title=jsProps##title, 36 | ~footer=?jsProps##footer, 37 | ~children=jsProps##children 38 | ) 39 | ); 40 | -------------------------------------------------------------------------------- /src/components/Article.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Article"); 2 | 3 | let make = 4 | ( 5 | ~title="Article Title", 6 | ~author="Article Author", 7 | ~description="Article Description", 8 | ~abstract="Article Abstract", 9 | ~footer: array(ReasonReact.reactElement)=[||], 10 | _children 11 | ) => { 12 | ...component, 13 | render: _self => 14 |
    15 |

    16 | (ReasonReact.stringToElement(title)) 17 |

    18 |

    (ReasonReact.stringToElement(author))

    19 |

    20 | (ReasonReact.stringToElement(description)) 21 |

    22 |

    23 | (ReasonReact.stringToElement(abstract)) 24 |

    25 |
    26 | (ReasonReact.arrayToElement(footer)) 27 |
    28 |
    29 | }; 30 | 31 | let default = 32 | ReasonReact.wrapReasonForJs(~component, jsProps => 33 | make( 34 | ~title=jsProps##title, 35 | ~author=jsProps##author, 36 | ~description=jsProps##description, 37 | ~abstract=jsProps##abstract, 38 | ~footer= 39 | switch (Js.Null_undefined.to_opt(jsProps##footer)) { 40 | | None => [||] 41 | | Some(footer) => footer 42 | }, 43 | [||] 44 | ) 45 | ); 46 | -------------------------------------------------------------------------------- /src/components/TextField.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("TextField"); 2 | 3 | let make = 4 | ( 5 | ~label=?, 6 | ~value=?, 7 | ~htmlType="text", 8 | ~name=?, 9 | ~placeholder=?, 10 | ~color="teal", 11 | ~hasError=false, 12 | _children 13 | ) => { 14 | ...component, 15 | render: _self => { 16 | let errorStyle = 17 | [ 18 | Js.Boolean.to_js_boolean(hasError) === Js.Boolean.to_js_boolean(true) ? 19 | "border-red hover:border-red" : 20 | {j|border-grey-light hover:border-$(color)|j} 21 | ] 22 | |> String.concat(""); 23 | let labelComponent = 24 | switch label { 25 | | None => ReasonReact.nullElement 26 | | Some(label) => ReasonReact.stringToElement(label) 27 | }; 28 |
    29 | ( 30 | labelComponent === ReasonReact.stringToElement("") ? 31 | : 35 | ReasonReact.nullElement 36 | ) 37 | 44 |
    ; 45 | } 46 | }; 47 | 48 | let default = 49 | ReasonReact.wrapReasonForJs(~component, jsProps => 50 | make( 51 | ~label=?jsProps##label, 52 | ~value=?jsProps##value, 53 | ~htmlType=jsProps##htmlType, 54 | ~name=?jsProps##name, 55 | ~placeholder=?jsProps##placeholder, 56 | ~color=jsProps##color, 57 | ~hasError=?jsProps##hasError, 58 | [||] 59 | ) 60 | ); 61 | -------------------------------------------------------------------------------- /src/components/ImageCarousel.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | export default class ImageCarousel extends PureComponent { 4 | static defaultProps = { 5 | color: 'grey' 6 | } 7 | 8 | state = { 9 | selectedImage: 0 10 | } 11 | 12 | handleSelect = selectedImage => { 13 | this.setState({ selectedImage }) 14 | } 15 | 16 | render() { 17 | const { images, color } = this.props 18 | const { selectedImage } = this.state 19 | const showNav = images && images.length > 1 20 | return ( 21 |
    22 |
    25 | {images && 26 | images[selectedImage].src && ( 27 | {images[selectedImage].alt} 32 | )} 33 |
    34 | {showNav && ( 35 |
    36 | {images && 37 | images.map((image, i) => ( 38 |
    42 |
    this.handleSelect(i)} 45 | > 46 | {image.alt} 51 |
    52 |
    53 | ))} 54 |
    55 | )} 56 |
    57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Select.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Select"); 2 | 3 | let make = 4 | (~label=?, ~value=?, ~name=?, ~placeholder=?, ~color="teal", ~children) => { 5 | ...component, 6 | render: _self => { 7 | let labelComponent = 8 | switch label { 9 | | None => ReasonReact.nullElement 10 | | Some(label) => ReasonReact.stringToElement(label) 11 | }; 12 |
    13 | ( 14 | labelComponent === ReasonReact.stringToElement("") ? 15 | : 19 | ReasonReact.nullElement 20 | ) 21 | 28 |
    30 | 34 | 37 | 38 |
    39 |
    ; 40 | } 41 | }; 42 | 43 | let default = 44 | ReasonReact.wrapReasonForJs(~component, jsProps => 45 | make( 46 | ~label=?jsProps##label, 47 | ~value=?jsProps##value, 48 | ~name=?jsProps##name, 49 | ~placeholder=?jsProps##placeholder, 50 | ~color=jsProps##color, 51 | ~children=jsProps##children 52 | ) 53 | ); 54 | -------------------------------------------------------------------------------- /src/components/TextArea.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("TextArea"); 2 | 3 | let make = 4 | ( 5 | ~label=?, 6 | ~value=?, 7 | ~htmlType=?, 8 | ~name=?, 9 | ~placeholder=?, 10 | ~color="teal", 11 | ~rows=?, 12 | ~hasError=false, 13 | _children 14 | ) => { 15 | ...component, 16 | render: _self => { 17 | let errorStyle = 18 | [ 19 | Js.Boolean.to_js_boolean(hasError) === Js.Boolean.to_js_boolean(true) ? 20 | "border-red hover:border-red" : 21 | {j|border-grey-light hover:border-$(color)|j} 22 | ] 23 | |> String.concat(""); 24 | let labelComponent = 25 | switch label { 26 | | None => ReasonReact.nullElement 27 | | Some(label) => ReasonReact.stringToElement(label) 28 | }; 29 |
    30 | ( 31 | labelComponent === ReasonReact.stringToElement("") ? 32 | : 36 | ReasonReact.nullElement 37 | ) 38 |