@newschools/sdk
v0.2.8
Published
New Schools SDK - Multi-framework components and modules for integrating New Schools learning content
Maintainers
Readme
@newschools/sdk
Nuxt module for integrating New Schools learning content into your applications with automatic organization theming.
Features
✨ Auto-imported composables - useAsyncNewSchools and useOrganization available everywhere
🎨 Automatic theming - Organization brand colors applied as CSS variables
🔒 Type-safe - Full TypeScript support with entity types
⚡ SSR-compatible - Built on Nuxt's useAsyncData
🎯 Ready-to-use components - EntityHero, EntityBentoGrid, EntityCard
📐 Smart layouts - EntityBentoGrid automatically generates responsive layouts
🔑 API key authentication - Secure access to your organization's content
Installation
# Using npm
npm install @newschools/sdk
# Using yarn
yarn add @newschools/sdk
# Using pnpm
pnpm add @newschools/sdk
# Using bun
bun add @newschools/sdkSetup
1. Add module to nuxt.config.ts
export default defineNuxtConfig({
modules: ["@newschools/sdk"],
newschools: {
apiKey: process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY,
organizationSlug: "my-organization", // Optional: auto-fetch org data and apply brand colors
},
});2. Configure API key
Add your New Schools API key to .env:
NUXT_PUBLIC_NEWSCHOOLS_API_KEY=ns_live_xxxxxxxxxxxxx3. (Optional) Configure organization
If you want to automatically load organization data and apply brand theming:
export default defineNuxtConfig({
modules: ["@newschools/sdk"],
newschools: {
apiKey: process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY,
organizationSlug: "webdesignwill", // Loads org data on init
},
});When organizationSlug is set, the SDK will:
- ✅ Auto-fetch organization data on app initialization
- ✅ Apply brand colors (
primary,secondary,tertiary) as CSS variables - ✅ Make organization data available via
useOrganization()composable
Automatic Brand Theming
The SDK automatically applies your organization's brand colors as CSS variables:
// In nuxt.config.ts
export default defineNuxtConfig({
newschools: {
organizationSlug: "my-org", // Has primary: "#FF0000"
},
});The SDK sets:
--ns-color-primary→#FF0000--ns-color-secondary→ (your secondary color)--ns-color-tertiary→ (your tertiary color)
If colors are not set or cleared by admin, they revert to SDK defaults:
--ns-color-primary→#4a3f8f(Deep Purple)--ns-color-secondary→#ff6b6b(Vibrant Coral)--ns-color-tertiary→#0fa3b1(Rich Teal)
Use these variables in your components:
<template>
<div class="my-component">
<h1>Automatically themed!</h1>
</div>
</template>
<style scoped>
.my-component {
background: var(--ns-color-primary);
color: white;
}
h1 {
border-bottom: 2px solid var(--ns-color-secondary);
}
</style>Usage
Organization Data
Access organization data loaded by the SDK:
<script setup lang="ts">
const organization = useOrganization();
</script>
<template>
<div v-if="organization">
<h1>{{ organization.name }}</h1>
<img v-if="organization.coverImageUrl" :src="organization.coverImageUrl" />
</div>
</template>Pre-built Components
EntityHero
Display entity header with cover image and branding. Supports 4 layout variants:
Basic Usage:
<template>
<EntityHero :entity="organization" variant="left">
<template #sub-title>
<p>Welcome to our learning platform</p>
</template>
<template #content>
<div>Custom content area for CTAs, forms, etc.</div>
</template>
</EntityHero>
</template>
<script setup lang="ts">
const organization = useOrganization();
</script>Layout Variants:
variant="left"(default) - Image on left, content on right (grid on tablet+, stacked on mobile)variant="right"- Content on left, image on right (grid on tablet+, stacked on mobile)variant="fullscreen"- Full screen image with content overlay at bottom left (all screen sizes)variant="horizontal"- Image on top (50vh with clamp), content below (stacked on all sizes)
Variant Examples:
<!-- Fullscreen: Content overlays image -->
<EntityHero :entity="organization" variant="fullscreen">
<template #sub-title>Journey subtitle</template>
<template #content>
<p>This content appears over the image, under the title.</p>
</template>
</EntityHero>
<!-- Horizontal: Image top, content bottom -->
<EntityHero :entity="organization" variant="horizontal">
<template #sub-title>Course overview</template>
<template #content>
<div>Course details and enrollment form</div>
</template>
</EntityHero>
<!-- Right: Reversed grid layout -->
<EntityHero :entity="organization" variant="right">
<template #sub-title>Featured content</template>
<template #content>
<button>Get Started</button>
</template>
</EntityHero>Responsive Behavior:
- Title and subtitle always overlay the image (all variants)
leftandrightvariants stack vertically on mobile (<768px)fullscreenmaintains overlay layout on all screen sizeshorizontaluses flexible image height:clamp(40vh, 50vh, 60vh)
EntityBentoGrid (Recommended)
Display entities in a responsive bento grid with automatic layout:
<template>
<!-- Simple: Auto-renders journeys as cards -->
<EntityBentoGrid :items="journeys" />
<!-- Advanced: Custom card rendering -->
<EntityBentoGrid :items="journeys">
<template #item="{ item, index }">
<EntityCard :image-url="item.coverImageUrl" :image-alt="item.title">
<template #title>
<h3>{{ item.title }}</h3>
</template>
<template #description>
<p>{{ item.description }}</p>
</template>
</EntityCard>
</template>
</EntityBentoGrid>
</template>
<script setup lang="ts">
const { data: journeys } = await useAsyncNewSchools("journeys");
</script>What it does:
- ✅ Automatically generates optimized layouts for 1-6+ items
- ✅ Works with any entity type (journeys, teachers, waypoints, activities)
- ✅ Default rendering extracts
title,description, and images automatically - ✅ Fully customizable via
#itemslot - ✅ Responsive (mobile-first, 3-column grid on tablet+)
BentoGrid (Advanced)
For complete manual control, use BentoGrid directly:
Create responsive masonry-style layouts:
<template>
<BentoGrid>
<!-- 2x2 Featured card -->
<BentoGridItem :col-span="2" :row-span="2">
<EntityCard image-url="/hero.jpg" image-alt="Featured content">
<template #title>
<h3>Featured Content</h3>
</template>
<template #description>
<p>Main featured content with prominent display</p>
</template>
</EntityCard>
</BentoGridItem>
<!-- Standard cards -->
<BentoGridItem>
<EntityCard image-url="/card.jpg">
<template #title>
<h3>Regular Card</h3>
</template>
<template #description>
<p>Standard card content</p>
</template>
</EntityCard>
</BentoGridItem>
<!-- Explicit positioning (advanced) -->
<BentoGridItem :col-span="2" :row-span="2" :col-start="2" :row-start="3">
<EntityCard image-url="/bottom-right.jpg">
<template #title>
<h3>Bottom Right</h3>
</template>
<template #description>
<p>Explicitly positioned at column 2, row 3</p>
</template>
</EntityCard>
</BentoGridItem>
</BentoGrid>
</template>Fetching Learning Content
The useAsyncNewSchools composable is auto-imported and available everywhere.
Display journeys in a bento grid
<script setup lang="ts">
const { data: journeys } = await useAsyncNewSchools("journeys");
</script>
<template>
<EntityBentoGrid v-if="journeys" :items="journeys" />
</template>Fetch all journeys
<script setup lang="ts">
const { data: journeys, error } = await useAsyncNewSchools("journeys");
</script>
<template>
<div v-if="journeys">
<div v-for="journey in journeys" :key="journey.id">
<h2>{{ journey.title }}</h2>
<p>{{ journey.description }}</p>
</div>
</div>
</template>Fetch a specific journey
<script setup lang="ts">
const route = useRoute();
const { data: journey } = await useAsyncNewSchools("journey", {
id: route.params.slug,
});
</script>
<template>
<div v-if="journey">
<h1>{{ journey.title }}</h1>
<p>{{ journey.description }}</p>
</div>
</template>Fetch waypoints for a journey
<script setup lang="ts">
const { data: waypoints } = await useAsyncNewSchools("waypoints", {
journeyId: "my-journey-slug",
});
</script>Fetch a specific waypoint
<script setup lang="ts">
const { data: waypoint } = await useAsyncNewSchools("waypoint", {
journeyId: "journey-slug",
waypointId: "waypoint-slug",
});
</script>TypeScript Support
Full type safety for all entities:
import type {
Organisation,
OrganisationBrand,
Journey,
Waypoint,
Activity,
JourneyTheme,
WaypointType,
LayoutMode,
} from "@newschools/sdk";
// Types are automatically inferred in composables
const organization = useOrganization();
// organization is typed as Ref<Organisation | null>
const { data: journeys } = await useAsyncNewSchools("journeys");
// journeys is typed as Journey[] | nullModule Configuration
All configuration options for nuxt.config.ts:
export default defineNuxtConfig({
newschools: {
/**
* New Schools API key (required)
* Can also be set via NUXT_PUBLIC_NEWSCHOOLS_API_KEY env variable
*/
apiKey: string;
/**
* Base URL for New Schools API
* Default: "https://newschools.ai"
*/
baseUrl?: string;
/**
* Organization slug for auto-fetch and theming
* When set, SDK will:
* - Automatically fetch organization data on init
* - Apply brand colors as CSS variables
* - Make data available via useOrganization()
*
* Example: "my-organization"
*/
organizationSlug?: string;
},
});Do you need baseUrl?
No, unless you're using a custom API endpoint or testing against a development server.
The default value is "https://newschools.ai", which points to production.
// ✅ Minimal config (most common)
export default defineNuxtConfig({
newschools: {
apiKey: process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY,
},
});
// ✅ With organization theming
export default defineNuxtConfig({
newschools: {
apiKey: process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY,
organizationSlug: "webdesignwill",
},
});
// ⚠️ Only needed for custom environments
export default defineNuxtConfig({
newschools: {
apiKey: process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY,
baseUrl: "https://staging.newschools.ai", // Testing against staging
},
});Available CSS Variables
The SDK provides themeable CSS tokens:
Brand Colors (Automatically set from organization)
--ns-color-primary- Primary brand color--ns-color-secondary- Secondary brand color--ns-color-tertiary- Tertiary brand color
Additional Tokens
--ns-color-grey-800,--ns-color-grey-900--ns-spacing-xsthrough--ns-spacing-3xl--ns-border-radius-smthrough--ns-border-radius-2xl--ns-shadow-smthrough--ns-shadow-xl
See tokens.css for the complete list.
Advanced Usage
Manual Client (Without Nuxt Composables)
For use in server routes, plugins, or non-component contexts:
import { createClient } from "@newschools/sdk";
const client = createClient({
apiKey: "ns_live_xxxxxxxxxxxxx",
baseUrl: "https://newschools.ai", // optional
});
const journeys = await client.get<Journey[]>("/journeys");
const journey = await client.get<Journey>("/journeys/my-slug");Environment Variables
| Variable | Description | Required | Default |
| --------------------------------- | ------------------------ | -------- | --------------------- |
| NUXT_PUBLIC_NEWSCHOOLS_API_KEY | Your New Schools API key | Yes | - |
| NUXT_PUBLIC_NEWSCHOOLS_BASE_URL | API base URL | No | https://newschools.ai |
Components Reference
<EntityHero>
Display entity header with cover image and branding
Props:
entity: Organisation- Entity object (Organization, Journey, etc.) (required)variant?: 'left' | 'right' | 'fullscreen' | 'horizontal'- Layout variant (default: 'left')
Slots:
sub-title- Content below entity name (overlays image)content- Main content area (position varies by variant)
Layout Behavior:
left: Grid layout (image left, content right) on tablet+, stacks on mobileright: Grid layout (content left, image right) on tablet+, stacks on mobilefullscreen: Content overlays image at bottom left (all screen sizes)horizontal: Image top with flexible height, content below (all screen sizes)
<EntityBentoGrid>
Automatically display entities in a responsive bento grid layout
Props:
items: any[]- Array of entities (journeys, teachers, waypoints, etc.) (required)key-field?: string- Custom key field for v-for (default: 'id')
Slots:
item- Scoped slot for custom item renderingitem- The entity objectindex- Item index
Auto-extracts fields:
- Title:
titleorname - Description:
descriptionorsummary - Image:
coverImageUrl,avatarUrl, orimageUrl
Layout patterns:
- 1 item: Full width
- 2 items: 2/3 + 1/3 split
- 3 items: 1 large (2x2) + 2 small stacked
- 4 items: 1 large (2x2) + 2 small + 1 wide bottom
- 5 items: 1 large + 4 small corners
- 6+ items: Mirrored pattern (repeating)
<BentoGrid>
Responsive masonry-style grid layout (advanced manual control)
<BentoGridItem>
Grid item with flexible sizing and optional explicit positioning
Props:
col-span?: number- Column span (1-3, default: 1)row-span?: number- Row span (1-2, default: 1)col-start?: number- Column start position (1-3, optional for explicit positioning)row-start?: number- Row start position (1+, optional for explicit positioning)
Note: When col-start and row-start are omitted, items use CSS Grid auto-placement. Use explicit positioning for complex layouts where auto-flow doesn't achieve the desired result.
<EntityCard>
Card component for displaying content with automatic text clamping
Props:
image-url?: string- Cover image URLimage-alt?: string- Image alt text
Slots:
title- Card title (automatically clamped to 2 lines with ellipsis)description- Card description (automatically clamped to 3 lines with ellipsis)
Example:
<EntityCard
image-url="https://example.com/image.jpg"
image-alt="Course thumbnail"
>
<template #title>
<h3>{{ journey.title }}</h3>
</template>
<template #description>
<p>{{ journey.description }}</p>
</template>
</EntityCard><EntityImage>
Optimized image component with loading states
Props:
src?: string- Image URLalt: string- Alt text
Composables Reference
useOrganization()
Access globally loaded organization data
Returns: Ref<Organisation | null>
useAsyncNewSchools(resource, params?)
Fetch data from New Schools v1 API with SSR support
Resources:
'journeys'- List all journeys'journey'- Get single journey'waypoints'- List waypoints'waypoint'- Get single waypoint'activities'- List activities'activity'- Get single activity
Returns: AsyncData<T>
License
MIT
