Procházet zdrojové kódy

Create Tabs component

Tatiana Inama před 7 roky
rodič
revize
8eb1b6e5ab

+ 0 - 1
recipes/.storybook/config.js

@@ -1,5 +1,4 @@
 import { configure } from '@storybook/react';
-import '../src/index.css';
 
 const req = require.context('../src/components', true, /\.stories\.js$/);
 

+ 3 - 0
recipes/.storybook/preview-head.html

@@ -0,0 +1,3 @@
+<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lekton">

+ 4 - 0
recipes/package.json

@@ -12,7 +12,11 @@
     "@material/react-list": "^0.11.0",
     "@material/react-material-icon": "^0.12.0",
     "@material/react-select": "^0.12.1",
+    "@material/react-tab": "^0.13.0",
+    "@material/react-tab-bar": "^0.13.0",
+    "@material/react-tab-indicator": "^0.13.0",
     "@material/react-text-field": "^0.11.0",
+    "@material/ripple": "^2.3.0",
     "@types/jest": "^24.0.11",
     "@types/node": "^11.13.0",
     "@types/react": "^16.8.12",

+ 1 - 1
recipes/src/components/Button/index.tsx

@@ -21,7 +21,7 @@ export default function CBKButton(props: any) {
   if (props.icon) {
     return (
       <IconButton onClick={props.onClick} {...props}>
-        <MaterialIcon icon={props.icon} />
+        <MaterialIcon icon={props.icon}/>
       </IconButton>
     );
   } else {

+ 128 - 0
recipes/src/components/IngredientForm/index.tsx

@@ -0,0 +1,128 @@
+import React from 'react';
+import Input from 'components/Input';
+import Select from 'components/Select';
+import List, {ListItem, ListItemText} from '@material/react-list';
+import Tab from '@material/react-tab';
+import TabBar from '@material/react-tab-bar';
+
+import IRecipe, { ISubRecipe, IAuthor, IDetails, _recipe, _subRecipe, _ingredient, IIngredient, ISuggestion } from 'types/recipes';
+
+import '@material/react-list/dist/list.min.css';
+import '@material/react-tab-bar/dist/tab-bar.css';
+import '@material/react-tab-scroller/dist/tab-scroller.css';
+import '@material/react-tab/dist/tab.css';
+import '@material/react-tab-indicator/dist/tab-indicator.css';
+import './styles.scss';
+import MaterialIcon from '@material/react-material-icon';
+
+type IngredientFormProps = {
+  components: ISubRecipe[],
+  updateField: (key: any[]) => (e: any) => void,
+
+};
+
+class IngredientForm extends React.Component<IngredientFormProps, { activeGroup: number }>{
+
+  constructor(props: IngredientFormProps) {
+    super(props);
+    this.state = {
+      activeGroup: 0,
+    };
+  }
+
+  handleActiveIndexUpdate = (activeGroup: number) => this.setState({ activeGroup })
+
+  changeTabName = (event: any) => {
+    console.log('change', event);
+  }
+
+  render() {
+    const {updateField, components} = this.props;
+
+    return (
+      <div className='cbk-ingredient-form'>
+        <div className='cbk-ingredients-subrecipe-list'>
+          <TabBar>
+          {
+            components.map((group, groupIdx) => (
+              // <div>
+              //   <Input
+              //     label='sub recipe name'
+              //     value={group.name}
+              //     style='display'
+              //     onChange={updateField(['ingredients', groupIdx, 'name'])}
+              //   />
+              // </div>
+              <Tab key={groupIdx}>
+                <Input
+                   label='sub recipe name'
+                   value={group.name}
+                   style='display'
+                   onChange={updateField(['ingredients', groupIdx, 'name'])}
+                />
+              </Tab>
+            ))
+          }
+          </TabBar>
+        </div>
+        {
+          components.map((group, groupIdx) => (
+            <div key={groupIdx} className='cbk-ingredients-component'>
+              {/* <Input
+                label='group name'
+                value={group.name}
+                onChange={updateField(['ingredients', groupIdx, 'name'])}
+              /> */}
+              <div className='cbk-ingredients-component-list'>
+                <div className='cbk-ingredients-component-header'>
+                  <div className='cbk-title'>Ingredient Name</div>
+                  <div className='cbk-title'>Quantity</div>
+                  <div className='cbk-title'>Unit</div>
+                  <div className='cbk-title'>Notes</div>
+                  <div className='cbk-title'>Original</div>
+                </div>
+                {
+                  group.ingredients.map((ingredient, ingredientIdx) => (
+                    <div className='cbk-ingredients-component-item' key={ingredientIdx}>
+                      <Input
+                        label=''
+                        value={ingredient.name}
+                        onChange={updateField(['ingredients', groupIdx, 'ingredients', ingredientIdx, 'name'])}
+                      />
+                      <Input
+                        label=''
+                        value={ingredient.quantity}
+                        onChange={updateField(['ingredients', groupIdx, 'ingredients', ingredientIdx, 'quantity'])}
+                      />
+                      <Select
+                        label=''
+                        options={[
+                          {label: 'grams', value: 'gr'},
+                          {label: 'cups', value: 'cup'},
+                          {label: 'mililiters', value: 'ml'},
+                        ]}
+                        onChange={(x) => {console.log('selected', x)}}
+                      />
+                      <Input
+                        label=''
+                        value={''}
+                        onChange={updateField(['ingredients', groupIdx, 'ingredients', ingredientIdx, '_original'])}
+                      />
+                      <Input
+                        label=''
+                        value={ingredient._original}
+                        onChange={updateField(['ingredients', groupIdx, 'ingredients', ingredientIdx, '_original'])}
+                      />
+                    </div>
+                  ))
+                }
+              </div>
+            </div>
+          ))
+        }
+      </div>
+    )
+  }
+}
+
+export default IngredientForm;

+ 22 - 0
recipes/src/components/IngredientForm/styles.scss

@@ -0,0 +1,22 @@
+.cbk-ingredient-form {
+  .cbk-ingredients-subrecipe-list {
+    display: flex;
+    border: 1px dashed rgba(0,0,0,0.20);
+  }
+  .cbk-ingredients-component {
+    border: 1px dashed rgba(0,0,0,0.20);
+    &-header {
+      display: flex;
+      .cbk-title {
+        flex: 1;
+        align-self: center;
+      }
+    }
+    &-item {
+      display: flex;
+      div {
+        flex: 1;
+      }
+    }  
+  }
+}

+ 2 - 2
recipes/src/components/Input/styles.scss

@@ -18,7 +18,7 @@
 
 .cbk-input.mdc-text-field {
   width: 100%;
-  background-color: white;
+  background-color: rgba(0,0,0,0);
   
   input {
     border-bottom: 1px dashed rgba(0, 0, 0, 0.2)!important;
@@ -54,7 +54,7 @@
 .cbk-input.mdc-text-field--focused,
 .cbk-input.mdc-text-field:hover,
 .mdc-text-field__input:hover {
-  background-color: white;
+  background-color: rgba(0,0,0,0);
 }
 
 .mdc-text-field:hover::before {

+ 118 - 0
recipes/src/components/TabBar/index.tsx

@@ -0,0 +1,118 @@
+import React from 'react';
+import Tab from '@material/react-tab';
+import TabBar from '@material/react-tab-bar';
+import TabIndicator from '@material/react-tab-indicator';
+import Input from 'components/Input';
+import Button from 'components/Button';
+import { assocPath, remove } from 'ramda';
+import classnames from 'classnames';
+
+import '@material/react-list/dist/list.min.css';
+import '@material/react-tab-bar/dist/tab-bar.css';
+import '@material/react-tab-scroller/dist/tab-scroller.css';
+import '@material/react-tab/dist/tab.css';
+import '@material/react-tab-indicator/dist/tab-indicator.css';
+
+import './styles.scss';
+
+type TabBarProps = {
+  tabs: string[]
+  onUpdateTab?: (newValue: string) => void,
+  onDeleteTab?: (deletedIndex: number) => void,
+  onClickText?: (e: any) => void,
+  onClickTab?: (e: any) => void,
+}
+
+type TabBarState = {
+  activeIndex: number,
+  tabs: string[],
+}
+
+class CBKTabBar extends React.Component<TabBarProps, TabBarState> {
+  constructor(props: TabBarProps) {
+    super(props);
+    this.state = {
+      activeIndex: 0,
+      tabs: props.tabs
+    };
+  }
+
+  handleActiveIndexUpdate = (activeIndex:number) => this.setState({activeIndex});
+
+  updateTabs = (key: string|number[]) => {
+    return (e: any) => {
+      const newValue = e.currentTarget.value;
+      this.setState({
+        tabs: assocPath(key, newValue, this.state.tabs)
+      });
+    }
+  }
+
+  deleteTab = (index: number) => {
+    return (e: any) => {
+      this.setState({
+        tabs: remove(index, 1, this.state.tabs)
+      })
+    }
+  }
+
+  clickTab = (activeIndex: number) => (event: any) => {
+    this.setState({
+      activeIndex
+    })
+  } 
+
+  addTab = (e: any) => {
+    this.setState({
+      tabs: this.state.tabs.concat([''])
+    })
+  }
+
+  render () {
+    const { onClickText, onClickTab } = this.props;
+    const { tabs, activeIndex } = this.state;
+
+    return (
+      <div>
+        {/* <TabBar
+          activeIndex={this.state.activeIndex}
+          handleActiveIndexUpdate={this.handleActiveIndexUpdate}
+        >
+          { tabs.map((tab, index) => (
+              <Tab key={index}>
+                {tab}
+              </Tab>
+            ))
+          }
+        </TabBar> */}
+        <div className='cbk-tab-bar' role="tablist">
+          { tabs.map((tab, index) => (
+            <div
+              className={classnames({
+                'cbk-tab': true,
+                'cbk-tab--active': index === activeIndex
+              })}
+              onClick={this.clickTab(index)}>
+              <div className='cbk-tab__content'> 
+                <Input
+                  value={tab}
+                  label=''
+                  onChange={this.updateTabs([index])}
+                />
+              </div>
+              <div className='cbk-tab__actions'>
+                <Button icon='clear' onClick={this.deleteTab(index)}></Button>
+              </div>
+              <TabIndicator active={index === activeIndex}/>
+            </div>
+          )) }
+          <div className='cbk-tab cbk-tab-add'>
+            <Button icon='add' onClick={this.addTab}></Button>
+          </div>
+        </div>
+      </div>
+    )
+  }
+}
+
+export default CBKTabBar;

+ 63 - 0
recipes/src/components/TabBar/styles.scss

@@ -0,0 +1,63 @@
+@import 'styles/_variables.scss';
+
+.cbk-tab-bar {
+  display: flex;
+  justify-content: space-between;
+
+  .cbk-tab {
+    display: flex;
+    align-items: flex-end;
+    justify-content: center;
+    flex: 1 0 auto;
+    padding: 0 $spacing-regular;
+    position: relative;
+
+    &:hover {
+      background-color: rgb(248, 248, 248);
+      cursor: pointer;
+    }
+
+    &--active {
+      background-color: rgb(248, 248, 248);
+    }
+
+    &__content {
+      font-size: 0.875rem;
+      line-height: 2.25rem;
+      font-weight: 500;
+      letter-spacing: 0.08929em;
+      text-decoration: none;
+      text-transform: uppercase;
+      flex-basis: 95%;
+
+      .cbk-input input {
+        padding: 0;
+        text-align: left;
+        border-bottom: 0px!important;
+        font-weight: 500;
+        font-size: $font-size-regular;
+      }
+    }
+
+    &__actions {
+      align-self: center;
+      .mdc-icon-button {
+        --mdc-ripple-fg-size: 14px;
+        --mdc-ripple-left: 5px;
+        --mdc-ripple-top: 5px;
+        width: 24px;
+        height: 24px;
+        padding: 6px;
+        font-size: 12px;
+
+        .material-icons {
+          font-size: 12px;
+        }
+      }
+    }
+
+    &-add {
+      align-self: center;
+    }
+  }
+}

+ 15 - 0
recipes/src/components/TabBar/tabBar.stories.js

@@ -0,0 +1,15 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import TabBar from './index';
+
+const tabs = [
+  'for the chicken',
+  'for the sauce',
+  'for the rice',
+  'for the extra steps',
+]
+
+storiesOf('TabBar', module)
+  .add('eee', () => <TabBar tabs={tabs} onClickText={action('text-click')} onClickTab={action('tab-click')}/>)

+ 25 - 5
recipes/src/containers/Recipes/Create.tsx

@@ -3,17 +3,17 @@ import { assocPath, remove } from 'ramda';
 import  Navbar from 'components/Navbar';
 import { Grid, Row, Cell } from '@material/react-layout-grid';
 import Btn from 'components/Button';
-
 import Input from 'components/Input';
 import TagInput from 'components/TagInput';
 import Select from 'components/Select';
+import IngredientForm from 'components/IngredientForm';
 
 import './styles.scss';
 
 import { scrapeRecipe } from './services';
 
 import sample_img from "../../sample.png";
-import IRecipe, { ISubRecipe, IAuthor, IDetails, _recipe, _subRecipe, _ingredient, IIngredient } from 'types/recipes';
+import IRecipe, { ISubRecipe, IAuthor, IDetails, _recipe, _subRecipe, _ingredient, IIngredient, ISuggestion } from 'types/recipes';
 
 type CreateRecipeProps = {
 };
@@ -25,6 +25,16 @@ type CreateRecipeState = {
 
 type FormKeys = keyof IRecipe | keyof ISubRecipe | keyof IIngredient | keyof IAuthor | keyof IDetails | number;
 
+const Suggestions = (suggestions: ISuggestion[] = []) => (
+  <div>
+    {
+      suggestions.map(suggestion => (
+        <Btn>{suggestion.name}</Btn>
+      ))
+    }
+  </div>
+)
+
 class CreateRecipe extends React.Component<CreateRecipeProps, CreateRecipeState> {
   constructor(props: any) {
     super(props);
@@ -257,8 +267,12 @@ class CreateRecipe extends React.Component<CreateRecipeProps, CreateRecipeState>
             </section>
 
             <h5>Ingredients</h5>
-            <section>  
-              {
+            <section>
+              <IngredientForm
+                components={form.ingredients}
+                updateField={this.updateField}
+              />
+              {/* {
                 form.ingredients.map((group, i) => (
                   <div key={i}>
                     <Row>
@@ -271,6 +285,7 @@ class CreateRecipe extends React.Component<CreateRecipeProps, CreateRecipeState>
                         />
                       </Cell>
                     </Row>
+                    <div>
                     {
                       group.ingredients.map((ingredient, j) => (
                         <Row key={j}>
@@ -307,12 +322,17 @@ class CreateRecipe extends React.Component<CreateRecipeProps, CreateRecipeState>
                               button={this.actionButton(form.ingredients[i].ingredients, j, this.addIngredient(i, j), this.removeIngredient(i, j))}
                             />
                           </Cell>
+                          <Cell columns={12}>
+                            { Suggestions(ingredient.suggestions) }
+                          </Cell>
                         </Row>
                       ))
                     }
+                    </div>
+                    
                   </div>
                 ))
-              }
+              } */}
             </section>
 
             <h5>Instructions</h5>

+ 14 - 0
recipes/src/styles/_mixins.scss

@@ -0,0 +1,14 @@
+@mixin ripple($color, $active-color) {
+  background-position: center;
+  transition: background 0.5s;
+
+  &:hover { 
+    background: $color radial-gradient(circle, transparent 1%, $color 1%) center/15000%;
+  }
+
+  &:active { 
+    background-color: $active-color;
+    background-size: 100%;
+    transition: background 0s;
+  }
+}

+ 1 - 0
recipes/src/styles/_variables.scss

@@ -1,4 +1,5 @@
 @import './_colors.scss';
 @import './_fonts.scss';
+@import './_mixins.scss';
 @import './ui.scss';
 @import './spacing.scss';

+ 26 - 0
recipes/src/types/recipes.ts

@@ -3,6 +3,7 @@ export interface IIngredient {
   quantity: number,
   unit: string,
   _original: string,
+  suggestions?: ISuggestion[]
 }
 
 export interface ISubRecipe {
@@ -53,6 +54,31 @@ export const _recipe: IRecipe = {
   tags: []
 }
 
+export interface IEquivalences {
+  cup?: number,
+  tbsp?: number,
+  tsp?: number,
+  stick?: number,
+  gr?: number,
+  ml?: number,
+  oz?: number,
+  lb?: number,
+}
+
+export interface ISuggestion {
+  _id?: string,
+  name: string,
+  variants?: string[],
+  equivalences: IEquivalences,
+  prefferedUnit: keyof IEquivalences,
+  referenceUnit: keyof IEquivalences,
+  translation?: {
+    english?: string,
+    dutch?: string,
+    spanish?: string
+  }
+}
+
 export default interface IRecipe {
   _id: string,
   author: IAuthor,