【Nuxt3+Tailwind+PrimeVue】環境構築からダッシュボードレイアウトを組むまで


一般的なダッシュボードレイアウトを作るよ！
コマンドは `yarn` を使うので適宜読み替えてください。  

## 登場人物

### Nuxt3

Vue に自動インポートやルーター機能、サーバー機能を載せた、なんでもフレームワーク。  
React で言う Next。
Nuxt3 になってから、かなり綺麗に整備されており、正直 Next よりシンプルにまとまってると思う。（信者感）

https://nuxt.com/

### Tailwind

流行りの CSS 全部 Class で書こうライブラリ。
賛否両論はあると思うが、Vue や React で採用すると、UI レイアウトを組むのが非常にはかどる。  
慣れたら戻れなくなる。  

https://tailwindcss.com/

### PrimeVue

Vue の UI ライブラリ。
Vue と言ったら Vuetify が良く言われるが、個人的にはこちらを激押しする。
Tailwind によって組まれており、カスタマイズ、上書きがとても簡単。
ちょっと使いにくいと思うところは、気軽に上書きしていこう。
（あとは [Naive UI](https://www.naiveui.com/) もおすすめ）  

https://primevue.org/



## 環境構築

Tailwind は moduler が用意されてるので、とても簡単に導入できる。  
PrimeVue は手動で入れていく。StyleMode がおすすめ。

参考：  
https://nuxt.com/docs/getting-started/installation  
https://tailwindcss.com/docs/guides/nuxtjs#modules  
https://primevue.org/nuxt  
https://primevue.org/icons/  

```bash
$ npx nuxi@latest init nuxt3-primevue
> Which package manager would you like to use?
yarn

$ cd nuxt3-primevue

# install tailwind
$ npx nuxi module add @nuxtjs/tailwindcss
$ npx tailwindcss init

# install primevue
$ yarn add primevue @primevue/themes primeicons
$ yarn add --dev @primevue/nuxt-module
```

PrimeVue の設定を `nuxt.config.ts` に追記する。  
テーマは大きく「Aura」と「Lara」がある。
Lara のほうが余白を大きくしたレイアウト。お好きなほうを。

```ts:nuxt.config.ts
import Aura from '@primevue/themes/aura';

export default defineNuxtConfig({
    modules: [
        '@primevue/nuxt-module',
    ],
    css: [
        'primeicons/primeicons.css',
    ],
    primevue: {
        options: {
            theme: {
                preset: Aura,
            },
        },
    },
})
```

`App.vue` にコンポーネントを入れてみて、使えるか確認する。  

```vue:app.vue
<template>
  <div>
    <div class="bg-blue-500">
      <Button icon="pi pi-home" label="おうち"></Button>
    </div>

    <NuxtRouteAnnouncer />
    <NuxtWelcome />
  </div>
</template>
```

[http://localhost:3000](http://localhost:3000)  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/4e09a487-c16c-38b4-ce2e-9c257e5d1d1b.png)  

導入成功！  



## 1. ダッシュボードレイアウトを作る

大きく以下の項目に分かれる、よく見るレイアウト。  

![drawio (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/78bc8bd5-5b5d-7299-2375-ee94e6f20012.png)  

レイアウトは Nuxt の Layout 機能を使う。とても便利。

https://nuxt.com/docs/guide/directory-structure/layouts  



### レイアウトを仮組みする

私は `flex` を多用してレイアウトを組みがちです。  
それぞれの `div` に色を付けて、配置していきます。
（figuma とか使った方が現代的なんだろうけどさ...）  

Layout を使うときは `app.vue` の中身を以下で上書きする。  

```vue:app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>
```

そして `layouts/dashboardLayout.vue` `pages/index.vue` のファイルを作成する。
（久々に触ったら Vue のファイル名、小文字が推奨されている...）

```vue:layouts/dashboardLayout.vue
<template>
  <div class="h-screen flex flex-col bg-red-800">
    <div class="h-[4rem] bg-orange-800">
      header
    </div>

    <div class="grow flex">
      <div class="w-[10rem] bg-blue-800">
        sidebar
      </div>

      <div class="grow bg-green-800">
        <slot />
      </div>
    </div>
  </div>
</template>
```

```vue:pages/index.vue
<template>
  <NuxtLayout name="dashboard-layout">
    content
  </NuxtLayout>
</template>
```

[http://localhost:3000](http://localhost:3000)  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/40df5320-10a3-11d3-b2cf-9d919ca6bfff.png)

ベースはこんなところ。  
まず上下に区切って、余りを全て下に渡す。（`flex flex-col` と `grow`）
次に下を左右に区切って、余りを全て右に渡す。（`flex` と `grow`）  



### スクロールを考慮する

このままでは画面をはみ出したときに、サイドバーがつられてスクロールしてしまう。
必要なエリアのみがスクロールする方が、良い体験だと思っている。  

色んな方法があるが、私は `flex` を多用する。（2回目）
今回は css variable を利用する。  

```vue:layouts/dashboardLayout.vue
<template>
  <div class="h-screen flex flex-col bg-red-800"> <!-- (1) -->
    <div class="h-[var(--app-header-height)] bg-orange-800"> <!-- (2) -->
      header
    </div>

    <div class="grow h-[var(--app-header-height)] flex"> <!-- (3) -->
      <div class="w-[var(--app-sidebar-width)] overflow-auto bg-blue-800"> <!-- (4) -->
        sidebar
      </div>

      <div class="grow overflow-auto p-2 bg-green-800"> <!-- (5) -->
        <slot />
      </div>
    </div>
  </div>
</template>

<style lang="css">
:root {
  --app-header-height: 3.5rem;
  --app-sidebar-width: 12rem;
}
</style>
```

![drawio (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/9bcb804a-f076-549f-d0c4-b3dd1db8b99f.png)

こんな感じ。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/995cb12a-9c52-711b-49d9-7e23a4046147.gif)

ヘッダやサイドバーは大きさが固定なので、`:root` 要素に値を追加した。
こちらを使用することで、後で書き換えやすくしている。
そしてスクロールさせたい箇所に `overflow-auto` を付与した。

flex はお絵描きすると理解しやすい。
これで独立スクロールの完成。
通常はここまでで十分。  





## 2. コンテンツにカードとテーブルを配置する

私がよくやるレイアウト。  
`content` 領域にカードをフルサイズで配置して、カードの body だけスクロールさせたい。  

![drawio (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/dba469c9-a419-f190-f76d-23894aab65b6.png)

こんな感じ。コンテンツ領域をスクロールするのではなく、テーブル要素だけスクロールさせる。  
必要な要素だけスクロールさせると、目にとても良い。（個人の感想）  
テーブルとか、現在の検索情報とか常に最上部にあってほしい。業務アプリならなおさら。

これらを使う：  
https://primevue.org/card/  
https://primevue.org/datatable/  

```vue:pages/index.vue
<template>
  <NuxtLayout name="dashboard-layout">
    <Card <!-- (1) 内部 -->
      class="h-full"
      :pt="{
        body: 'h-full',
        content: 'h-full relative grow', <!-- (3) -->
      }"
    >
      <template #title> <!-- (2) -->
        たいとる<br>asd<br>asd
      </template>

      <template #content> <!-- (3) -->
        <div class="absolute size-full"> <!-- (5) -->
          <DataTable
            :value="items"
            scrollable
            :pt="{
              root: 'h-full',
              tablecontainer: 'h-full border',
              table: 'h-full',
              tbody: 'h-full',
              bodyrow: 'h-full',
              bodycell: 'h-full',
            }"
          >
            <Column field="id" header="ID" body-class="min-w-[2rem] max-w-[2rem]" />
            <Column field="product" header="商品名" body-class="min-w-[8rem] max-w-[8rem]" />
            <Column field="price" header="価格" body-class="min-w-[5rem] max-w-[5rem]"/>
            <Column field="qty" header="個数" body-class="min-w-[5rem] max-w-[5rem]"/>
            <Column field="note" header="説明文" body-class="whitespace-nowrap"/>
          </DataTable>
        </div>
      </template>

      <template #footer> <!-- (4) -->
        フッダ
      </template>
    </Card>
  </NuxtLayout>
</template>

<script setup lang="ts">
const items = computed(() => {
  return Array(50).fill({
    id: 1,
    product: 'たまねぎ',
    price: 100,
    qty: 5,
    note: '淡路島産のおいしいたまねぎです。とても甘く、たまねぎスープで食べるのがおすすめです。',
  })
})
</script>
```

![drawio (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/14f302f3-2a60-6238-e6c4-7c8af4a42369.png)
こんな感じ。  
ヘッダを増加させても問題なく伸縮できる。  

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/93c22cd5-2a73-ac2f-9727-95352759935c.gif)

PrimeVue の pt 機能を使っているため、少しわかりにくい可能性あり。

テーブル要素を画面内でスクロールさせようとすると、高さを指定する必要が出てくる。
でも、描画時の高さは簡単には分からず、JavaScript で高さ計算を実装したことがある人も多いと思う。
またこの `grow` が厄介で、こいつ自身は高さ要素を持たない。
そのため、残った余りの高さを簡単に取り扱うことができない。。。

そこで！ポイントは二点！

- `h-screen` を指定したタグからすべてのタグに `h-full` と 100% を使用する。   
- `grow` 要素に `relative` を付与して、その子供に `abosolude size-full` を使用する。

こうすることで、grow 要素はそのままに height: 100% が実現できるという。
この仕組みを知った時、とても感動した。。。  

https://zenn.dev/keppy/articles/22c5b9f6c5b6af




## まとめ

PrimeVue いいよ！  
使う人にやさしい UI 作ろうね！

（grid はまだ未勉強...）