diff --git a/.gitignore b/.gitignore index e39312d..69b1d40 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# Remote Content -/docs/**/examples/**/*.md -/docs/**/examples/**/*.png diff --git a/docs/developer-documentation/examples-and-templates/examples/computed-form-fields.md b/docs/developer-documentation/examples-and-templates/examples/computed-form-fields.md new file mode 100644 index 0000000..c07f4cc --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/computed-form-fields.md @@ -0,0 +1,134 @@ +--- +description: Automatically calculates a read-only field in forms +title: Computed Form Fields +id: computed-form-fields +--- + +# Computed Form Fields Example + +This example shows you how to build a form with computed form fields. Computed form fields automatically calculate a value based on a separate read-only field. For example, if a user inputs a quantity, a computed form field can multiply that number by a price to give a total cost. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Computed Form Fields](./src/assets/computed-form-fields.png) + +## Steps + +### 1. Define Constants for Input Fields + +Before building out your form, define constants for each input field. Using constants helps reduce errors and makes your logic more maintainable. + +```jsx +const SPECIES = "species"; +const NUMBER_OF_FISH = "numberOfFish"; +const COMPUTED_PRICE = "computedPrice"; +``` + +In this example, we will build a form with three inputs. The values from the first two inputs will compute the value of `computedPrice`. + +### 2. Initialize the Form Component with State + +Next, we'll define the main form component. To manage the data entered in the form, we initialize it with a `formData` state variable, which is an empty object. This state will dynamically store the values of the form fields as users interact with the form. + +The `setFormData` function allows us to update the state whenever an input changes. This ensures the form data is kept in sync. + +```jsx +const ComputedForm = () => { + const [formData, setFormData] = useState({}); + + return ( + // form JSX will go here + ); +}; +``` + +### 3. Structuring the Form with Inputs + +In the `return` statement of the component, use the Trussworks `Form` component to structure your form. Within the form, include input components such as `TextInput`, referencing the variables defined in Step 1 (e.g. `NUMBER_OF_FISH`) instead of hardcoding strings. This practice helps avoid typos and ensures consistency when accessing the `formData` state. For better organization and styling, wrap your inputs within the `FormGroup` component provided by Trussworks. + +```jsx +return ( +
+ + + +``` + +### 4. Adding Input Handlers for Form Fields + +Use `handleNumberFishChange` and `handleSelectChange` to manage the state for the **Number of Fish** and **Species** fields. These functions update the `formData` state whenever the user interacts with the inputs. + +For the **Number of Fish** input, the `handleNumberFishChange` function captures the input value and updates the corresponding field in the `formData` state. Similarly, the `handleSelectChange` function updates the state when the user selects a species. + +This function handles changes in the **Number of Fish** input field: +```jsx +const handleNumberFishChange = (event, formData) => { + const { value } = event.target; + setFormData({ + ...formData, // Preserve existing form data + [NUMBER_OF_FISH]: value, // Update the "Number of Fish" field + }); +}; +``` + +Use the `handleNumberFishChange` function to manage the **Number of Fish** input: +```jsx + handleNumberFishChange(event, formData)} // Trigger the handler on change +/> +``` + +### 5. Calculating Computed Price + +In addition to updating the input value of `numberOfFish`, we also want to update the value of `computedPrice`. We can do that by adding a helper function to run some logic to compute that value. We then call that helper function in the input's `onChange` handler: + +This maps species to their respective prices: +```jsx +const speciesPriceMap = { + grouper: 25.0, + salmon: 58.0, + marlin: 100.0, + mahimahi: 44.0, +}; +``` + +This function is kept outside the component since it doesn't rely on React state or lifecycle: +```jsx +const computeFieldValue = (numberOfFish, species) => { + let computedPrice = parseInt(numberOfFish || 0) * parseInt(speciesPriceMap[species] || 0); + return computedPrice.toString(); +}; +``` + +This function updates the **Number of Fish** and dynamically computes the "Computed Price": +```jsx +const handleNumberFishChange = (event, formData) => { + const { value } = event.target; + setFormData({ + ...formData, + [NUMBER_OF_FISH]: value, + [COMPUTED_PRICE]: computeFieldValue(value, formData?.species || ""), // Compute and update the "Computed Price" + }); +}; +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/conditional-form-fields.md b/docs/developer-documentation/examples-and-templates/examples/conditional-form-fields.md new file mode 100644 index 0000000..6624b42 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/conditional-form-fields.md @@ -0,0 +1,133 @@ +--- +description: Dynamically show/hide fields based on other inputs +title: Conditional Form Fields +id: conditional-form-fields +--- + +# Conditional Form Fields Example + +This example shows you how to create a dynamic form with conditional fields. The visibility of certain input fields depends on the values of other fields. Specifically, when the `fullName` field is filled out, the `nickname` field becomes visible. If the `fullName` field is empty, the `nickname` field is hidden and removed from the `formState`. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Conditional Form Fields](./src/assets/conditional-form-fields.png) + + +## Steps + +### 1. Define Constants for Input Fields + +Before building out your form, define constants for each input field. Using constants helps reduce errors and makes your logic more maintainable. + +```jsx +const FULL_NAME = "fullName"; +const NICKNAME = "nickname"; +``` + +In this example, we will build a form with two inputs. The value from `FULL_NAME` inputs will control the visiblity of `NICKNAME`. + +### 2. Initialize the Form Component with State + +Next, we'll define the main form component. To manage the data entered in the form, we initialize it with a `formData` state variable, which is an empty object. This state will dynamically store the values of the form fields as users interact with the form. + +The `setFormData` function allows us to update the state whenever an input changes. This ensures the form data is kept in sync. + +```jsx +const ConditionalForm = () => { + const [formData, setFormData] = useState({}); + + return ( + // form JSX will go here + ); +}; +``` + +### 3. Structuring the Form with Inputs + +In the `return` statement of the component, use the Trussworks `Form` component to structure your form. Within the form, include input components such as `TextInput`, referencing the variables defined in Step 1 (e.g., `FULL_NAME`) instead of hardcoding strings. This practice helps avoid typos and ensures consistency when accessing the `formData` state. For better organization and styling, wrap your inputs within the `FormGroup` component provided by Trussworks. + +```jsx +return ( + + + + +``` + +### 4. Adding Input Handlers for Form Fields + +Now we can create an `onChange` handler for each input. This captures the input's value as it is being typed. We'll use `handleFullNameChange` to update state with a copy of the existing `formState`, and update only the field value that we want to update. In this case, the value we want to update is `fullName`. The value of this input will then re-render with the updated value from `formState`. + +This function handles changes in the **Full Name** input field: +```jsx +const handleFullNameChange = (event, formData) => { + const { value } = event.target; + setFormData({ + ...formData, // Preserve existing form data + [FULL_NAME]: value, // Update the "Full Name" field + }); +}; +``` + +Use the `handleFullNameChange` function to manage the **Full Name** input: +```jsx + handleFullNameChange(event, formData)} // Call the handleFullNameChange function to update the "Full Name" field +/> +``` + +### 5. Conditionally Render Nickname Field + +Next, we want to conditionally render the `nickname` component. The condition depends on whether or not the `fullName` value is set in `formState`. This input field will only render if `formData[FULL_NAME]` evaluates to `true`. + +```jsx +{ + formData[FULL_NAME] && ( + <> + + handleNickNameChange(event, formData)} + /> + + ) +} +``` + +### 6. Managing Dependent Field Values + +In this step, we ensure that the value of the **Nickname** field is cleared whenever the **Full Name** field is empty. Do this by adding logic to the `onChange` handler which calls the `handleFullNameChange` function. + +```jsx +const handleFullNameChange = (event, formData) => { + const { value } = event.target; + setFormData({ + ...formData, + [FULL_NAME]: value, + [NICKNAME]: value === "" ? "" : formData[NICKNAME], // Clear the "Nickname" field if "Full Name" is empty + }); +}; +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/field-validators.md b/docs/developer-documentation/examples-and-templates/examples/field-validators.md new file mode 100644 index 0000000..668d9f2 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/field-validators.md @@ -0,0 +1,99 @@ +--- +description: Enforce validation logic, like disallowing numbers +title: Field Validators +id: field-validators +--- + +# Form Field Validators Example + +This example shows you how to enforce field validation logic on certain fields within your form. The validator ensures that no numbers are allowed within the **Full Name** input field. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Field Validators](./src/assets/field-validators.png) + +## Steps + +### 1. Adding Validation with `onBlur` Handlers +In this step, we add validation logic to our form inputs using an `onBlur` handler. This ensures the data is validated when the user navigates away from the input field. It also provides immediate feedback on any validation errors. + +This function validates the input value. It uses the validation rules and updates the state with any validation errors: +```jsx +const handleBlur = (event, validators) => { + const { name, value } = event.target; + setValidationErrors((prev) => ({ + ...prev, + ...handleInputValidationLogic(name, value, validators), + })); +}; +``` + +This helper function loops through the provided validators and checks if the input value passes the validation criteria. If not, it returns the appropriate error message: +```jsx +const handleInputValidationLogic = (name, value, validators) => { + if (validators && validators.length > 0) { + for (let validator of validators) { + if (!validator.test(value)) { + return { [name]: validator.message }; + } + } + } + return { [name]: null }; // Clear error if value is valid +}; +``` + +Here’s how to use the onBlur handler in the TextInput component. The input dynamically sets its validation status and ARIA attributes based on validation errors: +```jsx + handleBlur(e, fullNameValidators)} +/> +``` + +### 2. Testing with Validator +We include the validator needed to check the contents of the field input. In this case `fullNameValidators` has a single test that checks if the input contains a number. This array can include several tests for more complex validation logic. They are each included as separate objects within each validator array. + +```jsx +// utilities/fieldValidators.js +const fullNameValidators = [ + { + test: (value) => !/\d/.test(value) || value === "", + message: "Full Name should not contain numbers.", + }, +]; + +export { fullNameValidators }; + +// in Form.js +import { fullNameValidators } from "../utilities/fieldValidators"; +``` + +### 3. Trigger Validation Logic and Render Error Message +We can update the properties on our input so the validation logic gets triggered when a user navigates away from the form field. We also conditionally render an `ErrorMessage` when there is an error present. + +```jsx + handleBlur(e, fullNameValidators)} +/> +{ + validationErrors[FULL_NAME] && {validationErrors[FULL_NAME]} +} +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/form-structure.md b/docs/developer-documentation/examples-and-templates/examples/form-structure.md new file mode 100644 index 0000000..80fcefd --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/form-structure.md @@ -0,0 +1,125 @@ +--- +description: Build complex forms using Trussworks core components +title: Form Structure +id: form-structure +--- + +# Form Structure Example + +This example shows you how to build a non-trivial form. It uses core components provided by [Trussworks react-uswds](https://github.com/trussworks/react-uswds). It is __not__ intended to show a full form implementation, such as sending a request on submit. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Form Structure](./src/assets/form-structure.png) + +## Steps + +### 1. Define the Page Structure + +Use Trussworks `Grid` components to define the layout of your page in `src/pages/Home.jsx`. This approach helps organize content while maintaining a clean and responsive design. + +#### Page structure with a single column: +```jsx + + + +

+ Private Recreational Tilefish +

+
+
+
+``` + +#### Aligning two inputs in a single row: +To place two input components side by side, use the `Grid` components. The `gap` property adds spacing between columns. + +```jsx + + + + + + + + +``` + +### 4. Display `FormData` Values on `Submit` and Reset the Form + +When the form is submitted, the values of all fields are displayed, and the form is reset. This ensures that users receive feedback on their input. And, the form state is cleared for a new submission. + +```jsx +const handleSubmit = (event) => { + event.preventDefault(); + const formData = new FormData(event.target); + const values = {}; + let alertString = ''; + + for (const [key, value] of formData.entries()) { + values[key] = value; + alertString += `${key}: ${value}\n`; + } + + window.alert(alertString); + // Reset for after triggering Submit + event.target.reset(); + // Set focus on first input after form is submitted. + setResetToggle(true); +}; +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/mock-api.md b/docs/developer-documentation/examples-and-templates/examples/mock-api.md new file mode 100644 index 0000000..8de5698 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/mock-api.md @@ -0,0 +1,214 @@ +--- +description: Mock API for frontend development with MSW and fetch +title: Mock API +id: mock-api +--- + +# Mock API Example + +This example shows you how to use [Mock Service Worker](https://mswjs.io/) and the native `fetch` API. It builds out a mock API on the frontend to consume data without needing to rely on a backend system. This allows developers to use mock endpoints for testing while backend APIs are developed. Later, mock endpoints can easily be swapped out for production endpoints. + +This example does __not__ include any backend persistence via IndexedDB. This is out of scope for this example. Instead, this example provides two simplified endpoints to call: + +- `[GET] /species` returns a list of 4 species. +- `[POST] /species` returns the original list, with an additional species added (this is hard coded for simplicity). + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Mock API](./src/assets/mock-api.png) + +## Steps + +### 1. Enable Mocking in `index.jsx` + +In the `index.jsx` file, enable mocking from the mock service worker. This ensures that your mock API is being called while in development. + +```jsx +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./styles/theme.css"; +import App from "./App"; + +// Enable the mock service worker +async function enableMocking() { + const { worker } = await import("./mocks/browser"); + const onUnhandledRequest = "bypass"; // Ignore unhandled requests to prevent errors in development + + if (import.meta.env.MODE === "development") { + return worker.start({ + onUnhandledRequest, + serviceWorker: { + url: `/mockServiceWorker.js`, // Path to the mock service worker + }, + }); + } + + // For non-development environments, fallback to a different service worker + return worker.start({ + onUnhandledRequest, + serviceWorker: { + url: `/service-worker.js`, + }, + }); +} + +const root = ReactDOM.createRoot(document.getElementById("root")); + +enableMocking().then(() => { + root.render( + + + , + ); +}); +``` + +**Key Points:** + +- Ensure the `mockServiceWorker.js` file is present in the `src` directory. +- This setup dynamically starts the service worker based on the environment. + +### 2. Create the Mocks Directory + +1. Create a `mocks` directory in `src`. +2. Create `browser.js` and `handlers.js` files. + +### 3. Define Mock Endpoints in `browser.js` + +The `browser.js` file will import the endpoints configured in the `handlers.js` file. It wraps them in a higher order function from the `msw/browser` library. This sets up mock endpoints and intercepts them when getting called from the client: + +```js +// src/mocks/browser.js +import { setupWorker } from "msw/browser"; +import { handlers } from "./handlers"; + +// Configure the mock service worker with the handlers +export const worker = setupWorker(...handlers); +``` + +**Explanation:** + +- `setupWorker` initializes the service worker. +- Spread the `handlers` array to register mock endpoints. + +### 4. Configure Endpoints in `handlers.js` + +You can setup any endpoints that you want to mock within `handlers.js`. Be sure to export these handlers so that they can be imported into `browser.js` appropriately: + +```js +import { http, HttpResponse } from "msw"; + +export const MSW_ENDPOINT = { + SPECIES: "/species", // Endpoint for fetching fish species +}; + +export const species = [ + { name: "grouper", price: 25.0, src: "./sample-img.jpg" }, + { name: "salmon", price: 58.0, src: "./sample-img.jpg" }, + { name: "marlin", price: 100.0, src: "./sample-img.jpg" }, + { name: "mahimahi", price: 44.0, src: "./sample-img.jpg" }, +]; + +// Mock API handlers +export const handlers = [ + // GET endpoint: Fetch list of species + // This implementation can/should change depending on your needs + http.get(MSW_ENDPOINT.SPECIES, () => { + return HttpResponse.json( + { + data: species, + }, + { status: 200 }, + ); + }), + + // POST endpoint: Add a new species + // In a full stack implementation, there will likely be some logic on the server to handle/store persistent data + http.post(MSW_ENDPOINT.SPECIES, async ({ request }) => { + const requestData = await request.json(); + const response = [requestData.data, ...species]; + + return HttpResponse.json({ data: response }, { status: 201 }); + }), +]; +``` + +### 5. Access Mock Endpoints with Fetch + +You can now use these endpoints in your application by making HTTP requests with the native `fetch` API. + +**GET Request Example** + +Fetch the list of fish species: + +```jsx +import { MSW_ENDPOINT } from "./mocks/handlers"; + +const getRequestWithFetch = async (endpoint) => { + try { + const response = await fetch(`${endpoint}`, { + headers: { + "X-Access-Token": "your-access-token", + }, + }); + + if (!response.ok) { + const error = await response.json(); + return error; + } + + const data = await response.json(); + return data; + } catch (err) { + const error = `[GET]: Error fetching data: ${err}`; + return error; + } +}; + +// [GET] /species +const { data } = await getRequestWithFetch(MSW_ENDPOINT.SPECIES); +``` + +**POST Request Example** + +Add a new species to the mock database: + +```jsx +const postRequestWithFetch = async (endpoint, submittedData) => { + try { + const response = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Access-Token": "your-access-token", + }, + body: JSON.stringify({ + ...submittedData, + }), + }); + + if (!response.ok) { + const error = await response.json(); + return error; + } + + const data = await response.json(); + return data; + } catch (err) { + const error = `[GET]: Error fetching data: ${err}`; + return error; + } +}; + +// [POST] /species +const { data } = await postRequestWithFetch(MSW_ENDPOINT.SPECIES, { + data: { + name: "tuna", + price: 75, + src: "./sample-img.jpg", + }, +}); +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/multistep-form.md b/docs/developer-documentation/examples-and-templates/examples/multistep-form.md new file mode 100644 index 0000000..f006283 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/multistep-form.md @@ -0,0 +1,247 @@ +--- +description: Multi-step form saving progress and inputs locally +title: Multistep Form +id: multistep-form +--- + +# Multistep Form Example + +This example demonstrates how to create a multi-step form with persistent state using RADFish's storage system. Each step in the form is encapsulated within its own `FormGroup`. The form tracks the user's progress through RADFish's Application and Collection patterns. + +## Key RADFish Concepts + +- **Application Instance**: Configured in `index.jsx` with stores and collections +- **Collections**: Provide structured data storage with schema validation +- **Step-based Persistence**: Form data is saved when navigating between steps +- **Session Resumption**: Users return to their last step with all data intact + +The form uses RADFish's IndexedDB storage to persist both the current step and all form data. When users revisit the form, they see their previously entered data and resume at their saved step. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Multistep Form](./src/assets/multistep.png) + +## Steps + +### 1. Define the Total Number of Steps +Declare the total number of steps for your multi-step form using a constant variable. This allows you to control the flow and logic of the form. + +```jsx +const TOTAL_STEPS = 2; +``` + +This declaration ensures the form knows how many steps are included. This helps manage navigation between steps. + +### 2. Configure the Application Schema +In `index.jsx`, define the schema for your form data. This schema validates and structures your data: + +```jsx +const app = new Application({ + stores: { + formData: { + connector: new IndexedDBConnector("multistep-form-app"), + collections: { + formData: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + fullName: { type: "string" }, + email: { type: "string" }, + city: { type: "string" }, + state: { type: "string" }, + zipcode: { type: "string" }, + currentStep: { type: "number" }, + totalSteps: { type: "number" }, + submitted: { type: "boolean" }, + }, + }, + }, + }, + }, + }, +}); +``` + +Key schema concepts: +- **primaryKey**: The `id` field uniquely identifies each form +- **Field types**: Define the expected data type for validation +- **Form state fields**: `currentStep`, `totalSteps`, and `submitted` track form progress + +### 3. Access the RADFish Collection + +First, in `Home.jsx`, access the formData collection using the RADFish Application pattern: + +```jsx +// Get the application instance and access the formData collection +const application = useApplication(); +const formDataCollection = application.stores.formData.getCollection("formData"); +``` + +### 4. Initialize Multi-Step Form + +Create a new form entry with a unique ID for persistence: + +```jsx +const handleInit = async () => { + const newForm = { + id: crypto.randomUUID(), + currentStep: 1, + totalSteps: TOTAL_STEPS, + submitted: false, + }; + await formDataCollection.create(newForm); + setFormData(newForm); + navigate(`/${newForm.id}`); // Navigate to the form using its unique ID +}; +``` +This function: +- Creates a new form with a unique `id` using `crypto.randomUUID()` +- Initializes the form at step 1 with the total number of steps +- Uses the collection's `create()` method to persist the form +- Navigates to the form's unique URL + +### 5. Create Navigation Helper Functions +Define helper functions to navigate between steps. These functions save all form data when moving between steps, ensuring data persistence without saving on every keystroke: + +```jsx +// Navigate to next step and persist all form data +const stepForward = async () => { + if (formData.currentStep < TOTAL_STEPS && id) { + const nextStep = formData.currentStep + 1; + const updatedData = { ...formData, currentStep: nextStep }; + setFormData(updatedData); + + // Save form data when navigating to next step + try { + await formDataCollection.update({ id: id, ...updatedData }); + } catch (error) { + console.error("Failed to save form progress:", error); + } + } +}; + +// Navigate to previous step and persist all form data +const stepBackward = async () => { + if (formData.currentStep > 1 && id) { + const prevStep = formData.currentStep - 1; + const updatedData = { ...formData, currentStep: prevStep }; + setFormData(updatedData); + + // Save form data when navigating to previous step + try { + await formDataCollection.update({ id: id, ...updatedData }); + } catch (error) { + console.error("Failed to save form progress:", error); + } + } +}; +``` + +These functions: +- Update the current step in the form state +- Save all form data (including user inputs) when navigating +- Ensure data persists across sessions without excessive database writes +- Handle errors gracefully if the save operation fails + +### 6. Load Form Data from the URL Parameter +Subscribe to the `id` parameter in the URL to load the correct form data: + +```jsx +const { id } = useParams(); + +// Load existing form data when navigating to a form by ID +useEffect(() => { + const loadData = async () => { + if (id) { + const [found] = await formDataCollection.find({ + id: id, // Query the collection using the form ID + }); + + if (found) { + setFormData({ ...found, id: id, totalSteps: TOTAL_STEPS }); // Load the data into state + } else { + navigate("/"); // Redirect to the root if no data is found + } + } + }; + loadData(); +}, [id]); +``` +This ensures: + +- The form resumes at the correct step when the user revisits the page +- Invalid or missing IDs redirect users to start a new form +- All previously entered data is restored + + +### 7. Handle Form Input Changes +Implement a simple change handler to update form state as users type: + +```jsx +// Update form data in state as user types +const handleChange = (event) => { + const { name, value } = event.target; + setFormData((prev) => ({ ...prev, [name]: value })); +}; +``` + +Note: Form data is persisted when users navigate between steps or submit the form, not on every keystroke. This approach reduces database writes while still ensuring data persistence. + +### 8. Render the Current Step Dynamically +Use the `formData.currentStep` value to conditionally render the correct form step. This allows you to show only the relevant inputs for the current step while keeping the form flexible. + +```jsx +{ + formData.currentStep === 1 && ( + + + + + + + + + + + + ) +} +``` +**Key Points:** + +- `stepForward` moves to the next step, while `stepBackward` goes to the previous step. +- The "Prev Step" button is disabled on the first step to prevent invalid navigation. +- The form dynamically updates based on the `currentStep` value. + diff --git a/docs/developer-documentation/examples-and-templates/examples/network-status.md b/docs/developer-documentation/examples-and-templates/examples/network-status.md new file mode 100644 index 0000000..bd7bde9 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/network-status.md @@ -0,0 +1,48 @@ +--- +description: Detect network connectivity and display offline alerts +title: Network Status +id: network-status +--- + +# Network Status Example + +This example demonstrates how to detect a user's network connectivity. If the user is offline, the built-in status indicator will display `Offline ❌`. If online, it will show `Online ✅`. + +The `useOfflineStatus` hook provides an `isOffline` utility that detects the user's network connectivity. It uses the `navigator` browser API. Refer to the [MDN Navigator Docs](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) for limitations. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview + +This example will render as shown in this screenshot: + +![Network Status](./src/assets/network-status.png) + +## Steps + +### 1. Import Required Dependencies + +Import the following libraries in the `App.jsx` file: + +```jsx +import React, { useEffect } from "react"; +import { useOfflineStatus } from "@nmfs-radfish/react-radfish"; +import { Alert } from "@trussworks/react-uswds"; +``` + +### 2. Use `useOfflineStatus` to Access Network State + +Within the `HomePage` component, use `useOfflineStatus` to retrieve the `isOffline` property, which indicates whether the application is currently offline: + +```jsx +const HomePage = () => { + const { isOffline } = useOfflineStatus(); // Retrieve the isOffline state + + return ( +
+

Network Status Example

+

Network Status: {isOffline ? "Offline ❌" : "Online ✅"}

+
+ ); +}; +``` diff --git a/docs/developer-documentation/examples-and-templates/examples/on-device-storage.md b/docs/developer-documentation/examples-and-templates/examples/on-device-storage.md new file mode 100644 index 0000000..c3094bc --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/on-device-storage.md @@ -0,0 +1,177 @@ +--- +description: Use IndexedDB/Dexie.js for offline or local storage +title: On Device Storage +id: on-device-storage +--- + +# On-Device Storage Example + +This example demonstrates how to use RADFish's storage system with IndexedDB for on-device data persistence. It showcases the Application and Collection patterns for managing structured data with schema validation. + +## Key RADFish Concepts + +- **Application Instance**: Configured with stores and collections +- **Schema-based Storage**: Type-safe data validation +- **Collection API**: Consistent CRUD operations +- **IndexedDB Integration**: Automatic persistence with offline support + +Use cases include: +- Offline on-device data storage (most cases) +- Local non-relational database (online or offline use) +- Form data persistence across sessions +- Structured data with validation + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![On-Device Storage](./src/assets/on-device-storage.png) + +## Steps + +### 1. Configure RADFish Application with Schema +In the `index.jsx` file, define your Application with stores and schemas: + +```jsx +import { Application } from "@nmfs-radfish/radfish"; +import { IndexedDBConnector } from "@nmfs-radfish/radfish/storage"; + +const app = new Application({ + stores: { + fishingData: { + connector: new IndexedDBConnector("on-device-storage-app"), + collections: { + formData: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + fullName: { type: "string" }, + email: { type: "string" }, + phoneNumber: { type: "string" }, + numberOfFish: { type: "number" }, + species: { type: "string" }, + computedPrice: { type: "number" }, + isDraft: { type: "boolean" }, + }, + }, + }, + species: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + name: { type: "string" }, + price: { type: "number" }, + }, + }, + }, + }, + }, + }, +}); +``` + +Key schema concepts: +- **Primary Key**: Each collection must have a field marked as `primaryKey: true` +- **Field Types**: Ensures data validation (string, number, boolean) +- **Multiple Collections**: Organize related data in separate collections + +### 2. Initialize and Provide the Application Instance +The Application waits for stores to be ready before rendering: + +```jsx +const root = ReactDOM.createRoot(document.getElementById("root")); + +app.on("ready", () => { + root.render( + + + + + , + ); +}); +``` + +The `App` component wraps children with the `Application` context provider: + +```jsx +const App = ({ application }) => { + return ( + +
+ + + } /> + + +
+
+ ); +}; +``` + +## Using Collections in Components + +### Access Collections with `useApplication` Hook + +In any component within the Application context, access collections using the `useApplication` hook: + +```jsx +import { useApplication } from "@nmfs-radfish/react-radfish"; + +const HomePage = () => { + const application = useApplication(); + const formDataCollection = application.stores.fishingData.getCollection("formData"); + + // Use collection methods... +}; +``` + +### Collection API Methods + +Collections provide the following methods for data operations: + +#### Create +```jsx +const newData = { + id: crypto.randomUUID(), + fullName: "John Doe", + species: "Tuna", + numberOfFish: 3, + computedPrice: 150, + isDraft: false +}; + +await formDataCollection.create(newData); +``` + +#### Find +```jsx +// Find all records +const allData = await formDataCollection.find(); + +// Find with criteria +const draftRecords = await formDataCollection.find({ isDraft: true }); +``` + +#### Update +```jsx +const updatedData = { + id: "existing-id", + fullName: "Jane Doe", + numberOfFish: 5, + // ... other fields +}; + +await formDataCollection.update(updatedData); +``` + +#### Delete +```jsx +// Delete by ID +await formDataCollection.delete({ id: "record-id" }); +``` + +All methods are type-safe and validate data against the schema defined in your Application configuration. + diff --git a/docs/developer-documentation/examples-and-templates/examples/persisted-form.md b/docs/developer-documentation/examples-and-templates/examples/persisted-form.md new file mode 100644 index 0000000..665b17b --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/persisted-form.md @@ -0,0 +1,150 @@ +--- +description: Save and persist form data locally with FormWrapper +title: Persisted Form +id: persisted-form +--- + +# Persisted Form Example + +This example demonstrates how to create a persistent form using RADFish's Application and Collection patterns. The form automatically saves data to IndexedDB with schema validation, allowing users to return to their form at any time. + +## Key RADFish Concepts + +- **Application Instance**: Configured with stores and collections for structured data management +- **Schema Validation**: Type-safe form fields with automatic validation +- **Collection API**: Consistent CRUD operations for form data persistence +- **URL-based Loading**: Forms can be bookmarked and shared via unique URLs + +Use cases include: +- Contact forms that save progress automatically +- Survey forms with complex validation requirements +- Data entry forms that need offline persistence +- Any form where data loss prevention is critical + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Preview +This example will render as shown in this screenshot: + +![Persisted Form](./src/assets/persisted-form.png) + +## Steps + +### 1. Configure RADFish Application with Schema +In the `index.jsx` file, define your Application with stores and schema validation: + +```jsx +import { Application } from "@nmfs-radfish/radfish"; +import { IndexedDBConnector } from "@nmfs-radfish/radfish/storage"; + +const app = new Application({ + serviceWorker: { + url: import.meta.env.MODE === "development" ? "/mockServiceWorker.js" : "/service-worker.js", + }, + stores: { + formData: { + connector: new IndexedDBConnector("persisted-form-app"), + collections: { + formData: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + fullName: { type: "string" }, + numberOfFish: { type: "number" }, + species: { type: "string" }, + computedPrice: { type: "number" }, + isDraft: { type: "boolean" }, + }, + }, + }, + }, + }, + }, +}); +``` + +Key schema features: +- **Primary Key**: Each form has a unique `id` for URL-based loading +- **Type Validation**: Numbers are automatically validated and converted +- **Boolean Flags**: Track form state (draft vs. completed) + +### 2. Initialize and Provide the Application +The Application waits for stores to be ready before rendering: + +```jsx +const root = ReactDOM.createRoot(document.getElementById("root")); + +app.on("ready", async () => { + root.render( + + + + + , + ); +}); +``` + +### 3. Access Collections in Components +In your form components, use the `useApplication` hook to access collections: + +```jsx +import { useApplication } from "@nmfs-radfish/react-radfish"; + +const HomePage = () => { + const application = useApplication(); + const formDataCollection = application.stores.formData.getCollection("formData"); + + // Use collection methods for CRUD operations... +}; +``` + +### 4. Implement Form Persistence +The form demonstrates several key persistence patterns: + +#### Creating New Forms +```jsx +const handleSubmit = async (e) => { + e.preventDefault(); + // Extract form values... + + if (!id) { + // Create new form with unique ID + const newForm = { + id: crypto.randomUUID(), + ...values, + isDraft: false, + }; + await formDataCollection.create(newForm); + navigate(`/${newForm.id}`); // Navigate to shareable URL + } +}; +``` + +#### Loading Existing Forms +```jsx +const findExistingForm = async () => { + if (id) { + const [found] = await formDataCollection.find({ id: id }); + if (found) { + setFormData({ ...found }); + } else { + navigate("/"); // Redirect if form not found + } + } +}; +``` + +#### Updating Forms +```jsx +// Update existing form +const updatedForm = { id, ...values, isDraft: false }; +await formDataCollection.update(updatedForm); +``` + +### 5. Key Features Demonstrated +- **URL-based Form Loading**: Each form gets a unique URL for bookmarking and sharing +- **Type Safety**: Number fields are automatically converted and validated +- **Auto-save on Submit**: Data persists across browser sessions +- **Schema Validation**: Form data structure is enforced by the collection schema + diff --git a/docs/developer-documentation/examples-and-templates/examples/react-query.md b/docs/developer-documentation/examples-and-templates/examples/react-query.md new file mode 100644 index 0000000..e2721de --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/react-query.md @@ -0,0 +1,21 @@ +--- +description: Manage app state with React Query's simplified API +title: React Query +id: react-query +--- + +# React Query Example + +This example shows you how to use the [React Query library](https://tanstack.com/query/latest) to manage state in your application. + +This example does __not__ include any backend persistence via IndexedDB. as this is out of scope for this example. Instead, this example provides two simplified endpoints to call: + +- `[GET] /species` returns a list of 4 species + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + + +## Preview +This example will render as shown in this screenshot: + +![React Query](./src/assets/react-query.png) diff --git a/docs/developer-documentation/examples-and-templates/examples/server-sync.md b/docs/developer-documentation/examples-and-templates/examples/server-sync.md new file mode 100644 index 0000000..4541421 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/server-sync.md @@ -0,0 +1,291 @@ +--- +description: Sync client-server data with offline storage support +title: ServerSync +id: server-sync +--- + +# Server Sync Example + +This example demonstrates server-to-client data synchronization using RADFish's Application and Collection patterns. It shows how to fetch data from an API, compare it with local data, and persist updates in IndexedDB with schema validation. + +## Key RADFish Concepts + +- **Application Instance**: Configured with stores and collections for data management +- **Schema Validation**: Type-safe collections ensure data integrity +- **Offline Detection**: Built-in network status monitoring and appropriate user feedback +- **Smart Synchronization**: Only updates local data when server data differs + +Use cases include: +- Periodic data synchronization from APIs +- Offline-first applications with server backup +- Data caching for improved performance +- Background sync processes + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Features + +- Synchronizes data with a remote server. +- Handles offline status and provides appropriate messages. +- Provides a loading state during synchronization. +- Displays the last synchronization time. + +## Preview + +This example will render as shown in this screenshot: + +![Server Sync](./src/assets/server-sync.png) + +## Steps + +### 1. Configure Application Schema +In `index.jsx`, define your Application with stores and collections for sync data: + +```jsx +import { Application } from "@nmfs-radfish/radfish"; +import { IndexedDBConnector } from "@nmfs-radfish/radfish/storage"; + +const app = new Application({ + stores: { + syncData: { + connector: new IndexedDBConnector("server-sync-app"), + collections: { + localData: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + value: { type: "string" }, + isSynced: { type: "boolean" }, + }, + }, + }, + lastSyncFromServer: { + schema: { + fields: { + id: { type: "string", primaryKey: true }, + time: { type: "number" }, + }, + }, + }, + }, + }, + }, +}); +``` + +Key schema design: +- **localData**: Stores the actual data synchronized from the server +- **lastSyncFromServer**: Tracks synchronization timestamps +- **Primary Keys**: Each collection uses `id` as the unique identifier + +### 2. Access Collections and Status +Import RADFish utilities and access your collections: + +```jsx +import { useOfflineStatus, useApplication } from "@nmfs-radfish/react-radfish"; + +const { isOffline } = useOfflineStatus(); +const application = useApplication(); +const localDataCollection = application.stores.syncData.getCollection("localData"); +const lastSyncCollection = application.stores.syncData.getCollection("lastSyncFromServer"); +``` + +#### Key utilities: +- **useOfflineStatus**: Provides real-time network connectivity status +- **useApplication**: Accesses the Application instance and its collections +- **Collections**: Type-safe interfaces for data operations + +### 3. Define Helper Functions + +Create helper functions to make network requests and fetch data from an API endpoint. + +#### `getRequestWithFetch` + +This function performs GET requests and handles errors: + +```jsx +const getRequestWithFetch = async (endpoint) => { + try { + const response = await fetch(`${endpoint}`, { + headers: { "X-Access-Token": "your-access-token" }, + }); + + if (!response.ok) { + const error = await response.json(); + return error; + } + + return await response.json(); + } catch (err) { + return { error: `[GET]: Error fetching data: ${err}` }; + } +}; +``` + +### 4. Implement Server Synchronization + +The `syncToServer` function demonstrates complete server-to-client synchronization using Collections: + +```jsx +const syncToServer = async () => { + if (isOffline) { + setSyncStatus({ message: SERVER_SYNC_FAILED, lastSynced: syncStatus.lastSynced }); + return; + } + + setIsLoading(true); + try { + // Fetch data from the server + const { data: serverData } = await getRequestWithFetch(MSW_ENDPOINT.GET); + + // Retrieve existing local data + const offlineData = await localDataCollection.find(); + + // Compare server data with local data + if (JSON.stringify(offlineData) !== JSON.stringify(serverData)) { + // Clear existing local data + const existingData = await localDataCollection.find(); + for (const item of existingData) { + await localDataCollection.delete({ id: item.id }); + } + + // Insert new server data with proper schema mapping + for (const item of serverData) { + await localDataCollection.create({ + id: item.uuid || crypto.randomUUID(), + value: item.value, + isSynced: item.isSynced || true, + }); + } + + // Update sync timestamp + const currentTimestamp = Date.now(); + const existingSync = await lastSyncCollection.find({ id: "lastSynced" }); + + if (existingSync.length > 0) { + await lastSyncCollection.update({ id: "lastSynced", time: currentTimestamp }); + } else { + await lastSyncCollection.create({ id: "lastSynced", time: currentTimestamp }); + } + + const lastSyncTime = new Date(currentTimestamp).toLocaleString(); + setSyncStatus({ message: SERVER_SYNC_SUCCESS, lastSynced: lastSyncTime }); + + // Refresh UI data + const updatedData = await localDataCollection.find(); + setData(updatedData); + } else { + setSyncStatus({ message: OFFLINE_ALREADY_SYNCED, lastSynced: syncStatus.lastSynced }); + } + } catch (error) { + console.error("Sync error:", error); + setSyncStatus({ message: "Sync failed due to an error.", lastSynced: syncStatus.lastSynced }); + } finally { + setIsLoading(false); + } +}; +``` + +**Key synchronization steps**: +- **Offline Check**: Prevents sync attempts when network is unavailable +- **Data Comparison**: Only updates when server data differs from local data +- **Schema Mapping**: Transforms server data to match local collection schema +- **Timestamp Tracking**: Records successful sync times for user feedback + +### 5. Load and Display Sync Status + +Load the last synchronization time from the collection and update the UI: + +```jsx +useEffect(() => { + const loadLastSyncedTime = async () => { + const [lastSyncRecord] = await lastSyncCollection.find({ id: "lastSynced" }); + if (lastSyncRecord?.time) { + const lastSyncTime = new Date(lastSyncRecord.time).toLocaleString(); + setSyncStatus((prev) => ({ + ...prev, + lastSynced: lastSyncTime, + })); + } + }; + + loadLastSyncedTime(); +}, [isOffline, lastSyncCollection]); +``` + +### 6. Render the Component + +The complete component demonstrates sync functionality with user feedback: + +```jsx +export const HomePage = () => { + const { isOffline } = useOfflineStatus(); + const application = useApplication(); + const localDataCollection = application.stores.syncData.getCollection("localData"); + const lastSyncCollection = application.stores.syncData.getCollection("lastSyncFromServer"); + + const [isLoading, setIsLoading] = useState(false); + const [syncStatus, setSyncStatus] = useState({ message: "", lastSynced: "" }); + const [data, setData] = useState([]); + + return ( + <> +

Server Sync Example

+ + This example demonstrates server synchronization with offline detection. + Use DevTools Network tab to toggle offline mode and test sync behavior. + + +
+ + +
+ {syncStatus.message} +
+ +
+ {syncStatus.lastSynced && ( + Last Synced: {syncStatus.lastSynced} + )} +
+ + + + + ); +}; +``` + +Note that the table now uses `id` instead of `uuid` to match the new schema. + +### 7. Test Offline/Online Behavior + +Verify that the application handles network connectivity changes correctly: + +#### Enable Offline Mode +1. Open browser **Developer Tools** (`F12`) +2. Navigate to the **Network** tab +3. Find the **Throttling** dropdown +4. Select **Offline** to simulate network disconnection + +#### Test Sync Behavior +1. **Offline Test**: Click "Sync with Server" - should show error message +2. **Online Test**: Change throttling back to "No throttling" and sync again +3. **Data Verification**: Check Application > IndexedDB in DevTools to see stored data + +#### Expected Results +- **Offline**: Error message appears, no data changes +- **Online**: Success message, data updates if different from server +- **Data Persistence**: All synchronized data persists in IndexedDB collections +- **Schema Validation**: Data follows the defined collection schemas + +This example demonstrates how RADFish's Collections provide a robust foundation for building offline-first applications with server synchronization capabilities. diff --git a/docs/developer-documentation/examples-and-templates/examples/simple-table.md b/docs/developer-documentation/examples-and-templates/examples/simple-table.md new file mode 100644 index 0000000..8dc6ae5 --- /dev/null +++ b/docs/developer-documentation/examples-and-templates/examples/simple-table.md @@ -0,0 +1,179 @@ +--- +description: Display tabular data using the
component +title: Simple Table +id: simple-table +--- + +# Simple Table Example + +This example shows you how to use the `
` component to display tabular data. + +Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. + +## Table Overview + +The `
` component is flexible and customizable. It lets you define column configurations, enable sorting, and paginate large datasets. In this example, we use mock data representing different fish species with attributes such as status (draft/submitted), UUID, species name, image, and price. + +## Features + +- **Sorting**: Click on any column header to sort the table by that column. Sorting toggles between ascending, descending, and unsorted states. +- **Multi-Column Sorting**: Multi-column sorting allows you to sort data by multiple columns. Click additional column headers in the desired order of sorting priority. +- **Pagination**: Use the pagination controls below the table to navigate through multiple pages of data. You can move between the first, previous, next, and last page, and see the current page number and total pages. +- **Custom Rendering**: Certain columns (like the image and price) use custom render functions to display data in a more user-friendly way. +- **Status Submission**: Rows with a "Draft" status display a "Submit" button, which updates the status to "Submitted" when clicked. + +## Preview +This example will render as shown in this screenshot: + +![Simple Table](./src/assets/simple-table.png) + +## Steps + +### 1. Data Structure + +The `data` prop is an array of objects where each object represents a row in the table. Each object should have keys that correspond to the `key` values defined in your `columns` array. + +```jsx +const mockData = [ + { + uuid: "1", + isDraft: true, + species: "Marlin", + price: 50, + image: "./sample-img.webp", + }, + { + uuid: "2", + isDraft: false, + species: "Mahimahi", + price: 100, + image: "./sample-img.webp", + }, + // More data... +]; +``` + +### 2. Columns Configuration + +The `columns` array defines how the table displays and interacts with the data. Each column object can include: + +- **`key`**: (string) A unique identifier that matches a key in your data object. +- **`label`**: (string) The header text displayed for the column. +- **`sortable`**: (boolean) A boolean that determines whether sorting is enabled for this column. +- **`render`**: (function) _(Optional)_ A function that customizes how data is displayed in that column. +- **`className`**: (string) _(Optional)_ For custom styling. + +```jsx +const columns = [ + { + key: "isDraft", + label: "Status", + sortable: true, + render: (row) => ( + + {row.isDraft ? "Draft" : "Submitted"} + {row.isDraft && } + + ), + }, + { + key: "uuid", + label: "ID", + sortable: true, + }, + { + key: "species", + label: "Species", + sortable: true, + }, + { + key: "price", + label: "Price", + sortable: true, + render: (row) => ${row.price}, + }, + { + key: "image", + label: "Image", + render: (row) => {row.species}, + }, +]; +``` + +### 3. Custom Rendering with render + +Use the `render` function to customize how data is displayed in a column. This is useful for rendering components like buttons or images. + +- Rendering a Button: + + ```jsx + { + key: "isDraft", + label: "Status", + render: (row) => ( + + {row.isDraft ? "Draft" : "Submitted"} + {row.isDraft && ( + + )} + + ), + }, + ``` + +- Rendering an Image: + + ```jsx + { + key: "image", + label: "Image", + render: (row) => ( + {row.species} + ), + }, + ``` + +### 4. Pagination + +Control table pagination using the `paginationOptions` prop. + +Pagination Options: + +- `pageSize`: (number) Number of rows displayed per page. +- `currentPage`: (number) The current page number (starting from 1). +- `totalRows`: (number) Total number of rows in your dataset. +- `onPageChange`: (function) Function called when the page changes. + +```jsx +const paginationOptions = { + pageSize: 5, + currentPage: 1, + onPageChange: onPageChange, + totalRows: data.length, +}; +``` + +### 5. Additional Props and Styling + +Our `
` component is built on the Trussworks `
` component from the `@trussworks/react-uswds` library. This integration lets you use additional props to customize its appearance and behavior. + +#### Using Trussworks Props + +You can pass these props directly to the `
` component to enhance its styling: + +```jsx +
+``` + +#### Referencing Trussworks Documentation + +For a complete list of available props and detailed descriptions, refer to the [Trussworks Table Component Documentation](https://trussworks.github.io/react-uswds/?path=/docs/components-table--docs). diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/computed-form-fields.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/computed-form-fields.png new file mode 100644 index 0000000..d8afdcb Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/computed-form-fields.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/conditional-form-fields.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/conditional-form-fields.png new file mode 100644 index 0000000..c3371a3 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/conditional-form-fields.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/field-validators.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/field-validators.png new file mode 100644 index 0000000..80331ca Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/field-validators.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/form-structure.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/form-structure.png new file mode 100644 index 0000000..9aa575d Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/form-structure.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/mock-api.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/mock-api.png new file mode 100644 index 0000000..8d99492 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/mock-api.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/multistep.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/multistep.png new file mode 100644 index 0000000..35c76ba Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/multistep.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/network-status.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/network-status.png new file mode 100644 index 0000000..96cdd95 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/network-status.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/on-device-storage.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/on-device-storage.png new file mode 100644 index 0000000..0d89243 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/on-device-storage.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/persisted-form.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/persisted-form.png new file mode 100644 index 0000000..9b8df95 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/persisted-form.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/react-query.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/react-query.png new file mode 100644 index 0000000..0926379 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/react-query.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/server-sync.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/server-sync.png new file mode 100644 index 0000000..38f08a4 Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/server-sync.png differ diff --git a/docs/developer-documentation/examples-and-templates/examples/src/assets/simple-table.png b/docs/developer-documentation/examples-and-templates/examples/src/assets/simple-table.png new file mode 100644 index 0000000..79e505b Binary files /dev/null and b/docs/developer-documentation/examples-and-templates/examples/src/assets/simple-table.png differ diff --git a/docusaurus.config.js b/docusaurus.config.js index 444745f..8dae4b6 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -62,498 +62,6 @@ const config = { // Options here }, ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "computed-form-fields-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/computed-form-fields/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["computed-form-fields.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Computed Form Fields - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/computed-form-fields#readme - name: "computed-form-fields", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["computed-form-fields/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Automatically calculates a read-only field in forms -title: Computed Form Fields -id: computed-form-fields ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "conditional-form-fields-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/conditional-form-fields/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["conditional-form-fields.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Conditional Form Fields - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/conditional-form-fields#readme - name: "conditional-form-fields", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["conditional-form-fields/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Dynamically show/hide fields based on other inputs -title: Conditional Form Fields -id: conditional-form-fields ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "field-validators-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/field-validators/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["field-validators.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Field Validators - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/field-validators#readme - name: "field-validators", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["field-validators/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Enforce validation logic, like disallowing numbers -title: Field Validators -id: field-validators ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "network-status-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/network-status/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["network-status.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Network Status - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/network-status#readme - name: "network-status", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["network-status/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Detect network connectivity and display offline alerts -title: Network Status -id: network-status ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "mock-api-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/mock-api/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["mock-api.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Mock API - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/mock-api#readme - name: "mock-api", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["mock-api/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Mock API for frontend development with MSW and fetch -title: Mock API -id: mock-api ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "on-device-storage-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/on-device-storage/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["on-device-storage.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // On Device Storage - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/on-device-storage#readme - name: "on-device-storage", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["on-device-storage/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Use IndexedDB/Dexie.js for offline or local storage -title: On Device Storage -id: on-device-storage ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "multistep-form-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/multistep-form/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["multistep.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Multistep Form - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/multistep-form#readme - name: "multistep-form", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["multistep-form/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Multi-step form saving progress and inputs locally -title: Multistep Form -id: multistep-form ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "server-sync-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/server-sync/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["server-sync.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // ServerSync - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/server-sync#readme - name: "server-sync", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["server-sync/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Sync client-server data with offline storage support -title: ServerSync -id: server-sync ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "react-query-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/react-query/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["react-query.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // React Query - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/react-query#readme - name: "react-query", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["react-query/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Manage app state with React Query's simplified API -title: React Query -id: react-query ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "persisted-form-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/persisted-form/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["persisted-form.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Persisted Form - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/persisted-form#readme - name: "persisted-form", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["persisted-form/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Save and persist form data locally with FormWrapper -title: Persisted Form -id: persisted-form ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "simple-table-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/simple-table/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["simple-table.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Simple Table - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/simple-table#readme - name: "simple-table", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["simple-table/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Display tabular data using the
component -title: Simple Table -id: simple-table ---- - -${content}`, - }; - }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // options here - name: "form-structure-image-content", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/form-structure/src/assets/", // the base url for the markdown (gets prepended to all of the documents when fetching) - outDir: - "docs/developer-documentation/examples-and-templates/examples/src/assets", // the base directory to output to. - documents: ["form-structure.png"], // the file names to download - requestConfig: { responseType: "arraybuffer" }, - }, - ], - [ - "docusaurus-plugin-remote-content", - { - // Structured Form - // Remote content configuration to fetch repo README.md for - // https://github.com/NMFS-RADFish/boilerplate/tree/main/examples/form-structure#readme - name: "form-structure", // used by CLI, must be path safe - sourceBaseUrl: - "https://raw.githubusercontent.com/NMFS-RADFish/boilerplate/main/examples/", - outDir: "docs/developer-documentation/examples-and-templates/examples/", // the base directory to output to. - documents: ["form-structure/README.md"], // the file to download - modifyContent( - /** @type {string} */ filename, - /** @type {string} */ content - ) { - return { - filename: readmeToNamedMd(filename), - content: `--- -description: Build complex forms using Trussworks core components -title: Form Structure -id: form-structure ---- - -${content}`, - }; - }, - }, - ], ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */