Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5e9f7ad
feat(atoms): add Switch types and styles
eumaninho54 May 3, 2026
3a38386
feat(atoms): add Switch hooks
eumaninho54 May 3, 2026
0221f22
feat(atoms): add Switch view and tests
eumaninho54 May 3, 2026
0b4c4eb
docs(atoms): add Switch storybook story
eumaninho54 May 3, 2026
824be3b
style(atoms): add padding and hitSlop to Switch
eumaninho54 May 3, 2026
bda11cf
refactor(atoms): extract Switch constants to constants/
eumaninho54 May 3, 2026
41c351e
docs(claude): add component patterns rules
eumaninho54 May 3, 2026
5aa7f56
Merge pull request #24 from Salve-Software/feat/switch-atom
eumaninho54 May 3, 2026
2f8a8e1
feat(atoms): add inline bold parsing to Text
eumaninho54 May 3, 2026
17ba9df
refactor(atoms): move parseInlineBold to Text library
eumaninho54 May 3, 2026
892e38f
refactor(atoms): extract Text ViewModel with memoization
eumaninho54 May 3, 2026
5ae38a6
feat(example): load Geist fonts via expo-font
eumaninho54 May 3, 2026
1bea480
feat(atoms): use semibold for Text inline bold
eumaninho54 May 3, 2026
629fd7a
docs(storybook): add Text inline bold stories
eumaninho54 May 3, 2026
eb014f5
Merge pull request #25 from Salve-Software/feat/text-inline-semibold
eumaninho54 May 3, 2026
ac7f874
feat(molecules): add Button molecule
eumaninho54 May 3, 2026
a515609
refactor(molecules): move builds to Button ViewModel
eumaninho54 May 3, 2026
da4341f
style(molecules): polish Button formatting and naming
eumaninho54 May 3, 2026
b93aa04
refactor(molecules): remove fullWidth prop from Button
eumaninho54 May 3, 2026
3261628
style(molecules): set Button alignSelf flex-start by default
eumaninho54 May 3, 2026
21751e3
docs(storybook): add Button FullWidth story
eumaninho54 May 3, 2026
ae489dc
docs(storybook): add Button Inline story and fix default width
eumaninho54 May 3, 2026
0218802
test(molecules): add useButtonViewModel tests
eumaninho54 May 3, 2026
8014ce9
Merge pull request #26 from Salve-Software/feat/molecules-button
eumaninho54 May 3, 2026
5d804be
Merge branch 'feat/molecules-button' into release/v0.3.0
eumaninho54 May 3, 2026
114725a
style(molecules): increase border radius to lg
eumaninho54 May 3, 2026
9f53ad0
feat(toaster): add ToasterApi controller and types
eumaninho54 May 4, 2026
08c333e
feat(toaster): add useToasterViewModel and reanimated styles
eumaninho54 May 4, 2026
4592b5c
feat(toaster): add Toaster component and styles
eumaninho54 May 4, 2026
a5745e3
test(toaster): add ToasterApi, ViewModel and component tests
eumaninho54 May 4, 2026
6886256
docs(storybook): add Toaster stories
eumaninho54 May 4, 2026
0d18ba9
fix(toaster): extract duration and slide offset constants
eumaninho54 May 4, 2026
88b6bdd
fix(toaster): resolve review issues in component and hooks
eumaninho54 May 4, 2026
e199a4c
refactor(toaster): move types to folder, map to constants, simplify vm
eumaninho54 May 4, 2026
2672413
style(toaster): remove unnecessary eslint-disable comment
eumaninho54 May 4, 2026
a6d068c
refactor(toaster): move IToasterApi to own file in types folder
eumaninho54 May 4, 2026
ca28b89
feat(toaster): add drag-to-dismiss, slingshot and close button
eumaninho54 May 4, 2026
a79f424
feat(toaster): spring animation on show, timing on hide
eumaninho54 May 4, 2026
9ec75b7
fix(toaster): reduce drag dismiss threshold from 80 to 40
eumaninho54 May 4, 2026
f1c94f3
fix(toaster): dismiss by drag animates down instead of snapping back
eumaninho54 May 4, 2026
fd10aff
fix(toaster): separate opacity and translateY animations to avoid spr…
eumaninho54 May 4, 2026
9845ec8
style(ui): increase activeOpacity from 0.7 to 0.8
eumaninho54 May 4, 2026
be17e94
style(toaster): improve close button touch feedback
eumaninho54 May 4, 2026
88f12b2
Merge pull request #28 from Salve-Software/feat/organisms-toaster
eumaninho54 May 4, 2026
d490db7
feat(provider): add Toaster to RubberDuckUIProvider
eumaninho54 May 4, 2026
2a1bd07
fix(storybook): remove duplicate Toaster from stories
eumaninho54 May 4, 2026
36ff5f4
feat(toaster): support local ref usage via React 19 ref prop
eumaninho54 May 4, 2026
4631af7
docs(storybook): add InsideModal story with local Toaster ref
eumaninho54 May 4, 2026
6b11d6e
docs(storybook): add BottomSheetView as a wrapper
eumaninho54 May 4, 2026
114e816
feat(toaster): add 999 z-index to toaster
eumaninho54 May 4, 2026
d7329d0
feat(SimpleText): add rightIcon, secureTextEntry and autoCapitalize p…
eumaninho54 May 5, 2026
ee20fa4
fix(storybook): invert Eye/EyeOff icon logic on Password story
eumaninho54 May 5, 2026
6f6ca49
style(tokens): rebalance FONT_SIZES scale following 4/8 rule
eumaninho54 May 5, 2026
9b2c331
fix(tokens): remap Text size props to preserve visual sizes after FON…
eumaninho54 May 5, 2026
09f17fa
docs: add font size migration guide for v0.3.0
eumaninho54 May 5, 2026
55dc073
feat(store): support partial color overrides
eumaninho54 May 6, 2026
68df393
test(Button): update fontSize expectations after font size remap
eumaninho54 May 6, 2026
9da6029
Merge pull request #29 from Salve-Software/feat/partial-color-overrides
eumaninho54 May 6, 2026
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
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
@rules/aiworkers/skills-format.md
@rules/aiworkers/jsx-style.md
@rules/aiworkers/conventional-commits.md
@rules/component-patterns.md
38 changes: 38 additions & 0 deletions .claude/rules/component-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Component patterns

