|
1 | | -import React from 'react'; |
| 1 | +import React, { useState } from 'react'; |
2 | 2 | import styled from 'styled-components'; |
| 3 | +import { Search as SearchIcon } from '@mui/icons-material'; |
3 | 4 | import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; |
4 | 5 |
|
| 6 | +import SupportersJoined from './SupportersJoined'; |
| 7 | +import SupportersInvited from './SupportersInvited'; |
| 8 | +import SupportersToRemind from './SupportersToRemind'; |
| 9 | + |
5 | 10 | export default function SupporterTracking () { |
| 11 | + const [activeTab, setActiveTab] = useState('joined'); |
| 12 | + |
| 13 | + // TO DO: Replace these placeholders with actual logic |
| 14 | + const joinedCount = 3; |
| 15 | + const invitedCount = 3; |
| 16 | + const remindCount = 1; |
| 17 | + |
| 18 | + const renderTabContent = () => { |
| 19 | + switch (activeTab) { |
| 20 | + case 'joined': |
| 21 | + return <SupportersJoined />; |
| 22 | + case 'invited': |
| 23 | + return <SupportersInvited />; |
| 24 | + case 'remind': |
| 25 | + return <SupportersToRemind />; |
| 26 | + default: |
| 27 | + return <SupportersJoined />; |
| 28 | + } |
| 29 | + }; |
| 30 | + |
6 | 31 | return ( |
7 | | - <Placeholder> |
8 | | - Tracking (coming soon) |
9 | | - </Placeholder> |
| 32 | + <> |
| 33 | + <HeaderRow> |
| 34 | + <H2>Tracking</H2> |
| 35 | + <HeaderDivider /> |
| 36 | + <SearchIconButton |
| 37 | + type="button" |
| 38 | + aria-label="Search" |
| 39 | + onClick={() => console.log('TODO: implement search feature')} |
| 40 | + > |
| 41 | + <SearchIcon fontSize="medium" /> |
| 42 | + </SearchIconButton> |
| 43 | + </HeaderRow> |
| 44 | + <TrackingText>Follow up on your invitees and those who joined.</TrackingText> |
| 45 | + <TabRow> |
| 46 | + <Tab |
| 47 | + active={activeTab === 'joined'} |
| 48 | + onClick={() => setActiveTab('joined')} |
| 49 | + data-hidden-bold-text={`Joined WeVote (${joinedCount})`} |
| 50 | + > |
| 51 | + Joined WeVote ({joinedCount}) |
| 52 | + </Tab> |
| 53 | + <Tab |
| 54 | + active={activeTab === 'invited'} |
| 55 | + onClick={() => setActiveTab('invited')} |
| 56 | + data-hidden-bold-text={`Invited (${invitedCount})`} |
| 57 | + > |
| 58 | + Invited ({invitedCount}) |
| 59 | + </Tab> |
| 60 | + <Tab |
| 61 | + active={activeTab === 'remind'} |
| 62 | + onClick={() => setActiveTab('remind')} |
| 63 | + data-hidden-bold-text={`Reminder needed (${remindCount})`} |
| 64 | + > |
| 65 | + Reminder needed ({remindCount}) |
| 66 | + </Tab> |
| 67 | + </TabRow> |
| 68 | + <TabContent> |
| 69 | + {renderTabContent()} |
| 70 | + </TabContent> |
| 71 | + </> |
10 | 72 | ); |
11 | 73 | } |
12 | 74 |
|
13 | | -const Placeholder = styled.div` |
14 | | - background: ${DesignTokenColors.neutralUI50}; |
15 | | - border: 1px dashed ${DesignTokenColors.neutralUI300}; |
16 | | - border-radius: 12px; |
17 | | - color: ${DesignTokenColors.neutralUI600}; |
18 | | - padding: 24px; |
| 75 | +// Styles |
| 76 | + |
| 77 | +const H2 = styled.h2` |
| 78 | + color: ${DesignTokenColors.neutralUI900}; |
| 79 | + font-size: 20px; |
| 80 | + font-weight: 400; |
| 81 | + margin: 0; |
| 82 | +`; |
| 83 | + |
| 84 | +const HeaderDivider = styled.span` |
| 85 | + border-left: 1.5px solid ${DesignTokenColors.neutralUI100}; |
| 86 | + height: 30px; |
| 87 | + margin: 0 4px 0 12px; |
| 88 | +`; |
| 89 | + |
| 90 | +const HeaderRow = styled.div` |
| 91 | + align-items: center; |
| 92 | + display: flex; |
| 93 | + gap: 2px; |
| 94 | + margin: -6px 0 8px; |
| 95 | +`; |
| 96 | + |
| 97 | +const SearchIconButton = styled.button` |
| 98 | + align-items: center; |
| 99 | + background: none; |
| 100 | + border: none; |
| 101 | + border-radius: 8px; |
| 102 | + color: ${DesignTokenColors.neutralUI700}; |
| 103 | + cursor: pointer; |
| 104 | + display: inline-flex; |
| 105 | + padding: 6px; |
| 106 | + &:hover { |
| 107 | + background: ${DesignTokenColors.neutralUI50}; |
| 108 | + color: ${DesignTokenColors.neutralUI900}; |
| 109 | + } |
| 110 | +`; |
| 111 | + |
| 112 | +const Tab = styled.button` |
| 113 | + background: none; |
| 114 | + border: none; |
| 115 | + padding: 12px 16px 4px 16px; |
| 116 | + font-size: 15px; |
| 117 | + font-weight: ${props => props.active ? '600' : '500'}; |
| 118 | + color: ${props => props.active ? DesignTokenColors.primary600 : DesignTokenColors.neutralUI700}; |
| 119 | + border-bottom: 2px solid ${props => props.active ? DesignTokenColors.primary600 : 'transparent'}; |
| 120 | + cursor: pointer; |
| 121 | + position: relative; |
| 122 | + bottom: -1px; |
| 123 | +
|
| 124 | + // We use data-hidden-bold-text to render an invisible bolded duplicate of the tab's text |
| 125 | + // so there's no awkward shiftiness in the layout when switching between tabs. |
| 126 | + // It's hidden visually, un-interactable, and hidden from screen readers. |
| 127 | + &::after { |
| 128 | + content: attr(data-hidden-bold-text); |
| 129 | + height: 0; |
| 130 | + visibility: hidden; |
| 131 | + overflow: hidden; |
| 132 | + user-select: none; |
| 133 | + pointer-events: none; |
| 134 | + font-weight: 600; |
| 135 | + display: block; |
| 136 | + @media speech { |
| 137 | + display: none; |
| 138 | + } |
| 139 | + } |
| 140 | + &:hover { |
| 141 | + color: ${DesignTokenColors.primary600}; |
| 142 | + } |
| 143 | +`; |
| 144 | + |
| 145 | +const TabContent = styled.div` |
| 146 | + flex: 1; |
| 147 | +`; |
| 148 | + |
| 149 | +const TabRow = styled.div` |
| 150 | + display: flex; |
| 151 | + gap: 0; |
| 152 | + border-bottom: 1px solid ${DesignTokenColors.neutralUI200}; |
| 153 | + margin-bottom: 24px; |
| 154 | +`; |
| 155 | + |
| 156 | +const TrackingText = styled.p` |
| 157 | + color: ${DesignTokenColors.neutralUI700}; |
| 158 | + margin: 0 0 6px; |
19 | 159 | `; |
0 commit comments