Skip to content

Commit fd434f5

Browse files
authored
Merge branch 'main' into wsun/eas-updates
2 parents 258cb04 + 9b8734a commit fd434f5

File tree

116 files changed

+10245
-2928
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+10245
-2928
lines changed

.github/workflows/update-release-changelog.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# On every push to a release branch (Version-v* or release/*), this workflow rebuilds the matching
1+
# On every push to a release branch (release/x.y.z format), this workflow rebuilds the matching
22
# changelog branch, re-runs the auto-changelog script, and either updates or recreates the changelog PR.
3+
# Note: This workflow validates the branch name to ensure it matches the semantic versioning pattern
4+
# (release/x.y.z) and skips execution for other branch names like release/x.y.z-Changelog.
35
name: Update Release Changelog PR
46

57
on:
@@ -12,7 +14,34 @@ concurrency:
1214
cancel-in-progress: false
1315

1416
jobs:
17+
validate-branch:
18+
name: Validate release branch format
19+
runs-on: ubuntu-latest
20+
outputs:
21+
is-valid-release: ${{ steps.check.outputs.is-valid }}
22+
version: ${{ steps.check.outputs.version }}
23+
steps:
24+
- name: Check if branch matches release/x.y.z pattern
25+
id: check
26+
run: |
27+
BRANCH_NAME="${{ github.ref_name }}"
28+
echo "Checking branch: $BRANCH_NAME"
29+
30+
# Validate branch matches release/x.y.z format (semantic versioning)
31+
if [[ "$BRANCH_NAME" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
32+
VERSION="${BRANCH_NAME#release/}"
33+
echo "Valid release branch detected: $BRANCH_NAME (version: $VERSION)"
34+
echo "is-valid=true" >> "$GITHUB_OUTPUT"
35+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
36+
else
37+
echo "Branch '$BRANCH_NAME' does not match release/x.y.z pattern. Skipping changelog update."
38+
echo "is-valid=false" >> "$GITHUB_OUTPUT"
39+
fi
40+
1541
refresh-changelog:
42+
name: Update changelog
43+
needs: validate-branch
44+
if: needs.validate-branch.outputs.is-valid-release == 'true'
1645
permissions:
1746
contents: write
1847
pull-requests: write

.js.env.example

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,10 @@ export MM_PERPS_ENABLED="true"
165165
export MM_PERPS_SERVICE_INTERRUPTION_BANNER_ENABLED="false"
166166
export MM_PERPS_BLOCKED_REGIONS="US,CA-ON,GB,BE"
167167
export MM_PERPS_GTM_MODAL_ENABLED="true"
168-
# HIP-3 Feature Flags
169-
export MM_PERPS_EQUITY_ENABLED="false"
170-
export MM_PERPS_ENABLED_DEXS=""
168+
# HIP-3 Feature Flags (remote override with local fallback)
169+
export MM_PERPS_HIP3_ENABLED="true"
170+
export MM_PERPS_HIP3_ALLOWLIST_MARKETS="" # Allowlist: Empty = enable all markets. Examples: "xyz:XYZ100,xyz:TSLA" or "xyz:*,abc:TSLA"
171+
export MM_PERPS_HIP3_BLOCKLIST_MARKETS="" # Blocklist: Empty = no blocking. Examples: "BTC,ETH" or "xyz:*"
171172

172173
## Card
173174
export MM_CARD_BAANX_API_CLIENT_KEY_DEV=""

.storybook/storybook.requires.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
3+
import { Box, BoxFlexDirection } from '@metamask/design-system-react-native';
4+
5+
import ButtonFilter from './ButtonFilter';
6+
import { ButtonFilterProps } from './ButtonFilter.types';
7+
8+
const ButtonFilterMeta = {
9+
title: 'Components Temp / Buttons / ButtonFilter',
10+
component: ButtonFilter,
11+
argTypes: {
12+
isActive: {
13+
control: 'boolean',
14+
},
15+
children: {
16+
control: 'text',
17+
},
18+
},
19+
args: {
20+
children: 'Filter',
21+
onPress: () => {
22+
// For demo purposes only
23+
},
24+
},
25+
};
26+
27+
export default ButtonFilterMeta;
28+
29+
export const Default = {};
30+
31+
export const Active = {
32+
args: {
33+
isActive: true,
34+
},
35+
};
36+
37+
export const FilterGroup = {
38+
render: (args: ButtonFilterProps) => (
39+
<Box flexDirection={BoxFlexDirection.Row} gap={3}>
40+
<ButtonFilter {...args} isActive>
41+
All
42+
</ButtonFilter>
43+
<ButtonFilter {...args}>Purchased</ButtonFilter>
44+
<ButtonFilter {...args}>Sold</ButtonFilter>
45+
</Box>
46+
),
47+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from 'react';
2+
import { render, fireEvent } from '@testing-library/react-native';
3+
4+
import ButtonFilter from './ButtonFilter';
5+
6+
describe('ButtonFilter', () => {
7+
const defaultProps = {
8+
children: 'All',
9+
onPress: jest.fn(),
10+
};
11+
12+
beforeEach(() => {
13+
jest.clearAllMocks();
14+
});
15+
16+
it('renders with children prop', () => {
17+
const { getByRole } = render(<ButtonFilter {...defaultProps} />);
18+
19+
expect(getByRole('button')).toBeOnTheScreen();
20+
});
21+
22+
it('renders correctly in active state', () => {
23+
const { toJSON } = render(<ButtonFilter {...defaultProps} isActive />);
24+
25+
expect(toJSON()).toMatchSnapshot();
26+
});
27+
28+
it('renders correctly in inactive state', () => {
29+
const { toJSON } = render(
30+
<ButtonFilter {...defaultProps} isActive={false} />,
31+
);
32+
33+
expect(toJSON()).toMatchSnapshot();
34+
});
35+
36+
it('calls onPress when pressed', () => {
37+
const mockOnPress = jest.fn();
38+
const { getByRole } = render(
39+
<ButtonFilter {...defaultProps} onPress={mockOnPress} />,
40+
);
41+
42+
const button = getByRole('button');
43+
fireEvent.press(button);
44+
45+
expect(mockOnPress).toHaveBeenCalledTimes(1);
46+
});
47+
48+
it('verifies disabled state prevents interaction', () => {
49+
const mockOnPress = jest.fn();
50+
const { getByTestId } = render(
51+
<ButtonFilter
52+
{...defaultProps}
53+
testID="filter-button"
54+
onPress={mockOnPress}
55+
isDisabled
56+
/>,
57+
);
58+
59+
const button = getByTestId('filter-button');
60+
expect(button).toBeDisabled();
61+
62+
fireEvent.press(button);
63+
expect(mockOnPress).not.toHaveBeenCalled();
64+
});
65+
66+
it('uses custom accessibility label when provided', () => {
67+
const { getByTestId } = render(
68+
<ButtonFilter
69+
{...defaultProps}
70+
testID="filter-button"
71+
accessibilityLabel="Filter by all"
72+
/>,
73+
);
74+
75+
const button = getByTestId('filter-button');
76+
expect(button).toHaveAccessibleName('Filter by all');
77+
});
78+
79+
it('spreads additional props to ButtonBase', () => {
80+
const customStyle = { marginTop: 20 };
81+
82+
const { getByTestId } = render(
83+
<ButtonFilter
84+
{...defaultProps}
85+
testID="filter-button"
86+
style={customStyle}
87+
/>,
88+
);
89+
90+
const button = getByTestId('filter-button');
91+
expect(button).toBeOnTheScreen();
92+
expect(button).toHaveStyle(customStyle);
93+
});
94+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { useCallback } from 'react';
2+
3+
import { ButtonBase } from '@metamask/design-system-react-native';
4+
import { useTailwind } from '@metamask/design-system-twrnc-preset';
5+
6+
import { ButtonFilterProps } from './ButtonFilter.types';
7+
8+
const ButtonFilter = ({
9+
children,
10+
isActive = false,
11+
textClassName,
12+
style,
13+
...props
14+
}: ButtonFilterProps) => {
15+
const tw = useTailwind();
16+
17+
const getTextClassName = useCallback(
18+
() => (isActive ? 'text-icon-inverse' : 'text-default'),
19+
[isActive],
20+
);
21+
22+
const getStyle = useCallback(
23+
({ pressed }: { pressed: boolean }) => [
24+
tw.style(isActive ? 'bg-icon-default' : 'bg-background-muted'),
25+
typeof style === 'function' ? style({ pressed }) : style,
26+
],
27+
[tw, isActive, style],
28+
);
29+
30+
return (
31+
<ButtonBase
32+
textClassName={textClassName || getTextClassName}
33+
{...props}
34+
style={getStyle}
35+
>
36+
{children}
37+
</ButtonBase>
38+
);
39+
};
40+
41+
export default ButtonFilter;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ButtonBaseProps } from '@metamask/design-system-react-native';
2+
3+
/**
4+
* ButtonFilter component props.
5+
*/
6+
export interface ButtonFilterProps extends ButtonBaseProps {
7+
/**
8+
* Whether the button is in an active state.
9+
*/
10+
isActive?: boolean;
11+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# ButtonFilter
2+
3+
`ButtonFilter` is a reusable filter button component that displays an active or inactive state. It's commonly used for filtering lists or toggling between different views.
4+
5+
## Props
6+
7+
This component extends `ButtonBaseProps` from `@metamask/design-system-react-native`, so all ButtonBase props are available.
8+
9+
### Required
10+
11+
- **`children`** (string | ReactNode): The content to display in the button.
12+
- **`onPress`** (function): Function to trigger when pressing the button.
13+
14+
### Optional
15+
16+
- **`isActive`** (boolean): Whether the button is in an active state. Defaults to `false`.
17+
- **`size`** (ButtonSize): The size of the button. Defaults to `Md`.
18+
- **`isDisabled`** (boolean): Whether the button is disabled.
19+
- **`isLoading`** (boolean): Whether the button is in a loading state.
20+
- **`startIconName`** (IconName): Icon to display before the content.
21+
- **`endIconName`** (IconName): Icon to display after the content.
22+
- **`...ButtonBaseProps`**: All other props from ButtonBase (including `testID`, `accessibilityLabel`, etc.)
23+
24+
## Usage
25+
26+
```tsx
27+
import ButtonFilter from 'app/component-library/components-temp/ButtonFilter';
28+
29+
const MyComponent = () => {
30+
const [filter, setFilter] = useState('all');
31+
32+
return (
33+
<ButtonFilter
34+
isActive={filter === 'all'}
35+
onPress={() => setFilter('all')}
36+
>
37+
All
38+
</ButtonFilter>
39+
);
40+
};
41+
```
42+
43+
## Styling
44+
45+
The component uses Tailwind CSS via the `useTailwind` hook and follows the MetaMask Design System styling patterns:
46+
47+
- **Active state**: Uses `bg-icon-default` background with `text-icon-inverse` text color
48+
- **Inactive state**: Uses `bg-background-muted` background with `text-default` text color
49+
- **Pressed state**: Applies `opacity-70` for visual feedback
50+
51+
## Example
52+
53+
```tsx
54+
<Box flexDirection={BoxFlexDirection.Row} gap={3}>
55+
<ButtonFilter
56+
label="All"
57+
isActive={currentFilter === 'all'}
58+
onPress={() => setCurrentFilter('all')}
59+
/>
60+
<ButtonFilter
61+
label="Purchased"
62+
isActive={currentFilter === 'purchased'}
63+
onPress={() => setCurrentFilter('purchased')}
64+
/>
65+
<ButtonFilter
66+
label="Sold"
67+
isActive={currentFilter === 'sold'}
68+
onPress={() => setCurrentFilter('sold')}
69+
/>
70+
</Box>
71+
```
72+

0 commit comments

Comments
 (0)