From ddd79aaa35a8326cb0bf98b2bece5dfb977b654c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Fri, 9 May 2025 18:02:24 +0200 Subject: [PATCH 1/5] feat: implement component registry system - Add framework-agnostic component system to react-providers - Add Chakra-specific component implementations - Allow component overrides through ClientProvider - Maintain backward compatibility through direct exports --- packages/chakra-components/src/client.tsx | 17 ++++- .../src/components/defaultComponents.ts | 33 +++++++++ packages/chakra-components/src/index.ts | 28 +++++++- packages/chakra-components/src/types.ts | 45 ++++++++++++ .../src/components/ComponentsProvider.tsx | 69 +++++++++++++++++++ .../react-providers/src/components/index.ts | 2 + .../react-providers/src/components/types.ts | 24 +++++++ packages/react-providers/src/index.ts | 1 + 8 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 packages/chakra-components/src/components/defaultComponents.ts create mode 100644 packages/chakra-components/src/types.ts create mode 100644 packages/react-providers/src/components/ComponentsProvider.tsx create mode 100644 packages/react-providers/src/components/index.ts create mode 100644 packages/react-providers/src/components/types.ts diff --git a/packages/chakra-components/src/client.tsx b/packages/chakra-components/src/client.tsx index 0de917e0..951527d1 100644 --- a/packages/chakra-components/src/client.tsx +++ b/packages/chakra-components/src/client.tsx @@ -1,18 +1,29 @@ import { ToastProvider } from '@chakra-ui/react' -import { ClientProviderComponentProps, ClientProvider as RPClientProvider } from '@vocdoni/react-providers' +import { + ComponentsProvider, + ClientProvider as RPClientProvider, + VocdoniComponentDefinition, +} 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 || {}), } + + const mergedComponents = customComponents ? merge(defaultComponents, customComponents) : defaultComponents + return ( <> - {children} + + {children} + ) diff --git a/packages/chakra-components/src/components/defaultComponents.ts b/packages/chakra-components/src/components/defaultComponents.ts new file mode 100644 index 00000000..7499e7f5 --- /dev/null +++ b/packages/chakra-components/src/components/defaultComponents.ts @@ -0,0 +1,33 @@ +import { ChakraVocdoniComponents } from '../types' +import { ElectionActions } from './Election/Actions/Actions' +import { ElectionDescription } from './Election/Description' +import { Election } from './Election/Election' +import { Envelope } from './Election/Envelope' +import { ElectionHeader } from './Election/Header' +import { ElectionQuestion } from './Election/Questions/Fields' +import { ElectionResults } from './Election/Results' +import { ElectionSchedule } from './Election/Schedule' +import { SpreadsheetAccess } from './Election/SpreadsheetAccess' +import { ElectionStatusBadge } from './Election/StatusBadge' +import { ElectionTitle } from './Election/Title' +import { VoteButton } from './Election/VoteButton' +import { VoteWeight } from './Election/VoteWeight' + +// Initialize all components with their proper types +export const defaultComponents: ChakraVocdoniComponents = { + Election: { + Actions: ElectionActions, + Description: ElectionDescription, + Election, + Envelope, + Header: ElectionHeader, + Questions: ElectionQuestion, + Results: ElectionResults, + Schedule: ElectionSchedule, + SpreadsheetAccess, + StatusBadge: ElectionStatusBadge, + Title: ElectionTitle, + VoteButton, + VoteWeight, + }, +} diff --git a/packages/chakra-components/src/index.ts b/packages/chakra-components/src/index.ts index 2576a5df..c6bb796b 100644 --- a/packages/chakra-components/src/index.ts +++ b/packages/chakra-components/src/index.ts @@ -1,4 +1,30 @@ -export * from './client' +// Export components export * from './components' + +// Export environment and theme export * from './environment' export * from './theme' + +// Export client provider +export { ClientProvider } from './client' +export type { ChakraClientProviderProps } from './types' + +// Export component types +export type { + ChakraVocdoniComponents, + ElectionActionProps, + ElectionDescriptionProps, + ElectionEnvelopeProps, + ElectionHeaderProps, + ElectionQuestionsProps, + ElectionResultsProps, + ElectionScheduleProps, + ElectionSpreadsheetAccessProps, + ElectionStatusBadgeProps, + ElectionTitleProps, + ElectionVoteButtonProps, + ElectionVoteWeightProps, +} from './types' + +// Export default components for initialization +export { defaultComponents } from './components/defaultComponents' diff --git a/packages/chakra-components/src/types.ts b/packages/chakra-components/src/types.ts new file mode 100644 index 00000000..edbdc706 --- /dev/null +++ b/packages/chakra-components/src/types.ts @@ -0,0 +1,45 @@ +import { ChakraProps, HeadingProps, TagProps } from '@chakra-ui/react' +import { ClientProviderComponentProps, ElectionProviderComponentProps } from '@vocdoni/react-providers' +import { ReactMarkdownProps } from 'react-markdown/lib/complex-types' +import { IPFSImageProps } from './components/layout' + +// Define the prop types for each component +export type ElectionActionProps = ChakraProps +export type ElectionDescriptionProps = ReactMarkdownProps & ChakraProps +export type ElectionEnvelopeProps = ChakraProps & { votePackage: any } +export type ElectionHeaderProps = IPFSImageProps +export type ElectionQuestionsProps = ChakraProps +export type ElectionResultsProps = ChakraProps +export type ElectionScheduleProps = HeadingProps & { + format?: string + showRemaining?: boolean + showCreatedAt?: boolean +} +export type ElectionSpreadsheetAccessProps = ChakraProps +export type ElectionStatusBadgeProps = TagProps +export type ElectionTitleProps = HeadingProps +export type ElectionVoteButtonProps = ChakraProps +export type ElectionVoteWeightProps = ChakraProps + +// Define our components structure matching VocdoniComponentDefinition +export interface ChakraVocdoniComponents { + Election: { + Actions: React.ComponentType + Description: React.ComponentType + Election: React.ComponentType + Envelope: React.ComponentType + Header: React.ComponentType + Questions: React.ComponentType + Results: React.ComponentType + Schedule: React.ComponentType + SpreadsheetAccess: React.ComponentType + StatusBadge: React.ComponentType + Title: React.ComponentType + VoteButton: React.ComponentType + VoteWeight: React.ComponentType + } +} + +export interface ChakraClientProviderProps extends ClientProviderComponentProps { + components?: Partial +} diff --git a/packages/react-providers/src/components/ComponentsProvider.tsx b/packages/react-providers/src/components/ComponentsProvider.tsx new file mode 100644 index 00000000..692ec2a9 --- /dev/null +++ b/packages/react-providers/src/components/ComponentsProvider.tsx @@ -0,0 +1,69 @@ +import { createContext, PropsWithChildren, useContext, useState } from 'react' +import { ComponentsContextValue, VocdoniComponentDefinition } from './types' + +const ComponentsContext = createContext(undefined) + +export interface ComponentsProviderProps { + components?: Partial +} + +export const useComponents = () => { + const context = useContext(ComponentsContext) + if (!context) { + throw new Error('useComponents must be used within a ComponentsProvider') + } + return context +} + +const createEmptyComponent = () => () => null + +const createDefaultComponents = (): VocdoniComponentDefinition => ({ + Election: { + Actions: createEmptyComponent(), + Description: createEmptyComponent(), + Election: createEmptyComponent(), + Envelope: createEmptyComponent(), + Header: createEmptyComponent(), + Questions: createEmptyComponent(), + Results: createEmptyComponent(), + Schedule: createEmptyComponent(), + SpreadsheetAccess: createEmptyComponent(), + StatusBadge: createEmptyComponent(), + Title: createEmptyComponent(), + VoteButton: createEmptyComponent(), + VoteWeight: createEmptyComponent(), + }, +}) + +const mergeComponents = ( + base: VocdoniComponentDefinition, + override?: Partial +): VocdoniComponentDefinition => { + if (!override) return base + return { + Election: { + ...base.Election, + ...override.Election, + }, + } +} + +export const ComponentsProvider = ({ + children, + components: initialComponents = {}, +}: PropsWithChildren) => { + const [components, setComponents] = useState(() => + mergeComponents(createDefaultComponents(), initialComponents) + ) + + const override = (overrides: Partial) => { + setComponents((prev) => mergeComponents(prev, overrides)) + } + + const value: ComponentsContextValue = { + components, + override, + } + + return {children} +} diff --git a/packages/react-providers/src/components/index.ts b/packages/react-providers/src/components/index.ts new file mode 100644 index 00000000..f1992d4a --- /dev/null +++ b/packages/react-providers/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './ComponentsProvider' +export * from './types' diff --git a/packages/react-providers/src/components/types.ts b/packages/react-providers/src/components/types.ts new file mode 100644 index 00000000..41a949c8 --- /dev/null +++ b/packages/react-providers/src/components/types.ts @@ -0,0 +1,24 @@ +import { ComponentType } from 'react' + +export interface VocdoniComponentDefinition { + Election: { + Actions: ComponentType + Description: ComponentType + Election: ComponentType + Envelope: ComponentType + Header: ComponentType + Questions: ComponentType + Results: ComponentType + Schedule: ComponentType + SpreadsheetAccess: ComponentType + StatusBadge: ComponentType + Title: ComponentType + VoteButton: ComponentType + VoteWeight: ComponentType + } +} + +export interface ComponentsContextValue { + components: VocdoniComponentDefinition + override: (overrides: Partial) => void +} diff --git a/packages/react-providers/src/index.ts b/packages/react-providers/src/index.ts index ed9a74f8..5fee2726 100644 --- a/packages/react-providers/src/index.ts +++ b/packages/react-providers/src/index.ts @@ -1,4 +1,5 @@ export * from './client' +export * from './components' export * from './election' export * from './i18n' export * from './organization' From 2ea5a339fd52f83221f98e1483037713b03dd42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Fri, 9 May 2025 18:05:33 +0200 Subject: [PATCH 2/5] docs: add documentation for component system - Add framework-agnostic component system docs to react-providers - Add Chakra-specific component customization docs to chakra-components - Include examples and available components --- packages/chakra-components/README.md | 102 ++++++++++++++++++++++++--- packages/react-providers/README.md | 54 +++++++++++++- 2 files changed, 146 insertions(+), 10 deletions(-) diff --git a/packages/chakra-components/README.md b/packages/chakra-components/README.md index 6784000e..d3a7623f 100644 --- a/packages/chakra-components/README.md +++ b/packages/chakra-components/README.md @@ -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) @@ -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 ( + + {/* your app content */} + + ) +} +``` + +## Customizing Components + +You can customize any component in two ways: + +### 1. Through the ClientProvider + +```tsx +import { ClientProvider } from '@vocdoni/chakra-components' + +const CustomTitle = (props) => ( +