## Before implementing a new component

Always read an existing component of the same category before writing any code. Do not guess the structure — read the actual code.

- New Atom → read another existing Atom
- New Molecule → read another existing Molecule
- Component uses Reanimated → read the `useReanimatedStyles` hook of a component that already uses it (e.g. `CheckBox`)

This ensures hooks, file structure, test patterns, and import style are consistent with the rest of the library.

## Fixed values

No hardcoded numeric values in code. Always use design system tokens:

- Spacing and sizes: `Tokens.spacer({ key: '...' })`
- Border radii: `Tokens.radii({ key: '...' })`

Component-specific layout values (e.g. a Switch track width) belong in the component's `constants/` folder — one file per constant, all exported via `index.ts`:

```
constants/
├── NAME_OF_CONSTANT.ts ← one constant per file
└── index.ts ← export * from each file
```

Never define layout constants directly in `styles.ts` or in the component file.

## Storybook

Every new component needs a story at `example/.rnstorybook/stories/<Category>/<Component>/`.

Minimum variants to cover:
- Default state (off / false / empty)
- Active state (on / true / filled)
- Disabled (when applicable)
- `Interactive` — with `useState` to test the real animation in Storybook
29 changes: 29 additions & 0 deletions __mocks__/react-native-gesture-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { View } from 'react-native';

const makePanGesture = () => {
const gesture = {
onUpdate: () => gesture,
onEnd: () => gesture,
onStart: () => gesture,
onBegin: () => gesture,
onFinalize: () => gesture,
enabled: () => gesture,
minDistance: () => gesture,
activeOffsetX: () => gesture,
activeOffsetY: () => gesture,
};
return gesture;
};

