Storybook で Atomic Design を実践しよう

現代の Web 開発において、コンポーネントの再利用性と保守性は非常に重要な課題です。特に大規模なプロジェクトでは、一貫性のあるデザインシステムを構築することが成功の鍵となります。
この記事では、Atomic Designという設計思想とStorybookという開発ツールを組み合わせることで、効率的で保守しやすいコンポーネントライブラリを構築する方法をご紹介します。
実際のコード例とエラー解決方法を含めて、初心者の方でも実践できる内容となっています。最後まで読んでいただければ、明日からでも実践できる知識が身につくはずです。
Atomic Design の基本概念
Atomic Design は、Brad Frost 氏によって提唱された UI 設計の方法論です。自然界の原子から分子、有機体、テンプレート、ページという階層構造に基づいて、UI コンポーネントを体系的に整理します。
5 つの階層構造
階層 | 説明 | 例 |
---|---|---|
Atoms(原子) | 最小単位のコンポーネント | ボタン、入力フィールド、ラベル |
Molecules(分子) | Atoms を組み合わせた機能的な単位 | 検索フォーム、ナビゲーションアイテム |
Organisms(有機体) | Molecules を組み合わせた複雑なセクション | ヘッダー、フッター、サイドバー |
Templates(テンプレート) | ページの構造を定義するレイアウト | ページの骨組み、グリッドレイアウト |
Pages(ページ) | 実際のコンテンツを含む完成形 | ホームページ、商品詳細ページ |
この階層構造により、コンポーネントの再利用性が大幅に向上し、一貫性のあるデザインシステムを構築できます。
Storybook の導入とセットアップ
Storybook は、UI コンポーネントを独立して開発・テスト・ドキュメント化できるツールです。Atomic Design と組み合わせることで、各階層のコンポーネントを効率的に管理できます。
プロジェクトの初期化
まず、新しい Next.js プロジェクトを作成し、Storybook を導入しましょう。
bash# Next.jsプロジェクトの作成
yarn create next-app atomic-design-storybook --typescript
# プロジェクトディレクトリに移動
cd atomic-design-storybook
# Storybookの初期化
npx storybook@latest init
よくあるエラーと解決方法
Storybook の初期化時に以下のエラーが発生することがあります:
bash# エラー例1: 依存関係の競合
error An unexpected error occurred: "ENOENT: no such file or directory, 'package.json'"
# 解決方法: プロジェクトディレクトリにいることを確認
pwd
ls -la
bash# エラー例2: Node.jsバージョンの問題
error The engine "node" is incompatible with this module. Expected version ">=18.0.0".
# 解決方法: Node.jsバージョンを確認・更新
node --version
# 必要に応じてnvmでバージョン変更
nvm use 18
Storybook 設定ファイルの確認
初期化後、以下のファイルが作成されます:
typescript// .storybook/main.ts
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: [
'../src/**/*.mdx',
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/nextjs',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
プロジェクト構造の設定
Atomic Design に基づいたディレクトリ構造を作成します:
bashmkdir -p src/components/{atoms,molecules,organisms,templates,pages}
mkdir -p src/stories/{atoms,molecules,organisms,templates,pages}
Atoms(原子)コンポーネントの作成
Atoms は、UI の最小単位となるコンポーネントです。これらは他のコンポーネントの基盤となり、再利用性が最も高い階層です。
基本的な Button コンポーネント
まず、シンプルなボタンコンポーネントを作成しましょう。
typescript// src/components/atoms/Button/Button.tsx
import React from 'react';
// ボタンのバリアントを定義
export type ButtonVariant =
| 'primary'
| 'secondary'
| 'danger';
// ボタンのサイズを定義
export type ButtonSize = 'small' | 'medium' | 'large';
// ボタンのProps型定義
export interface ButtonProps {
children: React.ReactNode;
variant?: ButtonVariant;
size?: ButtonSize;
disabled?: boolean;
onClick?: () => void;
className?: string;
}
// ボタンコンポーネントの実装
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'medium',
disabled = false,
onClick,
className = '',
}) => {
// バリアントに応じたスタイルクラスを生成
const getVariantClasses = () => {
switch (variant) {
case 'primary':
return 'bg-blue-500 hover:bg-blue-600 text-white';
case 'secondary':
return 'bg-gray-500 hover:bg-gray-600 text-white';
case 'danger':
return 'bg-red-500 hover:bg-red-600 text-white';
default:
return 'bg-blue-500 hover:bg-blue-600 text-white';
}
};
// サイズに応じたスタイルクラスを生成
const getSizeClasses = () => {
switch (size) {
case 'small':
return 'px-3 py-1 text-sm';
case 'medium':
return 'px-4 py-2 text-base';
case 'large':
return 'px-6 py-3 text-lg';
default:
return 'px-4 py-2 text-base';
}
};
return (
<button
className={`
${getVariantClasses()}
${getSizeClasses()}
rounded-md font-medium transition-colors
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
Button コンポーネントの Story
Storybook で Button コンポーネントを表示するための Story を作成します。
typescript// src/stories/atoms/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '../../components/atoms/Button/Button';
// メタデータの定義
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary', 'danger'],
},
size: {
control: { type: 'select' },
options: ['small', 'medium', 'large'],
},
disabled: {
control: { type: 'boolean' },
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// プライマリボタンのStory
export const Primary: Story = {
args: {
children: 'プライマリボタン',
variant: 'primary',
size: 'medium',
},
};
// セカンダリボタンのStory
export const Secondary: Story = {
args: {
children: 'セカンダリボタン',
variant: 'secondary',
size: 'medium',
},
};
// 危険ボタンのStory
export const Danger: Story = {
args: {
children: '削除',
variant: 'danger',
size: 'medium',
},
};
// 無効化されたボタンのStory
export const Disabled: Story = {
args: {
children: '無効ボタン',
variant: 'primary',
size: 'medium',
disabled: true,
},
};
よくあるエラーと解決方法
bash# エラー例: TypeScriptの型エラー
error TS2307: Cannot find module './Button' or its corresponding type declarations.
# 解決方法: インデックスファイルを作成
typescript// src/components/atoms/Button/index.ts
export { Button } from './Button';
export type {
ButtonProps,
ButtonVariant,
ButtonSize,
} from './Button';
Molecules(分子)コンポーネントの作成
Molecules は、複数の Atoms を組み合わせて機能的な単位を作成する階層です。検索フォームやナビゲーションアイテムなどが該当します。
検索フォームコンポーネント
Atoms の Button と Input を組み合わせて、検索フォームを作成しましょう。
typescript// src/components/atoms/Input/Input.tsx
import React from 'react';
export interface InputProps {
type?: 'text' | 'email' | 'password' | 'search';
placeholder?: string;
value?: string;
onChange?: (value: string) => void;
disabled?: boolean;
className?: string;
}
export const Input: React.FC<InputProps> = ({
type = 'text',
placeholder,
value,
onChange,
disabled = false,
className = '',
}) => {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={(e) => onChange?.(e.target.value)}
disabled={disabled}
className={`
px-3 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
/>
);
};
検索フォーム Molecule の作成
typescript// src/components/molecules/SearchForm/SearchForm.tsx
import React, { useState } from 'react';
import { Button } from '../../atoms/Button/Button';
import { Input } from '../../atoms/Input/Input';
export interface SearchFormProps {
onSearch?: (query: string) => void;
placeholder?: string;
className?: string;
}
export const SearchForm: React.FC<SearchFormProps> = ({
onSearch,
placeholder = '検索キーワードを入力...',
className = '',
}) => {
const [query, setQuery] = useState('');
// 検索実行のハンドラー
const handleSearch = () => {
if (query.trim()) {
onSearch?.(query.trim());
}
};
// Enterキーでの検索実行
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleSearch();
}
};
return (
<div className={`flex gap-2 ${className}`}>
<Input
type='search'
placeholder={placeholder}
value={query}
onChange={setQuery}
onKeyPress={handleKeyPress}
className='flex-1'
/>
<Button
variant='primary'
size='medium'
onClick={handleSearch}
disabled={!query.trim()}
>
検索
</Button>
</div>
);
};
検索フォームの Story
typescript// src/stories/molecules/SearchForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { SearchForm } from '../../components/molecules/SearchForm/SearchForm';
const meta: Meta<typeof SearchForm> = {
title: 'Molecules/SearchForm',
component: SearchForm,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
placeholder: {
control: { type: 'text' },
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// 基本的な検索フォーム
export const Default: Story = {
args: {
placeholder: '商品を検索...',
},
};
// カスタムプレースホルダー
export const CustomPlaceholder: Story = {
args: {
placeholder: 'ユーザー名を入力してください',
},
};
// 検索実行時のコールバック
export const WithCallback: Story = {
args: {
placeholder: '検索してください',
onSearch: (query: string) => {
console.log('検索実行:', query);
alert(`検索クエリ: ${query}`);
},
},
};
Organisms(有機体)コンポーネントの作成
Organisms は、複数の Molecules を組み合わせて、より複雑で独立したセクションを作成する階層です。ヘッダーやフッター、サイドバーなどが該当します。
ヘッダーコンポーネント
ナビゲーションと検索フォームを組み合わせたヘッダーを作成しましょう。
typescript// src/components/molecules/Navigation/Navigation.tsx
import React from 'react';
import { Button } from '../../atoms/Button/Button';
export interface NavigationItem {
label: string;
href: string;
active?: boolean;
}
export interface NavigationProps {
items: NavigationItem[];
onItemClick?: (item: NavigationItem) => void;
className?: string;
}
export const Navigation: React.FC<NavigationProps> = ({
items,
onItemClick,
className = '',
}) => {
return (
<nav className={`flex gap-4 ${className}`}>
{items.map((item, index) => (
<Button
key={index}
variant={item.active ? 'primary' : 'secondary'}
size='small'
onClick={() => onItemClick?.(item)}
className='text-decoration-none'
>
{item.label}
</Button>
))}
</nav>
);
};
ヘッダー Organism の作成
typescript// src/components/organisms/Header/Header.tsx
import React from 'react';
import { Navigation } from '../../molecules/Navigation/Navigation';
import { SearchForm } from '../../molecules/SearchForm/SearchForm';
export interface HeaderProps {
logo?: string;
navigationItems?: Array<{
label: string;
href: string;
active?: boolean;
}>;
onSearch?: (query: string) => void;
onNavigationClick?: (item: any) => void;
className?: string;
}
export const Header: React.FC<HeaderProps> = ({
logo = 'MyApp',
navigationItems = [],
onSearch,
onNavigationClick,
className = '',
}) => {
return (
<header
className={`bg-white shadow-md p-4 ${className}`}
>
<div className='max-w-7xl mx-auto flex items-center justify-between'>
{/* ロゴセクション */}
<div className='flex items-center'>
<h1 className='text-xl font-bold text-gray-800'>
{logo}
</h1>
</div>
{/* ナビゲーションセクション */}
<div className='flex items-center gap-6'>
<Navigation
items={navigationItems}
onItemClick={onNavigationClick}
/>
<SearchForm
placeholder='サイト内検索...'
onSearch={onSearch}
className='w-64'
/>
</div>
</div>
</header>
);
};
ヘッダーの Story
typescript// src/stories/organisms/Header.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Header } from '../../components/organisms/Header/Header';
const meta: Meta<typeof Header> = {
title: 'Organisms/Header',
component: Header,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
argTypes: {
logo: {
control: { type: 'text' },
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// 基本的なヘッダー
export const Default: Story = {
args: {
logo: 'MyApp',
navigationItems: [
{ label: 'ホーム', href: '/', active: true },
{ label: '商品', href: '/products' },
{ label: '会社概要', href: '/about' },
{ label: 'お問い合わせ', href: '/contact' },
],
},
};
// 検索機能付きヘッダー
export const WithSearch: Story = {
args: {
logo: 'Eコマース',
navigationItems: [
{ label: 'ホーム', href: '/' },
{ label: 'カテゴリ', href: '/categories' },
{ label: '特集', href: '/featured' },
{ label: 'マイページ', href: '/mypage' },
],
onSearch: (query: string) => {
console.log('ヘッダー検索:', query);
},
},
};
Templates(テンプレート)の作成
Templates は、ページの構造を定義するレイアウトです。実際のコンテンツは含まず、ページの骨組みを提供します。
基本的なページテンプレート
typescript// src/components/templates/PageTemplate/PageTemplate.tsx
import React from 'react';
import { Header } from '../../organisms/Header/Header';
export interface PageTemplateProps {
children: React.ReactNode;
headerProps?: {
logo?: string;
navigationItems?: Array<{
label: string;
href: string;
active?: boolean;
}>;
onSearch?: (query: string) => void;
};
showHeader?: boolean;
className?: string;
}
export const PageTemplate: React.FC<PageTemplateProps> = ({
children,
headerProps = {},
showHeader = true,
className = '',
}) => {
return (
<div className={`min-h-screen bg-gray-50 ${className}`}>
{/* ヘッダー */}
{showHeader && (
<Header
logo={headerProps.logo}
navigationItems={headerProps.navigationItems}
onSearch={headerProps.onSearch}
/>
)}
{/* メインコンテンツ */}
<main className='max-w-7xl mx-auto py-6 px-4'>
{children}
</main>
{/* フッター(簡易版) */}
<footer className='bg-gray-800 text-white py-8 mt-auto'>
<div className='max-w-7xl mx-auto px-4 text-center'>
<p>© 2024 MyApp. All rights reserved.</p>
</div>
</footer>
</div>
);
};
テンプレートの Story
typescript// src/stories/templates/PageTemplate.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { PageTemplate } from '../../components/templates/PageTemplate/PageTemplate';
const meta: Meta<typeof PageTemplate> = {
title: 'Templates/PageTemplate',
component: PageTemplate,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof meta>;
// 基本的なページテンプレート
export const Default: Story = {
args: {
headerProps: {
logo: 'MyApp',
navigationItems: [
{ label: 'ホーム', href: '/', active: true },
{ label: '商品', href: '/products' },
{ label: '会社概要', href: '/about' },
],
},
children: (
<div className='bg-white p-6 rounded-lg shadow'>
<h1 className='text-2xl font-bold mb-4'>
ページコンテンツ
</h1>
<p>
これはページテンプレートのサンプルコンテンツです。
</p>
</div>
),
},
};
// ヘッダーなしのテンプレート
export const WithoutHeader: Story = {
args: {
showHeader: false,
children: (
<div className='bg-white p-6 rounded-lg shadow'>
<h1 className='text-2xl font-bold mb-4'>
ヘッダーなしページ
</h1>
<p>
ヘッダーが表示されないページテンプレートです。
</p>
</div>
),
},
};
Pages(ページ)の作成
Pages は、実際のコンテンツを含む完成形のページです。Templates に具体的なコンテンツを配置して作成します。
ホームページの作成
typescript// src/components/pages/HomePage/HomePage.tsx
import React from 'react';
import { PageTemplate } from '../../templates/PageTemplate/PageTemplate';
import { Button } from '../../atoms/Button/Button';
export interface HomePageProps {
onSearch?: (query: string) => void;
onNavigationClick?: (item: any) => void;
}
export const HomePage: React.FC<HomePageProps> = ({
onSearch,
onNavigationClick,
}) => {
const headerProps = {
logo: 'MyApp',
navigationItems: [
{ label: 'ホーム', href: '/', active: true },
{ label: '商品', href: '/products' },
{ label: '会社概要', href: '/about' },
{ label: 'お問い合わせ', href: '/contact' },
],
onSearch,
};
return (
<PageTemplate headerProps={headerProps}>
{/* ヒーローセクション */}
<section className='text-center py-12 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg mb-8'>
<h1 className='text-4xl font-bold mb-4'>
ようこそ MyApp へ
</h1>
<p className='text-xl mb-6'>
最高のユーザー体験を提供するアプリケーション
</p>
<div className='flex gap-4 justify-center'>
<Button variant='secondary' size='large'>
始める
</Button>
<Button variant='primary' size='large'>
詳細を見る
</Button>
</div>
</section>
{/* 機能紹介セクション */}
<section className='grid md:grid-cols-3 gap-6 mb-8'>
<div className='bg-white p-6 rounded-lg shadow'>
<h3 className='text-xl font-semibold mb-3'>
簡単操作
</h3>
<p>直感的で使いやすいインターフェース</p>
</div>
<div className='bg-white p-6 rounded-lg shadow'>
<h3 className='text-xl font-semibold mb-3'>
高速処理
</h3>
<p>最新技術による高速なレスポンス</p>
</div>
<div className='bg-white p-6 rounded-lg shadow'>
<h3 className='text-xl font-semibold mb-3'>
安全安心
</h3>
<p>セキュリティを重視した設計</p>
</div>
</section>
{/* CTAセクション */}
<section className='bg-white p-8 rounded-lg shadow text-center'>
<h2 className='text-2xl font-bold mb-4'>
今すぐ始めませんか?
</h2>
<p className='text-gray-600 mb-6'>
無料でお試しいただけます
</p>
<Button variant='primary' size='large'>
無料で始める
</Button>
</section>
</PageTemplate>
);
};
ホームページの Story
typescript// src/stories/pages/HomePage.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { HomePage } from '../../components/pages/HomePage/HomePage';
const meta: Meta<typeof HomePage> = {
title: 'Pages/HomePage',
component: HomePage,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof meta>;
// 基本的なホームページ
export const Default: Story = {
args: {
onSearch: (query: string) => {
console.log('ホームページ検索:', query);
},
onNavigationClick: (item: any) => {
console.log('ナビゲーションクリック:', item);
},
},
};
Storybook でのコンポーネント管理
Storybook では、作成したコンポーネントを効率的に管理・テストできます。
コンポーネントのドキュメント化
各コンポーネントに JSDoc コメントを追加することで、自動的にドキュメントが生成されます。
typescript// src/components/atoms/Button/Button.tsx の例
/**
* 再利用可能なボタンコンポーネント
*
* @example
* ```tsx
* <Button variant="primary" size="medium" onClick={() => alert('クリックされました')}>
* クリックしてください
* </Button>
* ```
*/
export const Button: React.FC<ButtonProps> = ({ ... }) => {
// 実装
};
インタラクションテスト
Storybook のインタラクションテスト機能を使用して、コンポーネントの動作をテストできます。
typescript// src/stories/atoms/Button.stories.tsx に追加
import { expect } from '@storybook/test';
import {
within,
userEvent,
} from '@storybook/testing-library';
// インタラクションテスト付きのStory
export const WithInteraction: Story = {
args: {
children: 'テストボタン',
variant: 'primary',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
// ボタンが存在することを確認
expect(button).toBeInTheDocument();
// クリックイベントをシミュレート
await userEvent.click(button);
},
};
よくあるエラーと解決方法
bash# エラー例: Storybookのビルドエラー
error Module not found: Can't resolve './Button' in '/path/to/stories'
# 解決方法: パスの確認とインデックスファイルの作成
typescript// src/components/atoms/index.ts
export { Button } from './Button/Button';
export { Input } from './Input/Input';
// 他のatomsも同様にエクスポート
実践的な開発ワークフロー
Atomic Design と Storybook を組み合わせた効率的な開発ワークフローをご紹介します。
1. コンポーネント設計の流れ
bash# 1. Atomsから作成
yarn storybook # Storybookを起動
# ブラウザで http://localhost:6006 にアクセス
# 2. コンポーネントの作成とテスト
# src/components/atoms/ に新しいコンポーネントを作成
# src/stories/atoms/ に対応するStoryを作成
# 3. 段階的に上位階層を作成
# Molecules → Organisms → Templates → Pages
2. コンポーネントの命名規則
typescript// 推奨する命名規則
// ファイル名: PascalCase
Button.tsx
SearchForm.tsx
Header.tsx
// コンポーネント名: PascalCase
export const Button: React.FC<ButtonProps> = ...
// Props型: ComponentName + Props
export interface ButtonProps { ... }
// Story名: 階層/コンポーネント名
title: 'Atoms/Button'
title: 'Molecules/SearchForm'
3. スタイリングの統一
Tailwind CSS を使用して一貫したスタイリングを実現します。
typescript// src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
/* カスタムコンポーネントクラス */
@layer components {
.btn-primary {
@apply bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md;
}
.btn-secondary {
@apply bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md;
}
}
4. 型安全性の確保
TypeScript を使用して型安全性を確保します。
typescript// src/types/index.ts
// 共通の型定義
export interface BaseComponentProps {
className?: string;
children?: React.ReactNode;
}
// テーマ関連の型
export type Theme = 'light' | 'dark';
export type ColorScheme =
| 'primary'
| 'secondary'
| 'success'
| 'warning'
| 'error';
5. テストの自動化
bash# Storybookのテスト実行
yarn test-storybook
# ビジュアルリグレッションテスト
yarn storybook:build
npx chromatic --project-token=your-token
まとめ
この記事では、Storybook と Atomic Design を組み合わせた効率的なコンポーネント開発手法をご紹介しました。
学んだこと
- Atomic Design の階層構造: Atoms → Molecules → Organisms → Templates → Pages の 5 段階の設計思想
- Storybook の活用: コンポーネントの独立した開発・テスト・ドキュメント化
- 実践的な開発ワークフロー: 段階的なコンポーネント作成と効率的な管理方法
- エラー解決: よくある問題とその解決方法
次のステップ
- 実際のプロジェクトで Atomic Design を実践してみる
- Storybook のアドオンを活用して機能を拡張する
- チーム内でコンポーネントライブラリの共有体制を構築する
- 継続的な改善とメンテナンスの仕組みを作る
この手法を実践することで、再利用性が高く、保守しやすいコンポーネントライブラリを構築できます。最初は時間がかかりますが、長期的には開発効率の大幅な向上につながるでしょう。
関連リンク
- Atomic Design Methodology - Brad Frost 氏の公式サイト
- Storybook Documentation - Storybook の公式ドキュメント
- Tailwind CSS - ユーティリティファースト CSS フレームワーク
- TypeScript Handbook - TypeScript の公式ハンドブック
- Next.js Documentation - Next.js の公式ドキュメント
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来