Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 94 additions & 8 deletions packages/chakra-components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The best place to learn about using this package is the [developer portal](https

### Table of Contents
- [Getting Started](#getting-started)
- [Component System](#component-system)
- [Customizing Components](#customizing-components)
- [Reference](#reference)
- [Examples](#examples)
- [Preview](#preview)
Expand All @@ -72,6 +74,98 @@ After that, you can add the required vocdoni components dependencies:
yarn add @vocdoni/sdk @vocdoni/chakra-components react-markdown remark-gfm
~~~

## Component System

This package provides Chakra UI implementations of the Vocdoni component system. The components are initialized automatically when you use the `ClientProvider`:

```tsx
import { ClientProvider } from '@vocdoni/chakra-components'

function App() {
return (
<ClientProvider>
{/* your app content */}
</ClientProvider>
)
}
```

## Customizing Components

You can customize any component in two ways:

### 1. Through the ClientProvider

```tsx
import { ClientProvider } from '@vocdoni/chakra-components'

const CustomTitle = (props) => (
<h1 style={{ color: 'red' }} {...props}>
{props.children}
</h1>
)

function App() {
return (
<ClientProvider
components={{
Election: {
Title: CustomTitle
}
}}
>
{/* your app content */}
</ClientProvider>
)
}
```

### 2. Using the useComponents Hook

```tsx
import { useComponents } from '@vocdoni/react-providers'

function CustomizationExample() {
const { override } = useComponents()

useEffect(() => {
override({
Election: {
Title: CustomTitle
}
})
}, [])

return <div>{/* your content */}</div>
}
```

### Available Components

The following components can be customized:

```typescript
interface ChakraVocdoniComponents {
Election: {
Actions: React.ComponentType<ElectionActionProps>
Description: React.ComponentType<ElectionDescriptionProps>
Election: React.ComponentType<ElectionProviderComponentProps>
Envelope: React.ComponentType<ElectionEnvelopeProps>
Header: React.ComponentType<ElectionHeaderProps>
Questions: React.ComponentType<ElectionQuestionsProps>
Results: React.ComponentType<ElectionResultsProps>
Schedule: React.ComponentType<ElectionScheduleProps>
SpreadsheetAccess: React.ComponentType<ElectionSpreadsheetAccessProps>
StatusBadge: React.ComponentType<ElectionStatusBadgeProps>
Title: React.ComponentType<ElectionTitleProps>
VoteButton: React.ComponentType<ElectionVoteButtonProps>
VoteWeight: React.ComponentType<ElectionVoteWeightProps>
}
}
```

Each component has its own prop types that extend Chakra UI's base props.

## Reference

The developer portal includes a [reference](https://developer.vocdoni.io/ui-components) for the `@vocdoni/chakra-components` package.
Expand Down Expand Up @@ -116,11 +210,3 @@ This repository is licensed under the [GNU Affero General Public License v3.0.](
along with this program. If not, see <https://www.gnu.org/licenses/>.

[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

[`@vocdoni/chakra-components`]: ./packages/chakra-components/README.md
[`@vocdoni/rainbowkit-wallets`]: ./packages/rainbowkit-wallets/README.md
[`@vocdoni/react-providers`]: ./packages/react-providers/README.md

[chakra-components badge]: https://img.shields.io/npm/v/%40vocdoni%2Fchakra-components?label=%40vocdoni%2Fchakra-components
[rainbowkit-wallets badge]: https://img.shields.io/npm/v/%40vocdoni%2Frainbowkit-wallets?label=%40vocdoni%2Frainbowkit-wallets
[react-providers badge]: https://img.shields.io/npm/v/%40vocdoni%2Freact-providers?label=%40vocdoni%2Freact-providers
33 changes: 30 additions & 3 deletions packages/chakra-components/src/client.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
import { ToastProvider } from '@chakra-ui/react'
import { ClientProviderComponentProps, ClientProvider as RPClientProvider } from '@vocdoni/react-providers'
import { ComponentsProvider, ClientProvider as RPClientProvider } from '@vocdoni/react-providers'
import merge from 'ts-deepmerge'
import { ConfirmProvider } from './components'
import { defaultComponents } from './components/defaultComponents'
import { locales } from './i18n/locales'
import { ChakraClientProviderProps } from './types'

export const ClientProvider = ({ children, ...props }: ClientProviderComponentProps) => {
export const ClientProvider = ({ children, components: customComponents, ...props }: ChakraClientProviderProps) => {
const loc = {
locale: merge(locales, props.locale || {}),
}

// Merge components safely
const mergedComponents = customComponents
? {
Election: {
...defaultComponents.Election,
...customComponents.Election,
},
Organization: {
...defaultComponents.Organization,
...customComponents.Organization,
},
Account: {
...defaultComponents.Account,
...customComponents.Account,
},
Pagination: {
...defaultComponents.Pagination,
...customComponents.Pagination,
},
}
: defaultComponents

return (
<>
<ToastProvider />
<RPClientProvider {...props} {...loc}>
<ConfirmProvider>{children}</ConfirmProvider>
<ComponentsProvider components={mergedComponents}>
<ConfirmProvider>{children}</ConfirmProvider>
</ComponentsProvider>
</RPClientProvider>
</>
)
Expand Down
11 changes: 8 additions & 3 deletions packages/chakra-components/src/components/Account/Balance.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Tag, TagProps } from '@chakra-ui/react'
import { useClient } from '@vocdoni/react-providers'
import { Tag } from '@chakra-ui/react'
import { useClient, withRegistry } from '@vocdoni/react-providers'
import { AccountBalanceProps } from '../../types'

export const Balance = (props: TagProps) => {
const BaseBalance = (props: AccountBalanceProps) => {
const { balance, localize } = useClient()

if (balance < 0) {
Expand All @@ -21,3 +22,7 @@ export const Balance = (props: TagProps) => {
</Tag>
)
}
BaseBalance.displayName = 'BaseBalance'

export const Balance = withRegistry(BaseBalance, 'Account', 'Balance')
Balance.displayName = 'Balance'
Original file line number Diff line number Diff line change
@@ -1,65 +1,60 @@
import { ButtonGroup, chakra, ChakraProps, IconButton, useMultiStyleConfig } from '@chakra-ui/react'
import { ButtonGroup, chakra, IconButton, useMultiStyleConfig } from '@chakra-ui/react'
import { useClient, useElection } from '@vocdoni/react-providers'
import { areEqualHexStrings, ElectionStatus, PublishedElection } from '@vocdoni/sdk'
import { FaPause, FaPlay, FaStop } from 'react-icons/fa'
import { ImCross } from 'react-icons/im'
import { ElectionActionProps } from '../../../types'
import { ActionsProvider } from './ActionsProvider'
import { ActionCancel } from './Cancel'
import { ActionContinue } from './Continue'
import { ActionEnd } from './End'
import { ActionPause } from './Pause'

const Cancel = chakra(ImCross)
const Cross = chakra(ImCross)
const Play = chakra(FaPlay)
const Pause = chakra(FaPause)
const Stop = chakra(FaStop)

export const ElectionActions = (props: ChakraProps) => {
export const ElectionActions = (props: ElectionActionProps) => {
const { localize, account } = useClient()
const { election } = useElection()
const styles = useMultiStyleConfig('ElectionActions')

if (
!election ||
!(election instanceof PublishedElection) ||
(election && !areEqualHexStrings(election.organizationId, account?.address)) ||
[ElectionStatus.CANCELED, ElectionStatus.ENDED, ElectionStatus.RESULTS].includes(election.status)
) {
return null
}
if (!election || !(election instanceof PublishedElection)) return null

const isOwner = areEqualHexStrings(election.organizationId, account?.address)

if (!isOwner) return null

return (
<ButtonGroup size='sm' isAttached variant='outline' position='relative' sx={styles.group} {...props}>
<ButtonGroup sx={styles.wrapper} {...props}>
<ActionsProvider>
<ActionContinue
as={IconButton}
aria-label={localize('actions.continue')}
title={localize('actions.continue')}
sx={styles.buttons}
icon={<Play sx={styles.icons} />}
/>
<ActionPause
as={IconButton}
aria-label={localize('actions.pause')}
title={localize('actions.pause')}
sx={styles.buttons}
icon={<Pause sx={styles.icons} />}
/>
<ActionEnd
as={IconButton}
aria-label={localize('actions.end')}
title={localize('actions.end')}
sx={styles.buttons}
icon={<Stop sx={styles.icons} />}
{election.status === ElectionStatus.ONGOING && (
<IconButton
aria-label={localize('process_actions.pause', { defaultValue: 'Pause' })}
icon={<Pause />}
as={ActionPause}
/>
)}
{election.status === ElectionStatus.PAUSED && (
<IconButton
aria-label={localize('process_actions.continue', { defaultValue: 'Continue' })}
icon={<Play />}
as={ActionContinue}
/>
)}
<IconButton
aria-label={localize('process_actions.end', { defaultValue: 'End' })}
icon={<Stop />}
as={ActionEnd}
/>
<ActionCancel
as={IconButton}
aria-label={localize('actions.cancel')}
title={localize('actions.cancel')}
sx={styles.buttons}
icon={<Cancel sx={styles.icons} />}
<IconButton
aria-label={localize('process_actions.cancel', { defaultValue: 'Cancel' })}
icon={<Cross />}
as={ActionCancel}
/>
</ActionsProvider>
</ButtonGroup>
)
}
ElectionActions.displayName = 'ElectionActions'
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChakraProps, useStyleConfig } from '@chakra-ui/react'
import { useElection } from '@vocdoni/react-providers'
import { useStyleConfig } from '@chakra-ui/react'
import { useElection, withRegistry } from '@vocdoni/react-providers'
import { PublishedElection } from '@vocdoni/sdk'
import { ReactMarkdownProps } from 'react-markdown/lib/complex-types'
import { ElectionDescriptionProps } from '../../types'
import { Markdown } from '../layout'

export const ElectionDescription = (props: Omit<ReactMarkdownProps, 'children' | 'node'> & ChakraProps) => {
const BaseElectionDescription = (props: ElectionDescriptionProps) => {
const styles = useStyleConfig('ElectionDescription', props)
const { election } = useElection()

Expand All @@ -18,4 +18,7 @@ export const ElectionDescription = (props: Omit<ReactMarkdownProps, 'children' |
</Markdown>
)
}
BaseElectionDescription.displayName = 'BaseElectionDescription'

export const ElectionDescription = withRegistry(BaseElectionDescription, 'Election', 'Description')
ElectionDescription.displayName = 'ElectionDescription'
31 changes: 16 additions & 15 deletions packages/chakra-components/src/components/Election/Election.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ElectionActions,
ElectionDescription,
ElectionHeader,
ElectionQuestions,
ElectionQuestion,
ElectionResults,
ElectionSchedule,
ElectionStatusBadge,
Expand All @@ -25,35 +25,36 @@ Election.displayName = 'Election'
// Not exported since we're not allowing it to be configured. If you want to customize it past
// this level, create a custom Election component with the provided election components.
const ElectionBody = () => {
const {
errors: { election: error },
election,
connected,
} = useElection()
const { election } = useElection()

if (error) {
if (!election) {
return (
<Alert status='error'>
<AlertIcon />
<AlertDescription>{error}</AlertDescription>
<AlertDescription>Election not found</AlertDescription>
</Alert>
)
}

if (!(election instanceof PublishedElection)) {
return null
}

return (
<>
<ElectionHeader />
<ElectionStatusBadge />
<ElectionTitle />
<ElectionDescription />
<ElectionHeader />
<ElectionSchedule />
<ElectionStatusBadge />
<HR />
<ElectionActions />
<ElectionDescription />
<HR />
<ElectionQuestions />
<SpreadsheetAccess />
<VoteButton />
{election instanceof PublishedElection && election?.get('census.type') === 'spreadsheet' && connected && (
<SpreadsheetAccess />
)}
{election.questions.map((question, index) => (
<ElectionQuestion key={index} question={question} index={index.toString()} />
))}
<ElectionResults />
</>
)
Expand Down
Loading