+ {props.children} +

+) + +function App() { + return ( + + {/* your app content */} + + ) +} +``` + +### 2. Using the useComponents Hook + +```tsx +import { useComponents } from '@vocdoni/react-providers' + +function CustomizationExample() { + const { override } = useComponents() + + useEffect(() => { + override({ + Election: { + Title: CustomTitle + } + }) + }, []) + + return
{/* your content */}
+} +``` + +### Available Components + +The following components can be customized: + +```typescript +interface ChakraVocdoniComponents { + Election: { + Actions: React.ComponentType + Description: React.ComponentType + Election: React.ComponentType + Envelope: React.ComponentType + Header: React.ComponentType + Questions: React.ComponentType + Results: React.ComponentType + Schedule: React.ComponentType + SpreadsheetAccess: React.ComponentType + StatusBadge: React.ComponentType + Title: React.ComponentType + VoteButton: React.ComponentType + VoteWeight: React.ComponentType + } +} +``` + +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. @@ -116,11 +210,3 @@ This repository is licensed under the [GNU Affero General Public License v3.0.]( along with this program. If not, see . [![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 diff --git a/packages/react-providers/README.md b/packages/react-providers/README.md index 6ec3fbe8..609d6b66 100644 --- a/packages/react-providers/README.md +++ b/packages/react-providers/README.md @@ -49,12 +49,12 @@ This package includes react context & providers for integration with the Vocdoni protocol via the [Vocdoni SDK](https://developer.vocdoni.io/sdk). - The best place to learn about using this package is the [developer portal](https://developer.vocdoni.io/ui-components). ### Table of Contents - [Getting Started](#getting-started) - [Reference](#reference) +- [Component System](#component-system) - [Examples](#examples) - [Preview](#preview) - [Disclaimer](#disclaimer) @@ -64,7 +64,6 @@ The best place to learn about using this package is the [developer portal](https ## Getting Started - Using your favorite package manager: ~~~bash @@ -88,6 +87,57 @@ const App = () => { `ClientProvider` is a dependency of the other providers, so you'll have to ensure you initialize it first as the parent. +## Component System + +The package includes a framework-agnostic component system that allows UI packages to provide their own component implementations. This system consists of: + +### ComponentsProvider + +The `ComponentsProvider` manages a registry of components that can be used throughout your application: + +```tsx +import { ComponentsProvider } from '@vocdoni/react-providers' + +function App() { + return ( + + {/* your app content */} + + ) +} +``` + +### Component Structure + +The component system defines a standard structure for election-related components: + +```typescript +interface VocdoniComponentDefinition { + Election: { + Actions: ComponentType + Description: ComponentType + Election: ComponentType + // ... other components + } +} +``` + +### Component Registration + +UI packages can register their components with the system: + +```typescript +const { override } = useComponents() + +override({ + Election: { + Title: CustomTitleComponent + } +}) +``` + +This system allows UI packages to provide their own implementations while maintaining a consistent structure. + ## Reference The developer portal includes a [reference](https://developer.vocdoni.io/ui-components) for using the `@vocdoni/react-providers` package. From 9fb86de4860a18ffcd4beaf02e22cfdb27075655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Fri, 9 May 2025 18:13:03 +0200 Subject: [PATCH 3/5] fix: improve component type safety --- packages/chakra-components/src/client.tsx | 2 +- packages/chakra-components/src/types.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/chakra-components/src/client.tsx b/packages/chakra-components/src/client.tsx index 951527d1..7cc96aa4 100644 --- a/packages/chakra-components/src/client.tsx +++ b/packages/chakra-components/src/client.tsx @@ -21,7 +21,7 @@ export const ClientProvider = ({ children, components: customComponents, ...prop <> - + {children} diff --git a/packages/chakra-components/src/types.ts b/packages/chakra-components/src/types.ts index edbdc706..ea95e640 100644 --- a/packages/chakra-components/src/types.ts +++ b/packages/chakra-components/src/types.ts @@ -40,6 +40,11 @@ export interface ChakraVocdoniComponents { } } +// Make each category's components optional +export type PartialChakraVocdoniComponents = { + Election: Partial +} + export interface ChakraClientProviderProps extends ClientProviderComponentProps { - components?: Partial + components?: PartialChakraVocdoniComponents } From 78e43721de688ba5073cfd87b2c224ef7c011bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Fri, 9 May 2025 18:58:04 +0200 Subject: [PATCH 4/5] feat(component registry): export registry-aware components --- .../src/components/Account/Balance.tsx | 11 +++-- .../src/components/Election/Title.tsx | 7 ++- .../src/components/Organization/Avatar.tsx | 12 +++-- .../components/Organization/Description.tsx | 11 +++-- .../src/components/Organization/Header.tsx | 11 +++-- .../src/components/Organization/Name.tsx | 10 ++-- .../src/components/Pagination/Button.tsx | 11 +++-- .../components/Pagination/EllipsisButton.tsx | 15 +++--- .../src/components/Pagination/Pagination.tsx | 35 ++++++------- .../src/components/defaultComponents.ts | 23 ++++++++- packages/chakra-components/src/types.ts | 49 +++++++++++++++++-- .../react-providers/src/components/index.ts | 1 + .../react-providers/src/components/types.ts | 14 ++++++ .../src/components/withRegistry.tsx | 27 ++++++++++ 14 files changed, 179 insertions(+), 58 deletions(-) create mode 100644 packages/react-providers/src/components/withRegistry.tsx diff --git a/packages/chakra-components/src/components/Account/Balance.tsx b/packages/chakra-components/src/components/Account/Balance.tsx index fdafad02..c73b4953 100644 --- a/packages/chakra-components/src/components/Account/Balance.tsx +++ b/packages/chakra-components/src/components/Account/Balance.tsx @@ -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) { @@ -21,3 +22,7 @@ export const Balance = (props: TagProps) => { ) } +BaseBalance.displayName = 'BaseBalance' + +export const Balance = withRegistry(BaseBalance, 'Account', 'Balance') +Balance.displayName = 'Balance' diff --git a/packages/chakra-components/src/components/Election/Title.tsx b/packages/chakra-components/src/components/Election/Title.tsx index 49b85f38..d3e9c0de 100644 --- a/packages/chakra-components/src/components/Election/Title.tsx +++ b/packages/chakra-components/src/components/Election/Title.tsx @@ -1,8 +1,8 @@ import { chakra, forwardRef, HeadingProps, omitThemingProps, useStyleConfig } from '@chakra-ui/react' -import { useElection } from '@vocdoni/react-providers' +import { useElection, withRegistry } from '@vocdoni/react-providers' import { PublishedElection } from '@vocdoni/sdk' -export const ElectionTitle = forwardRef((props, ref) => { +const BaseElectionTitle = forwardRef((props, ref) => { const { election } = useElection() const styles = useStyleConfig('ElectionTitle', props) const rest = omitThemingProps(props) @@ -17,4 +17,7 @@ export const ElectionTitle = forwardRef((props, ref) => { ) }) +BaseElectionTitle.displayName = 'BaseElectionTitle' + +export const ElectionTitle = withRegistry(BaseElectionTitle, 'Election', 'Title') ElectionTitle.displayName = 'ElectionTitle' diff --git a/packages/chakra-components/src/components/Organization/Avatar.tsx b/packages/chakra-components/src/components/Organization/Avatar.tsx index 05a5a46d..cd6857a4 100644 --- a/packages/chakra-components/src/components/Organization/Avatar.tsx +++ b/packages/chakra-components/src/components/Organization/Avatar.tsx @@ -1,10 +1,10 @@ import { useStyleConfig } from '@chakra-ui/react' -import { useOrganization } from '@vocdoni/react-providers' -import { Avatar, Image, IPFSAvatarProps, IPFSImageProps } from '../layout' +import { useOrganization, withRegistry } from '@vocdoni/react-providers' +import { OrganizationAvatarProps } from '../../types' +import { Avatar, Image, IPFSImageProps } from '../layout' -export const OrganizationAvatar = (props: IPFSAvatarProps) => { +const BaseOrganizationAvatar = (props: OrganizationAvatarProps) => { const styles = useStyleConfig('OrganizationAvatar', props) - const { organization } = useOrganization() let avatar = organization?.account.avatar @@ -15,11 +15,13 @@ export const OrganizationAvatar = (props: IPFSAvatarProps) => { return } +BaseOrganizationAvatar.displayName = 'BaseOrganizationAvatar' + +export const OrganizationAvatar = withRegistry(BaseOrganizationAvatar, 'Organization', 'Avatar') OrganizationAvatar.displayName = 'OrganizationAvatar' export const OrganizationImage = (props: IPFSImageProps) => { const styles = useStyleConfig('OrganizationImage', props) - const { organization } = useOrganization() let avatar = organization?.account.avatar diff --git a/packages/chakra-components/src/components/Organization/Description.tsx b/packages/chakra-components/src/components/Organization/Description.tsx index 3de7a5ad..13e1eb87 100644 --- a/packages/chakra-components/src/components/Organization/Description.tsx +++ b/packages/chakra-components/src/components/Organization/Description.tsx @@ -1,9 +1,9 @@ -import { ChakraProps, useStyleConfig } from '@chakra-ui/react' -import { useOrganization } from '@vocdoni/react-providers' -import { ReactMarkdownProps } from 'react-markdown/lib/complex-types' +import { useStyleConfig } from '@chakra-ui/react' +import { useOrganization, withRegistry } from '@vocdoni/react-providers' +import { OrganizationDescriptionProps } from '../../types' import { Markdown } from '../layout' -export const OrganizationDescription = (props: Omit & ChakraProps) => { +const BaseOrganizationDescription = (props: OrganizationDescriptionProps) => { const styles = useStyleConfig('OrganizationDescription', props) const { organization } = useOrganization() @@ -16,4 +16,7 @@ export const OrganizationDescription = (props: Omit ) } +BaseOrganizationDescription.displayName = 'BaseOrganizationDescription' + +export const OrganizationDescription = withRegistry(BaseOrganizationDescription, 'Organization', 'Description') OrganizationDescription.displayName = 'OrganizationDescription' diff --git a/packages/chakra-components/src/components/Organization/Header.tsx b/packages/chakra-components/src/components/Organization/Header.tsx index 0814c5c1..1f4892c3 100644 --- a/packages/chakra-components/src/components/Organization/Header.tsx +++ b/packages/chakra-components/src/components/Organization/Header.tsx @@ -1,10 +1,10 @@ import { useStyleConfig } from '@chakra-ui/react' -import { useOrganization } from '@vocdoni/react-providers' -import { Image, IPFSImageProps } from '../layout' +import { useOrganization, withRegistry } from '@vocdoni/react-providers' +import { OrganizationHeaderProps } from '../../types' +import { Image } from '../layout' -export const OrganizationHeader = (props: IPFSImageProps) => { +const BaseOrganizationHeader = (props: OrganizationHeaderProps) => { const styles = useStyleConfig('OrganizationHeader', props) - const { organization } = useOrganization() if (!organization) return null @@ -12,4 +12,7 @@ export const OrganizationHeader = (props: IPFSImageProps) => { return } +BaseOrganizationHeader.displayName = 'BaseOrganizationHeader' + +export const OrganizationHeader = withRegistry(BaseOrganizationHeader, 'Organization', 'Header') OrganizationHeader.displayName = 'OrganizationHeader' diff --git a/packages/chakra-components/src/components/Organization/Name.tsx b/packages/chakra-components/src/components/Organization/Name.tsx index 5ed066ab..6b69d122 100644 --- a/packages/chakra-components/src/components/Organization/Name.tsx +++ b/packages/chakra-components/src/components/Organization/Name.tsx @@ -1,7 +1,8 @@ -import { chakra, forwardRef, HeadingProps, omitThemingProps, useStyleConfig } from '@chakra-ui/react' -import { useOrganization } from '@vocdoni/react-providers' +import { chakra, forwardRef, omitThemingProps, useStyleConfig } from '@chakra-ui/react' +import { useOrganization, withRegistry } from '@vocdoni/react-providers' +import { OrganizationNameProps } from '../../types' -export const OrganizationName = forwardRef((props, ref) => { +const BaseOrganizationName = forwardRef((props, ref) => { const { organization } = useOrganization() const styles = useStyleConfig('OrganizationName', props) const rest = omitThemingProps(props) @@ -14,4 +15,7 @@ export const OrganizationName = forwardRef((props, ref) => { ) }) +BaseOrganizationName.displayName = 'BaseOrganizationName' + +export const OrganizationName = withRegistry(BaseOrganizationName, 'Organization', 'Name') OrganizationName.displayName = 'OrganizationName' diff --git a/packages/chakra-components/src/components/Pagination/Button.tsx b/packages/chakra-components/src/components/Pagination/Button.tsx index ed00048a..041a3c5d 100644 --- a/packages/chakra-components/src/components/Pagination/Button.tsx +++ b/packages/chakra-components/src/components/Pagination/Button.tsx @@ -1,7 +1,12 @@ -import { Button, ButtonProps, forwardRef, useStyleConfig } from '@chakra-ui/react' +import { Button, forwardRef, useStyleConfig } from '@chakra-ui/react' +import { withRegistry } from '@vocdoni/react-providers' +import { PaginationButtonProps } from '../../types' -export const PaginationButton = forwardRef((props, ref) => { +const BasePaginationButton = forwardRef((props, ref) => { const styles = useStyleConfig('PaginationButton', props) - return ) } +BaseEllipsisButton.displayName = 'BaseEllipsisButton' + +export const EllipsisButton = withRegistry(BaseEllipsisButton, 'Pagination', 'EllipsisButton') EllipsisButton.displayName = 'EllipsisButton' diff --git a/packages/chakra-components/src/components/Pagination/Pagination.tsx b/packages/chakra-components/src/components/Pagination/Pagination.tsx index e2edcf4a..c760837b 100644 --- a/packages/chakra-components/src/components/Pagination/Pagination.tsx +++ b/packages/chakra-components/src/components/Pagination/Pagination.tsx @@ -1,25 +1,11 @@ -import { - ButtonGroup, - ButtonGroupProps, - ButtonProps, - chakra, - InputProps, - Text, - useMultiStyleConfig, -} from '@chakra-ui/react' -import { useLocalize, usePagination, useRoutedPagination } from '@vocdoni/react-providers' -import { PaginationResponse } from '@vocdoni/sdk' +import { ButtonGroup, ButtonProps, chakra, InputProps, Text, useMultiStyleConfig } from '@chakra-ui/react' +import { useLocalize, usePagination, useRoutedPagination, withRegistry } from '@vocdoni/react-providers' import { ReactElement, useMemo } from 'react' import { Link as RouterLink } from 'react-router-dom' +import { PaginationProps } from '../../types' import { PaginationButton as PaginatorButton } from './Button' import { EllipsisButton } from './EllipsisButton' -export type PaginationProps = ButtonGroupProps & { - maxButtons?: number | false - buttonProps?: ButtonProps - inputProps?: InputProps -} & PaginationResponse - type PaginatorButtonProps = { page: number currentPage: number @@ -105,8 +91,9 @@ const PaginationButtons = ({ currentPage: number createPageButton: CreatePageButtonType goToPage: GotoPageType -} & ButtonGroupProps & - Pick) => { + maxButtons?: number | false + buttonProps?: ButtonProps +} & Omit) => { const styles = useMultiStyleConfig('Pagination') const t = useLocalize() @@ -124,7 +111,7 @@ const PaginationButtons = ({ return ( - + {totalPages === undefined ? ( <> { +const BasePagination = ({ maxButtons = 10, buttonProps, inputProps, pagination, ...rest }: PaginationProps) => { const { setPage } = usePagination() const totalPages = pagination.lastPage + 1 const page = pagination.currentPage @@ -169,10 +156,14 @@ export const Pagination = ({ maxButtons = 10, buttonProps, inputProps, paginatio totalPages={totalPages} totalItems={pagination.totalItems} maxButtons={maxButtons} + buttonProps={buttonProps} {...rest} /> ) } +BasePagination.displayName = 'BasePagination' + +export const Pagination = withRegistry(BasePagination, 'Pagination', 'Pagination') Pagination.displayName = 'Pagination' export const RoutedPagination = ({ maxButtons = 10, buttonProps, pagination, ...rest }: PaginationProps) => { @@ -190,6 +181,8 @@ export const RoutedPagination = ({ maxButtons = 10, buttonProps, pagination, ... currentPage={currentPage} totalPages={totalPages} totalItems={pagination.totalItems} + maxButtons={maxButtons} + buttonProps={buttonProps} {...rest} /> ) diff --git a/packages/chakra-components/src/components/defaultComponents.ts b/packages/chakra-components/src/components/defaultComponents.ts index 7499e7f5..b90db2a3 100644 --- a/packages/chakra-components/src/components/defaultComponents.ts +++ b/packages/chakra-components/src/components/defaultComponents.ts @@ -1,4 +1,5 @@ import { ChakraVocdoniComponents } from '../types' +import { Balance } from './Account/Balance' import { ElectionActions } from './Election/Actions/Actions' import { ElectionDescription } from './Election/Description' import { Election } from './Election/Election' @@ -12,8 +13,14 @@ import { ElectionStatusBadge } from './Election/StatusBadge' import { ElectionTitle } from './Election/Title' import { VoteButton } from './Election/VoteButton' import { VoteWeight } from './Election/VoteWeight' +import { OrganizationAvatar } from './Organization/Avatar' +import { OrganizationDescription } from './Organization/Description' +import { OrganizationHeader } from './Organization/Header' +import { OrganizationName } from './Organization/Name' +import { PaginationButton } from './Pagination/Button' +import { EllipsisButton } from './Pagination/EllipsisButton' +import { Pagination } from './Pagination/Pagination' -// Initialize all components with their proper types export const defaultComponents: ChakraVocdoniComponents = { Election: { Actions: ElectionActions, @@ -30,4 +37,18 @@ export const defaultComponents: ChakraVocdoniComponents = { VoteButton, VoteWeight, }, + Organization: { + Avatar: OrganizationAvatar, + Description: OrganizationDescription, + Header: OrganizationHeader, + Name: OrganizationName, + }, + Account: { + Balance, + }, + Pagination: { + Button: PaginationButton, + EllipsisButton, + Pagination, + }, } diff --git a/packages/chakra-components/src/types.ts b/packages/chakra-components/src/types.ts index ea95e640..cb029a23 100644 --- a/packages/chakra-components/src/types.ts +++ b/packages/chakra-components/src/types.ts @@ -1,5 +1,10 @@ -import { ChakraProps, HeadingProps, TagProps } from '@chakra-ui/react' -import { ClientProviderComponentProps, ElectionProviderComponentProps } from '@vocdoni/react-providers' +import { ButtonGroupProps, ButtonProps, ChakraProps, HeadingProps, InputProps, TagProps } from '@chakra-ui/react' +import { + ClientProviderComponentProps, + ElectionProviderComponentProps, + VocdoniComponentDefinition, +} from '@vocdoni/react-providers' +import { PaginationResponse } from '@vocdoni/sdk' import { ReactMarkdownProps } from 'react-markdown/lib/complex-types' import { IPFSImageProps } from './components/layout' @@ -21,8 +26,30 @@ export type ElectionTitleProps = HeadingProps export type ElectionVoteButtonProps = ChakraProps export type ElectionVoteWeightProps = ChakraProps +// Organization component props +export type OrganizationAvatarProps = ChakraProps +export type OrganizationDescriptionProps = ReactMarkdownProps & ChakraProps +export type OrganizationHeaderProps = ChakraProps +export type OrganizationNameProps = HeadingProps + +// Account component props +export type AccountBalanceProps = TagProps + +// Pagination component props +export type PaginationButtonProps = ChakraProps +export type PaginationEllipsisButtonProps = ChakraProps & { + gotoPage: (page: number) => void + inputProps?: InputProps +} +export type PaginationProps = ButtonGroupProps & { + maxButtons?: number | false + buttonProps?: ButtonProps + inputProps?: InputProps + pagination: PaginationResponse['pagination'] +} + // Define our components structure matching VocdoniComponentDefinition -export interface ChakraVocdoniComponents { +export interface ChakraVocdoniComponents extends VocdoniComponentDefinition { Election: { Actions: React.ComponentType Description: React.ComponentType @@ -38,11 +65,25 @@ export interface ChakraVocdoniComponents { VoteButton: React.ComponentType VoteWeight: React.ComponentType } + Organization: { + Avatar: React.ComponentType + Description: React.ComponentType + Header: React.ComponentType + Name: React.ComponentType + } + Account: { + Balance: React.ComponentType + } + Pagination: { + Button: React.ComponentType + EllipsisButton: React.ComponentType + Pagination: React.ComponentType + } } // Make each category's components optional export type PartialChakraVocdoniComponents = { - Election: Partial + [Category in keyof ChakraVocdoniComponents]?: Partial } export interface ChakraClientProviderProps extends ClientProviderComponentProps { diff --git a/packages/react-providers/src/components/index.ts b/packages/react-providers/src/components/index.ts index f1992d4a..379dc01d 100644 --- a/packages/react-providers/src/components/index.ts +++ b/packages/react-providers/src/components/index.ts @@ -1,2 +1,3 @@ export * from './ComponentsProvider' export * from './types' +export * from './withRegistry' diff --git a/packages/react-providers/src/components/types.ts b/packages/react-providers/src/components/types.ts index 41a949c8..8b3d17b7 100644 --- a/packages/react-providers/src/components/types.ts +++ b/packages/react-providers/src/components/types.ts @@ -16,6 +16,20 @@ export interface VocdoniComponentDefinition { VoteButton: ComponentType VoteWeight: ComponentType } + Organization: { + Avatar: ComponentType + Description: ComponentType + Header: ComponentType + Name: ComponentType + } + Account: { + Balance: ComponentType + } + Pagination: { + Button: ComponentType + EllipsisButton: ComponentType + Pagination: ComponentType + } } export interface ComponentsContextValue { diff --git a/packages/react-providers/src/components/withRegistry.tsx b/packages/react-providers/src/components/withRegistry.tsx new file mode 100644 index 00000000..e2f4480c --- /dev/null +++ b/packages/react-providers/src/components/withRegistry.tsx @@ -0,0 +1,27 @@ +import { ComponentType } from 'react' +import { useComponents } from './ComponentsProvider' +import { VocdoniComponentDefinition } from './types' + +export const withRegistry = < + P extends object, + Category extends keyof VocdoniComponentDefinition, + Component extends keyof VocdoniComponentDefinition[Category] +>( + BaseComponent: ComponentType

, + category: Category, + key: Component +) => { + const RegistryAwareComponent = (props: P) => { + const { components } = useComponents() + const OverrideComponent = components?.[category]?.[key] as ComponentType

| undefined + + if (OverrideComponent) { + return + } + + return + } + + RegistryAwareComponent.displayName = `Registry(${BaseComponent.displayName || BaseComponent.name})` + return RegistryAwareComponent +} From 67df124c899de210b51be1220af66ba2e38e4c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Fri, 9 May 2025 19:12:30 +0200 Subject: [PATCH 5/5] feat: continue component registry implementation (broken!) --- packages/chakra-components/src/client.tsx | 30 ++++++-- .../components/Election/Actions/Actions.tsx | 71 +++++++++---------- .../src/components/Election/Description.tsx | 11 +-- .../src/components/Election/Election.tsx | 31 ++++---- .../src/components/Election/Envelope.tsx | 13 ++-- .../src/components/Election/Header.tsx | 11 ++- .../components/Election/Questions/Fields.tsx | 14 ++-- .../src/components/Election/Results.tsx | 11 ++- .../src/components/Election/Schedule.tsx | 17 ++--- .../components/Election/SpreadsheetAccess.tsx | 9 +-- .../src/components/Election/StatusBadge.tsx | 11 ++- .../src/components/Election/VoteButton.tsx | 9 ++- .../src/components/Election/VoteWeight.tsx | 13 ++-- packages/chakra-components/src/types.ts | 6 +- .../src/components/ComponentsProvider.tsx | 26 +++++++ .../src/components/withRegistry.tsx | 21 +++++- 16 files changed, 192 insertions(+), 112 deletions(-) diff --git a/packages/chakra-components/src/client.tsx b/packages/chakra-components/src/client.tsx index 7cc96aa4..8a1c6a02 100644 --- a/packages/chakra-components/src/client.tsx +++ b/packages/chakra-components/src/client.tsx @@ -1,9 +1,5 @@ import { ToastProvider } from '@chakra-ui/react' -import { - ComponentsProvider, - ClientProvider as RPClientProvider, - VocdoniComponentDefinition, -} 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' @@ -15,13 +11,33 @@ export const ClientProvider = ({ children, components: customComponents, ...prop locale: merge(locales, props.locale || {}), } - const mergedComponents = customComponents ? merge(defaultComponents, customComponents) : defaultComponents + // 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 ( <> - + {children} diff --git a/packages/chakra-components/src/components/Election/Actions/Actions.tsx b/packages/chakra-components/src/components/Election/Actions/Actions.tsx index 57f5fdd2..6ae6c289 100644 --- a/packages/chakra-components/src/components/Election/Actions/Actions.tsx +++ b/packages/chakra-components/src/components/Election/Actions/Actions.tsx @@ -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 ( - + - } - /> - } - /> - } + {election.status === ElectionStatus.ONGOING && ( + } + as={ActionPause} + /> + )} + {election.status === ElectionStatus.PAUSED && ( + } + as={ActionContinue} + /> + )} + } + as={ActionEnd} /> - } + } + as={ActionCancel} /> ) } +ElectionActions.displayName = 'ElectionActions' diff --git a/packages/chakra-components/src/components/Election/Description.tsx b/packages/chakra-components/src/components/Election/Description.tsx index f7d8bf76..c74ba353 100644 --- a/packages/chakra-components/src/components/Election/Description.tsx +++ b/packages/chakra-components/src/components/Election/Description.tsx @@ -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 & ChakraProps) => { +const BaseElectionDescription = (props: ElectionDescriptionProps) => { const styles = useStyleConfig('ElectionDescription', props) const { election } = useElection() @@ -18,4 +18,7 @@ export const ElectionDescription = (props: Omit ) } +BaseElectionDescription.displayName = 'BaseElectionDescription' + +export const ElectionDescription = withRegistry(BaseElectionDescription, 'Election', 'Description') ElectionDescription.displayName = 'ElectionDescription' diff --git a/packages/chakra-components/src/components/Election/Election.tsx b/packages/chakra-components/src/components/Election/Election.tsx index 4ab98a9b..3a9dfb3d 100644 --- a/packages/chakra-components/src/components/Election/Election.tsx +++ b/packages/chakra-components/src/components/Election/Election.tsx @@ -6,7 +6,7 @@ import { ElectionActions, ElectionDescription, ElectionHeader, - ElectionQuestions, + ElectionQuestion, ElectionResults, ElectionSchedule, ElectionStatusBadge, @@ -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 ( - {error} + Election not found ) } + if (!(election instanceof PublishedElection)) { + return null + } + return ( <> - + + + - +


-
- + - {election instanceof PublishedElection && election?.get('census.type') === 'spreadsheet' && connected && ( - - )} + {election.questions.map((question, index) => ( + + ))} ) diff --git a/packages/chakra-components/src/components/Election/Envelope.tsx b/packages/chakra-components/src/components/Election/Envelope.tsx index fc8f11ff..e2e0a3c3 100644 --- a/packages/chakra-components/src/components/Election/Envelope.tsx +++ b/packages/chakra-components/src/components/Election/Envelope.tsx @@ -1,4 +1,4 @@ -import { chakra, ChakraProps, List, ListItem, Text, useMultiStyleConfig } from '@chakra-ui/react' +import { chakra, List, ListItem, Text, useMultiStyleConfig } from '@chakra-ui/react' import { useDatesLocale, useElection } from '@vocdoni/react-providers' import { ElectionResultsTypeNames, @@ -11,15 +11,11 @@ import { } from '@vocdoni/sdk' import { format } from 'date-fns' import { Component, ErrorInfo, PropsWithChildren } from 'react' +import { ElectionEnvelopeProps } from '../../types' export type VotePackageType = IVotePackage | IVoteEncryptedPackage -export const Envelope = ({ - votePackage, - ...props -}: { - votePackage: VotePackageType -} & ChakraProps) => { +export const Envelope = ({ votePackage, ...props }: ElectionEnvelopeProps) => { const styles = useMultiStyleConfig('Envelope') const { election, localize } = useElection() const locale = useDatesLocale() @@ -46,7 +42,7 @@ export const Envelope = ({ {election.questions.map((q, i) => { return ( - + {localize('envelopes.question_title', { title: q.title.default })} @@ -57,6 +53,7 @@ export const Envelope = ({ ) } +Envelope.displayName = 'Envelope' const SelectedOptions = ({ question, diff --git a/packages/chakra-components/src/components/Election/Header.tsx b/packages/chakra-components/src/components/Election/Header.tsx index f376a68a..ff91823d 100644 --- a/packages/chakra-components/src/components/Election/Header.tsx +++ b/packages/chakra-components/src/components/Election/Header.tsx @@ -1,9 +1,10 @@ import { useStyleConfig } from '@chakra-ui/react' -import { useElection } from '@vocdoni/react-providers' +import { useElection, withRegistry } from '@vocdoni/react-providers' import { PublishedElection } from '@vocdoni/sdk' -import { Image, IPFSImageProps } from '../layout' +import { ElectionHeaderProps } from '../../types' +import { Image } from '../layout' -export const ElectionHeader = (props: IPFSImageProps) => { +const BaseElectionHeader = (props: ElectionHeaderProps) => { const styles = useStyleConfig('ElectionHeader', props) const { election } = useElection() @@ -13,3 +14,7 @@ export const ElectionHeader = (props: IPFSImageProps) => { return } +BaseElectionHeader.displayName = 'BaseElectionHeader' + +export const ElectionHeader = withRegistry(BaseElectionHeader, 'Election', 'Header') +ElectionHeader.displayName = 'ElectionHeader' diff --git a/packages/chakra-components/src/components/Election/Questions/Fields.tsx b/packages/chakra-components/src/components/Election/Questions/Fields.tsx index f87fdb15..20c8f6b1 100644 --- a/packages/chakra-components/src/components/Election/Questions/Fields.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Fields.tsx @@ -1,6 +1,5 @@ import { chakra, - ChakraProps, Checkbox, FormControl, FormErrorMessage, @@ -9,9 +8,10 @@ import { Stack, useMultiStyleConfig, } from '@chakra-ui/react' -import { useElection } from '@vocdoni/react-providers' +import { useElection, withRegistry } from '@vocdoni/react-providers' import { ElectionResultsTypeNames, ElectionStatus, IQuestion, PublishedElection } from '@vocdoni/sdk' import { Controller, useFormContext } from 'react-hook-form' +import { ElectionQuestionsProps } from '../../../types' import { Markdown } from '../../layout' import { QuestionChoice } from './Choice' import { QuestionTip } from './Tip' @@ -21,16 +21,16 @@ export type QuestionProps = { question: IQuestion } -export type QuestionFieldProps = ChakraProps & QuestionProps +export type QuestionFieldProps = ElectionQuestionsProps & QuestionProps -export const ElectionQuestion = ({ question, index }: QuestionFieldProps) => { +const BaseElectionQuestion = ({ question, index, ...props }: QuestionFieldProps) => { const styles = useMultiStyleConfig('ElectionQuestion') const { formState: { errors }, } = useFormContext() return ( - + {question.title.default} @@ -48,6 +48,10 @@ export const ElectionQuestion = ({ question, index }: QuestionFieldProps) => { ) } +BaseElectionQuestion.displayName = 'BaseElectionQuestion' + +export const ElectionQuestion = withRegistry(BaseElectionQuestion, 'Election', 'Questions') +ElectionQuestion.displayName = 'ElectionQuestion' export const FieldSwitcher = (props: QuestionProps) => { const { election } = useElection() diff --git a/packages/chakra-components/src/components/Election/Results.tsx b/packages/chakra-components/src/components/Election/Results.tsx index 351f9563..749727a4 100644 --- a/packages/chakra-components/src/components/Election/Results.tsx +++ b/packages/chakra-components/src/components/Election/Results.tsx @@ -1,5 +1,5 @@ -import { Box, chakra, ChakraProps, Flex, Progress, Text, useMultiStyleConfig } from '@chakra-ui/react' -import { useClient, useDatesLocale, useElection } from '@vocdoni/react-providers' +import { Box, chakra, Flex, Progress, Text, useMultiStyleConfig } from '@chakra-ui/react' +import { useClient, useDatesLocale, useElection, withRegistry } from '@vocdoni/react-providers' import { ElectionResultsTypeNames, ElectionStatus, @@ -9,12 +9,13 @@ import { PublishedElection, } from '@vocdoni/sdk' import { format } from 'date-fns' +import { ElectionResultsProps } from '../../types' const percent = (result: number, total: number) => ((Number(result) / total) * 100 || 0).toFixed(1) + '%' export const results = (result: number, decimals?: number) => decimals ? parseInt(formatUnits(BigInt(result), decimals), 10) : result -export const ElectionResults = (props: ChakraProps) => { +const BaseElectionResults = (props: ElectionResultsProps) => { const styles = useMultiStyleConfig('ElectionResults') const { election } = useElection() const { localize } = useClient() @@ -75,6 +76,10 @@ export const ElectionResults = (props: ChakraProps) => { ) } +BaseElectionResults.displayName = 'BaseElectionResults' + +export const ElectionResults = withRegistry(BaseElectionResults, 'Election', 'Results') +ElectionResults.displayName = 'ElectionResults' const electionChoices = (election: PublishedElection, q: IQuestion, abstainLabel: string) => { const nchoices = [...q.choices] diff --git a/packages/chakra-components/src/components/Election/Schedule.tsx b/packages/chakra-components/src/components/Election/Schedule.tsx index 51eb47a2..cc9ca36c 100644 --- a/packages/chakra-components/src/components/Election/Schedule.tsx +++ b/packages/chakra-components/src/components/Election/Schedule.tsx @@ -1,15 +1,10 @@ -import { chakra, forwardRef, HeadingProps, useMultiStyleConfig } from '@chakra-ui/react' -import { useDatesLocale, useElection, useLocalize } from '@vocdoni/react-providers' +import { chakra, forwardRef, useMultiStyleConfig } from '@chakra-ui/react' +import { useDatesLocale, useElection, useLocalize, withRegistry } from '@vocdoni/react-providers' import { ElectionStatus, PublishedElection } from '@vocdoni/sdk' import { format as dformat, formatDistance } from 'date-fns' +import { ElectionScheduleProps } from '../../types' -export type ElectionScheduleProps = HeadingProps & { - format?: string - showRemaining?: boolean // If true, it return the remaining time to start, end, if ended or paused, instead of the full schedule - showCreatedAt?: boolean -} - -export const ElectionSchedule = forwardRef( +const BaseElectionSchedule = forwardRef( ({ format = 'PPp', showRemaining = false, showCreatedAt = false, ...rest }, ref) => { const styles = useMultiStyleConfig('ElectionSchedule', rest) const { election } = useElection() @@ -78,11 +73,13 @@ export const ElectionSchedule = forwardRef( } return ( - + {text} ) } ) +BaseElectionSchedule.displayName = 'BaseElectionSchedule' +export const ElectionSchedule = withRegistry(BaseElectionSchedule, 'Election', 'Schedule') ElectionSchedule.displayName = 'ElectionSchedule' diff --git a/packages/chakra-components/src/components/Election/SpreadsheetAccess.tsx b/packages/chakra-components/src/components/Election/SpreadsheetAccess.tsx index 44ef0917..2e65d757 100644 --- a/packages/chakra-components/src/components/Election/SpreadsheetAccess.tsx +++ b/packages/chakra-components/src/components/Election/SpreadsheetAccess.tsx @@ -1,6 +1,5 @@ import { Button, - ChakraProps, FormControl, FormErrorMessage, FormHelperText, @@ -23,6 +22,7 @@ import { errorToString, useClient, useElection, walletFromRow } from '@vocdoni/r import { PublishedElection, VocdoniSDKClient } from '@vocdoni/sdk' import { useEffect, useState } from 'react' import { RegisterOptions, useForm } from 'react-hook-form' +import { ElectionSpreadsheetAccessProps } from '../../types' type MetaSpecs = { [name: string]: { @@ -48,11 +48,7 @@ type MetaSpecs = { } } -export type SpreadsheetAccessProps = ChakraProps & { - hashPrivateKey?: boolean -} - -export const SpreadsheetAccess = ({ hashPrivateKey, ...rest }: SpreadsheetAccessProps) => { +export const SpreadsheetAccess = ({ hashPrivateKey, ...rest }: ElectionSpreadsheetAccessProps) => { const { isOpen, onOpen, onClose } = useDisclosure() const styles = useMultiStyleConfig('SpreadsheetAccess', rest) const { connected, clearClient } = useElection() @@ -291,3 +287,4 @@ export const SpreadsheetAccess = ({ hashPrivateKey, ...rest }: SpreadsheetAccess ) } +SpreadsheetAccess.displayName = 'SpreadsheetAccess' diff --git a/packages/chakra-components/src/components/Election/StatusBadge.tsx b/packages/chakra-components/src/components/Election/StatusBadge.tsx index 00105039..53c35c8f 100644 --- a/packages/chakra-components/src/components/Election/StatusBadge.tsx +++ b/packages/chakra-components/src/components/Election/StatusBadge.tsx @@ -1,8 +1,9 @@ -import { Tag, TagProps } from '@chakra-ui/react' -import { useElection } from '@vocdoni/react-providers' +import { Tag } from '@chakra-ui/react' +import { useElection, withRegistry } from '@vocdoni/react-providers' import { ElectionStatus, InvalidElection, PublishedElection } from '@vocdoni/sdk' +import { ElectionStatusBadgeProps } from '../../types' -export const ElectionStatusBadge = (props: TagProps) => { +const BaseElectionStatusBadge = (props: ElectionStatusBadgeProps) => { const { election, localize } = useElection() if (!election) return null @@ -33,3 +34,7 @@ export const ElectionStatusBadge = (props: TagProps) => { ) } +BaseElectionStatusBadge.displayName = 'BaseElectionStatusBadge' + +export const ElectionStatusBadge = withRegistry(BaseElectionStatusBadge, 'Election', 'StatusBadge') +ElectionStatusBadge.displayName = 'ElectionStatusBadge' diff --git a/packages/chakra-components/src/components/Election/VoteButton.tsx b/packages/chakra-components/src/components/Election/VoteButton.tsx index a3b8f57a..387ec503 100644 --- a/packages/chakra-components/src/components/Election/VoteButton.tsx +++ b/packages/chakra-components/src/components/Election/VoteButton.tsx @@ -1,10 +1,11 @@ import { Button, ButtonProps, Text } from '@chakra-ui/react' import { Signer } from '@ethersproject/abstract-signer' -import { useClient, useElection } from '@vocdoni/react-providers' +import { useClient, useElection, withRegistry } from '@vocdoni/react-providers' import { ElectionStatus, InvalidElection } from '@vocdoni/sdk' import { useState } from 'react' +import { ElectionVoteButtonProps } from '../../types' -export const VoteButton = (props: ButtonProps) => { +const BaseVoteButton = (props: ElectionVoteButtonProps) => { const { connected } = useClient() const { client, @@ -60,3 +61,7 @@ export const VoteButton = (props: ButtonProps) => { return