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:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
@@ -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;