tsx 带URL路径的故事书

.md
For stories to use URL Path
### When?
- Components works differently by URL path

### Solution
1. use mobx stores to save path whenever `Nav` components called
2. use that path in stories file

---
### Why Mobx over WithRouter?
- Checking URLs to set condition whenever WithRouter changes are **Expensive**
- Having one and only store for state
- WithRouter can be replaced
stories.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import ReportablesSection from 'src/components/reportables/Section';
import { REPORTABLES_URL } from 'src/navigation/urls';
import { WithStore } from 'src/_mobx';

storiesOf('ReportablesSection', module)
  .add('default', () => (
    <WithStore>
      {store => {
        store.byPath.setPath(REPORTABLES_URL)
        return (
          <ReportablesSection reportables={REPORTABLES} />
        )
      }}
    </WithStore>
  ))

const REPORTABLES = ...
byPath.ts
import { observable, action, computed } from 'mobx';
import { REPORTABLES_URL, EXPENSES_URL, BOSIGEUM_URL, WAREHOUSEINGS_URL } from '../../navigation/urls';

export class ByPath {
  @observable path = ''
  @action setPath = (path: string) => {
    this.path = path
  }

  /**
   * used for
   * - hide individual reportable createdWhen
   * - show Calendar in Option
   */
  @computed get isReportablesView() {
    const urls = [REPORTABLES_URL, EXPENSES_URL, BOSIGEUM_URL, WAREHOUSEINGS_URL]
    for (var key in urls) {
      if (this.path.includes(urls[key]))
        return true
    }
    return false
  }
}
usage.tsx
import React from 'react'
import moment from 'moment';
import { WithStore } from '../../_mobx';
import styled from 'styled-components';

const Date = styled.span`
  flex: 0 0 auto;
  width: 5em;
`

type IPureCreatedWhenProps = { value: string }
export const PureCreatedWhen: React.FC<IPureCreatedWhenProps> = ({ value }) => (
  <Date>
    {moment(value).format("YY/MM/DD")}
  </Date>
)

type PropsType = IPureCreatedWhenProps
const CreatedWhen: React.FC<PropsType> = (props) => {
  return (
    <WithStore>
      {store => {
\!h        if (store.byPath.isReportablesView)
          return null
        return (<div>
          <PureCreatedWhen {...props}></PureCreatedWhen>
        </div>)
      }}
    </WithStore>
  )
}
export default CreatedWhen

tsx moment.js locale bug修复

.tsx
import "moment/locale/ko"
/**
 * some how `moment.locale('ko')` is not working for CRA
 * so I have to use workaround of using `import "moment/locale/ko"`
 */
moment.locale('ko') 

tsx 抽屉(材料-ui)

.tsx
import React from 'react';
import Drawer from '@material-ui/core/Drawer';

class BottomDrawer extends React.Component<{
  button: (show: () => void) => React.ReactNode
  render: (close: () => void) => React.ReactNode
}> {
  state = {
    show: false,
  };

  toggleDrawer = (open: boolean) => () => {
    this.setState({
      show: open,
    });
  };

  render() {
    return (
      <div>
        {this.props.button(this.toggleDrawer(true))}

        <Drawer
          anchor="bottom"
          open={this.state.show}
          onClose={this.toggleDrawer(false)}
        >
          {this.props.render(this.toggleDrawer(false))}
        </Drawer>
      </div>
    );
  }
}

export default BottomDrawer

tsx withRouter模式:按URL路径更改

.tsx
import React from 'react'
import { IRite } from '../rites/byPerson/Query';
import { Wrapper, CreatedWhen, FlexEnd, Control } from '../../common/styled/ReportableElement';
import { RiteTitle, PersonName } from '../../common/badges';
import { Summary } from '../../common/styled/FinancialComponents';
import RiteUpdateModal from '../Update/Modal';
import RiteDeleteButton from '../Delete';
import { withRouter, RouteComponentProps } from 'react-router-dom'
import { RITE_TITLES_URL } from '../../../navigation/RiteTitlesNav';

