@react-xp/native
v0.1.0-beta.1
Published
This is a React Experience native package
Downloads
102
Readme
React XP React Native Design System
Design system para React Native com foco em DX e consistência visual. Inclui tokens de design, theming com override granular e componentes acessíveis.
Componentes incluídos
- Botões (
Button,IconButton) - Tipografia (
Typography) - Inputs (
TextField,TextArea,PasswordField) - Inputs avançados (
PinInput,SearchInput) - Cartões (
Card,CardHeader,CardContent,CardFooter) - Tabelas (
DataTable) - Tags (
Tag) - Chips e filtros (
Chip) - Barra de progresso (
ProgressBar) - Loader (
Loaderinline e global) - Selects (
Select,MultiSelect) - Overlays (
Modal,Drawer) - Form primitives (
Form,Form.Section,Form.Field,Form.Footer) - Controles booleanos (
Checkbox,Radio,RadioGroup) - Ações flutuantes (
FloatingActionButton) - Layout primitives (
Stack,Inline,Divider)
Templates de ecrã
AnalyticsDashboardTemplate: métricas, tendências comProgressBare feed de atividades para backoffices analíticos.ListDetailTemplate: master/detail com cartões clicáveis e painel de detalhes para listas de pedidos, tarefas ou contactos.OnboardingTemplate: fluxo de onboarding multi-step com progress bar, ilustrações e CTAs configuráveis.CommerceProductTemplate: apresentação de produto com galeria, badges, features e reviews resumidas.MessagingTemplate: inbox + thread com bubbles e composer integrado.SupportCenterTemplate: centro de ajuda com ações rápidas, artigos destacados e pedidos recentes.
Páginas base
LoginPage: autenticação com suporte opcional a social login/magic link via feature flags.RegisterPage: fluxo de registo com password confirmation, consentimento e código de convite controlado por flags.SettingsPage: gestão de sessão, configuração do bloqueio por inatividade/PIN, painel de feature flags e limpeza de storage encriptado.
Páginas de demonstração
DesignSystemPlayground: página interativa para testar variantes, estados e espaçamentos dos componentes com dados fictícios e loaders inline/global.DesignSystemShowcase: catálogo que agrupa tipografia, formulários, tabelas, overlays e feedback visual num único ecrã responsivo.
import {
DesignSystemPlayground,
DesignSystemShowcase,
ThemeProvider,
} from '@react-xp/rn-ds';
export function DemoScreens() {
return (
<ThemeProvider>
<DesignSystemShowcase />
<DesignSystemPlayground />
</ThemeProvider>
);
}Utilização básica
import { ThemeProvider, Button, Typography } from '@react-xp/rn-ds';
export function App() {
return (
<ThemeProvider>
<Stack space="lg" style={{ padding: 24 }}>
<Typography variant="display" weight="bold">
React XP RN Design System
</Typography>
<Button variant="primary" onPress={() => {}}>
Começar agora
</Button>
</Stack>
</ThemeProvider>
);
}Camada de styling e escalabilidade
- Na comparação com Tailwind (
twrnc), Radix UI e shadcn/ui avaliados na comunidade, optámos por manter uma camadastyledorientada a tokens: garante paridade com React Native +react-native-web, evita dependências nativas adicionais e oferece DX typed semelhante a Restyle/Tamagui, padrões adotados em design systems escaláveis. - A biblioteca expõe um helper
styledinspirado emstyled-components/native, que aplica os tokens do tema automaticamente e funciona tanto em React Native como em React (viareact-native-web). - O
ThemeProviderexportado injeta o tema partilhado para os componentes do design system e para o helperstyled, permitindo criar extensões sem duplicar tokens. - Cada componente consome exclusivamente tokens (
spacing,radius,typography,components) para espaçamentos, cores e tamanhos; as props de tamanho/variante convertem-se em leituras de tema, garantindo responsividade por device. - É possível criar componentes adicionais que respeitam o tema partilhado:
import { ThemeProvider, styled } from '@react-xp/rn-ds';
const Pill = styled.View(({ theme }) => ({
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.xs,
borderRadius: theme.radius.pill,
backgroundColor: theme.colors.surface,
}));
- Em React web basta reutilizar o mesmo
ThemeProviderjuntamente comreact-native-web. O helperstyledpartilha o tema e garante paridade visual.
Customização de tema
Select e MultiSelect
import { useState } from 'react';
import { MultiSelect, Select, Stack } from '@react-xp/rn-ds';
const statusOptions = [
{ label: 'Pendente', value: 'pending' },
{ label: 'Em progresso', value: 'in_progress' },
{ label: 'Concluído', value: 'done' },
];
export function Filters() {
const [status, setStatus] = useState<string | undefined>();
const [tags, setTags] = useState<string[]>([]);
return (
<Stack space="md">
<Select
label="Status"
options={statusOptions}
value={status}
onValueChange={(value) => setStatus(value)}
placeholder="Filtrar por status"
/>
<MultiSelect
label="Tags"
options={statusOptions}
value={tags}
onValueChange={(values) => setTags(values)}
placeholder="Seleciona um ou mais"
/>
</Stack>
);
}Modal e Drawer
import { useState } from 'react';
import { Button, Drawer, Modal, Select, Stack, TextField } from '@react-xp/rn-ds';
export function Overlays() {
const [modalOpen, setModalOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
return (
<>
<Button onPress={() => setModalOpen(true)}>Abrir modal</Button>
<Button variant="outline" onPress={() => setDrawerOpen(true)}>
Abrir drawer
</Button>
<Modal
open={modalOpen}
onClose={() => setModalOpen(false)}
title="Enviar relatório"
description="Confirma o envio do relatório mensal?"
actions={
<Stack direction="row" space="sm">
<Button variant="ghost" onPress={() => setModalOpen(false)}>
Cancelar
</Button>
<Button onPress={() => setModalOpen(false)}>Confirmar</Button>
</Stack>
}
/>
<Drawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
title="Filtros avançados"
footer={
<Stack direction="row" space="sm">
<Button variant="ghost" onPress={() => setDrawerOpen(false)}>
Fechar
</Button>
<Button onPress={() => setDrawerOpen(false)}>Aplicar filtros</Button>
</Stack>
}
>
<TextField label="Projeto" placeholder="Nome do projeto" />
<Select label="Prioridade" options={[{ label: 'Alta', value: 'high' }]} />
</Drawer>
</>
);
}Form helpers
import { Button, Form, TextField } from '@react-xp/rn-ds';
export function ProfileForm() {
return (
<Form>
<Form.Section title="Informações pessoais">
<Form.Field label="Nome" required>
<TextField placeholder="Nome completo" />
</Form.Field>
<Form.Field label="Email" helperText="Usado para notificações">
<TextField keyboardType="email-address" autoCapitalize="none" />
</Form.Field>
</Form.Section>
<Form.Footer>
<Button variant="ghost">Cancelar</Button>
<Button>Guardar</Button>
</Form.Footer>
</Form>
);
}PinInput e autenticação
import { useState } from 'react';
import { PinInput, Stack } from '@react-xp/rn-ds';
export function TwoFactorStep() {
const [code, setCode] = useState('');
return (
<Stack space="md">
<PinInput
label="Código de verificação"
length={6}
value={code}
onChange={setCode}
onComplete={(value) => console.log('Código completo', value)}
helperText="Enviámos o código para o teu email."
/>
</Stack>
);
}Pesquisa assíncrona
import { useState } from 'react';
import { SearchInput, Stack } from '@react-xp/rn-ds';
const cities = ['Lisboa', 'Porto', 'Braga', 'Faro'];
async function fetchCities(query: string) {
await new Promise((resolve) => setTimeout(resolve, 350));
return cities
.filter((city) => city.toLowerCase().includes(query.toLowerCase()))
.map((city) => ({ id: city, title: city }));
}
export function SearchExample() {
const [selected, setSelected] = useState<string | undefined>();
return (
<Stack space="md">
<SearchInput
label="Cidade"
placeholder="Procura uma cidade"
fetchResults={fetchCities}
onSelectResult={(result) => setSelected(result.title)}
helperText={selected ? `Selecionado: ${selected}` : undefined}
/>
</Stack>
);
}Checkbox, Radio e Chip
import { useState } from 'react';
import {
Checkbox,
Chip,
Radio,
RadioGroup,
Stack,
} from '@react-xp/rn-ds';
export function Preferences() {
const [newsletter, setNewsletter] = useState(true);
const [priority, setPriority] = useState('standard');
const [tags, setTags] = useState(['design']);
return (
<Stack space="lg">
<Checkbox
label="Receber newsletter"
helperText="Atualizações mensais sobre o produto"
checked={newsletter}
onChange={setNewsletter}
/>
<RadioGroup
label="Prioridade"
value={priority}
onValueChange={setPriority}
direction="row"
gap={16}
>
<Radio value="standard" label="Standard" />
<Radio value="express" label="Express" />
</RadioGroup>
<Stack direction="row" space="sm">
{['design', 'mobile', 'react'].map((tag) => {
const selected = tags.includes(tag);
return (
<Chip
key={tag}
label={tag}
selected={selected}
onPress={() =>
setTags((current) =>
selected
? current.filter((item) => item !== tag)
: [...current, tag],
)
}
/>
);
})}
</Stack>
</Stack>
);
}Loader inline e global
import { useState } from 'react';
import { Button, Loader, Stack } from '@react-xp/rn-ds';
export function LoadingStates() {
const [overlay, setOverlay] = useState(false);
return (
<>
<Stack space="md">
<Loader size="sm" message="A carregar lista" />
<Button onPress={() => setOverlay(true)}>Mostrar loader global</Button>
</Stack>
<Loader
mode="overlay"
visible={overlay}
message="A sincronizar dados"
onRequestClose={() => setOverlay(false)}
/>
</>
);
}FloatingActionButton
import { FloatingActionButton } from '@react-xp/rn-ds';
export function FabExample() {
return (
<FloatingActionButton
label="Nova tarefa"
onPress={() => console.log('Adicionar tarefa')}
position={{ bottom: 48, right: 24 }}
/>
);
}Override rápido de tokens
const customTheme = {
colors: {
primary: '#7c3aed',
primaryForeground: '#f8fafc'
}
};
<ThemeProvider theme={customTheme}>
<Button variant="primary">Personalizado</Button>
</ThemeProvider>;Todos os tokens suportam override parcial. Arrays e objetos internos são combinados de forma profunda.
Feature flags e experiências condicionais
FeatureFlagProvider: carrega overrides assíncronos, suporta rollout percentual comaudienceKeye dependências entre flags.useFeatureFlag,useIsFeatureEnabledeFeatureGate: hooks/componentes para consumir flags com DX segura.FeatureFlagDevPanel: painel interactivo para overrides manuais e visualização do rollout.
Segurança e produtividade cross-app
createEncryptedStorage: wrapper com codificação XOR + fallback para AsyncStorage/memória, pensado para tokens sensíveis.InactivityLockProvider: monitoriza inatividade e permite auto lock, mensagem custom e overlay comPinInput.InactivityLockOverlayeInactivityLockTouchBoundary: helpers para proteger o ecrã após X segundos sem interação.WebContentView: componente resiliente para embutir websites viareact-native-webview, com fallback e abertura externa.
Boas práticas
- Utilizar os helpers
useThemedStyleseuseThemepara derivar estilos com tokens atualizados. - Preferir os componentes do design system em vez de
Text,ViewePressablepuros para manter consistência. - Mantém as variantes alinhadas com
defaultTheme.components.
Scripts
pnpm --filter "./apps/rn-ds" run typecheck
