Skip to content

Commit

Permalink
migrate components
Browse files Browse the repository at this point in the history
  • Loading branch information
NgocNhi123 committed Jun 30, 2024
1 parent 26a3a68 commit 4c4b0e3
Show file tree
Hide file tree
Showing 15 changed files with 1,075 additions and 9 deletions.
103 changes: 103 additions & 0 deletions new-docs/src/components/dialog.stories.tsx
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"
/>
);
110 changes: 110 additions & 0 deletions new-docs/src/components/icon.stories.tsx
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} />;
};
150 changes: 150 additions & 0 deletions new-docs/src/components/input.stories.tsx
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>
);
Loading

0 comments on commit 4c4b0e3

Please sign in to comment.