const PureRiteView: React.FC<{
  rite: IRite,
  showName?: boolean
}> = (props) => {
  const { createdWhen, title, amount, quantity, person } = props.rite
  return (
    <Wrapper>
      <CreatedWhen value={createdWhen} />
      <RiteTitle title={title} />
      {props.showName && <PersonName person={person} />}
      <FlexEnd>
        <Summary amount={amount} quantity={quantity} />
        <Control>
          <RiteUpdateModal rite={props.rite} />
          <RiteDeleteButton id={props.rite.id} />
        </Control>
      </FlexEnd>
    </Wrapper>
  )
}

type PropsType = RouteComponentProps<any> & {
  rite: IRite
}
const PureRiteViewWithRouter: React.FC<PropsType> = (props) => {
  const showName = props.location.pathname.includes(RITE_TITLES_URL) ? true : false
  return (
    <PureRiteView {...props} showName={showName} />
  )
}
const RiteView = withRouter(PureRiteViewWithRouter)

export default RiteView


tsx React-select和mobx

要使用react-select <br/>,选项必须看起来像<br/>```js <br/> {value:'normal',label:'원'} <br/>``` <br/>这对于其他React组件<br/>来说有点太多了,所以我使用enum和@computed值来更容易使用

mobx.ts
export enum CurrencyUnit {
  Normal,
  TenThoushand
}

//react-select adapter
export type CurrencyOption =
  { value: CurrencyUnit.Normal, label: '원' } |
  { value: CurrencyUnit.TenThoushand, label: '만원' }
export const currencyOptions: CurrencyOption[] = [
  { value: CurrencyUnit.Normal, label: '원' },
  { value: CurrencyUnit.TenThoushand, label: '만원' },
]

class Currency {
  //react-select adapter
  @observable option = currencyOptions[0]
  @action selectOption = (option: CurrencyOption | undefined) => {
    if (option === undefined)
      throw `Not existing Currency option of ${JSON.stringify(option)}`
    this.option = option
  }

  //it is actually used from other components for easier use
  @computed get currencyUnit(){
    return this.option.value
  }
}
ReactSelect.tsx
import React from 'react'
import { WithStore } from '../../_mobx/WithStore';
import { CurrencyOption, currencyOptions } from '../../_mobx/MobxWrapper';

import Select from 'react-select';

export const PureSelectFilter: React.FC<{
  value?: CurrencyOption,
  handleChange?: any
}> = (props) => {
  return (
    <Select
      value={props.value}
      onChange={props.handleChange}
      options={currencyOptions}
    />
  )
}

const Unit = () => {
  return (<WithStore>
    {store => {
      return (
        <div>
          현금 단위 선택
            <PureSelectFilter
            value={store.currency.option}
            handleChange={(selectedOption: CurrencyOption) => store.currency.selectOption(selectedOption)}
          />
        </div>
      )
    }}
  </WithStore>)
}

export default Unit
Usages.tsx
import React from 'react'
import styled from 'styled-components';
import { WithStore } from '../../_mobx/WithStore';
import { CurrencyUnit } from '../../_mobx/MobxWrapper';
import { toMoney } from '../../_utils/formats';


const MoneyFrame = styled("div") <{ unit: CurrencyUnit }>`
  text-align: right;
  width: ${props => props.unit === CurrencyUnit.TenThoushand ? "5em" : "6em"}; 
`
export const Money: React.FC<any> = ({ children }) => {
  return (
    <WithStore>
      {store => (
        <MoneyFrame unit={store.currency.currencyUnit}>
          {toMoney(children, store.currency.currencyUnit)}
        </MoneyFrame>
      )}
    </WithStore>
  )
}

tsx Transculent(透明)反应容器

.tsx
import React from 'react'
import styled from 'styled-components';

/**
 * I don't understand it cearly
 * check this link 
 * https://medium.freecodecamp.org/z-index-explained-how-to-stack-elements-using-css-7c5aa0f179b3
 * to figure out better idea of why
 */
