Components

Components are reusable pieces which can be created manually using built in components. Creating components gives you flexibility in designing your UI. To create a component, create a vue file in .theme/components directory.

Basic Component

Basic component is our understanding of a component in its simplest form, to create a basic component create the vue file BasicComponent.vue in .theme/components directory. To create more complex components this base component can be used as a starting point. Then use this component in a page as shown in .theme/pages/demo/basic.vue.

Demo is at /demo/components/basic.

Defining Properties

To add properties to a component, defineProps can be used and type and default value can be set as shown in ./theme/components/ComponentWithProps.vue. More properties can be added later on. Values of these properties can be assigned when using the component with properties as shown in .theme/demo/defining-props.vue

When we use defineProps, we prefer the language to be typescript to keep the props type-safe.

Demo is at /demo/components/defining-props

Complex Type Props

Complex types can be used with defineProps as seen in ./theme/components/ComponentWithTypedProp.vue.

Demo is at /demo/components/complex-type-props

For type instances in your scripts to work, script language should be set to TypeScript. TypeCheck should be enabled. If you have enabled type checking in your project required properties are enforced during compile.

Reusing Types

To reuse types, create a types folder at the root of the project, and create an index.ts file to export types. You can add your types in this folder and export them via index.ts, see ./theme/types/Demo.ts. These types can be used in components as seen in ./theme/components/ComponentThatReusesType.vue

Demo is at /demo/components/reusing-types

Emit & Ref

Emit is the way to raise events from a component to its parent, like a click event. Ref is used for reactivity, so when a state is expected to change use ref for changes to apply to the template immediately.

Demo is at /demo/components/emit-and-ref

Global Emit

To emit a global change, we use nuxt's state management. To achieve this;

  1. Introduce a state with its default value function as shown in .theme/composables/states.js
  2. Import and modify it as shown in .theme/components/ComponentThatEmitsGlobally.vue
  3. Watch it as shown in .theme/pages/demo/components/global-emit.vue

Demo is at /demo/components/global-emit

Prose

Prose components are wrappers of html tags that are used to render markdown content. When you want to change the way nuxt renders markdown you need to override a prose component.

To override a prose, let's say <table>, go to the Nuxt Content page and find the source code of the component on github, in this case ProseTable. Copy the code from there, and place it under exactly the same path .theme/components/Prose/, and make any change you want.

Below is a demonstration of overriding ProseTable.vue;

OverrideThisTable
UsingProseComponents
ToAddBorder

You need to configure ~/components/Prose as a global directory to enable this. See .theme/nuxt.config.ts.

Slot Manipulation

We need this one to create an alarm component out of a blockquote with an icon in its first line like below;

Demo warning message

Content of this blockquote is passed to ProseBlockquote component in <slot/>. To parse the first line of this slot, we used useSlots in script where nuxt gives access to element tree of a markdown content.

<slot/> does not allow to manipulate its content, so we used <component :is="..."/> instead. This is the way to render a slot that is programmatically changed or created.

Other types demonstrated below;

Demo info message

Demo default message

Provide & Inject

When you need to access a data that cannot be passed to a component (such as prose components), we use provide and inject functions. Basically you provide a value in a parent component to be injected in any child components.

diagram

Demo is at /demo/provide-and-inject

Content Transformer

We use Content Transformers when we want to manipulate the html content of a markdown content before it is rendered, after it is parsed.

See /demo/transformer to see what can be done.

See /transformers/optimus-prime.ts to see how it is done.

For Content Transformers to work, we need to add them as modules to Nuxt. Nuxt does this automatically if the nuxt modules are under /modules. You can see how to add them as modules in /modules/transform-module.ts.

Pinia

We use Pinia when we try to fetch the same data multiple times. For example, let's say we use the same titles for the items in the header, footer and side menu. With Pinia, we can fetch the data once and distribute it to other components.

See /store/piniaStore for the pinia store where we store the fetched data.

See demo/pinia for demo of how we use the Pinia store to add and import data.

Mutex

We use async-mutex when we want to synchronize concurrent processes. When a page is rendered, multiple components may fetch data from an api but may need to refresh access token. Since components will not be rendered syncronized, many requests will be performed to acquire new access token. In order to prevent this issue, we lock refresh token flow, so that each component will wait for first refresh token request and use the newly obtained token.

See demo/mutex for demo of synchronizing concurrent processes

PrimeVue

We use PrimeVue for app development. Install it using below script;

npm i primevue primeicons @primevue/themes # base packages
npm i chart.js # to enable Chart component
npm i --save-dev @primevue/nuxt-module @nuxtjs/tailwindcss # dev dependencies

To use prime icons you need to import it in assets/styles.scss.

@import "primeicons/primeicons.css";

Finally nuxt.config.ts should include below configurations;

import Aura from "@primevue/themes/aura";

export default defineNuxtConfig({
  ...
  modules: [
    ...
    "@nuxtjs/tailwindcss",
    "@primevue/nuxt-module",
    ...
  ],
  primevue: {
    options: {
      theme: {
        preset: Aura
      }
    },
    autoImport: true
  },
  ...
});

For demo purposes we followed primevue-tailwind documentation to setup and properly use primevue themes with tailwind

Customizing Themes

To make customizations in a theme, include definePreset and use a theme as a base, e.g., Aura.

import Aura from "@primevue/themes/aura";
import { definePreset } from "@primevue/themes";

const Mouseless = definePreset(Aura, {
  semantic: {
    primary: {
      50: "{red.50}",
      ...
      950: "{red.950}"
    }
  }
});

export default defineNuxtConfig({
  ...
  primevue: {
    options: {
      theme: {
        preset: Mouseless
      }
    }
  },
  ...
});

See https://primevue.org/theming/styled/#customization for detailed official docs.

Form

We use Form component from PrimeVue Forms library. Install it using below command;

npm install @primevue/forms

To use Form component, you need to import it in .vue files

import { Form } from '@primevue/forms';

Demo is at /demo/primevue.

We experienced issues when following the given documentation when trying to configure resolvers so implemented a custom resolver to demonstrate how invalid inputs are visualized