const Gesture = {
Pan: makePanGesture,
};

const GestureDetector = ({ children }: { children: React.ReactNode }) =>
React.createElement(View, null, children);

module.exports = {
Gesture,
GestureDetector,
};
3 changes: 3 additions & 0 deletions __mocks__/react-native-reanimated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const useSharedValue = (init: unknown) => ({ value: init });
const useAnimatedStyle = (fn: () => object) => fn();
const withTiming = (value: unknown) => value;
const withSpring = (value: unknown) => value;
const interpolate = (_value: unknown, _input: unknown, output: number[]) =>
output[0];
const interpolateColor = (_value: unknown, _range: unknown, colors: string[]) =>
colors[0];
const runOnJS = (fn: (...args: unknown[]) => unknown) => fn;
Expand Down Expand Up @@ -42,6 +44,7 @@ module.exports = {
useAnimatedGestureHandler,
withTiming,
withSpring,
interpolate,
interpolateColor,
runOnJS,
runOnUI,
Expand Down
72 changes: 72 additions & 0 deletions docs/migration-font-sizes-v0.3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Migration: Font Size Scale — v0.3.0

## O que mudou

A escala `FONT_SIZES` foi rebalanceada seguindo a regra dos múltiplos de 4/8, com `md` agora valendo **16px** (era 14px).

### Escala anterior → nova

| Key | Antes | Depois |
|-----|-------|--------|
| `xs` | 10 | 12 |
| `sm` | 12 | 14 |
| `md` | 14 | **16** |
| `lg` | 16 | 20 |
| `xl` | 20 | 24 |
| `xxl` | 24 | 32 |
| `xxxl` | 32 | 40 |
| `display` | 40 | 48 |

---

## Impacto no seu app

Se você usa o componente `<Text>` do DS **sem passar `size`**, o tamanho padrão era `md=14px` e agora passa a ser `md=16px`. Seu texto vai aparecer **maior** do que antes.

### Como corrigir

Passe `size` explicitamente onde quiser manter o visual anterior:

```tsx
// Antes — recebia 14px via default
<Text>Meu texto</Text>

// Depois — para manter 14px, passe size="sm" explicitamente
<Text size="sm">Meu texto</Text>
```

### Tabela de remapeamento

Use esta tabela para converter os valores que você já passava explicitamente:

| Você passava | Valor visual | Passe agora |
|---|---|---|
| `size="sm"` | 12px | `size="xs"` |
| `size="md"` ou nada | 14px | `size="sm"` |
| `size="lg"` | 16px | `size="md"` |
| `size="xl"` | 20px | `size="lg"` |
| `size="xxl"` | 24px | `size="xl"` |
| `size="xxxl"` | 32px | `size="xxl"` |
| `size="display"` | 40px | `size="xxxl"` |

---

## O que já foi ajustado internamente no DS

Os componentes internos do DS já foram atualizados para manter seus tamanhos visuais. Você não precisa fazer nada em relação a:

- `Button` (todas as variantes: `sm`, `md`, `lg`)
- `CheckBox`
- `RadioButton`
- `SimpleText`, `NumberField`, `MultilineText`, `MoneyField`
- `Toaster`
- `Avatar`
- `BottomListModal`

Esses componentes continuam com a mesma aparência de antes.

---

## Se você quer aproveitar a nova escala

Se preferir que seus textos cresçam proporcionalmente com a nova escala (ao invés de preservar o tamanho antigo), basta **não passar `size`** e deixar o novo default `md=16px` agir.
57 changes: 57 additions & 0 deletions example/.rnstorybook/stories/Atoms/Switch/Switch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import React, { useState } from 'react';
import { Switch } from 'rubber-duck-ui';

const meta = {
title: 'Atoms/Switch',
component: Switch,
args: {
isOn: false,
onPress: () => {},
},
argTypes: {
isOn: { control: 'boolean' },
disabled: { control: 'boolean' },
},
} satisfies Meta<typeof Switch>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const On: Story = {
args: {
isOn: true,
},
};

export const Disabled: Story = {
args: {
disabled: true,
},
};

export const DisabledOn: Story = {
args: {
isOn: true,
disabled: true,
},
};

const InteractiveSwitch = (args: React.ComponentProps<typeof Switch>) => {
const [isOn, setIsOn] = useState(false);

return (
<Switch
{...args}
isOn={isOn}
onPress={() => setIsOn((prev) => !prev)}
/>
);
};

export const Interactive: Story = {
render: (args) => <InteractiveSwitch {...args} />,
};
17 changes: 17 additions & 0 deletions example/.rnstorybook/stories/Atoms/Text/Text.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,20 @@ export const Truncated: Story = {
'This is a very long text that should be truncated after one line because numberOfLines is set to 1',
},
};

