diff --git a/apps/browser-extension-wallet/package.json b/apps/browser-extension-wallet/package.json
index 7b93f63baf..22f390146c 100644
--- a/apps/browser-extension-wallet/package.json
+++ b/apps/browser-extension-wallet/package.json
@@ -54,6 +54,7 @@
"@lace/common": "0.1.0",
"@lace/core": "0.1.0",
"@lace/staking": "0.1.0",
+ "@lace/ui": "^0.1.0",
"@react-rxjs/core": "^0.9.8",
"@react-rxjs/utils": "^0.9.5",
"@vespaiach/axios-fetch-adapter": "^0.3.0",
diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx
new file mode 100644
index 0000000000..7f06c9d6d2
--- /dev/null
+++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.test.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { EditAccountDrawer } from './EditAccountDrawer';
+import '@testing-library/jest-dom';
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({ t: jest.fn() })
+}));
+
+describe('EditAccountDrawer', () => {
+ const onSaveMock = jest.fn();
+ const hideMock = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('displays default account name', () => {
+ render();
+
+ expect(screen.getByTestId('edit-account')).toBeInTheDocument();
+ expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Account #1');
+ expect(screen.getByTestId('edit-account-name-input')).toHaveValue('');
+ expect(screen.getByTestId('edit-account-save-btn')).toBeDisabled();
+ });
+
+ it('displays correct account name', () => {
+ render();
+
+ expect(screen.getByTestId('edit-account')).toBeInTheDocument();
+ expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Test Account');
+ expect(screen.getByTestId('edit-account-name-input')).toHaveValue('Test Account');
+ expect(screen.getByTestId('edit-account-save-btn')).toBeDisabled();
+ });
+
+ it('updates input value on change and enables save button', () => {
+ render();
+
+ const input = screen.getByTestId('edit-account-name-input');
+
+ fireEvent.change(input, { target: { value: 'New Account Name' } });
+ fireEvent.click(screen.getByTestId('edit-account-save-btn'));
+
+ expect(input).toHaveValue('New Account Name');
+ expect(screen.getByTestId('drawer-navigation-title')).toHaveTextContent('Test Account');
+ expect(screen.getByTestId('edit-account-save-btn')).toBeEnabled();
+ expect(onSaveMock).toHaveBeenCalledWith('New Account Name');
+ });
+
+ it('calls hide function when Cancel button is clicked', () => {
+ render();
+
+ fireEvent.click(screen.getByTestId('edit-account-cancel-btn'));
+
+ expect(hideMock).toHaveBeenCalled();
+ });
+});
diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx
new file mode 100644
index 0000000000..1e1f27b568
--- /dev/null
+++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/EditAccountDrawer.tsx
@@ -0,0 +1,60 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Box, Flex, Button, Text, TextBox } from '@lace/ui';
+import { Drawer, DrawerNavigation } from '@lace/common';
+
+export type Props = {
+ onSave: (name: string) => void;
+ visible: boolean;
+ hide: () => void;
+ name: string;
+ index: string;
+};
+
+export const EditAccountDrawer = ({ name, index, visible, onSave, hide }: Props): React.ReactElement => {
+ const { t } = useTranslation();
+ const [currentName, setCurrentName] = useState(name);
+
+ return (
+ }
+ footer={
+
+
+ onSave(currentName)}
+ data-testid="edit-account-save-btn"
+ label={t('account.edit.footer.save')}
+ />
+
+
+
+ }
+ >
+
+
+ {t('account.edit.title')}
+
+
+ {t('account.edit.subtitle')}
+
+ setCurrentName(e.target.value)}
+ />
+
+
+ );
+};
diff --git a/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts b/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts
new file mode 100644
index 0000000000..f17ee3d5f8
--- /dev/null
+++ b/apps/browser-extension-wallet/src/features/account/components/EditAccount/hooks.ts
@@ -0,0 +1,11 @@
+import { useState } from 'react';
+
+export const useEditAccountDrawer = (): { isOpen: boolean; open: () => void; hide: () => void } => {
+ const [visible, setVisible] = useState(false);
+
+ return {
+ isOpen: visible,
+ open: () => setVisible(true),
+ hide: () => setVisible(false)
+ };
+};
diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json
index a383db68f8..6b60b78510 100644
--- a/apps/browser-extension-wallet/src/lib/translations/en.json
+++ b/apps/browser-extension-wallet/src/lib/translations/en.json
@@ -1313,5 +1313,14 @@
"errorText": "Wallet failed to sync",
"successText": "Wallet synced successfully"
}
+ },
+ "account": {
+ "edit": {
+ "title": "Edit account name",
+ "subtitle": "Choose a name to identify your account",
+ "input.label": "Account name",
+ "footer.save": "Save",
+ "footer.cancel": "Cancel"
+ }
}
}
diff --git a/packages/ui/src/design-system/index.ts b/packages/ui/src/design-system/index.ts
index f865c3f124..e4c37ec074 100644
--- a/packages/ui/src/design-system/index.ts
+++ b/packages/ui/src/design-system/index.ts
@@ -27,3 +27,4 @@ export { PasswordBox } from './password-box';
export { Metadata } from './metadata';
export { TextLink } from './text-link';
export * as ProfileDropdown from './profile-dropdown';
+export { TextBox } from './text-box';
diff --git a/packages/ui/src/design-system/text-box/index.ts b/packages/ui/src/design-system/text-box/index.ts
new file mode 100644
index 0000000000..65e82881ee
--- /dev/null
+++ b/packages/ui/src/design-system/text-box/index.ts
@@ -0,0 +1,2 @@
+export { TextBox } from './text-box.component';
+export type { TextBoxProps } from './text-box.component';
diff --git a/packages/ui/src/design-system/text-box/text-box.component.tsx b/packages/ui/src/design-system/text-box/text-box.component.tsx
new file mode 100644
index 0000000000..4103d53e4a
--- /dev/null
+++ b/packages/ui/src/design-system/text-box/text-box.component.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+
+import * as Form from '@radix-ui/react-form';
+import cn from 'classnames';
+
+import { Flex } from '../flex';
+import * as Typography from '../typography';
+
+import * as cx from './text-box.css';
+
+export interface TextBoxProps extends Form.FormControlProps {
+ required?: boolean;
+ disabled?: boolean;
+ id?: string;
+ label: string;
+ name?: string;
+ value: string;
+ errorMessage?: string;
+ onChange?: (event: Readonly>) => void;
+ containerClassName?: string;
+ containerStyle?: React.CSSProperties;
+ 'data-testid'?: string;
+}
+
+export const TextBox = ({
+ required = false,
+ disabled = false,
+ id,
+ label,
+ name,
+ value,
+ errorMessage = '',
+ containerClassName = '',
+ onChange,
+ containerStyle,
+ ...rest
+}: Readonly): JSX.Element => {
+ return (
+
+
+
+
+
+
+ {label}
+
+
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+ );
+};
diff --git a/packages/ui/src/design-system/text-box/text-box.css.ts b/packages/ui/src/design-system/text-box/text-box.css.ts
new file mode 100644
index 0000000000..9743bda7c3
--- /dev/null
+++ b/packages/ui/src/design-system/text-box/text-box.css.ts
@@ -0,0 +1,57 @@
+import { globalStyle, vars, style } from '../../design-tokens';
+
+export const container = style({
+ background: vars.colors.$input_container_bgColor,
+ paddingTop: vars.spacing.$12,
+ maxHeight: vars.spacing.$52,
+ borderRadius: vars.radius.$medium,
+ width: 'auto',
+ fontWeight: vars.fontWeights.$semibold,
+ fontFamily: vars.fontFamily.$nova,
+});
+
+export const input = style({
+ width: 'calc(100% - 90px)',
+ fontSize: vars.fontSizes.$18,
+ padding: `${vars.spacing.$10} ${vars.spacing.$20}`,
+ border: 'none',
+ outline: 'none',
+ background: 'transparent',
+ color: vars.colors.$input_value_color,
+});
+
+export const label = style({
+ position: 'relative',
+ display: 'block',
+ left: vars.spacing.$20,
+ top: '-40px',
+ transitionDuration: '0.2s',
+ pointerEvents: 'none',
+ color: vars.colors.$input_label_color,
+ fontSize: vars.fontSizes.$18,
+});
+
+export const errorMessage = style({
+ color: vars.colors.$input_error_message_color,
+ marginLeft: vars.spacing.$20,
+});
+
+globalStyle(
+ `${input}:focus + ${label}, ${input}:not(:placeholder-shown) + ${label}`,
+ {
+ top: '-52px',
+ fontSize: vars.fontSizes.$12,
+ },
+);
+
+globalStyle(`${container}:has(${input}:disabled)`, {
+ opacity: vars.opacities.$0_5,
+});
+
+globalStyle(`${container}:has(${input}:hover:not(:disabled))`, {
+ outline: `2px solid ${vars.colors.$input_container_hover_outline_color}`,
+});
+
+globalStyle(`${container}:has(${input}:focus)`, {
+ outline: `3px solid ${vars.colors.$input_container_focused_outline_color}`,
+});
diff --git a/packages/ui/src/design-system/text-box/text-box.stories.tsx b/packages/ui/src/design-system/text-box/text-box.stories.tsx
new file mode 100644
index 0000000000..eb5236347c
--- /dev/null
+++ b/packages/ui/src/design-system/text-box/text-box.stories.tsx
@@ -0,0 +1,96 @@
+import React from 'react';
+
+import type { Meta } from '@storybook/react';
+
+import { LocalThemeProvider, ThemeColorScheme } from '../../design-tokens';
+import { page, Section, Variants } from '../decorators';
+import { Divider } from '../divider';
+import { Flex } from '../flex';
+import { Cell, Grid } from '../grid';
+
+import { TextBox } from './text-box.component';
+
+export default {
+ title: 'Input Fields/Text Box',
+ component: TextBox,
+ decorators: [
+ page({
+ title: 'Text Box',
+ subtitle: 'A text input',
+ }),
+ ],
+} as Meta;
+
+const MainComponents = (): JSX.Element => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export const Overview = (): JSX.Element => {
+ const [value, setValue] = React.useState('');
+
+ return (
+
+
+
+
+ {
+ setValue(event.target.value);
+ }}
+ />
+
+
+
+
+
+
+ |
+
+ );
+};
+
+Overview.parameters = {
+ pseudo: {
+ hover: '#hover',
+ focus: '#focus',
+ },
+};
diff --git a/yarn.lock b/yarn.lock
index fb109c846a..fda8b47d21 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7853,6 +7853,7 @@ __metadata:
"@lace/common": 0.1.0
"@lace/core": 0.1.0
"@lace/staking": 0.1.0
+ "@lace/ui": ^0.1.0
"@react-rxjs/core": ^0.9.8
"@react-rxjs/utils": ^0.9.5
"@types/dotenv-webpack": 7.0.3