---
# System prepended metadata

title: 卡比 React 讀書會 | Tip calculator
tags: [React, ' 讀書會']

---

---
title: 卡比 React 讀書會 | Tip calculator
tags: React, 讀書會
---

# 卡比 React 讀書會 | Tip calculator | week 2

## Tip calculator

- [設計稿連結 - 請自行下載!](https://www.frontendmentor.io/challenges/tip-calculator-app-ugJNGbJUX)
- [卡比直播連結](https://youtu.be/oVmLNvF-C5o)
- Source code 自己去找卡比的 Github 找到算你贏XD

### 使用技術、工具

- React
- Tailwindcss
- Vite
- [Vitawind](https://vitawind-blog.vercel.app/)
- [clsx](https://www.npmjs.com/package/clsx)

### Vitawind

推薦使用兔兔大神製作好的 cli 方便快速~

- [Vitawind](https://vitawind-blog.vercel.app/scaffolding/)

### Tailwind 小提醒

- `sr-only` Tailwind 幫你隱藏，可用在 Radio button 的 `input` 上面 (雖然你要隱藏他，但他還是需要存在的時候可以用)
- 善用 `peer` 可以快速使用 CSS 來達到 Radio button 的效果，下面範例展示了，當 `input` 被 `checked` 的時候可以讓後面的 `label` 改變字體顏色，需要注意的是 `peer-{}` 必須要再 `peer` 後面並同一層才能觸發。

```jsx=
function RadioButton (){
    return (
        <div>
            <input className="peer"/>
            <label className="peer-checked:text-green-500"> Label </label>
        </div>
    )
}
```

### React 小提醒

1. 可以使用 `<>` 當作 fragment 來包裝要 return 出去的結構
```jsx=
function Component(){
    return (
        <>
            <label />
            <input />
        </>
    )
}
```

2. component 可以加上 children 的 prop 就可以從外層加入到該元件的指定位置

```jsx=
`./component.jsx`
function Component({ children }){
    return (
        <div class="bg-white rounded-lg">
            {children}
        </div>
    )
}
`./App.jsx`
import Component from './component.jsx'
function App(){
    return (
        <main>
            <Component>
                // ... 放你的東東
            </Component>
        </main>
    )
}
```

3. cloneElement 從父層丟元件進去子層時，子層可以來處理共用的 props
```jsx=
`./component.jsx`
// function Component({ children }){
//     return (
//         <div class="bg-white rounded-lg">
//             // ...
//             cloneElement
//         </div>
//     )
// }
`./App.jsx`
// import Component from './component.jsx'
// function App(){
//     return (
//         <main>
//             <Component />
//         </main>
//     )
// }
```

4. Form 處理
    - `onChangeCapture` 綁在 form 上面，當 form 裡面有任何改動，就可以從父層直接觸發指定 method
    - `onResetCapture` 用來處理 form reset 時要觸發的 method
        - 小提醒 `requestAnimationFrame` "可能" 會在這邊用到，用來處理使函式觸發時間延後

```jsx=
function Form(){
    function onChangeCapture(e){
        const form = Object.fromEntries(
            new FormData(e.currentTarget).entries()
        );
        console.log(form)
    }
    return (
        <form onChangeCapture={onChangeCapture}>
            <input type="text" />
            <input type="text" />
        </form>
    )
}
```


### Step 1 | 預先設定各種前置作業

1. Tailwindcss theme.colors 設定
2. 這邊選用在 CSS (`style.css`) 設定 body 背景色
3. SVG
    - 小提醒，SVG 有以下的調整才方便使用程式控制
        - 有 `viewbox`
        - `fill` 改成 `currentcolor` 
        - `width`, `height` 拿掉
    - 使用了 [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr) 來方便 import SVG

### Step 2 | 版面處理 && 邏輯處理

首先，去看影片啦! 我不會寫筆記QQ

- 分析版面
    - 雖然他沒有 submit，但還是要使用 `<form>`
    - 記得使用 `<header>`、`<main>`
    - input 一樣要有 `outline` or `border` or `ring`，才符合 accessibility 規範喔~
    - 善用 `button[type=reset]` 就不用特別處理了~

- 本日重點 - RadioGroup
    - 關鍵，這邊卡比把全部的按鈕都做成 radio button(包含 custom 欄位)，再使用 `custom` 作為 `prop` 丟進去，來判斷是否額外產生 `input[type=number]` 並隱藏 radio button，如此一來包含 custom 的欄位就都會有 radio 的控制，不需要特別判斷 custom 欄位與 radio 的交互作用。
    - 記得要用 `<fieldset>`
    - 使用 `label` 的 `for` 來對應 `input` 的 `id`，就可以在點擊 `label` 時也會觸發 `input` 的點擊

```jsx=
function RadioGroup({ id, label, children }){
    return (
        <fieldset>
            <legend>Label</legend>
            <div>
                {Children.map(children, (child) => cloneElement(child, {name: id}))}
            </div>
        </fieldset>
    )
}

function Radio({ label, value, name, custom }){
    return (
        <div>
            <input type="radio" id={value} name={name} value={value} />
            <lable htmlFor={value} className="clsx('...', custom && 'sr-only')" />
            
            <!-- 用 ref 作為 prop 拋子層，React 會出警告，這邊先略過，直接重製一個 input[type=number]  -->
            {
                custom && (
                    <input type="number" />
                )
            }
            
        </div>
    )
}

function App(){
    return(
        <div>
            <RadioGroup id="tips" label="Select Tip %" />
                <Radio label="5%" value="5" />
                <Radio label="10%" value="10" />
                <Radio label="custom" value="custom" custom />
            <RadioGroup />
        </div>
    )
}
```