export const InlineBold: Story = {
args: {
children: 'I agree to the **Terms & Privacy Policy**',
},
};

export const InlineBoldVariants: Story = {
render: () => (
<View style={styles.container}>
<Text>{'I agree to the **Terms & Privacy Policy**'}</Text>
<Text size="lg">{'**Bold** at the start of the sentence'}</Text>
<Text size="sm" color="textSecondary">{'Mix of **bold** and regular text here'}</Text>
<Text>{'**Multiple** bold **segments** in one text'}</Text>
</View>
),
};
116 changes: 116 additions & 0 deletions example/.rnstorybook/stories/Molecules/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import React, { useState } from 'react';
import { View } from 'react-native';
import { Button } from 'rubber-duck-ui';
import { styles } from './Button.styles';

const meta = {
title: 'Molecules/Button',
component: Button,
args: {
label: 'Button',
variant: 'primary',
size: 'md',
disabled: false,
isLoading: false,
onPress: () => {},
},
argTypes: {
label: { control: 'text' },
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline', 'ghost', 'destructive'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
disabled: { control: 'boolean' },
isLoading: { control: 'boolean' },
leftIcon: {
control: 'select',
options: [undefined, 'ArrowLeft', 'Plus', 'Check', 'Trash2'],
},
rightIcon: {
control: 'select',
options: [undefined, 'ArrowRight', 'Plus', 'Check', 'Trash2'],
},
},
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Variants: Story = {
render: (args) => (
<View style={styles.container}>
<Button {...args} variant="primary" label="Primary" />
<Button {...args} variant="secondary" label="Secondary" />
<Button {...args} variant="outline" label="Outline" />
<Button {...args} variant="ghost" label="Ghost" />
<Button {...args} variant="destructive" label="Destructive" />
</View>
),
};

export const Sizes: Story = {
render: (args) => (
<View style={styles.container}>
<Button {...args} size="sm" label="Small" />
<Button {...args} size="md" label="Medium" />
<Button {...args} size="lg" label="Large" />
</View>
),
};

export const Loading: Story = {
args: {
isLoading: true,
},
};

export const Disabled: Story = {
args: {
disabled: true,
},
};

export const WithIcons: Story = {
render: (args) => (
<View style={styles.container}>
<Button {...args} leftIcon="ArrowLeft" label="Back" />
<Button {...args} rightIcon="ArrowRight" label="Next" />
<Button {...args} leftIcon="Plus" rightIcon="ArrowRight" label="Add item" />
</View>
),
};

export const Inline: Story = {
render: (args) => (
<View style={styles.inline}>
<Button {...args} />
</View>
),
};

const InteractiveButton = (args: React.ComponentProps<typeof Button>) => {
const [loading, setLoading] = useState(false);

return (
<Button
{...args}
isLoading={loading}
onPress={() => {
setLoading(true);
setTimeout(() => setLoading(false), 2000);
}}
/>
);
};

export const Interactive: Story = {
render: (args) => <InteractiveButton {...args} />,
};
11 changes: 11 additions & 0 deletions example/.rnstorybook/stories/Molecules/Button/Button.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { StyleSheet } from 'react-native';

export const styles = StyleSheet.create({
container: {
gap: 16,
},

inline: {
alignSelf: 'flex-start',
},
});
Loading
Loading