Order summary
Subtotal, discount, shipping and tax added up to a total, with room for the items and a checkout button.
Install
If this is the first component in the project, set up once. This writes ermine.json and copies the theme stylesheet. The installation guide covers Vue, Nuxt and Astro.
npx ermineui initpnpm dlx ermineui inityarn dlx ermineui initbunx ermineui initThen add the component:
npx ermineui add order-summarypnpm dlx ermineui add order-summaryyarn dlx ermineui add order-summarybunx ermineui add order-summaryThat copies 2 files into src/components/ui/order-summary/, and the 1 component it is built from. Files you already have are kept.
Use
import { OrderSummary } from '@/components/ui/order-summary'<RadioGroup v-model="delivery" aria-label="Delivery">
<OptionCard value="standard" title="Standard" description="3 to 5 working days"><template #end>Free</template></OptionCard>
<OptionCard value="express" title="Express" description="1 to 2 working days"><template #end>$12.00</template></OptionCard>
</RadioGroup>
<CartLineItem v-model:quantity="item.quantity" :title="item.title" :image="item.image" options="Sand / 350 ml" :price="24" :max="6" @remove="remove(item)" />
<OrderSummary :subtotal="144" :discount="10" discount-label="SAVE10" :shipping="0" :tax="11.52">
<template #footer><Button size="lg">Checkout</Button></template>
</OrderSummary>In Nuxt, with the auto-import setting, the tags work in templates without the import. In Astro, use it inside a Vue island.
What gets copied
| File | Where it goes |
|---|---|
OrderSummary.vue | src/components/ui/order-summary/OrderSummary.vue |
index.ts | src/components/ui/order-summary/index.ts |
It is built from: Price. These are added with it.
The folders are the suggested ones. ermine.json says where yours are.
Source
This is exactly what the command copies. To install by hand, create each file below and the helpers listed above.
OrderSummary.vue 114 lines
<script setup lang="ts">
import { computed } from 'vue'
import { formatMoney } from '../price'
const props = withDefaults(
defineProps<{
/** The items added up, before anything else. Amounts are in whole units of the currency. */
subtotal: number
/** Money taken off, as a positive number. */
discount?: number
/** What the discount is, e.g. the code that was used. */
discountLabel?: string
/** 0 is written as "Free". Leave out until it is known. */
shipping?: number
/** Shown in place of the shipping cost while it is not known. */
shippingPending?: string
tax?: number
/** Worked out from the lines above when left out. */
total?: number
currency?: string
locale?: string
}>(),
{ discountLabel: 'Discount', shippingPending: 'Calculated at checkout', currency: 'USD', locale: 'en-US' },
)
const money = (amount: number) => formatMoney(amount, props.currency, props.locale)
const sum = computed(() => props.total ?? Math.max(0, props.subtotal - (props.discount ?? 0) + (props.shipping ?? 0) + (props.tax ?? 0)))
</script>
<template>
<div class="er-order-summary">
<!-- The items themselves, a discount code field, or anything else that belongs above the sums. -->
<slot />
<dl>
<div>
<dt>Subtotal</dt>
<dd>{{ money(subtotal) }}</dd>
</div>
<div v-if="discount" data-tone="discount">
<dt>{{ discountLabel }}</dt>
<dd>−{{ money(discount) }}</dd>
</div>
<div>
<dt>Shipping</dt>
<dd :data-pending="shipping === undefined ? '' : undefined">{{ shipping === undefined ? shippingPending : shipping === 0 ? 'Free' : money(shipping) }}</dd>
</div>
<div v-if="tax !== undefined">
<dt>Tax</dt>
<dd>{{ money(tax) }}</dd>
</div>
<div class="er-order-summary-total">
<dt>Total</dt>
<dd>{{ money(sum) }}</dd>
</div>
</dl>
<!-- The button that moves on, and any small print. -->
<slot name="footer" />
</div>
</template>
<style>
@layer components {
.er-order-summary {
display: grid;
gap: 1rem;
min-width: 0;
}
.er-order-summary dl {
display: grid;
gap: 0.5rem;
margin: 0;
font-size: 0.875rem;
}
.er-order-summary dl > div {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.er-order-summary dt {
color: var(--er-muted-foreground);
}
.er-order-summary dd {
margin: 0;
font-variant-numeric: tabular-nums;
text-align: right;
}
.er-order-summary dd[data-pending] {
color: var(--er-muted-foreground);
}
.er-order-summary [data-tone='discount'] dd {
color: var(--er-success);
}
.er-order-summary .er-order-summary-total {
margin-top: 0.25rem;
padding-top: 0.75rem;
border-top: 1px solid var(--er-border);
font-size: 1rem;
font-weight: var(--er-weight-semibold);
}
.er-order-summary-total dt {
color: var(--er-foreground);
}
}
</style>index.ts 3 lines
/** Order summary — subtotal, discount, shipping and tax added up to a total, with room for the items and a checkout button. */
export { default as OrderSummary } from './OrderSummary.vue'