mirror of
https://github.com/placeholder-soft/web.git
synced 2026-01-12 22:45:00 +08:00
Add Doc Feedback UI (#107)
* Add Modal and Icon components * Add buttons and modal form for doc feedback
This commit is contained in:
72
apps/base-docs/docusaurus.d.ts
vendored
72
apps/base-docs/docusaurus.d.ts
vendored
@@ -3,3 +3,75 @@
|
||||
/// <reference types="@docusaurus/module-type-aliases" />
|
||||
/// <reference types="@docusaurus/plugin-content-blog" />
|
||||
/// <reference types="@docusaurus/plugin-content-pages" />
|
||||
|
||||
enum ComponentType {
|
||||
unknown = 'unknown',
|
||||
banner = 'banner',
|
||||
button = 'button',
|
||||
card = 'card',
|
||||
chart = 'chart',
|
||||
content_script = 'content_script',
|
||||
dropdown = 'dropdown',
|
||||
link = 'link',
|
||||
page = 'page',
|
||||
modal = 'modal',
|
||||
table = 'table',
|
||||
search_bar = 'search_bar',
|
||||
service_worker = 'service_worker',
|
||||
text = 'text',
|
||||
text_input = 'text_input',
|
||||
tray = 'tray',
|
||||
checkbox = 'checkbox',
|
||||
icon = 'icon',
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
unknown = 'unknown',
|
||||
blur = 'blur',
|
||||
click = 'click',
|
||||
change = 'change',
|
||||
dismiss = 'dismiss',
|
||||
focus = 'focus',
|
||||
hover = 'hover',
|
||||
select = 'select',
|
||||
measurement = 'measurement',
|
||||
move = 'move',
|
||||
process = 'process',
|
||||
render = 'render',
|
||||
scroll = 'scroll',
|
||||
view = 'view',
|
||||
search = 'search',
|
||||
keyPress = 'keyPress',
|
||||
}
|
||||
|
||||
enum AnalyticsEventImportance {
|
||||
low = 'low',
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
type CCAEventData = {
|
||||
// Standard Attributes
|
||||
action: ActionType;
|
||||
componentType: ComponentType;
|
||||
// Custom Attributes
|
||||
doc_helpful?: boolean;
|
||||
doc_feedback_reason?: string | null;
|
||||
page_path?: string;
|
||||
};
|
||||
|
||||
export type LogEvent = (
|
||||
eventName: string,
|
||||
eventData: CCAEventData,
|
||||
importance?: AnalyticsEventImportance,
|
||||
) => void;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface Window {
|
||||
ClientAnalytics?: {
|
||||
logEvent: LogEvent;
|
||||
ActionType: typeof ActionType;
|
||||
ComponentType: typeof ComponentType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
72
apps/base-docs/src/components/DocFeedback/FeedbackModal.tsx
Normal file
72
apps/base-docs/src/components/DocFeedback/FeedbackModal.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import Modal from '../Modal';
|
||||
import ModalHeader from '../Modal/ModalHeader';
|
||||
import ModalBody from '../Modal/ModalBody';
|
||||
import ModalFooter from '../Modal/ModalFooter';
|
||||
import Icon from '../Icon';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
type FeedbackModalProps = {
|
||||
options: string[];
|
||||
visible: boolean;
|
||||
onRequestClose: () => void;
|
||||
onSubmit: (reason?: string) => void;
|
||||
};
|
||||
|
||||
export default function FeedbackModal({
|
||||
options,
|
||||
visible,
|
||||
onRequestClose,
|
||||
onSubmit,
|
||||
}: FeedbackModalProps) {
|
||||
const [selected, setSelected] = useState(options[0]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelected(e.target.value);
|
||||
},
|
||||
[selected],
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
onSubmit(selected);
|
||||
onRequestClose();
|
||||
}, [selected]);
|
||||
|
||||
return (
|
||||
<Modal visible={visible} onRequestClose={onRequestClose}>
|
||||
<ModalHeader>
|
||||
<div className={styles.feedbackModalHeader}>What is the reason for your feedback?</div>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<label htmlFor="docFeedback" className={styles.feedbackModalSelectLabel}>
|
||||
Please select an option:
|
||||
<div className={styles.feedbackModalSelectContainer}>
|
||||
<select
|
||||
id="docFeedback"
|
||||
className={styles.feedbackModalSelect}
|
||||
value={selected}
|
||||
onChange={handleSelect}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option}>{option}</option>
|
||||
))}
|
||||
</select>
|
||||
<Icon name="caret-down" width="14" height="14" />
|
||||
</div>
|
||||
</label>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={styles.feedbackModalFooter}>
|
||||
<button type="button" className={styles.feedbackModalCancel} onClick={onRequestClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" className={styles.feedbackModalSubmit} onClick={handleSubmit}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
105
apps/base-docs/src/components/DocFeedback/index.tsx
Normal file
105
apps/base-docs/src/components/DocFeedback/index.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import FeedbackModal from './FeedbackModal';
|
||||
import Icon from '../Icon';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const logDocFeedback = (isHelpful: boolean, reason?: string) => {
|
||||
if (window.ClientAnalytics) {
|
||||
const { logEvent, ActionType, ComponentType } = window.ClientAnalytics;
|
||||
|
||||
logEvent('doc_feedback', {
|
||||
action: ActionType.click,
|
||||
componentType: ComponentType.button,
|
||||
doc_helpful: isHelpful,
|
||||
doc_feedback_reason: reason ?? null,
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const helpfulReasons = ['Easy to understand', 'Solved my problem', 'Other'];
|
||||
const notHelpfulReasons = [
|
||||
'Missing the information I need',
|
||||
'Too complicated / too many steps',
|
||||
'Out of date',
|
||||
'Samples / code issue',
|
||||
'Other',
|
||||
];
|
||||
|
||||
export default function DocFeedback() {
|
||||
const [helpful, setHelpful] = useState<boolean | null>(null);
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
|
||||
const handleModalOpen = useCallback(() => setVisible(true), []);
|
||||
const handleModalClose = useCallback(() => setVisible(false), []);
|
||||
|
||||
const handleVote = (isHelpful: boolean) => {
|
||||
if (helpful !== null && helpful === isHelpful) {
|
||||
// User already voted and is trying to undo their vote.
|
||||
setHelpful(null);
|
||||
return false;
|
||||
}
|
||||
setHelpful(isHelpful);
|
||||
handleModalOpen();
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleHelpfulClick = useCallback(() => {
|
||||
const successful = handleVote(true);
|
||||
|
||||
if (successful) {
|
||||
logDocFeedback(true);
|
||||
}
|
||||
}, [helpful, window.location.pathname]);
|
||||
|
||||
const handleHelpfulModalSubmit = useCallback((reason?: string) => {
|
||||
logDocFeedback(true, reason);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleNotHelpfulClick = useCallback(() => {
|
||||
const successful = handleVote(false);
|
||||
|
||||
if (successful) {
|
||||
logDocFeedback(false);
|
||||
}
|
||||
}, [helpful, window.location.pathname]);
|
||||
|
||||
const handleNotHelpfulModalSubmit = useCallback((reason?: string) => {
|
||||
logDocFeedback(false, reason);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.docFeedbackContainer}>
|
||||
<p className={styles.feedbackPrompt}>Was this helpful?</p>
|
||||
<div className={styles.feedbackButtonContainer}>
|
||||
<button type="button" className={styles.helpfulButton} onClick={handleHelpfulClick}>
|
||||
{helpful !== true && <Icon name="thumbs-up" width="20" height="20" />}
|
||||
{helpful === true && <Icon name="thumbs-up-filled" width="20" height="20" />}
|
||||
</button>
|
||||
<button type="button" className={styles.notHelpfulButton} onClick={handleNotHelpfulClick}>
|
||||
{helpful !== false && <Icon name="thumbs-down" width="20" height="20" />}
|
||||
{helpful === false && <Icon name="thumbs-down-filled" width="20" height="20" />}
|
||||
</button>
|
||||
</div>
|
||||
{helpful && (
|
||||
<FeedbackModal
|
||||
visible={visible}
|
||||
onRequestClose={handleModalClose}
|
||||
onSubmit={handleHelpfulModalSubmit}
|
||||
options={helpfulReasons}
|
||||
/>
|
||||
)}
|
||||
{!helpful && (
|
||||
<FeedbackModal
|
||||
visible={visible}
|
||||
onRequestClose={handleModalClose}
|
||||
onSubmit={handleNotHelpfulModalSubmit}
|
||||
options={notHelpfulReasons}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
178
apps/base-docs/src/components/DocFeedback/styles.module.css
Normal file
178
apps/base-docs/src/components/DocFeedback/styles.module.css
Normal file
@@ -0,0 +1,178 @@
|
||||
.docFeedbackContainer {
|
||||
margin: 4rem 0 1rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feedbackPrompt {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.feedbackButtonContainer {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Helpful/Not Helpful Buttons */
|
||||
.helpfulButton,
|
||||
.notHelpfulButton {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 9999px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.helpfulButton:hover,
|
||||
.notHelpfulButton:hover {
|
||||
cursor: pointer;
|
||||
transform: rotate(-10deg);
|
||||
transition-property: all;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
[data-theme='light'] .helpfulButton svg,
|
||||
[data-theme='light'] .notHelpfulButton svg {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .helpfulButton svg,
|
||||
[data-theme='dark'] .notHelpfulButton svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
[data-theme='light'] .helpfulButton:hover,
|
||||
[data-theme='light'] .notHelpfulButton:hover {
|
||||
background-color: rgb(238, 240, 243);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .helpfulButton:hover,
|
||||
[data-theme='dark'] .notHelpfulButton:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.notHelpfulButton > svg {
|
||||
margin-bottom: -5px;
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
/* Feedbak Modal Header/Footer */
|
||||
.feedbackModalHeader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-theme='light'] .feedbackModalHeader {
|
||||
color: black;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .feedbackModalHeader {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.feedbackModalFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Feedback Modal Submit/Cancel Buttons */
|
||||
.feedbackModalSubmit,
|
||||
.feedbackModalCancel {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
font-family: var(--cds-font-display);
|
||||
height: 2.5rem;
|
||||
padding: 0 2rem;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feedbackModalSubmit:active,
|
||||
.feedbackModalCancel:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
[data-theme='light'] .feedbackModalSubmit {
|
||||
color: white;
|
||||
background-color: rgb(0, 82, 255);
|
||||
}
|
||||
[data-theme='light'] .feedbackModalSubmit:hover {
|
||||
background-color: rgb(1, 76, 236);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .feedbackModalSubmit {
|
||||
color: rgb(10, 11, 13);
|
||||
background-color: rgb(55, 115, 245);
|
||||
}
|
||||
[data-theme='dark'] .feedbackModalSubmit:hover {
|
||||
background-color: rgb(71, 126, 246);
|
||||
}
|
||||
|
||||
[data-theme='light'] .feedbackModalCancel {
|
||||
background-color: rgb(238, 240, 243);
|
||||
}
|
||||
[data-theme='light'] .feedbackModalCancel:hover {
|
||||
background-color: rgb(233, 235, 238);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .feedbackModalCancel {
|
||||
background-color: rgb(50, 53, 61);
|
||||
}
|
||||
[data-theme='dark'] .feedbackModalCancel:hover {
|
||||
background-color: rgb(58, 61, 69);
|
||||
}
|
||||
|
||||
/* Feedback Modal Select */
|
||||
.feedbackModalSelectLabel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.feedbackModalSelectContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.feedbackModalSelect {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
font-size: 1rem;
|
||||
font-family: var(--cds-font-display);
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
[data-theme='light'] .feedbackModalSelectContainer {
|
||||
background-color: white;
|
||||
border: 1px solid rgba(91, 97, 110, 0.5);
|
||||
}
|
||||
[data-theme='light'] .feedbackModalSelectContainer:hover {
|
||||
background-color: rgb(238, 240, 243);
|
||||
}
|
||||
[data-theme='light'] .feedbackModalSelect {
|
||||
color: black;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .feedbackModalSelectContainer {
|
||||
background-color: rgb(30, 32, 37);
|
||||
border: 1px solid rgba(138, 145, 158, 0.68);
|
||||
}
|
||||
[data-theme='dark'] .feedbackModalSelectContainer:hover {
|
||||
background-color: rgb(40, 42, 47);
|
||||
}
|
||||
[data-theme='dark'] .feedbackModalSelect {
|
||||
color: white;
|
||||
}
|
||||
[data-theme='dark'] .feedbackModalSelectContainer svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
@media (max-width: 997px) {
|
||||
.feedbackModalSelect {
|
||||
max-width: 225px;
|
||||
}
|
||||
}
|
||||
94
apps/base-docs/src/components/Icon/index.tsx
Normal file
94
apps/base-docs/src/components/Icon/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
type IconProps = {
|
||||
name: 'thumbs-up' | 'thumbs-up-filled' | 'thumbs-down' | 'thumbs-down-filled' | 'caret-down';
|
||||
color?: 'white' | 'black';
|
||||
width?: string;
|
||||
height?: string;
|
||||
};
|
||||
|
||||
export default function Icon({ name, color = 'white', width = '24', height = '24' }: IconProps) {
|
||||
if (name === 'thumbs-up') {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.5466 0.0420685L11.6316 -0.231445L8 10.1481V24.0002L19.0426 24.0002C20.5745 24.0002 21.9797 22.8632 22.0135 21.2643L23 12.0149V11.9618C23 10.3319 21.5792 9.16684 20.0284 9.16684L15.1921 9.16685L15.8437 6.01904C16.6257 3.45312 15.117 0.810491 12.5466 0.0420685ZM10 10.4879L12.8392 2.37324C13.835 3.06664 14.2897 4.30372 13.921 5.46663L13.9055 5.5157L12.7357 11.1669L20.0284 11.1668C20.6321 11.1668 20.9739 11.5767 20.9986 11.922L20.0142 21.1521V21.2052C20.0142 21.558 19.6696 22.0002 19.0426 22.0002L10 22.0002V10.4879Z"
|
||||
fill="current"
|
||||
/>
|
||||
<path d="M6.5 10.0001H1.5V24.0001H6.5V10.0001Z" fill="current" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
if (name === 'thumbs-up-filled') {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M1.5 10H6.5V24H1.5V10Z" fill="current" />
|
||||
<path
|
||||
d="M19.3151 24C20.4877 24 21.4383 23.1233 21.4383 22.0419L22.5 11.9581C22.5 10.8767 21.5494 10 20.3767 10L13.8457 10L14.8261 5.20216C15.5161 2.9972 14.2524 0.678776 12.0109 0L8.5 10.1649V24L19.3151 24Z"
|
||||
fill="current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
if (name === 'thumbs-down') {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 25"
|
||||
fill="current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.5466 23.9581C15.117 23.1897 16.6257 20.5471 15.8437 17.9811L15.1921 14.8333L20.0284 14.8333C21.5792 14.8333 23 13.6683 23 12.0384V11.9852L22.0135 2.73592C21.9797 1.13701 20.5745 0 19.0426 0L8 3.8147e-06V13.8521L11.6316 24.2316L12.5466 23.9581ZM10 13.5123V2L19.0426 2C19.6696 2 20.0142 2.44218 20.0142 2.79494V2.84811L20.9986 12.0782C20.9739 12.4235 20.6321 12.8333 20.0284 12.8333L12.7357 12.8333L13.9055 18.4845L13.921 18.5335C14.2897 19.6965 13.835 20.9335 12.8392 21.6269L10 13.5123Z"
|
||||
fill="current"
|
||||
/>
|
||||
<path d="M6.5 14.0001H1.5V5.14984e-05H6.5V14.0001Z" fill="current" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
if (name === 'thumbs-down-filled') {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M1.5 14H6.5V3.8147e-06H1.5V14Z" fill="current" />
|
||||
<path
|
||||
d="M19.3151 0C20.4877 0 21.4383 0.87668 21.4383 1.95811L22.5 12.0419C22.5 13.1233 21.5494 14 20.3767 14L13.8457 14L14.8261 18.7978C15.5161 21.0028 14.2524 23.3212 12.0109 24L8.5 13.8351V3.8147e-06L19.3151 0Z"
|
||||
fill="current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
if (name === 'caret-down') {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.4199 7.52002L11.9999 14.94L4.57994 7.52002L2.80994 9.29002L11.9999 18.48L21.1899 9.29002L19.4199 7.52002Z"
|
||||
fill="current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
9
apps/base-docs/src/components/Modal/ModalBody.tsx
Normal file
9
apps/base-docs/src/components/Modal/ModalBody.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export type ModalBodyProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function ModalBody({ children }: ModalBodyProps) {
|
||||
return <div className={styles.modalBody}>{children}</div>;
|
||||
}
|
||||
10
apps/base-docs/src/components/Modal/ModalFooter.tsx
Normal file
10
apps/base-docs/src/components/Modal/ModalFooter.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export type ModalFooterProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function ModalFooter({ children }: ModalFooterProps) {
|
||||
return <div className={styles.modalFooter}>{children}</div>;
|
||||
}
|
||||
9
apps/base-docs/src/components/Modal/ModalHeader.tsx
Normal file
9
apps/base-docs/src/components/Modal/ModalHeader.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export type ModalHeaderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function ModalHeader({ children }: ModalHeaderProps) {
|
||||
return <div className={styles.modalHeader}>{children}</div>;
|
||||
}
|
||||
27
apps/base-docs/src/components/Modal/index.tsx
Normal file
27
apps/base-docs/src/components/Modal/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export type ModalProps = {
|
||||
children: React.ReactNode;
|
||||
visible: boolean;
|
||||
onRequestClose: () => void;
|
||||
};
|
||||
|
||||
export default function Modal({ children, onRequestClose, visible }: ModalProps) {
|
||||
const handleModalClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation(),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onRequestClose}
|
||||
className={visible ? styles.modalOverlay : styles.modalOverlayHidden}
|
||||
>
|
||||
<div tabIndex={0} className={styles.modal} onClick={handleModalClick}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
87
apps/base-docs/src/components/Modal/styles.module.css
Normal file
87
apps/base-docs/src/components/Modal/styles.module.css
Normal file
@@ -0,0 +1,87 @@
|
||||
.modalOverlayHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modalOverlay {
|
||||
background-color: rgba(10, 11, 13, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: overlay-fade-in 0.1s ease-in-out;
|
||||
animation-timing-function: cubicBezier(0.6, 0, 1, 1);
|
||||
}
|
||||
|
||||
.modal {
|
||||
min-width: 500px;
|
||||
max-width: 612px;
|
||||
border-radius: 0.5rem;
|
||||
animation: modal-fade-in 0.1s ease-in-out;
|
||||
animation-timing-function: cubicBezier(0.6, 0, 1, 1);
|
||||
}
|
||||
[data-theme='light'] .modal {
|
||||
background-color: white;
|
||||
border: 1px solid rgba(91, 97, 110, 0.2);
|
||||
}
|
||||
[data-theme='dark'] .modal {
|
||||
background-color: rgb(30, 32, 37);
|
||||
border: 1px solid rgba(138, 145, 158, 0.2);
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
padding: 1.5rem 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
[data-theme='light'] .modalHeader {
|
||||
border-bottom: 1px solid rgba(91, 97, 110, 0.2);
|
||||
}
|
||||
[data-theme='dark'] .modalHeader {
|
||||
border-bottom: 1px solid rgba(138, 145, 158, 0.2);
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
[data-theme='light'] .modalFooter {
|
||||
border-top: 1px solid rgba(91, 97, 110, 0.2);
|
||||
}
|
||||
[data-theme='dark'] .modalFooter {
|
||||
border-top: 1px solid rgba(138, 145, 158, 0.2);
|
||||
}
|
||||
|
||||
@media (max-width: 997px) {
|
||||
.modal {
|
||||
min-width: 325px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlay-fade-in {
|
||||
from {
|
||||
opacity: 0%;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal-fade-in {
|
||||
from {
|
||||
opacity: 0%;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 100%;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
42
apps/base-docs/src/theme/DocItem/Content/index.js
Normal file
42
apps/base-docs/src/theme/DocItem/Content/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { ThemeClassNames } from '@docusaurus/theme-common';
|
||||
import { useDoc } from '@docusaurus/theme-common/internal';
|
||||
import Heading from '@theme/Heading';
|
||||
import MDXContent from '@theme/MDXContent';
|
||||
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import DocFeedback from '../../../components/DocFeedback/index.tsx';
|
||||
|
||||
/**
|
||||
Title can be declared inside md content or declared through
|
||||
front matter and added manually. To make both cases consistent,
|
||||
the added title is added under the same div.markdown block
|
||||
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
|
||||
|
||||
We render a "synthetic title" if:
|
||||
- user doesn't ask to hide it with front matter
|
||||
- the markdown content does not already contain a top-level h1 heading
|
||||
*/
|
||||
function useSyntheticTitle() {
|
||||
const { metadata, frontMatter, contentTitle } = useDoc();
|
||||
const shouldRender = !frontMatter.hide_title && typeof contentTitle === 'undefined';
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
return metadata.title;
|
||||
}
|
||||
export default function DocItemContent({ children }) {
|
||||
const syntheticTitle = useSyntheticTitle();
|
||||
return (
|
||||
<div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
|
||||
{syntheticTitle && (
|
||||
<header>
|
||||
<Heading as="h1">{syntheticTitle}</Heading>
|
||||
</header>
|
||||
)}
|
||||
<MDXContent>{children}</MDXContent>
|
||||
<BrowserOnly>{() => <DocFeedback />}</BrowserOnly>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user