Skip to content

Commit

Permalink
Merge pull request #84 from ubie-oss/add-icon-modal
Browse files Browse the repository at this point in the history
show icon with Modal
  • Loading branch information
takanorip authored Jun 28, 2024
2 parents f391476 + 71b2a33 commit 73bf141
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 156 deletions.
11 changes: 6 additions & 5 deletions scripts/extractIconNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const getIconNames = (iconDir) => {
return fs.readdirSync(iconDir)
.filter(file => file.endsWith('.js') && file !== 'index.js')
.map(file => path.basename(file, '.js'));
return fs
.readdirSync(iconDir)
.filter((file) => file.endsWith('.js') && file !== 'index.js')
.map((file) => path.basename(file, '.js'));
};

const outputIconNames = (iconNames, outputPath) => {
fs.writeFileSync(outputPath, JSON.stringify(iconNames, null, 2));
fs.writeFileSync(outputPath, JSON.stringify(iconNames, null, 2));
};

console.log('Extracting icon names...');
Expand All @@ -22,4 +23,4 @@ const iconNames = getIconNames(iconDir);
const outputPath = path.join(__dirname, '../src/metadata/iconNames.json');
outputIconNames(iconNames, outputPath);

console.log('Icon names extracted successfully!')
console.log('Icon names extracted successfully!');
10 changes: 10 additions & 0 deletions src/components/react/CopyButton.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.button {
display: inline-flex;
gap: var(--size-spacing-xxs);
align-items: center;
justify-content: center;
min-height: 32px;
Expand All @@ -16,6 +17,11 @@
background-color: transparent;
}

.button.secondary {
color: var(--color-text-main);
background-color: transparent;
}

