SCALE — Build Lab
UI部品 · REACT COMPONENT

日付範囲ピッカー

CATEGORYUI部品 TYPEReact Component EFFORT90〜180分 DIFFICULTY
PRIMARY CODE
tsx
'use client';
import { useState } from 'react';

const PRESETS = [
  { label: '今日',   from: () => { const d = new Date(); return d; }, to: () => new Date() },
  { label: '今週',   from: () => { const d = new Date(); d.setDate(d.getDate() - d.getDay()); return d; }, to: () => new Date() },
  { label: '今月',   from: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 1); }, to: () => new Date() },
  { label: '先月',   from: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth() - 1, 1); }, to: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 0); } },
  { label: '今四半期', from: () => { const d = new Date(); const q = Math.floor(d.getMonth() / 3) * 3; return new Date(d.getFullYear(), q, 1); }, to: () => new Date() },
];

export function DateRangePicker({ from, to, onChange }: {
  from: string; to: string; onChange: (from: string, to: string) => void;
}) {
  const setPreset = (p: typeof PRESETS[0]) => {
    const fmt = (d: Date) => d.toISOString().slice(0, 10);
    onChange(fmt(p.from()), fmt(p.to()));
  };

  return (
    <div style={{ display: 'flex', gap: '.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
      <input type="date" value={from} onChange={e => onChange(e.target.value, to)} />
      <span style={{ color: 'rgba(255,255,255,.55)' }}>〜</span>
      <input type="date" value={to}   onChange={e => onChange(from, e.target.value)} />
      <div style={{ display: 'flex', gap: '.25rem', marginLeft: '.5rem' }}>
        {PRESETS.map(p => (
          <button key={p.label} onClick={() => setPreset(p)} style={{
            fontSize: '.7rem', padding: '.25rem .65rem', background: 'transparent',
            border: '1px solid rgba(255,255,255,.07)', color: 'rgba(255,255,255,.55)',
            cursor: 'pointer', borderRadius: 3,
          }}>{p.label}</button>
        ))}
      </div>
    </div>
  );
}
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • 任意のダッシュボードに組み込み

日付範囲ピッカー

:LiTarget: 用途

開始日 + 終了日を選ぶピッカー。プリセット(今週/今月/先月/先週)対応。

:LiSparkle: 特徴

  • 開始/終了2フィールド
  • プリセット
  • バリデーション
  • 相対日付

:LiCode: コード(コピペ用)

'use client';
import { useState } from 'react';

const PRESETS = [
  { label: '今日',   from: () => { const d = new Date(); return d; }, to: () => new Date() },
  { label: '今週',   from: () => { const d = new Date(); d.setDate(d.getDate() - d.getDay()); return d; }, to: () => new Date() },
  { label: '今月',   from: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 1); }, to: () => new Date() },
  { label: '先月',   from: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth() - 1, 1); }, to: () => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 0); } },
  { label: '今四半期', from: () => { const d = new Date(); const q = Math.floor(d.getMonth() / 3) * 3; return new Date(d.getFullYear(), q, 1); }, to: () => new Date() },
];

export function DateRangePicker({ from, to, onChange }: {
  from: string; to: string; onChange: (from: string, to: string) => void;
}) {
  const setPreset = (p: typeof PRESETS[0]) => {
    const fmt = (d: Date) => d.toISOString().slice(0, 10);
    onChange(fmt(p.from()), fmt(p.to()));
  };

  return (
    <div style={{ display: 'flex', gap: '.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
      <input type="date" value={from} onChange={e => onChange(e.target.value, to)} />
      <span style={{ color: 'rgba(255,255,255,.55)' }}>〜</span>
      <input type="date" value={to}   onChange={e => onChange(from, e.target.value)} />
      <div style={{ display: 'flex', gap: '.25rem', marginLeft: '.5rem' }}>
        {PRESETS.map(p => (
          <button key={p.label} onClick={() => setPreset(p)} style={{
            fontSize: '.7rem', padding: '.25rem .65rem', background: 'transparent',
            border: '1px solid rgba(255,255,255,.07)', color: 'rgba(255,255,255,.55)',
            cursor: 'pointer', borderRadius: 3,
          }}>{p.label}</button>
        ))}
      </div>
    </div>
  );
}

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加