diff --git a/docs/getting-started-with-react/getting-started-with-react.stories.mdx b/docs/getting-started-with-react/getting-started-with-react.stories.mdx index 2256f5bda7..37814cd79e 100644 --- a/docs/getting-started-with-react/getting-started-with-react.stories.mdx +++ b/docs/getting-started-with-react/getting-started-with-react.stories.mdx @@ -89,7 +89,7 @@ For this example we are going to build a card that allows you to enter a topic and then search for a related gif. - + {() => { const [topic, setTopic] = useState(); const [url, setURL] = useState(); @@ -183,3 +183,43 @@ The final part of our example defines the custom actions needed to power our component. In this case the action when the user clicks on the **Search** button. Here we call the `setURL` setter to show the `loadingGIF`. Then, we call `setURL` awaiting the async response of `getGIF`. + +## Types of Components in Atlantis and their Ability for Customization + +#### 1. Primitive Components: + +Examples: [Icon](..?path=/docs/components-images-and-icons-icon--docs), +[Avatar](..?path=/docs/components-images-and-icons-avatar--docs) + +These components do not accept children and have minimal customization. + +#### 2. Simple Components: + +Examples: [Button](..?path=/docs/components-actions-button--docs), +[Link](..?path=/docs/components-text-and-typography-link--docs) + +These allow basic customization through props or limited children, like +`ReactNode` or specific child types. + +#### 3. Compound Components: + +Examples: [Chip](..?path=/docs/components-selections-chip--docs), +[DataList](..?path=/docs/components-lists-and-tables-datalist--docs) + +These are part of larger compositions and may validate their children types to +maintain internal consistency. + +#### 4. Strict Complex Components: + +Example: +[Autocomplete](..?path=/docs/components-forms-and-inputs-autocomplete--docs), + +These have multiple UI pieces they are built out of but a strict APIs and +limited customization. + +#### 5. Customizable Complex Components: + +Example: [Combobox](..?path=/docs/components-selections-combobox--docs) + +These also have multiple UI pieces they are built out of but offer options for +customization. diff --git a/docs/guides/create-composable-components.stories.mdx b/docs/guides/create-composable-components.stories.mdx deleted file mode 100644 index bb4380931a..0000000000 --- a/docs/guides/create-composable-components.stories.mdx +++ /dev/null @@ -1,187 +0,0 @@ -import { Meta } from "@storybook/addon-docs"; - - - -# Create Composable Components - -### Why Composability Matters - -**Feedback from Atlantis consumers has highlighted that**: - -- Developers want to make minor modifications without requesting feature - additions. -- Designers need more flexibility to tailor components to specific use cases. -- Current rigidity leads to workarounds, custom builds, and decreased adoption - of Atlantis. - -**Key Goals**: - -- Enhance flexibility while maintaining consistency. -- Empower teams to build their own variations using reusable building blocks. - -### Types of Components and Their Customization Options - -1. **Primitive Components**: - Examples: [Icon](..?path=/docs/components-images-and-icons-icon--docs), - [Avatar](..?path=/docs/components-images-and-icons-avatar--docs) - - These components do not accept children and have minimal customization. - -2. **Simple Components**: - Examples: [Button](..?path=/docs/components-actions-button--docs), - [Link](..?path=/docs/components-text-and-typography-link--docs) - - These allow basic customization through props or limited children, like - `ReactNode` or specific child types. - -3. **Compound Components**: - Examples: [Chip](..?path=/docs/components-selections-chip--docs), - [DataList](..?path=/docs/components-lists-and-tables-datalist--docs) - - These are part of larger compositions and may validate their children types - to maintain internal consistency. - -4. **Complex Components**: - Examples: - [Autocomplete](..?path=/docs/components-forms-and-inputs-autocomplete--docs), - [Combobox](..?path=/docs/components-selections-combobox--docs) - - These often have strict APIs, with customization limited to specific props. - Internal UI and behaviors are not easily modifiable. - -## Path Forward: Favoring a Custom Render Function - -To address these challenges, we are adopting a **custom render function** -approach as a preferred pattern for composability. - -#### Named Render Prop: `customRender____` - -This works by providing a named render prop (e.g., `customRenderItem`) that -allows consumers to inject their own UI while preserving default behaviors. For -example: - -```tsx -const renderProductItem = item => ( - - {item.price} - {item.name} - -); - -export const CustomRenderExample = () => ( - -); -``` - -**Why This Approach?** - -- **Clarity**: The `customRender` prefix clearly communicates the desire to do - advanced customization. -- **Flexibility**: Supports both simple and complex components without major - refactoring. -- **Trackability**: Easy to search for and monitor across codebases. - -### When a Custom Render Function Might Not Be the Right Solution - -While custom render functions are powerful, there are cases where they may not -be the optimal choice. - -#### Example: `Tab` Component (see [Tabs](..?path=/docs/components-navigation-tabs--docs)) - -Instead of introducing `customRenderLabel`, the `Tab` component was updated to -allow the `label` prop to accept a `ReactNode`. - -```tsx -} /> -``` - -This approach was chosen for the following reasons: - -1. **Incremental Improvement**: Extending the `label` prop type was a simpler - change that still met customization needs. -2. **No Internal State Exposure**: Customizing the `label` didn’t require - exposing or interacting with the `Tab`’s internal state (e.g., active state - styling). -3. **Clarity of Purpose**: The `children` prop is already used for other - purposes, and adding a render function could create unnecessary complexity. - -### Considerations When Deciding on a Custom Render Function - -When deciding whether to use a custom render function, consider the following -factors: - -1. **Internal State Requirements**: - - - If customization depends on internal state (e.g., active status), a custom - render function might be necessary. - -2. **Atomic vs. Compound Components**: - - - For atomic components like `Tab`, small changes like accepting `ReactNode` - might suffice. - - For compound components with multiple customizable parts, render functions - provide clearer APIs. - -3. **Existing API and Props**: - - - Avoid overloading components with too many customization methods. Leverage - existing props like `children` where appropriate. - -4. **Context and Identity**: - - - Assess what aspects of the component’s structure or behavior are integral - to its identity. For example, the green line under a `Tab` is required and - should not be customizable. - -5. **Collections vs. Single Components**: - - Collections often require opening up data/props for customization, which - can be more challenging to manage. - -### Alternatives to Custom Render Functions - -1. **Using `ReactNode` Props**: - - - For simple cases where customization does not depend on internal state, - extending a prop type (e.g., `label: string | ReactNode`) is an effective - and non-breaking change. - -2. **Compound Componentization**: - - - Breaking larger components into smaller, composable pieces (e.g., - ``) allows for more focused and flexible customization. - These smaller pieces could have custom render props added to them to allow - for more precise customization - -3. **Children as a Function**: - - - In cases where customization aligns with the component’s primary purpose, - the `children` prop can accept a render function to define the component's - content dynamically. - - #### Example: Dynamic Content in a Modal - - ```tsx - - {({ closeModal }) => ( -
-

Custom Modal Content

- -
- )} -
- ``` - - **Why It Works**: - - - `Modal` is a content-focused component, so it makes sense to use `children` - for defining dynamic or custom content. - - The `children` prop aligns with the component's primary purpose: displaying - custom modal content. - - **When Not to Use Render Props via `children`**: - - - If the component has multiple customizable sections, using `children` can - make the API confusing. Named render props like `customRenderMenu` are - better for disambiguation. - - If `children` is already reserved for other purposes (e.g., as content in a - `Tab`), avoid overloading its functionality. diff --git a/docs/guides/customizing-components.stories.mdx b/docs/guides/customizing-components.stories.mdx new file mode 100644 index 0000000000..9af8edd634 --- /dev/null +++ b/docs/guides/customizing-components.stories.mdx @@ -0,0 +1,77 @@ +import { Meta } from "@storybook/addon-docs"; + + + +# Customizing Components + +### Philosophy: Easy to Use, Flexible to Customize + +Atlantis is designed to make building consistent, accessible, and visually +appealing user interfaces as effortless as possible. Our goal is to create +components that work out of the box, requiring minimal setup or configuration +from consumers. + +While we strive to cover the most common design scenarios with thoughtful +defaults, we understand that unique use cases may require customization. For +these scenarios, we provide mechanisms to extend or modify our components in a +way that maintains flexibility without compromising the integrity of the +components. + +We recommend using the default implementations wherever possible to benefit from +consistency, maintainability, and built in accessibility. However, if you choose +to customize, please note that the responsibility for ensuring proper +functionality and cohesion will rest with you. + +Atlantis aims to strike a balance between simplicity, flexibility, and +consistency. + +### Primary Customization Approach: Named Render Props + +In Atlantis, our primary approach to enabling customization is through named +render props, using the `customRender____` pattern. This method allows consumers +to inject their own UI logic while retaining the default behaviors and +functionality of the component. + +#### Example: customRenderItem + +Here's an example of how to use a named render prop to customize the rendering +of items in a [List](..?path=/docs/components-lists-and-tables-list--docs) +component: + +```tsx +const renderProductItem = item => ( + + {item.price} + {item.name} + +); + +export const CustomRenderExample = () => ( + +); +``` + +In this example, the default List behavior (such as the main list structure and +handling item interactions) is preserved. Only the presentation of individual +items is customized. + +### Other Customization Approaches + +While custom render functions are our primary approach it is not used in all +places. For simple cases where customization does not depend on internal state, +we have chosen to extend prop types to allow for a `ReactNode` to be passed in. + +#### Example: Tab Component (see [Tabs](..?path=/docs/components-navigation-tabs--docs)) + +Instead of introducing `customRenderLabel`, the `Tab` component was updated to +allow the `label` prop to accept a `ReactNode`. + +```tsx +} /> +``` + +This approach was chosen to provide a simpler experience for those implementing +Atlantis components. Customizing the `label` didn’t require exposing or +interacting with the `Tab`’s internal state (e.g., active state styling). Once +the design becomes more complex a custom render function would become a better +fit.