mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 12:28:01 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../Button/index.js';
|
||||
import { styled } from '../styled.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Actions ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal-Actions`}>
|
||||
<Button.Group>
|
||||
{children}
|
||||
</Button.Group>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
background-color: var(--bg-input);
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
.ui--Button-Group {
|
||||
margin: 1rem 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Actions);
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { styled } from '../styled.js';
|
||||
|
||||
interface Props {
|
||||
align?: 'center' | 'left' | 'right';
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hint?: React.ReactNode;
|
||||
}
|
||||
|
||||
function Columns ({ align = 'left', children, className = '', hint }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal-Columns ${align}Align`}>
|
||||
<div className='ui--Modal-Columns-content'>{children}</div>
|
||||
{hint && (
|
||||
<div className='ui--Modal-Columns-hint'>{hint}</div>
|
||||
)}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&.centerAlign > div.ui--Modal-Columns-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.rightAlign > div.ui--Modal-Columns-content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&+& {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 0.25em 0;
|
||||
|
||||
&:nth-child(1) {
|
||||
flex: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
display: none;
|
||||
flex: 0%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
&:nth-child(1),
|
||||
&:only-child {
|
||||
flex: 0 65%;
|
||||
max-width: 65%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
flex: 0 34%;
|
||||
font-size: var(--font-size-small);
|
||||
opacity: 0.75;
|
||||
padding: 0.25rem 0 0.25rem 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Columns);
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { styled } from '../styled.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Content ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal-Content`}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
padding: 1.5rem;
|
||||
`;
|
||||
|
||||
export default React.memo(Content);
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../Button/index.js';
|
||||
import { styled } from '../styled.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Header ({ className = '', header, onClose }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal-Header`}>
|
||||
{header && (
|
||||
<h1>{header}</h1>
|
||||
)}
|
||||
<Button
|
||||
dataTestId='close-modal'
|
||||
icon='times'
|
||||
onClick={onClose}
|
||||
/>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1.5rem 0;
|
||||
`;
|
||||
|
||||
export default React.memo(Header);
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-bounties authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { lightTheme } from '@pezkuwi/react-components';
|
||||
import { useToggle } from '@pezkuwi/react-hooks';
|
||||
|
||||
import Button from '../Button/index.js';
|
||||
import i18next from '../i18n/index.js';
|
||||
import Modal from './index.js';
|
||||
|
||||
function TestModal () {
|
||||
const [isOpen, toggleIsOpen] = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon='plus'
|
||||
isDisabled={false}
|
||||
label='Open Test Modal'
|
||||
onClick={toggleIsOpen}
|
||||
/>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
header='Test Modal'
|
||||
onClose={toggleIsOpen}
|
||||
testId='test-modal'
|
||||
>
|
||||
<Modal.Content>
|
||||
Test Modal Content
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button
|
||||
icon='plus'
|
||||
label='Submit Modal'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderModal () {
|
||||
return render(
|
||||
<Suspense fallback='...'>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<TestModal />
|
||||
</ThemeProvider>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Modal Component', () => {
|
||||
beforeAll(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('opens and closes modal', async () => {
|
||||
renderModal();
|
||||
|
||||
await waitFor(async () => {
|
||||
await expectModalToBeClosed();
|
||||
await openModal();
|
||||
await expectModalToBeOpen();
|
||||
await closeModal();
|
||||
await expectModalToBeClosed();
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('renders all modal sections', async () => {
|
||||
renderModal();
|
||||
|
||||
await waitFor(async () => {
|
||||
await expectModalToBeClosed();
|
||||
await openModal();
|
||||
await expectModalToBeOpen();
|
||||
|
||||
await screen.findByText('Test Modal');
|
||||
await screen.findAllByText('Test Modal Content');
|
||||
await screen.findByRole('button', { name: 'Submit Modal' });
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('closes modal with ESC button', async () => {
|
||||
renderModal();
|
||||
|
||||
await waitFor(async () => {
|
||||
await expectModalToBeClosed();
|
||||
await openModal();
|
||||
await expectModalToBeOpen();
|
||||
});
|
||||
|
||||
fireEvent.keyDown(window, {
|
||||
charCode: 27,
|
||||
code: 'Escape',
|
||||
key: 'Escape',
|
||||
keyCode: 27
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
await expectModalToBeClosed();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function expectModalToBeClosed () {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await screen.findByRole('button', { name: 'Open Test Modal' });
|
||||
|
||||
expect(screen.queryAllByTestId('test-modal')).toHaveLength(0);
|
||||
}
|
||||
|
||||
async function expectModalToBeOpen () {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await screen.findByTestId('test-modal');
|
||||
}
|
||||
|
||||
async function openModal () {
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Open Test Modal' }));
|
||||
}
|
||||
|
||||
async function closeModal () {
|
||||
fireEvent.click(await screen.findByTestId('close-modal'));
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { useTheme } from '@pezkuwi/react-hooks';
|
||||
|
||||
import ErrorBoundary from '../ErrorBoundary.js';
|
||||
import { styled } from '../styled.js';
|
||||
import Actions from './Actions.js';
|
||||
import Columns from './Columns.js';
|
||||
import Content from './Content.js';
|
||||
import Header from './Header.js';
|
||||
|
||||
interface Props {
|
||||
size?: 'large' | 'medium' | 'small';
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onClose: () => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
function ModalBase ({ children, className = '', header, onClose, size = 'medium', testId = 'modal' }: Props): React.ReactElement<Props> {
|
||||
const { themeClassName } = useTheme();
|
||||
|
||||
const listenKeyboard = useCallback((event: KeyboardEvent) => {
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', listenKeyboard, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listenKeyboard, true);
|
||||
};
|
||||
}, [listenKeyboard]);
|
||||
|
||||
return createPortal(
|
||||
<StyledDiv
|
||||
className={`${className} ui--Modal ${size}Size ${themeClassName} `}
|
||||
data-testid={testId}
|
||||
>
|
||||
<DisableGlobalScroll />
|
||||
<div
|
||||
className='ui--Modal__overlay'
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className='ui--Modal__body'>
|
||||
<Header
|
||||
header={header}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</StyledDiv>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
const DisableGlobalScroll = createGlobalStyle`
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
|
||||
.ui--Modal__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(96, 96, 96, 0.5);
|
||||
}
|
||||
|
||||
.ui--Modal__body {
|
||||
margin-top: 30px;
|
||||
background: var(--bg-page);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
max-width: 900px;
|
||||
width: calc(100% - 16px);
|
||||
|
||||
color: var(--color-text);
|
||||
font: var(--font-sans);
|
||||
}
|
||||
|
||||
&.smallSize .ui--Modal__body {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
&.largeSize .ui--Modal__body {
|
||||
max-width: 1080px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Modal = React.memo(ModalBase) as unknown as typeof ModalBase & {
|
||||
Actions: typeof Actions;
|
||||
Columns: typeof Columns;
|
||||
Content: typeof Content;
|
||||
};
|
||||
|
||||
Modal.Actions = Actions;
|
||||
Modal.Columns = Columns;
|
||||
Modal.Content = Content;
|
||||
|
||||
export default Modal;
|
||||
Reference in New Issue
Block a user