Skip to content

Commit 2133d84

Browse files
committed
Generated complex list(home) page related components
1 parent 7891b70 commit 2133d84

6 files changed

Lines changed: 408 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import styled, { css } from 'styled-components';
2+
3+
interface ButtonProps {
4+
variant?: 'primary' | 'secondary' | 'danger';
5+
size?: 'small' | 'medium' | 'large';
6+
}
7+
8+
const baseButtonStyles = css`
9+
border: none;
10+
border-radius: 8px;
11+
padding: 0.625rem 1.25rem; /* 10px 20px */
12+
font-weight: 500;
13+
cursor: pointer;
14+
transition:
15+
background-color 0.2s ease-in-out,
16+
transform 0.1s ease-in-out;
17+
display: inline-flex;
18+
align-items: center;
19+
justify-content: center;
20+
text-align: center;
21+
white-space: nowrap;
22+
23+
&:hover {
24+
transform: translateY(-1px);
25+
}
26+
27+
&:active {
28+
transform: translateY(0);
29+
}
30+
`;
31+
32+
const primaryStyles = css`
33+
background-color: #007aff;
34+
color: white;
35+
box-shadow: 0 2px 4px rgba(0, 122, 255, 0.2);
36+
&:hover {
37+
background-color: #005ecb;
38+
}
39+
`;
40+
41+
const secondaryStyles = css`
42+
background-color: #e9ecef;
43+
color: #343a40;
44+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
45+
&:hover {
46+
background-color: #ced4da;
47+
}
48+
`;
49+
50+
const dangerStyles = css`
51+
background-color: #dc3545;
52+
color: white;
53+
box-shadow: 0 2px 4px rgba(220, 53, 69, 0.2);
54+
&:hover {
55+
background-color: #c82333;
56+
}
57+
`;
58+
59+
const sizeStyles = {
60+
small: css`
61+
padding: 0.375rem 0.75rem; /* 6px 12px */
62+
font-size: 0.875rem; /* 14px */
63+
`,
64+
medium: css`
65+
padding: 0.625rem 1.25rem; /* 10px 20px */
66+
font-size: 1rem; /* 16px */
67+
`,
68+
large: css`
69+
padding: 0.75rem 1.5rem; /* 12px 24px */
70+
font-size: 1.125rem; /* 18px */
71+
`,
72+
};
73+
74+
export const Button = styled.button<ButtonProps>`
75+
${baseButtonStyles}
76+
${({ variant }) => {
77+
switch (variant) {
78+
case 'primary':
79+
return primaryStyles;
80+
case 'secondary':
81+
return secondaryStyles;
82+
case 'danger':
83+
return dangerStyles;
84+
default:
85+
return primaryStyles; // Default to primary
86+
}
87+
}}
88+
${({ size }) => sizeStyles[size || 'medium']}
89+
`;
90+
91+
export default Button;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import Button from '../atoms/Button';
4+
5+
const HeaderWrapper = styled.header`
6+
background-color: rgba(255, 255, 255, 0.8);
7+
backdrop-filter: blur(10px);
8+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
9+
position: sticky;
10+
top: 0;
11+
z-index: 1000;
12+
width: 100%;
13+
`;
14+
15+
const NavContainer = styled.div`
16+
max-width: 1200px;
17+
margin: 0 auto;
18+
padding: 0 1rem; /* 16px */
19+
display: flex;
20+
align-items: center;
21+
justify-content: space-between;
22+
height: 4rem; /* 64px */
23+
`;
24+
25+
const Logo = styled.h1`
26+
font-size: 1.75rem; /* 28px */
27+
font-weight: 600;
28+
color: #1d1d1f;
29+
`;
30+
31+
interface HeaderProps {
32+
onAddNewComplex: () => void;
33+
}
34+
35+
const Header: React.FC<HeaderProps> = ({ onAddNewComplex }) => {
36+
return (
37+
<HeaderWrapper>
38+
<NavContainer>
39+
<Logo>Re:Fuel</Logo>
40+
<Button variant="primary" size="small" onClick={onAddNewComplex}>
41+
新しいコンプレックスを登録
42+
</Button>
43+
</NavContainer>
44+
</HeaderWrapper>
45+
);
46+
};
47+
48+
export default Header;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import type { Complex } from '../../../types/complex';
4+
import Button from '../../common/atoms/Button';
5+
6+
const CardWrapper = styled.div<{ delay: number }>`
7+
background-color: #ffffff;
8+
border-radius: 16px;
9+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.08);
10+
padding: 1.5rem; /* 24px */
11+
display: flex;
12+
flex-direction: column;
13+
justify-content: space-between;
14+
transition:
15+
transform 0.3s ease-out,
16+
box-shadow 0.3s ease-out;
17+
opacity: 0;
18+
transform: translateY(10px);
19+
animation: fadeIn 0.5s ease-out forwards;
20+
animation-delay: ${({ delay }) => delay}s;
21+
22+
&:hover {
23+
transform: translateY(-6px);
24+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
25+
}
26+
27+
@keyframes fadeIn {
28+
to {
29+
opacity: 1;
30+
transform: translateY(0);
31+
}
32+
}
33+
`;
34+
35+
const CategoryBadge = styled.span`
36+
display: inline-block;
37+
background-color: #e0e7ff; /* Indigo-100 like */
38+
color: #4338ca; /* Indigo-700 like */
39+
font-size: 0.75rem; /* 12px */
40+
font-weight: 500;
41+
padding: 0.25rem 0.75rem; /* 4px 12px */
42+
border-radius: 9999px; /* pill shape */
43+
margin-bottom: 0.75rem; /* 12px */
44+
`;
45+
46+
const ContentText = styled.p`
47+
color: #4b5563; /* Gray-600 like */
48+
font-size: 0.9375rem; /* 15px */
49+
line-height: 1.6;
50+
margin-bottom: 1rem; /* 16px */
51+
/* For line clamping */
52+
display: -webkit-box;
53+
-webkit-line-clamp: 4;
54+
-webkit-box-orient: vertical;
55+
overflow: hidden;
56+
text-overflow: ellipsis;
57+
min-height: calc(1.6em * 4); /* Ensure space for 4 lines */
58+
`;
59+
60+
const MetaText = styled.p`
61+
font-size: 0.75rem; /* 12px */
62+
color: #6b7280; /* Gray-500 like */
63+
margin-bottom: 1rem; /* 16px */
64+
`;
65+
66+
const ActionsWrapper = styled.div`
67+
display: flex;
68+
gap: 0.5rem; /* 8px */
69+
margin-top: auto; /* Pushes actions to the bottom */
70+
`;
71+
72+
interface ComplexCardProps {
73+
complex: Complex;
74+
onViewGoals: (id: number) => void;
75+
onEdit: (id: number) => void;
76+
onDelete: (id: number) => void;
77+
animationDelay: number;
78+
}
79+
80+
const ComplexCard: React.FC<ComplexCardProps> = ({
81+
complex,
82+
onViewGoals,
83+
onEdit,
84+
onDelete,
85+
animationDelay,
86+
}) => {
87+
const formatDate = (dateString: string) => {
88+
const options: Intl.DateTimeFormatOptions = {
89+
year: 'numeric',
90+
month: 'long',
91+
day: 'numeric',
92+
};
93+
return new Date(dateString).toLocaleDateString('ja-JP', options);
94+
};
95+
96+
return (
97+
<CardWrapper delay={animationDelay}>
98+
<div>
99+
<CategoryBadge>{complex.category}</CategoryBadge>
100+
<ContentText>{complex.content}</ContentText>
101+
</div>
102+
<div>
103+
<MetaText>最終更新: {formatDate(complex.updated_at)}</MetaText>
104+
<ActionsWrapper>
105+
<Button
106+
variant="primary"
107+
size="small"
108+
onClick={() => onViewGoals(complex.id)}
109+
style={{ flexGrow: 1 }}
110+
>
111+
目標を見る/設定
112+
</Button>
113+
<Button
114+
variant="secondary"
115+
size="small"
116+
onClick={() => onEdit(complex.id)}
117+
>
118+
編集
119+
</Button>
120+
<Button
121+
variant="danger"
122+
size="small"
123+
onClick={() => onDelete(complex.id)}
124+
>
125+
削除
126+
</Button>
127+
</ActionsWrapper>
128+
</div>
129+
</CardWrapper>
130+
);
131+
};
132+
133+
export default ComplexCard;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import type { Complex } from '../../../types/complex';
4+
import ComplexCard from './ComplexCard';
5+
import NoComplexesMessage from './NoComplexesMessage';
6+
7+
const ListWrapper = styled.div`
8+
display: grid;
9+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
10+
gap: 1.5rem; /* 24px */
11+
`;
12+
13+
interface ComplexListProps {
14+
complexes: Complex[];
15+
onViewGoals: (id: number) => void;
16+
onEdit: (id: number) => void;
17+
onDelete: (id: number) => void;
18+
onAddNewComplex: () => void; // For NoComplexesMessage
19+
}
20+
21+
const ComplexList: React.FC<ComplexListProps> = ({
22+
complexes,
23+
onViewGoals,
24+
onEdit,
25+
onDelete,
26+
onAddNewComplex,
27+
}) => {
28+
if (complexes.length === 0) {
29+
return <NoComplexesMessage onAddNewComplex={onAddNewComplex} />;
30+
}
31+
32+
return (
33+
<ListWrapper>
34+
{complexes.map((complex, index) => (
35+
<ComplexCard
36+
key={complex.id}
37+
complex={complex}
38+
onViewGoals={onViewGoals}
39+
onEdit={onEdit}
40+
onDelete={onDelete}
41+
animationDelay={index * 0.1} // Stagger animation
42+
/>
43+
))}
44+
</ListWrapper>
45+
);
46+
};
47+
48+
export default ComplexList;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import Button from '../../common/atoms/Button';
4+
5+
const MessageWrapper = styled.div`
6+
margin-top: 3rem; /* 48px */
7+
text-align: center;
8+
padding: 2rem;
9+
`;
10+
11+
const IconWrapper = styled.div`
12+
svg {
13+
margin: 0 auto;
14+
height: 3rem; /* 48px */
15+
width: 3rem; /* 48px */
16+
color: #9ca3af; /* Gray-400 like */
17+
}
18+
`;
19+
20+
const Title = styled.h3`
21+
margin-top: 0.5rem; /* 8px */
22+
font-size: 1.25rem; /* 20px */
23+
font-weight: 500;
24+
color: #111827; /* Gray-900 like */
25+
`;
26+
27+
const Subtitle = styled.p`
28+
margin-top: 0.25rem; /* 4px */
29+
font-size: 0.9375rem; /* 15px */
30+
color: #6b7280; /* Gray-500 like */
31+
`;
32+
33+
const ButtonWrapper = styled.div`
34+
margin-top: 1.5rem; /* 24px */
35+
`;
36+
37+
interface NoComplexesMessageProps {
38+
onAddNewComplex: () => void;
39+
}
40+
41+
const NoComplexesMessage: React.FC<NoComplexesMessageProps> = ({
42+
onAddNewComplex,
43+
}) => {
44+
return (
45+
<MessageWrapper>
46+
<IconWrapper>
47+
<svg
48+
fill="none"
49+
viewBox="0 0 24 24"
50+
stroke="currentColor"
51+
aria-hidden="true"
52+
>
53+
<path
54+
vectorEffect="non-scaling-stroke"
55+
strokeLinecap="round"
56+
strokeLinejoin="round"
57+
strokeWidth="2"
58+
d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"
59+
/>
60+
</svg>
61+
</IconWrapper>
62+
<Title>コンプレックスがまだありません</Title>
63+
<Subtitle>
64+
最初のコンプレックスを登録して、自己分析を始めましょう。
65+
</Subtitle>
66+
<ButtonWrapper>
67+
<Button variant="primary" onClick={onAddNewComplex}>
68+
コンプレックスを登録する
69+
</Button>
70+
</ButtonWrapper>
71+
</MessageWrapper>
72+
);
73+
};
74+
75+
export default NoComplexesMessage;

0 commit comments

Comments
 (0)