const Base = styled.div`
  position: relative;
  z-index: 0;
`

const Cover = styled.div<{ cover: boolean }>`
  display: inline-block;
  background: #e3e3e3bb;
  z-index: ${props => props.cover ? 1 : 0};

  :hover{
    cursor: ${props => props.cover ? 'not-allowed' : 'auto'};
  }
`

const Content = styled.div<{ cover: boolean }>`
  z-index: ${props => props.cover ? -1 : 0};
  position: relative;
  :hover{
    cursor: ${props => props.cover ? 'not-allowed' : 'auto'};
  }
`

const Translucent: React.FC<{
  cover: boolean,
  handleClick: any,
  children: any
}> = (props) => (
  <Base>
    <Cover cover={props.cover}
      onClick={props.handleClick}>
      <Content cover={props.cover}>
        {props.children}
      </Content>
    </Cover>
  </Base>
)

export default Translucent

tsx DateFilter模态备份

index.tsx
import React from 'react'
import Modal from '../../../_utils/Modal';
import { WithStore } from '../../../_mobx/WithStore';
import SelectFilter from '../SelectFilter';
import Calendar from '../Calendar';

const DateModal: React.FC<{
  isOpen?: boolean
}> = (props) => {
  return (
    <div>
      <Modal contentLabel="날짜 선택" {...props}>
        {() => (
          <div>
            <SelectFilter />
            <Calendar />
          </div>
        )}
      </Modal>

      <WithStore>
        {store => (<div>
          <div>{JSON.stringify(store.filter.start)}</div>
          <div>{JSON.stringify(store.filter.end)}</div>
        </div>)}
      </WithStore>
    </div>
  )
}

export default DateModal

tsx 打字稿

TypeScript.tsx
const { languageCode } = getLocales() as RNLocalize.Locale; // castowanie, że jesteśmy pewni, że coś zwróci

tsx Manager.tsx

Manager.tsx
import { Data, animate, Override, Animatable } from 'framer'
import * as MapboxGL from '@madlan145/mapbox-gl'
import { Popup } from '@madlan145/react-mapbox-gl'

interface Marker {
	offset?: MapboxGL.PointLike
	coordinates: MapboxGL.LngLatLike
	anchor?: MapboxGL.Anchor
	popup?: React.ReactElement<typeof Popup>
	draggable?: boolean
}

interface ManagerOptions {
	longitude?: number
	latitude?: number
	zoom?: number
	pitch?: number
	bearing?: number
}

const ManagerPropsDefaults = {
	longitude: 0,
	latitude: 0,
	zoom: 0,
	pitch: 0,
	bearing: 0,
}

/**
 * A manager for the Localize Map Component.
 * Handles imperative methods and controls the component.
 * @todo Introduce more props and methods to be controlled
 * @todo Control layers via this manager
 * @todo Control sources via this manager
 */
class Manager {
	loaded = false
	markers: Marker[] = []
	animation = {}
	map: MapboxGL.Map
	state = Data(ManagerPropsDefaults)
	mapView: any

	constructor(params = {} as ManagerOptions) {
		const {
			longitude = 0,
			latitude = 0,
			zoom = 3,
			pitch = 0,
			bearing = 0,
		} = params

		this.state = Data({ longitude, latitude, zoom, pitch, bearing })
	}

	/**
	 * Fly the camera to a given coordinate.
	 * @param {Number} longitude - The longitude of the destination
	 * @param {Number} latitude - The latitude of the destination
	 */
	flyTo = (longitude: number, latitude: number) => {
		this.setState({ longitude, latitude })
	}

	/**
	 * Set the camera to a given zoom.
	 * @param {Number} zoom - The zoom level, between 0 and 20.
	 */
	setZoom = (zoom: number) => {
		zoom = Math.min(Math.max(zoom, 0), 20)
		this.setState({ zoom })
	}

	/**
	 * Rotate the camera to a given bearing.
	 * @param {Number} bearing - The bearing amount.
	 */
	setBearing = (bearing: number) => {
		this.setState({ bearing })
	}

