【Angular 17】オシャレ UI「Spartan/ui」を導入する全手順

## 概要
Angular 17 に 「Spartan/ui」 を入れる手順の備忘録です。

「Nx コマンド（モノレポ管理ツール）」を使用するとより簡単に入りますが、今回は使わず、Angular CLI で構築したアプリケーションを前提に導入する手順を説明します。

### 前提
- Angular アプリケーションは構築済みであること
- バージョン
  - Node: v20.10.0
    - npm: v10.2.3
  - Angular: v17.0.7
  - Angular CLI: v17.0.7

Spartan は **Tailwind CSS が導入されている前提** で動くので、まだ導入していない人は導入しておいてください。導入の参考記事はこちら👇

https://qiita.com/to3izo/items/6bb5b2465caaf97ad749

## Spartan とは
 「[Spartan](https://www.spartan.ng/)（スパルタン）」 は、Angular にオシャレな UI を導入できるツールです。
 
React 向けのオシャレな UI コンポーネントセットである 「Shadcn/ui」風のコンポーネントを、Angular でも使えるようにしたイメージです。

いずれも Tailwind CSS をベースに動くものなので、Tailwind CSS を事前に導入する必要があります（Shadcn の場合は、更に「Radix UI」もベースに含まれている）。

### イメージは Tailwind CSS の活用事例集コンポーネント
Spartan も Shadcn も、「Tailwind CSS でこんな書き方ができるよ」テンプレ集の様なイメージです。

ボタンやダイアログの活用事例コンポーネントをたくさん用意しており、それを更に私たちがカスタマイズしてから再利用できるように作られています。
なので、独自のプログラムモジュールを依存関係としてインストールするのとはちょっと異なります。

### Primitives（プリミティブ）
Spartan は、事前に用意されているソースコードを特定のディレクトリにダウンロードし、それをベースに自分のコンポーネントで微調整・カスタマイズをして再利用するという使い方になっています。

例えば、Spartan が用意したオシャレな「Button（ボタン）」を利用するなら、
Button コンポーネントファイルをダウンロードし、そのファイルを自身の画面コンポーネントで呼び出し、色味などスタイルを微調整して、画面に表示する。…という流れになります。

一度、元となる「Button」のコンポーネントをダウンロードしていれば、他の画面で更にボタンを追加するときも、既にダウンロードしてあるコンポーネントを再利用する形式です。

この、ダウンロードする元となるコンポーネントファイルを **「Primitive（プリミティブ）」** と言います。

Spartan 利用時は、このプリミティブを格納する専用ディレクトリを用意し、そこに使用するプリミティブをどんどんダウンロードして追加していく使い方をします。

もちろん、最初から一気に全てのプリミティブをダウンロードしておくことも可能ですが、サイズが重くなるので、実際に必要なものだけを個別に追加するのがおすすめです。

## 手順（初期設定）

### 必須ファイルと設定の追加

Spartan CLI のプラグインを導入します。

```bash
npm i -D @spartan-ng/cli
```

コアパッケージをインストールします。

```bash
npm i @spartan-ng/ui-core
```

tailwind.config.js に、プリセットとプリミティブ管理ディレクトリの設定を追加します。

```diff_javascript:tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
+ presets: [require('@spartan-ng/ui-core/hlm-tailwind-preset')],
  content: [
    "./src/**/*.{html,ts}",
+   "./libs/ui/**/*.{html,ts}", // ※ このパスは一例
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

```

`"./libs/ui/**/*.{html,ts}"` の `libs/ui` 部分は、Spartan のプリミティブファイルをまとめる任意のディレクトリを指定します。
上記は一例ですので、自分のファイル構成に合わせてください（例えば `spartan` とか `primitives` とか `components` とかでも良いです。）。


尚、ここは公式では、
***"REPLACE_WITH_PATH_TO_YOUR_COMPONENTS_DIRECTORY"（あなたのコンポーネントのディレクトリパスに置換してください）*** とありますが、表現に語弊があり、正確には Spartan のプリミティブがインストールされるディレクトリを指定する必要があります。
自分の開発する画面コンポーネントの場所のことではないので、注意しましょう。

### テーマ設定（CSS 変数の追加）

Spartan のテーマ（固有の CSS 変数）をスタイルのエントリポイント（つまり、styles.css）に追加します。

```bash
# 追加実行コマンド
$ ng g @spartan-ng/cli:ui-theme

# ---------- 以下、実行結果 ----------
# Q1. どのアプリケーションに追加する？ (そのままエンターキーを押せば OK)
? Choose which application you want to add the theme to: … 
❯ demo-app # 現在のアプリ名

# Q2. どのテーマカラーにする？ (カーソルで選んでエンターキーで決定)
? Choose which theme to apply. You can always re-run this generator and add a custom prefix to add other themes. … 
❯ zinc
  slate
  stone
  gray
  neutral
  red
  rose
  blue
  green
  orange
  yellow
  violet

# Q3. 角丸の角度はどれくらいにする？ (カーソルで選んでエンターキーで決定)
? Which corner radius do you want to use with your theme: … 
  0
  0.3
❯ 0.5
  0.75
  1.0

# Q4. CSS のエントリーポイントのパスを教えて？ 未指定の場合、勝手に探しますが、見つからない場合はエラーになります
? Path to the styles entry point relative to the workspace root. If not provided the generator will do its best to find it and it will error if it can’t. › 
# 👉 今回はそのままエンターキー

# Q5. テーマのスタイル定義に適用されるプレフィックス (接頭辞) を決めてね
# 例: theme-rose, theme-zinc など
# グローバルテーマの場合は、空のままにしてね
? Prefix class name applied to your theme’s style definitions: e.g., theme-rose, theme-zinc. Leave empty for global theme. ›
# 👉 今回はそのままエンターキー

# 以下の表示で完了 (処理成功)
UPDATE src/styles.css (1334 bytes)
```

以上で、最低限の設定は完了です。
試しに、`npm run start` でアプリが正常に起動するか確認しておきましょう。

### アプリ起動でエラーが発生した場合
以下の様に、アプリが正常に起動できずエラーが出ることがあります。

```bash
⠼ Building...
✘ [ERROR] Could not resolve "@angular/cdk/overlay-prebuilt.css"

    src/styles.css:1:8:
      1 │ @import '@angular/cdk/overlay-prebuilt.css';
        ╵         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "@angular/cdk/overlay-prebuilt.css" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.

Application bundle generation failed. [1.280 seconds]
```

上記のエラーの場合は、必要な node モジュールが足りていないので、下記手順で対応しましょう。

```bash
# 不足しているモジュールを追加
$ npm install @angular/cdk

# 一旦 node モジュールを全て削除
$ rm -rf node_modules

# 全ての node モジュールを再インストール
$ npm install
```

これで、`npm run start` でアプリが正常に起動するかもう一度確認しておきましょう。

## 手順（プリミティブの追加）
プリミティブの追加をします。
方法は「個別に指定して追加」と「複数を選択してまとめて追加」の２つあります。

同じプリミティブが既にあるなら、この手順は不要なのでスキップしてください。

### 個別に指定して追加する方法
公式の [コンポーネントページ](https://www.spartan.ng/components/accordion) を閲覧して、気になるものがあったら、記載のインストールコマンドを実行します。

```bash
ng g @spartan-ng/cli:ui {使いたいコンポーネント}
```

実行すると、どこにプリミティブを配置するのか毎回尋ねてくるので、自分で設定したプリミティブディレクトリを指示してください。

**例）Button を利用したい場合**
```bash
# "Angular CLI" のインストールコマンド
$ ng g @spartan-ng/cli:ui button

# 実行結果 (どこに Spartan のライブラリを配置するのか教えてください。例: libs/ui)
? Choose a directory to place your spartan libraries, e.g. libs/ui 
# 👉 私の場合, "libl/ui" と入力してエンターキー

CREATE libs/ui/ui-button-helm/src/.DS_Store (6160 bytes)
CREATE libs/ui/ui-button-helm/src/index.ts (264 bytes)
CREATE libs/ui/ui-button-helm/src/lib/hlm-button.directive.ts (2044 bytes)
UPDATE tsconfig.json (942 bytes)
UPDATE package.json (1396 bytes)

added 1 package, removed 62 packages, and audited 1560 packages in 3s
# 👉 導入完了
```

### 複数を選択してまとめて追加する方法
個別インストールの引数を無しにして、インストールコマンドを実行します。

実行すると、「どのプリミティブを導入するのか」と「どこにプリミティブを配置するのか」を尋ねてくるので、自分で設定したプリミティブディレクトリを指示してください。

```bash
# プリミティブ追加コマンド
$ ng g @spartan-ng/cli:ui

# ---------- 以下、実行結果 ----------
# Q1. Spartan の ui ファイルをどこに追加する？ (ディレクトリを指定する)
? Choose a directory to place your spartan libraries, e.g. libs/ui 
# 👉 "libs/ui" と打ってエンターキー

# Q2. どのプリミティブをコピーする？ (スペースキーで複数チェック、エンターキーで実行)
? Choose which primitives you want to copy … 
✔ all
✔ accordion
✔ alert
✔ alertdialog
✔ aspectratio
✔ avatar
✔ badge
✔ button
✔ card
✔ command
✔ dialog
✔ icon
✔ input
✔ label
✔ menu
✔ popover
✔ progress
✔ radiogroup
✔ scrollarea
✔ separator
✔ sheet
✔ skeleton
# 👉 "all" にチェックを入れると、全てを導入する


# 以下の表示で完了
CREATE libs/ui/ui-accordion-helm/src/index.ts (1023 bytes)
  :
 (略)
  :
CREATE libs/ui/ui-checkbox-helm/src/lib/hlm-checkbox.directive.ts (1177 bytes)
UPDATE package.json (2495 bytes)
UPDATE tsconfig.json (3654 bytes)

added 23 packages, removed 62 packages, and audited 1581 packages in 40s
```

## 手順（コンポーネントに利用）
最後に、自分の画面コンポーネントで利用します。
先ほどの Button を例に、導入したボタンコンポーネントを再利用してみます。

まず、コンポーネントで呼び出します。必要なインポートを全て記載しましょう。
```diff_typescript:src/app/app.component.ts
  import { Component } from '@angular/core';
  import { CommonModule } from '@angular/common';
  import { RouterOutlet } from '@angular/router';

+ // Spartan UI
+ import { HlmButtonDirective } from '@spartan-ng/ui-button-helm';

  @Component({
    selector: 'app-root',
    standalone: true,
    imports: [
      CommonModule,
      RouterOutlet,
+     HlmButtonDirective,
    ],
    templateUrl: './app.component.html',
    styleUrl: './app.component.css'
  })
  export class AppComponent {
    title = 'example-angular-spartan';
  }

```

次に、画面にボタンを表示させます。

```diff_html:src/app/app.component.html
<main class="main">
  <div class="content">
    <h1>Spartan DEMO</h1>

+   <div>
+     <button hlmBtn>Button</button>
+   </div>
  </div>
</main>

<router-outlet></router-outlet>
```

以上で完了です。実際にブラウザで表示した画面がこちら👇

![demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787582/15407bf8-d905-5223-6393-92433cd4ace0.png)

細かい調整をしていないので、簡素な画面ですが、簡単にボタンの UI が導入できました。

この様に、公式のページを参考にしながら、好きな UI を導入してみましょう🙌✨