デジタル庁デザインシステム:国際化日付処理の@internationalized/date活用

デジタル庁デザインシステム:国際化日付処理の@internationalized/date活用

【免费下载链接】design-system-example-components デジタル庁デザインシステムのサンプルコンポーネント 【免费下载链接】design-system-example-components 项目地址: https://gitcode.com/GitHub_Trending/de/design-system-example-components

はじめに

グローバル化が進む現代のWebアプリケーション開発において、日付処理は最も複雑な課題の一つです。異なるタイムゾーン、ロケール、暦体系に対応しながら、ユーザーフレンドリーな日付選択UIを提供する必要があります。デジタル庁デザインシステムでは、この課題を解決するために @internationalized/date ライブラリを活用しています。

本記事では、デジタル庁デザインシステムの日付ピッカーコンポーネントにおける @internationalized/date の実装パターンとベストプラクティスを詳しく解説します。

@internationalized/dateの特徴と利点

@internationalized/date は、国際化対応の日付処理を簡素化するために設計されたライブラリです。以下のような特徴を持っています:

  • タイムゾーン対応: 自動的にローカルタイムゾーンを処理
  • 多言語対応: 様々なロケールと暦体系をサポート
  • 不変データ構造: 日付オブジェクトは不変で安全な操作を提供
  • React Aria連携: React Aria Componentsとシームレスに連携

デジタル庁デザインシステムでの実装例

基本的な日付ピッカーコンポーネント

import { CalendarDate, getLocalTimeZone, today } from '@internationalized/date';
import { useState } from 'react';

export const DatePickerExample = () => {
  const [selectedDate, setSelectedDate] = useState<CalendarDate | null>(null);

  const handleDateChange = (date: CalendarDate | null) => {
    setSelectedDate(date);
  };

  return (
    <DatePicker onDateChange={handleDateChange}>
      <DatePicker.Year />
      <DatePicker.Month />
      <DatePicker.Day />
    </DatePicker>
  );
};

カレンダー連携機能

import { CalendarDate, getLocalTimeZone, today } from '@internationalized/date';
import { useState } from 'react';

export const DatePickerWithCalendar = () => {
  const [yearInput, setYearInput] = useState('');
  const [monthInput, setMonthInput] = useState('');
  const [dayInput, setDayInput] = useState('');

  const handleCalendarChange = (newDate: CalendarDate | null) => {
    if (newDate) {
      setYearInput(String(newDate.year));
      setMonthInput(String(newDate.month).padStart(2, '0'));
      setDayInput(String(newDate.day).padStart(2, '0'));
    } else {
      setYearInput('');
      setMonthInput('');
      setDayInput('');
    }
  };

  return (
    <div className="date-picker-container">
      <DatePicker.Year value={yearInput} onChange={setYearInput} />
      <DatePicker.Month value={monthInput} onChange={setMonthInput} />
      <DatePicker.Day value={dayInput} onChange={setDayInput} />
      
      <CalendarPicker onDateSelect={handleCalendarChange} />
    </div>
  );
};

主要なAPIと使用方法

CalendarDateクラス

CalendarDate は日付を表現する主要なクラスです:

import { CalendarDate } from '@internationalized/date';

// 現在の日付を取得
const today = new CalendarDate(2025, 9, 2);

// 日付の操作(不変なので新しいインスタンスが返る)
const tomorrow = today.add({ days: 1 });
const nextMonth = today.add({ months: 1 });

// 日付の比較
const isAfter = today.compare(tomorrow) > 0;

タイムゾーン処理

import { getLocalTimeZone, today } from '@internationalized/date';

// ローカルタイムゾーンの今日の日付を取得
const localToday = today(getLocalTimeZone());

// 特定のタイムゾーンの日付を取得
const tokyoDate = today('Asia/Tokyo');

日付のパースとフォーマット

import { parseDate } from '@internationalized/date';

// 文字列から日付をパース
const parsedDate = parseDate('2025-09-02');

// 日付の検証
const isValid = !isNaN(parsedDate.year);

実装パターンとベストプラクティス

1. 状態管理のパターン

import { CalendarDate } from '@internationalized/date';
import { useCallback, useState } from 'react';

export const useDatePicker = (initialDate?: CalendarDate) => {
  const [date, setDate] = useState<CalendarDate | null>(
    initialDate || null
  );

  const updateDate = useCallback((newDate: CalendarDate | null) => {
    setDate(newDate);
  }, []);

  const clearDate = useCallback(() => {
    setDate(null);
  }, []);

  return {
    date,
    updateDate,
    clearDate,
    hasDate: date !== null
  };
};

2. バリデーションの実装

import { CalendarDate } from '@internationalized/date';

