料金テーブル/

料金比較表

Preview

プランを横並びで比較できる料金テーブル

Source Code
tsx
174 lines
1import { Fragment } from "react";
2
3export function PricingComparison001() {
4 const features = [
5 {
6 category: "CORE FEATURES",
7 items: [
8 { name: "Projects", starter: "5", pro: "Unlimited", enterprise: "Unlimited" },
9 { name: "Storage", starter: "5GB", pro: "50GB", enterprise: "Unlimited" },
10 { name: "Team members", starter: "3", pro: "15", enterprise: "Unlimited" },
11 { name: "API calls/month", starter: "10K", pro: "100K", enterprise: "Unlimited" },
12 ],
13 },
14 {
15 category: "COLLABORATION",
16 items: [
17 { name: "Real-time editing", starter: false, pro: true, enterprise: true },
18 { name: "Comments & mentions", starter: true, pro: true, enterprise: true },
19 { name: "Version history", starter: "7 days", pro: "90 days", enterprise: "Unlimited" },
20 { name: "Guest access", starter: false, pro: true, enterprise: true },
21 ],
22 },
23 {
24 category: "SECURITY",
25 items: [
26 { name: "SSO/SAML", starter: false, pro: false, enterprise: true },
27 { name: "Two-factor auth", starter: true, pro: true, enterprise: true },
28 { name: "Audit logs", starter: false, pro: true, enterprise: true },
29 { name: "Custom permissions", starter: false, pro: true, enterprise: true },
30 ],
31 },
32 {
33 category: "SUPPORT",
34 items: [
35 { name: "Email support", starter: true, pro: true, enterprise: true },
36 { name: "Priority support", starter: false, pro: true, enterprise: true },
37 { name: "Dedicated manager", starter: false, pro: false, enterprise: true },
38 { name: "SLA guarantee", starter: false, pro: false, enterprise: true },
39 ],
40 },
41 ];
42
43 const plans = [
44 { name: "STARTER", price: "$15", period: "month", cta: "Get Started" },
45 { name: "PRO", price: "$45", period: "month", cta: "Start Trial", featured: true },
46 { name: "ENTERPRISE", price: "Custom", period: "", cta: "Contact Us" },
47 ];
48
49 // セルの値をレンダリング
50 const renderValue = (value: boolean | string) => {
51 if (typeof value === "boolean") {
52 return value ? (
53 <svg className="mx-auto h-4 w-4 text-foreground/70" fill="none" viewBox="0 0 24 24" stroke="currentColor">
54 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 13l4 4L19 7" />
55 </svg>
56 ) : (
57 <span className="text-muted-foreground/40">-</span>
58 );
59 }
60 return <span className="text-sm text-muted-foreground">{value}</span>;
61 };
62
63 return (
64 <section className="bg-background py-24">
65 <div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
66 {/* Header */}
67 <div className="mb-16 text-center">
68 <p className="mb-3 text-xs font-medium uppercase tracking-[0.3em] text-muted-foreground">
69 Compare Plans
70 </p>
71 <h2 className="text-3xl font-light tracking-wide text-foreground sm:text-4xl">
72 Find the right plan for you
73 </h2>
74 </div>
75
76 {/* Comparison Table */}
77 <div className="overflow-x-auto">
78 <table className="w-full min-w-[600px]">
79 {/* Header Row */}
80 <thead>
81 <tr className="border-b border-border">
82 <th className="pb-6 pr-4 text-left">
83 <span className="text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground">
84 Features
85 </span>
86 </th>
87 {plans.map((plan) => (
88 <th key={plan.name} className="relative pb-6 text-center">
89 {plan.featured && (
90 <div className="absolute -top-3 left-1/2 -translate-x-1/2">
91 <span className="bg-primary px-3 py-1 text-[10px] font-medium uppercase tracking-[0.15em] text-primary-foreground">
92 Popular
93 </span>
94 </div>
95 )}
96 <div className="space-y-2">
97 <p className="text-xs font-medium uppercase tracking-[0.25em] text-muted-foreground">
98 {plan.name}
99 </p>
100 <div>
101 <span className="text-3xl font-light text-foreground">{plan.price}</span>
102 {plan.period && (
103 <span className="text-sm text-muted-foreground">/{plan.period}</span>
104 )}
105 </div>
106 </div>
107 </th>
108 ))}
109 </tr>
110 </thead>
111
112 {/* Feature Rows */}
113 <tbody>
114 {features.map((section) => (
115 <Fragment key={section.category}>
116 {/* Category Header */}
117 <tr>
118 <td colSpan={4} className="pt-10 pb-4">
119 <span className="text-[10px] font-medium uppercase tracking-[0.3em] text-muted-foreground">
120 {section.category}
121 </span>
122 </td>
123 </tr>
124
125 {/* Feature Items */}
126 {section.items.map((item, idx) => (
127 <tr
128 key={item.name}
129 className={idx !== section.items.length - 1 ? "border-b border-border/30" : ""}
130 >
131 <td className="py-4 pr-4">
132 <span className="text-sm tracking-wide text-muted-foreground">{item.name}</span>
133 </td>
134 <td className="py-4 text-center">{renderValue(item.starter)}</td>
135 <td className="py-4 text-center bg-muted/30">{renderValue(item.pro)}</td>
136 <td className="py-4 text-center">{renderValue(item.enterprise)}</td>
137 </tr>
138 ))}
139 </Fragment>
140 ))}
141
142 {/* CTA Row */}
143 <tr className="border-t border-border">
144 <td className="pt-8"></td>
145 {plans.map((plan) => (
146 <td key={plan.name} className="pt-8 px-2 text-center">
147 <button
148 className={`w-full max-w-[160px] py-3 text-xs font-medium uppercase tracking-[0.15em] transition-all ${
149 plan.featured
150 ? "bg-primary text-primary-foreground hover:bg-primary/90"
151 : "border border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground"
152 }`}
153 >
154 {plan.cta}
155 </button>
156 </td>
157 ))}
158 </tr>
159 </tbody>
160 </table>
161 </div>
162
163 {/* Corner Decoration */}
164 <div className="mt-16 flex justify-center">
165 <div className="flex items-center gap-2">
166 <div className="h-1 w-1 rounded-full bg-foreground/20" />
167 <div className="h-px w-16 bg-border" />
168 <div className="h-1 w-1 rounded-full bg-foreground/20" />
169 </div>
170 </div>
171 </div>
172 </section>
173 );
174}