Skip to content

Commit

Permalink
feat: new native search bar
Browse files Browse the repository at this point in the history
This commit merges a PR that introduces a new native Search bar that includes search on docs
  • Loading branch information
gagdiez authored Jul 25, 2024
2 parents 7f96c32 + e5189ce commit 90b81fc
Show file tree
Hide file tree
Showing 23 changed files with 2,565 additions and 1,415 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@near-wallet-selector/welldone-wallet": "8.9.7",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
Expand Down
2,754 changes: 1,369 additions & 1,385 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions src/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import styled from 'styled-components';

interface PageButtonProps {
active?: boolean;
disabled?: boolean;
onClick?: () => void;
}

interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}

const PaginationContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
`;

const PageButton = styled.button<PageButtonProps>`
padding: 4px 12px;
border-radius: 9999px;
background-color: ${(props) => (props.active ? '#3b82f6' : 'transparent')};
color: ${(props) => (props.active ? 'white' : 'inherit')};
cursor: ${(props) => (props.disabled ? 'default' : 'pointer')};
opacity: ${(props) => (props.disabled ? 0.5 : 1)};
transition: background-color 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
height: 36px;
border: 1px solid #eee;
&:hover:not(:disabled) {
background-color: ${(props) => (props.active ? '#3b82f6' : '#e5e7eb')};
}
`;

const Pagination: React.FC<PaginationProps> = ({ currentPage, totalPages, onPageChange }) => {
const pageNumbers: (number | string)[] = [];
const maxVisiblePages = 3;

if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
const leftOffset = Math.max(currentPage - Math.floor(maxVisiblePages / 2), 1);
const rightOffset = Math.min(leftOffset + maxVisiblePages - 1, totalPages);

if (leftOffset > 2) {
pageNumbers.push(1, '...');
} else if (leftOffset === 2) {
pageNumbers.push(1);
}

for (let i = leftOffset; i <= rightOffset; i++) {
pageNumbers.push(i);
}

if (rightOffset < totalPages - 1) {
pageNumbers.push('...', totalPages);
} else if (rightOffset === totalPages - 1) {
pageNumbers.push(totalPages);
}
}

return (
<PaginationContainer>
<PageButton onClick={() => onPageChange(currentPage - 1)} disabled={currentPage === 1}>
<i className="ph ph-caret-left"></i>
</PageButton>
{pageNumbers.map((number, index) => (
<PageButton
key={index}
onClick={() => typeof number === 'number' && onPageChange(number)}
active={number === currentPage}
disabled={typeof number !== 'number'}
>
{number}
</PageButton>
))}
<PageButton onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages}>
<i className="ph ph-caret-right"></i>
</PageButton>
</PaginationContainer>
);
};

export default Pagination;
94 changes: 94 additions & 0 deletions src/components/lib/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// originally copied from https://github.com/near/pagoda-experiments/blob/main/packages/ui/src/components/Input.tsx
import type { ComponentPropsWithRef, FormEventHandler, ReactElement } from 'react';
import { forwardRef } from 'react';

import * as S from './styles';

type Props = ComponentPropsWithRef<'input'> & {
assistive?: string;
error?: string;
iconLeft?: string; // meaning the className of ph-icon or bootstrap icon
iconRight?: string; // meaning the className of ph-icon or bootstrap icon
label?: string;
left?: ReactElement;
name: string;
right?: ReactElement;
success?: string;
};

export const Input = forwardRef<HTMLInputElement, Props>(
(
{
// assistive,
autoComplete,
error,
iconLeft,
iconRight,
inputMode,
label,
left,
name,
right,
style,
success,
type = 'text',
...props
},
ref,
) => {
// const assistiveTextId = `${name}-assistive-text`;
const variant: 'default' | 'success' | 'error' = error ? 'error' : success ? 'success' : 'default';

if (type === 'search' && !iconLeft) {
iconLeft = 'ph-bold ph-magnifying-glass';
}

const onInput: FormEventHandler<HTMLInputElement> = (event) => {
props.onInput && props.onInput(event);
};

return (
<S.Wrapper
data-disabled={props.disabled}
data-grow={typeof style?.width === 'undefined'}
data-type={type}
data-variant={variant}
style={style}
>
<S.LabelWrapper>
{label && <S.Label>{label}</S.Label>}

<S.InputWrapper>
{iconLeft && <S.Icon aria-hidden="true" className={iconLeft} />}

{left}

<S.Input
// aria-errormessage={error ? assistiveTextId : undefined}
aria-invalid={!!error}
autoComplete={autoComplete}
data-1p-ignore={!autoComplete}
inputMode={inputMode ?? undefined}
name={name}
ref={ref}
type={type}
{...props}
onInput={onInput}
style={{
textAlign: style?.textAlign,
}}
/>

{right}

{iconRight && <S.Icon aria-hidden="true" className={iconRight} />}
</S.InputWrapper>

{/* I'm going to leave this as an idea to implement such in future */}
{/* <AssistiveText variant={variant} message={error || success || assistive} id={assistiveTextId} /> */}
</S.LabelWrapper>
</S.Wrapper>
);
},
);
Input.displayName = 'Input';
1 change: 1 addition & 0 deletions src/components/lib/Input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Input';
195 changes: 195 additions & 0 deletions src/components/lib/Input/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// originally copied from https://github.com/near/pagoda-experiments/blob/main/packages/ui/src/components/Input.module.scss
import styled from 'styled-components';

export const Wrapper = styled.div`
--input-icon-size: 1rem;
--input-color-background: var(--sand1);
--input-color-border: var(--sand6);
--input-color-text: var(--sand12);
--input-color-icon: var(--sand10);
--transition-speed: 200ms;
position: relative;
flex-shrink: 0;
&[data-grow='true'] {
width: 100%;
flex-grow: 1;
flex-basis: 0;
}
`;

export const LabelWrapper = styled.label`
display: flex;
width: 100%;
flex-direction: column;
gap: 5px;
`;

export const Label = styled.span`
display: block;
font: var(--text-xs);
font-weight: 600;
color: var(--sand12);
`;

export const Icon = styled.i`
display: block;
width: var(--input-icon-size);
height: var(--input-icon-size);
pointer-events: none;
transition: all var(--transition-speed);
color: var(--input-color-icon);
`;

export const Input = styled.input`
display: block;
flex-grow: 1;
border: none;
background: none;
margin: 0;
min-width: 0;
width: 100%;
height: 40px;
line-height: 40px;
padding: 0;
color: var(--sand12);
font: var(--text-base);
font-size: 16px; // Make sure we always use 16px to prevent iOS auto-zoom
outline: none !important;
text-align: left;
transition: color var(--transition-speed), opacity var(--transition-speed);
&::placeholder {
color: var(--sand10);
font: var(--text-base);
font-size: 16px; // Make sure we always use 16px to prevent iOS auto-zoom
opacity: 1;
}
[data-disabled='true'] & {
opacity: 1;
color: var(--sand9);
&::placeholder {
color: var(--sand9);
}
}
[data-textarea='true'] & {
line-height: 1.5;
padding: 8px 12px;
height: unset;
min-height: 4rem;
field-sizing: content; // Progressive enhancement for browsers that support it: https://caniuse.com/?search=field-sizing
}
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: 'none';
margin: 0;
}
`;

export const InputWrapper = styled.div`
display: flex;
align-items: center;
padding: 0 12px;
column-gap: 10px;
position: relative;
border-radius: 6px;
color: var(--input-color-text);
border: 1px solid var(--input-color-border);
background-color: var(--input-color-background);
transition: background-color var(--transition-speed), border-color var(--transition-speed),
color var(--transition-speed), box-shadow var(--transition-speed);
&:hover {
--input-color-border: var(--sand7);
--input-color-background: var(--sand2);
}
[data-type='search'] & {
border-radius: 100px;
input::-webkit-search-cancel-button {
-webkit-appearance: none;
height: 1.25rem;
width: 1.25rem;
border-radius: 100px;
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iIzAwMDAwMCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxwYXRoIGQ9Ik0xMjgsMjRBMTA0LDEwNCwwLDEsMCwyMzIsMTI4LDEwNC4xMSwxMDQuMTEsMCwwLDAsMTI4LDI0Wm0zNy42NiwxMzAuMzRhOCw4LDAsMCwxLTExLjMyLDExLjMyTDEyOCwxMzkuMzFsLTI2LjM0LDI2LjM1YTgsOCwwLDAsMS0xMS4zMi0xMS4zMkwxMTYuNjksMTI4LDkwLjM0LDEwMS42NmE4LDgsMCwwLDEsMTEuMzItMTEuMzJMMTI4LDExNi42OWwyNi4zNC0yNi4zNWE4LDgsMCwwLDEsMTEuMzIsMTEuMzJMMTM5LjMxLDEyOFoiPjwvcGF0aD48L3N2Zz4=)
no-repeat 50% 50%;
background-size: contain;
opacity: 0.35;
position: relative;
right: -5px;
cursor: pointer;
transition: var(--transition-speed);
&:hover {
opacity: 0.5;
}
}
}
[data-variant='error'] & {
--input-color-background: var(--red1);
--input-color-border: var(--red9);
${Icon} {
--input-color-icon: var(--red9);
}
${Input} {
--input-color-icon: var(--red12);
}
&:hover {
--input-color-background: var(--red2);
--input-color-border: var(--red8);
}
}
[data-variant='success'] & {
--input-color-background: var(--green1);
--input-color-border: var(--green9);
${Icon} {
--input-color-icon: var(--green8);
}
${Input} {
--input-color-icon: var(--green12);
}
&:hover {
--input-color-background: var(--green2);
--input-color-border: var(--green8);
}
}
[data-disabled='true'] & {
pointer-events: none;
--input-color-background: var(--sand3);
--input-color-border: var(--sand3);
${Icon} {
--input-color-icon: var(--sand8);
}
}
[data-textarea='true'] & {
padding: 0;
}
[data-open='true'] & {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
&:focus-within {
--input-color-border: var(--violet8) !important;
--input-color-background: var(--white) !important;
outline: none;
box-shadow: 0 0 0 4px var(--violet4);
${Icon} {
--input-color-icon: var(--violet7);
}
${Input} {
--input-color-icon: var(--violet12);
}
}
`;
Loading

0 comments on commit 90b81fc

Please sign in to comment.