@media (hover: hover) {
.button:hover {
background-color: var(--color-ubie-black-100);
Expand All @@ -24,6 +30,10 @@
.button.invert:hover {
background-color: rgb(255 255 255 / 15%);
}

.button.secondary:hover {
color: var(--color-ubie-black-900);
}
}

.button:active {
Expand Down
11 changes: 9 additions & 2 deletions src/components/react/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ interface Props {
className?: string;
label?: string;
invert?: boolean;
secondary?: boolean;
block?: boolean;
}

const ButtonCopy: FC<Props> = ({ text, className = '', label, invert, block }) => {
const ButtonCopy: FC<Props> = ({ text, className = '', label, invert, secondary, block }) => {
const [copied, setCopied] = useState(false);

const handleClick = () => {
Expand All @@ -32,6 +33,7 @@ const ButtonCopy: FC<Props> = ({ text, className = '', label, invert, block }) =
className={clsx(
styles.button,
invert !== undefined ? styles.invert : null,
secondary !== undefined ? styles.secondary : null,
block !== undefined ? styles.block : null,
className,
)}
Expand All @@ -44,7 +46,12 @@ const ButtonCopy: FC<Props> = ({ text, className = '', label, invert, block }) =
<CheckAIcon />
</span>
) : label ? (
label
<>
<span className={styles.icon}>
<CopyIcon />
</span>
<span>{label}</span>
</>
) : (
<span className={styles.icon}>
<CopyIcon />
Expand Down
91 changes: 29 additions & 62 deletions src/components/react/IconList/IconWrapper.module.css
Original file line number Diff line number Diff line change
@@ -1,87 +1,54 @@
.wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
padding: 0;
padding-right: var(--size-spacing-xs);
padding-bottom: var(--size-spacing-xs);
padding-left: var(--size-spacing-xs);
background-color: var(--color-background-white);
border: none;
border-radius: var(--radius-lg);
}

.wrapper:hover {
background-color: var(--color-background-gray);
}

.icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
aspect-ratio: 1 / 0.96;
font-size: 36px;
aspect-ratio: 1 / 0.75;
font-size: 3rem;
color: var(--color-primary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
}

.name {
padding: 0;
margin: 0;
margin-top: var(--size-spacing-xxs);
font-size: var(--text-body-sm-size);
font-size: var(--text-note-md-size);
font-weight: bold;
line-height: var(--text-body-sm-line);
color: var(--color-text-sub);
text-align: center;
word-wrap: normal;
}

.copyOuter {
position: absolute;
top: 100%;
left: 50%;
z-index: 1;
display: none;
width: 115%;
translate: -50% 0;
}

.copyOuter.show {
display: block;
}

.copy {
position: relative;
width: 100%;
background-color: #fff;
isolation: isolate;
border: 2px solid var(--color-primary);
border-radius: var(--radius-md);
transform: 0 -100%;
word-break: break-word;
}

.copy::before {
position: absolute;
top: -8px;
left: 50%;
display: block;
width: 12px;
height: calc(tan(60deg) * 12px / 3);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
content: '';
background: var(--color-primary);
translate: -50% 0;
.modalContent {
display: flex;
flex-direction: column;
gap: var(--size-spacing-lg);
text-align: left;
}

.copy::after {
position: absolute;
top: -5px;
left: 50%;
.modalIcon {
display: block;
width: 10px;
height: calc(tan(60deg) * 10px / 3 + 1px);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
content: '';
background: var(--color-ubie-white);
translate: -50% 0;
}

.copyInner {
overflow: hidden;
border-radius: var(--radius-md);
}

.copy:focus,
.wrapper:hover .copy {
opacity: 1;
font-size: 6rem;
line-height: 0;
color: var(--color-primary);
}
112 changes: 35 additions & 77 deletions src/components/react/IconList/IconWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,57 @@
import { Text, Box } from '@ubie/ubie-ui';
import clsx from 'clsx';
import { useState, useRef, useCallback, useEffect } from 'react';
import { Heading, MessageModal, Stack, Text } from '@ubie/ubie-ui';
import { useState, type FC, type JSX } from 'react';
import iconNames from '@metadata/iconNames.json';
import styles from './IconWrapper.module.css';
import CopyButton from '../CopyButton';
import type { FC, JSX, FocusEvent } from 'react';
import { SimpleCode } from '../SimpleCode';

interface Props {
children: JSX.Element;
index: number;
}

const toUbieIconsStatement = (iconName: string): string => {
return `import { ${iconName} } from '@ubie/ubie-icons'`;
};

const toUbieUIStatement = (iconName: string): string => {
return `<Icon icon="${iconName}" />`;
};

const IconWrapper: FC<Props> = ({ children, index }) => {
const name = iconNames[index];

if (!name) return null;

const humanReadableName = name.split(/(?=[A-Z])/).join(' ');
const [open, setOpen] = useState(false);

const [showCopyBubble, setShowCopyBubble] = useState(false);
const toUbieIconsImport = (iconName: string): string => {
return `import { ${iconName} } from '@ubie/ubie-icons';`;
};

const handleMouseEnter = useCallback(() => {
setShowCopyBubble(true);
}, []);
const handleMouseLeave = useCallback(() => {
setShowCopyBubble(false);
}, []);
const toUbieIconsComponent = (iconName: string): string => {
return `<${iconName} />`;
};

const handleFocus = useCallback(() => {
setShowCopyBubble(true);
}, []);
const wrapperRef = useRef<HTMLDivElement>(null);
const handleBlur = (e: FocusEvent) => {
if (wrapperRef.current == null) return;
const toUbieUIImport = `import { Icon } from '@ubie/ubie-ui';`;

if (!wrapperRef.current.contains(e.relatedTarget as Node)) {
setShowCopyBubble(false);
}
const toUbieUIComponent = (iconName: string): string => {
return `<Icon icon="${iconName}" />`;
};

const handleKeyDownEsc = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
setShowCopyBubble(false);
}
}, []);
useEffect(() => {
window.addEventListener('keydown', handleKeyDownEsc);

return () => {
window.removeEventListener('keydown', handleKeyDownEsc);
};
}, []);
const name = iconNames[index];

return (
<>
{/* eslint-disable jsx-a11y/no-noninteractive-tabindex */}
<div
className={styles.wrapper}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onFocus={handleFocus}
onBlur={handleBlur}
tabIndex={0}
ref={wrapperRef}
>
{/* eslint-enable */}
<div className={styles.wrapperInner}>
<div className={styles.icon} aria-label={`アイコン ${humanReadableName}`} role="img">
{children}
</div>
<p className={styles.name}>{humanReadableName}</p>
</div>

<div className={clsx(styles.copyOuter, showCopyBubble && styles.show)}>
<div className={styles.copy}>
<div className={styles.copyInner}>
<Box pt="xxs" pb="xxs">
<Text type="note" size="lg" color="main" bold textAlign="center">
copy
</Text>
</Box>
<CopyButton label="Ubie UI" text={toUbieUIStatement(name)} block />
<CopyButton label="Ubie Icons" text={toUbieIconsStatement(name)} block />
</div>
</div>
<button className={styles.wrapper} type="button" onClick={() => setOpen(true)}>
<span className={styles.icon} aria-label={`アイコン ${name}`} role="img">
{children}
</span>
<span className={styles.name}>{name}</span>
</button>
<MessageModal open={open} onClose={() => setOpen(false)}>
<div className={styles.modalContent}>
<span className={styles.modalIcon}>{children}</span>
<Heading as="h3" size="md">
{name}
</Heading>
<Stack spacing="md">
<Text size="sm">Ubie UIを利用している場合:</Text>
<SimpleCode>{toUbieUIImport}</SimpleCode>
<SimpleCode>{toUbieUIComponent(name)}</SimpleCode>
<Text size="sm">Ubie UIを利用していない場合:</Text>
<SimpleCode>{toUbieIconsImport(name)}</SimpleCode>
<SimpleCode>{toUbieIconsComponent(name)}</SimpleCode>
</Stack>
</div>
</div>
</MessageModal>
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/react/IconList/index.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(80px, 100%), 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(124px, 100%), 1fr));
grid-gap: var(--size-spacing-md) var(--size-spacing-lg);
list-style: none;
}
20 changes: 11 additions & 9 deletions src/components/react/IconList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ const iconArray = Object.values(icons);

const IconList: FC = () => {
return (
<ul className={styles.list}>
{iconArray.map((Icon, index) => (
<li key={index}>
<IconWrapper index={index}>
<Icon />
</IconWrapper>
</li>
))}
</ul>
<div>
<ul className={styles.list}>
{iconArray.map((Icon, index) => (
<li key={index}>
<IconWrapper index={index}>
<Icon />
</IconWrapper>
</li>
))}
</ul>
</div>
);
};

Expand Down
22 changes: 22 additions & 0 deletions src/components/react/SimpleCode.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.wrapper {
position: relative;
display: flex;
flex-direction: column;
gap: var(--size-spacing-xxs);
padding: var(--size-spacing-md);
color: var(--color-text-main);
background-color: var(--color-background-gray);
border-radius: var(--radius-md);
}

.copy {
display: flex;
justify-content: flex-end;
width: 100%;
}

.code {
overflow: auto;
color: var(--color-text-main);
white-space: pre-wrap;
}
Loading

0 comments on commit 73bf141

Please sign in to comment.