	/**
	 * Set the camera's pitch.
	 * @param {Number} pitch - The pitch amount.
	 */
	setPitch = (pitch: number) => {
		this.setState({ pitch })
	}

	/**
	 * Set multiple values of the camera' state at once.
	 * @param {ManagerOptions} options - The options to use.
	 * @param {Number} options.longitude
	 * @param {Number} options.latitude
	 * @param {Number} options.zoom
	 * @param {Number} options.bearing
	 * @param {Number} options.pitch
	 */
	setState = (options: ManagerOptions) => {
		Object.assign(this.state, options)
	}

	/**
	 * Connect a Localize Map Component instance to this Manager.
	 * @param {*} props - The props of the localize map component
	 */
	connect(props: any) {
		if (this.loaded) return
		this.loaded = true

		this.mapView = props.children[0]

		const { longitude, latitude, zoom, pitch, bearing } = this.mapView.props

		Object.assign(this.state, {
			longitude,
			latitude,
			zoom,
			pitch,
			bearing,
		})
	}
}

export default Manager

tsx Overrides.tsx

Overrides.tsx
import { Data, animate, Override, Animatable } from 'framer'
import Manager from './Manager'

/* --------------------------- Set up map manager --------------------------- */

/**
 * The Manager will act as an interface with the Localize Map component. While
 * that component is declarative, the properties it consumes will come from the
 * Manager, and we'll make all of our imperative actions (like manipulating the
 * camera or showing / hiding layers) through this Manager.
 */
const manager = new Manager()

/**
 * This Override connects a Localize Map Component instance to a manager instance.
 * From here on, the manager will control the component through the manager's state.
 * @param props The Localize Map Component's props
 */
export const isMap: Override = (props) => {
	manager.connect(props)
	return manager.state
}

/* -------------------------- Fly to a random point ------------------------- */

/**
 * This override shows the manager's `flyTo` method.
 */
export const flyToPoint: Override = () => {
	return {
		onClick() {
			const longitude = -50 + Math.random() * 100
			const latitude = -50 + Math.random() * 100
			manager.flyTo(longitude, latitude)
		},
	}
}

/* --------------------------- Set the camera zoom -------------------------- */

// Since we're not changing zoom until the button is clicked, store the value here
const zoomData = Data({
	zoom: manager.state.zoom,
})

export const zoomInput: Override = () => {
	return {
		value: zoomData.zoom,
		onValueChange(value: number) {
			zoomData.zoom = value
		},
	}
}

// Use zoomData in Manager.setZoom when clicked
export const zoomButton: Override = () => {
	return {
		onClick() {
			manager.setZoom(zoomData.zoom)
		},
	}
}

/* -------------------------- Fly to a given point -------------------------- */

// Since we're not changing position until the button is clicked, store the values here
const pointData = Data({
	latitude: manager.state.latitude,
	longitude: manager.state.latitude,
})

// Stupid, hacky fix for pointData
setTimeout(() => {
	pointData.latitude = manager.state.latitude
	pointData.longitude = manager.state.longitude
}, 500)

export const isLatitudeInput: Override = (props) => {
	return {
		value: manager.state.latitude,
		onValueChange(value: string) {
			pointData.latitude = parseFloat(value)
		},
	}
}

export const isLongitudeInput: Override = (props) => {
	return {
		value: manager.state.longitude,
		onValueChange(value: string) {
			pointData.longitude = parseFloat(value)
		},
	}
}

// Use pointData in Manager.flyTo when clicked
export const isFlyToPointButton: Override = () => {
	return {
		onClick() {
			const { longitude, latitude } = pointData
			manager.flyTo(longitude, latitude)
		},
	}
}

/* ------------------------- Manually set properties ------------------------ */

// With the Manager.setState method, we can control one or more properties at once
export const updateCamera: Override = () => {
	return {
		onClick() {
			manager.setState({
				latitude: 51.5662,
				longitude: -0.1439,
				zoom: 20,
			})
		},
	}
}