export const validateDate = (
  date: CalendarDate | null,
  options?: {
    minDate?: CalendarDate;
    maxDate?: CalendarDate;
    required?: boolean;
  }
): string[] => {
  const errors: string[] = [];

  if (options?.required && !date) {
    errors.push('日付は必須です');
  }

  if (date && options?.minDate && date.compare(options.minDate) < 0) {
    errors.push(`日付は ${formatDate(options.minDate)} 以降でなければなりません`);
  }

  if (date && options?.maxDate && date.compare(options.maxDate) > 0) {
    errors.push(`日付は ${formatDate(options.maxDate)} 以前でなければなりません`);
  }

  return errors;
};

3. アクセシビリティ対応

import { CalendarDate } from '@internationalized/date';

export const getAccessibilityProps = (date: CalendarDate | null) => {
  return {
    'aria-label': date 
      ? `選択された日付: ${formatDateForScreenReader(date)}`
      : '日付が選択されていません',
    'aria-required': true,
    'aria-invalid': !date
  };
};

const formatDateForScreenReader = (date: CalendarDate): string => {
  return `${date.year}年 ${date.month}月 ${date.day}日`;
};

パフォーマンス最適化

メモ化による再レンダリング防止

import { CalendarDate } from '@internationalized/date';
import { useMemo } from 'react';

export const useMemoizedDateOperations = (date: CalendarDate | null) => {
  const formattedDate = useMemo(() => {
    if (!date) return '';
    return `${date.year}-${String(date.month).padStart(2, '0')}-${String(date.day).padStart(2, '0')}`;
  }, [date]);

  const isWeekend = useMemo(() => {
    if (!date) return false;
    // 週末判定ロジック
    return false;
  }, [date]);

  return { formattedDate, isWeekend };
};

デバウンス処理

import { CalendarDate } from '@internationalized/date';
import { useCallback } from 'react';
import { debounce } from 'lodash-es';

export const useDebouncedDateChange = (
  onChange: (date: CalendarDate | null) => void,
  delay: number = 300
) => {
  const debouncedOnChange = useCallback(
    debounce((newDate: CalendarDate | null) => {
      onChange(newDate);
    }, delay),
    [onChange, delay]
  );

  return debouncedOnChange;
};

テスト戦略

単体テストの例

import { CalendarDate } from '@internationalized/date';
import { render, screen, fireEvent } from '@testing-library/react';
import { DatePicker } from './DatePicker';

describe('DatePicker', () => {
  it('should handle date selection correctly', () => {
    const mockOnChange = jest.fn();
    render(<DatePicker onChange={mockOnChange} />);

    const yearInput = screen.getByLabelText('年');
    fireEvent.change(yearInput, { target: { value: '2025' } });

    expect(mockOnChange).toHaveBeenCalledWith(
      expect.objectContaining({ year: 2025 })
    );
  });

  it('should validate invalid dates', () => {
    const { getByText } = render(<DatePicker />);
    
    const yearInput = screen.getByLabelText('年');
    fireEvent.change(yearInput, { target: { value: 'invalid' } });

    expect(getByText('有効な年を入力してください')).toBeInTheDocument();
  });
});

トラブルシューティング

よくある問題と解決策

問題原因解決策
タイムゾーンの不一致サーバーとクライアントのタイムゾーン差異getLocalTimeZone() で統一
日付のパース失敗フォーマットの不一致parseDate で明示的なフォーマット指定
パフォーマンス問題過剰な再レンダリングメモ化とデバウンスの導入
アクセシビリティ問題スクリーンリーダー対応不足ARIA属性の適切な設定

デバッグ手法

import { CalendarDate } from '@internationalized/date';

export const debugDate = (date: CalendarDate | null) => {
  if (!date) {
    console.log('Date is null');
    return;
  }

  console.log('Date details:', {
    year: date.year,
    month: date.month,
    day: date.day,
    era: date.era,
    calendar: date.calendar.identifier
  });
};

まとめ

デジタル庁デザインシステムにおける @internationalized/date の活用は、国際化対応の日付処理を効率的かつ堅牢に実現するための優れたアプローチです。以下のポイントが重要です:

  1. タイムゾーン対応: 自動的なローカルタイムゾーン処理
  2. 不変性: 安全な日付操作の保証
  3. 多言語サポート: 様々なロケールへの対応
  4. React Aria連携: アクセシビリティの確保

このアプローチを採用することで、ユーザーエクスペリエンスの向上とメンテナンス性の確保を両立できます。実際のプロジェクトでは、ここで紹介したパターンとベストプラクティスを参考に、独自の要件に合わせたカスタマイズを行うことをお勧めします。

【免费下载链接】design-system-example-components デジタル庁デザインシステムのサンプルコンポーネント 【免费下载链接】design-system-example-components 项目地址: https://gitcode.com/GitHub_Trending/de/design-system-example-components

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值