-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
NgocNhi123
committed
Jun 30, 2024
1 parent
26a3a68
commit 4c4b0e3
Showing
15 changed files
with
1,075 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { Meta } from "@storybook/react"; | ||
import { useState } from "react"; | ||
import { Button, Dialog, DivGrow, Paragraph } from "../../../core/src"; | ||
import { GalleryDialog } from "../../../gallery/src"; | ||
import { Utils } from "../utils/utils"; | ||
|
||
const meta: Meta = { | ||
title: "Components/Dialog", | ||
component: Dialog, | ||
} as Meta; | ||
|
||
Utils.page.component(meta, { | ||
primary: "none", | ||
shots: [<GalleryDialog key="1" />], | ||
}); | ||
|
||
export default meta; | ||
|
||
export const Primary = (): JSX.Element => <div>None</div>; | ||
|
||
/** | ||
* Dialog is a [controlled][1], [declarative][3] component: you should have a | ||
* boolean [state][2] for whether the dialog should be visible or not, and | ||
* conditionally render a dialog based on that state. | ||
* | ||
* A dialog will call its \`onEsc\` function when the user press the "Esc" key or | ||
* click on the backdrop. It's common to set your state to \`false\` here to | ||
* "close" the dialog. | ||
* | ||
* [1]: https://reactjs.org/docs/forms.html#controlled-components | ||
* [2]: https://reactjs.org/docs/hooks-state.html | ||
* [3]: https://reactjs.org/docs/refs-and-the-dom.html#when-to-use-refs | ||
*/ | ||
export const Basic = (): JSX.Element => { | ||
const [visible, setVisible] = useState(false); | ||
return ( | ||
<> | ||
<Button onClick={() => setVisible(true)} children="Show" /> | ||
{visible && ( | ||
<Dialog onEsc={() => setVisible(false)}>Hello world!</Dialog> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
/** | ||
* Out of the box, dialogs render their children as-is, without even a padding, | ||
* to allow maximum customization. Therefore, the Dialog component has supporting | ||
* components to give you common dialog layout: | ||
* | ||
* - \`Dialog.Body\` wraps its children inside a padding. | ||
* - \`Dialog.Footer\` places its children horizontally, with gaps, aligned to end. | ||
* - \`Dialog.Title\` makes its children bold and larger, like a title. | ||
*/ | ||
export const Layout = (): JSX.Element => { | ||
const [visible, setVisible] = useState(false); | ||
return ( | ||
<> | ||
<Button onClick={() => setVisible(true)} children="Show" /> | ||
{visible && ( | ||
<Dialog onEsc={() => setVisible(false)}> | ||
<Dialog.Body> | ||
<Dialog.Title>Title</Dialog.Title> | ||
<Paragraph>Body</Paragraph> | ||
</Dialog.Body> | ||
<Dialog.Footer> | ||
<Button>First</Button> | ||
<DivGrow /> | ||
<Button>Second</Button> | ||
<Button highlight>Third</Button> | ||
</Dialog.Footer> | ||
</Dialog> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
/** | ||
* The Dialog component has some utility methods as alternatives to the browser's | ||
* built-in dialogs: | ||
* | ||
* - \`Dialog.alert\` for [\`window.alert\`][1] | ||
* - \`Dialog.confirm\` for [\`window.confirm\`][2] | ||
* - \`Dialog.prompt\` for [\`window.prompt\`][3] | ||
* | ||
* They accept the same parameters as their built-in counterparts, but return | ||
* async results and thus do not block the main flow. They are implemented using | ||
* Moai's components. | ||
* | ||
* [1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/alert | ||
* [2]: https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm | ||
* [3]: https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt | ||
*/ | ||
export const Utilities = (): JSX.Element => ( | ||
<Button | ||
onClick={async () => { | ||
const name = await Dialog.prompt("What's your name?"); | ||
const yes = await Dialog.confirm(`Is your name: "${name}"?`); | ||
Dialog.alert(yes ? `Hello ${name}!` : "But you said so!"); | ||
}} | ||
children="Ask name" | ||
/> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { Meta } from "@storybook/react"; | ||
import { SVGAttributes } from "react"; | ||
import { FaHome } from "react-icons/fa"; | ||
import { Icon, text } from "../../../core/src"; | ||
import { GalleryIcon } from "../../../gallery/src"; | ||
import { Utils } from "../utils/utils"; | ||
|
||
const meta: Meta = { | ||
title: "Components/Icon", | ||
component: Icon, | ||
argTypes: { | ||
display: Utils.arg(["block", "inline"]), | ||
component: Utils.arg(null), | ||
size: Utils.arg("number"), | ||
}, | ||
}; | ||
|
||
Utils.page.component(meta, { | ||
primary: "sticky", | ||
shots: [<GalleryIcon key="1" />], | ||
}); | ||
|
||
export default meta; | ||
|
||
interface Props { | ||
display?: "block" | "inline"; | ||
size?: number; | ||
} | ||
|
||
export const Primary = (props: Props): JSX.Element => ( | ||
<div> | ||
<span>Some text </span> | ||
<Icon | ||
display={props.display ?? "block"} | ||
component={FaHome} | ||
size={props.size} | ||
/> | ||
<span> another text</span> | ||
</div> | ||
); | ||
|
||
/** | ||
* The recommended way to use the Icon component is to import an icon from the | ||
* [react-icons][1] package and pass it to the \`icon\` prop. All icons in the | ||
* package can be used with Moai out of the box. | ||
* | ||
* ~~~ts | ||
* | ||
* import { FaHome } from "react-icons/fa"; | ||
* ~~~ | ||
* | ||
* [1]: https://react-icons.github.io/react-icons/ | ||
*/ | ||
export const Basic = (): JSX.Element => <Icon component={FaHome} />; | ||
|
||
/** | ||
* To make layout more predictable, icons are rendered as [block elements][1] by | ||
* default. However, for icons that are parts of texts, they should be rendered as | ||
* [inline elements][2]. This is done by setting the \`display\` prop to | ||
* \`inline\`. | ||
* | ||
* [1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements | ||
* [2]: https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements | ||
*/ | ||
export const Display = (): JSX.Element => ( | ||
<p> | ||
<span>Some text </span> | ||
<Icon display="inline" component={FaHome} /> | ||
<span> another text</span> | ||
</p> | ||
); | ||
|
||
/** | ||
* SVG icons usually use the [\`currentcolor\`][1] keyword for their colors. This | ||
* means to set the color of an icon, you should set the [text color][2] of its | ||
* container. Moai has a [\`text\`][3] utility that provides predefined, | ||
* accessible colors for icons. | ||
* | ||
* [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword | ||
* [2]: https://developer.mozilla.org/en-US/docs/Web/CSS/color | ||
* [3]: /docs/patterns-color-text--page | ||
*/ | ||
export const Color = (): JSX.Element => ( | ||
<div className={text.highlightWeak}> | ||
<Icon component={FaHome} /> | ||
</div> | ||
); | ||
|
||
/** | ||
* Technically, the \`component\` prop expects a [function component][1] that | ||
* returns an SVG element. You can use it to display your own custom icons (e.g. | ||
* logos, product icons). With tools like [React SVGR][3], you can even create | ||
* your own icon sets to use with Moai. See the [Advanced section][2] in the Icon | ||
* Pattern guide for detail. | ||
* | ||
* [1]: https://reactjs.org/docs/components-and-props.html#function-and-class-components | ||
* [2]: /docs/patterns-icon--advanced | ||
* [3]: https://react-svgr.com | ||
*/ | ||
export const Advanced = (): JSX.Element => { | ||
// In practice, this should be defined outside of your component, or even | ||
// better, automatically generated by a tool like react-svgr. | ||
const Line = (props: SVGAttributes<SVGElement>) => ( | ||
<svg width="1em" height="1em" viewBox="0 0 48 1" {...props}> | ||
{/* This is just a horizontal line */} | ||
<path d="M0 0h48v1H0z" fill="currentColor" fillRule="evenodd" /> | ||
</svg> | ||
); | ||
return <Icon component={Line} />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { Meta } from "@storybook/react"; | ||
import * as Fm from "formik"; | ||
import { useState } from "react"; | ||
import { HiPhone } from "react-icons/hi"; | ||
import { Button, Dialog, DivPx, Input } from "../../../core/src"; | ||
import { GalleryInput1, GalleryInput2 } from "../../../gallery/src"; | ||
import { Utils } from "../utils/utils"; | ||
|
||
const meta: Meta = { | ||
title: "Components/Input", | ||
component: Input, | ||
argTypes: { | ||
type: Utils.arg("string", "Visual"), | ||
style: Utils.arg(Input.styles, "Visual"), | ||
size: Utils.arg(Input.sizes, "Visual"), | ||
icon: Utils.arg(null, "Visual"), | ||
list: Utils.arg(null, "Visual"), | ||
defaultValue: Utils.arg(null, "State"), | ||
value: Utils.arg(null, "State"), | ||
setValue: Utils.arg(null, "State"), | ||
onChange: Utils.arg(null, "State"), | ||
}, | ||
}; | ||
|
||
Utils.page.component(meta, { | ||
primary: "sticky", | ||
shots: [<GalleryInput1 key="1" />, <GalleryInput2 key="2" />], | ||
}); | ||
|
||
export default meta; | ||
|
||
interface Props { | ||
type?: string; | ||
style?: string; | ||
size?: string; | ||
maxLength?: number; | ||
disabled?: boolean; | ||
readOnly?: boolean; | ||
} | ||
|
||
export const Primary = (props: Props): JSX.Element => ( | ||
<div style={{ width: 200 }}> | ||
<Input | ||
type={props.type} | ||
// eslint-disable-next-line | ||
style={(Input.styles as any)[props.style!]} | ||
// eslint-disable-next-line | ||
size={(Input.sizes as any)[props.size!]} | ||
maxLength={props.maxLength} | ||
disabled={props.disabled} | ||
readOnly={props.readOnly} | ||
aria-label="Default input" | ||
/> | ||
</div> | ||
); | ||
|
||
/** | ||
* Input should be used as a [controlled][1] component. You should have a | ||
* [state][2] to store the text value, and give its control to an Input via its | ||
* \`value\` and \`setValue\` props. At the moment, these props work with | ||
* \`string\` values only. | ||
* | ||
* To have good accessibility, ensure that your inputs have their matching labels. | ||
* You can do it in many ways: wrap the input inside a \`label\`, or explicitly | ||
* [link][3] it to one (like in the example below), or via the \`aria-label\` and | ||
* \`aria-labelledby\` props. | ||
* | ||
* Note that Moai's inputs don't have the [confusing][4] default width. Instead, | ||
* inputs always fill 100% of their container width. This means you should control | ||
* the width of an input via its container. | ||
* | ||
* [1]: https://reactjs.org/docs/forms.html#controlled-components | ||
* [2]: https://reactjs.org/docs/hooks-state.html | ||
* [3]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label#attr-for | ||
* [4]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-size | ||
*/ | ||
export const Basic = (): JSX.Element => { | ||
const [text, setText] = useState<string>("Hello"); | ||
return ( | ||
<div style={{ width: 200 }}> | ||
<label htmlFor="basic-input">Basic example</label> | ||
<Input id="basic-input" value={text} setValue={setText} /> | ||
</div> | ||
); | ||
}; | ||
|
||
/** | ||
* Input follows the [standard approach][1] to support suggestion. You should | ||
* define your suggestion as a \`datalist\` element, then give its \`id\` to an | ||
* input via the \`list\` prop. | ||
* | ||
* As a convenient shortcut, you can also define your suggestion directly via the | ||
* \`list\` prop and Input will create the \`datalist\` element for you. You'll | ||
* still need an explicit \`id\` for the list: | ||
* | ||
* [1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist | ||
*/ | ||
export const Suggestion = (): JSX.Element => ( | ||
<div style={{ width: 200 }}> | ||
<Input | ||
aria-label="suggestion-input" | ||
list={{ id: "suggestion-list", values: ["red", "green", "blue"] }} | ||
/> | ||
</div> | ||
); | ||
|
||
/** | ||
* Input supports both [controlled][1] and [uncontrolled][2] usages, making it | ||
* easy to use them with form builders like [Formik][3] and [React Hook Form][4], | ||
* right out of the box. See our [Form guide][5] to learn more. | ||
* | ||
* [1]: https://reactjs.org/docs/forms.html#controlled-components | ||
* [2]: https://reactjs.org/docs/uncontrolled-components.html | ||
* [3]: https://formik.org | ||
* [4]: https://react-hook-form.com | ||
* [5]: /docs/guides-icons--primary | ||
*/ | ||
export const Form = (): JSX.Element => ( | ||
// "Fm" is the Formik's namespace | ||
<Fm.Formik | ||
initialValues={{ email: "" }} | ||
onSubmit={(values) => Dialog.alert(values.email)} | ||
> | ||
<Fm.Form style={{ width: 200 }}> | ||
<label htmlFor="form-email">Email</label> | ||
<Fm.Field id="form-email" type="email" name="email" as={Input} /> | ||
<DivPx size={8} /> | ||
<Button type="submit" highlight children="Submit" /> | ||
</Fm.Form> | ||
</Fm.Formik> | ||
); | ||
|
||
/** | ||
* An input can have an icon defined via the \`icon\` prop. This follows our [Icon | ||
* standard][1], which supports all SVG icons. See the [Icon guide][1] to learn | ||
* more. | ||
* | ||
* [1]: /docs/guides-icons--primary | ||
*/ | ||
export const Icon = (): JSX.Element => ( | ||
// The icon is imported from the "react-icons" external library, like | ||
// import { HiPhone } from "react-icons/hi"; | ||
<div style={{ width: 200 }}> | ||
<Input | ||
icon={HiPhone} | ||
placeholder="(888) 000-9999" | ||
aria-label="Enter phone" | ||
/> | ||
</div> | ||
); |
Oops, something went wrong.