@newschools/sdk 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Nuxt module for integrating New Schools learning content into your applications
8
8
  🎨 **Automatic theming** - Organization brand colors applied as CSS variables
9
9
  🔒 **Type-safe** - Full TypeScript support with entity types
10
10
  ⚡ **SSR-compatible** - Built on Nuxt's `useAsyncData`
11
- 🎯 **Ready-to-use components** - OrganizationHero, EntityBentoGrid, EntityCard
11
+ 🎯 **Ready-to-use components** - EntityHero, EntityBentoGrid, EntityCard
12
12
  📐 **Smart layouts** - EntityBentoGrid automatically generates responsive layouts
13
13
  🔑 **API key authentication** - Secure access to your organization's content
14
14
 
@@ -139,13 +139,15 @@ const organization = useOrganization();
139
139
 
140
140
  ### Pre-built Components
141
141
 
142
- #### OrganizationHero
142
+ #### EntityHero
143
143
 
144
- Display organization header with cover image and branding:
144
+ Display entity header with cover image and branding. Supports 4 layout variants:
145
+
146
+ **Basic Usage:**
145
147
 
146
148
  ```vue
147
149
  <template>
148
- <OrganizationHero :org="organization">
150
+ <EntityHero :org="organization" variant="left">
149
151
  <template #sub-title>
150
152
  <p>Welcome to our learning platform</p>
151
153
  </template>
@@ -153,7 +155,7 @@ Display organization header with cover image and branding:
153
155
  <template #content>
154
156
  <div>Custom content area for CTAs, forms, etc.</div>
155
157
  </template>
156
- </OrganizationHero>
158
+ </EntityHero>
157
159
  </template>
158
160
 
159
161
  <script setup lang="ts">
@@ -161,6 +163,48 @@ const organization = useOrganization();
161
163
  </script>
162
164
  ```
163
165
 
166
+ **Layout Variants:**
167
+
168
+ - `variant="left"` (default) - Image on left, content on right (grid on tablet+, stacked on mobile)
169
+ - `variant="right"` - Content on left, image on right (grid on tablet+, stacked on mobile)
170
+ - `variant="fullscreen"` - Full screen image with content overlay at bottom left (all screen sizes)
171
+ - `variant="horizontal"` - Image on top (50vh with clamp), content below (stacked on all sizes)
172
+
173
+ **Variant Examples:**
174
+
175
+ ```vue
176
+ <!-- Fullscreen: Content overlays image -->
177
+ <EntityHero :org="organization" variant="fullscreen">
178
+ <template #sub-title>Journey subtitle</template>
179
+ <template #content>
180
+ <p>This content appears over the image, under the title.</p>
181
+ </template>
182
+ </EntityHero>
183
+
184
+ <!-- Horizontal: Image top, content bottom -->
185
+ <EntityHero :org="organization" variant="horizontal">
186
+ <template #sub-title>Course overview</template>
187
+ <template #content>
188
+ <div>Course details and enrollment form</div>
189
+ </template>
190
+ </EntityHero>
191
+
192
+ <!-- Right: Reversed grid layout -->
193
+ <EntityHero :org="organization" variant="right">
194
+ <template #sub-title>Featured content</template>
195
+ <template #content>
196
+ <button>Get Started</button>
197
+ </template>
198
+ </EntityHero>
199
+ ```
200
+
201
+ **Responsive Behavior:**
202
+
203
+ - Title and subtitle always overlay the image (all variants)
204
+ - `left` and `right` variants stack vertically on mobile (<768px)
205
+ - `fullscreen` maintains overlay layout on all screen sizes
206
+ - `horizontal` uses flexible image height: `clamp(40vh, 50vh, 60vh)`
207
+
164
208
  #### EntityBentoGrid (Recommended)
165
209
 
166
210
  Display entities in a responsive bento grid with automatic layout:
@@ -452,19 +496,26 @@ const journey = await client.get<Journey>("/journeys/my-slug");
452
496
 
453
497
  ## Components Reference
454
498
 
455
- ### `<OrganizationHero>`
499
+ ### `<EntityHero>`
456
500
 
457
- Display organization header with cover image and branding
501
+ Display entity header with cover image and branding
458
502
 
459
503
  **Props:**
460
504
 
461
505
  - `org: Organisation` - Organization object (required)
506
+ - `variant?: 'left' | 'right' | 'fullscreen' | 'horizontal'` - Layout variant (default: 'left')
462
507
 
463
508
  **Slots:**
464
509
 
465
- - `sub-title` - Content below organization name
466
- - `content` - Right-side content area
467
- - `background` - Custom background component
510
+ - `sub-title` - Content below entity name (overlays image)
511
+ - `content` - Main content area (position varies by variant)
512
+
513
+ **Layout Behavior:**
514
+
515
+ - `left`: Grid layout (image left, content right) on tablet+, stacks on mobile
516
+ - `right`: Grid layout (content left, image right) on tablet+, stacks on mobile
517
+ - `fullscreen`: Content overlays image at bottom left (all screen sizes)
518
+ - `horizontal`: Image top with flexible height, content below (all screen sizes)
468
519
 
469
520
  ### `<EntityBentoGrid>`
470
521
 
@@ -1,16 +1,21 @@
1
1
  <template>
2
- <motion.div
2
+ <component
3
+ :is="isLink ? NuxtLinkEl : motion.div"
4
+ v-bind="rootAttrs"
3
5
  class="ns-entity-card"
4
- :initial="{ opacity: 0, y: 20 }"
5
- :animate="{ opacity: 1, y: 0 }"
6
- :transition="{ duration: 0.6, ease: [0.19, 1, 0.22, 1] }"
6
+ :initial="{ opacity: 0, scale: 0.95 }"
7
+ :whileInView="{ opacity: 1, scale: 1 }"
8
+ :transition="{ duration: 0.8, delay: 0.2, ease: [0.19, 1, 0.22, 1] }"
7
9
  >
8
10
  <!-- Image Container -->
9
11
  <div class="ns-entity-card__image">
10
- <FlickeringGridBackground :max-opacity="0.05" color="#FFFFFF" />
12
+ <EntityImage
13
+ v-if="imageUrl"
14
+ :src="imageUrl"
15
+ :alt="imageAlt || 'Card image'"
16
+ />
11
17
 
12
- <!-- Cover image if available -->
13
- <EntityImage :src="imageUrl" :alt="imageAlt || 'Card image'" />
18
+ <FlickeringGridBackground v-else :max-opacity="0.05" color="#FFFFFF" />
14
19
  </div>
15
20
 
16
21
  <!-- Content overlay at bottom -->
@@ -22,7 +27,7 @@
22
27
  <slot name="description" />
23
28
  </p>
24
29
  </div>
25
- </motion.div>
30
+ </component>
26
31
  </template>
27
32
 
28
33
  <script setup lang="ts">
@@ -36,9 +41,18 @@ interface Props {
36
41
 
37
42
  /** Image alt text */
38
43
  imageAlt?: string;
44
+
45
+ /** Navigation link (optional - makes card a NuxtLink) */
46
+ to?: string;
39
47
  }
40
48
 
41
- defineProps<Props>();
49
+ const props = defineProps<Props>();
50
+
51
+ const NuxtLinkEl = resolveComponent("NuxtLink");
52
+ const isLink = computed(() => !!props.to);
53
+
54
+ // Only pass valid attributes to the rendered root
55
+ const rootAttrs = computed(() => (isLink.value ? { to: props.to } : {}));
42
56
  </script>
43
57
 
44
58
  <style scoped>
@@ -49,9 +63,8 @@ defineProps<Props>();
49
63
  */
50
64
 
51
65
  .ns-entity-card {
52
- /* Glassmorphism effect - same as OrganizationHero glass container */
66
+ position: relative;
53
67
  background: rgba(255, 255, 255, 0.08);
54
- backdrop-filter: blur(20px);
55
68
  border-radius: var(--ns-border-radius-md);
56
69
  border: 1px solid rgba(255, 255, 255, 0.15);
57
70
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
@@ -67,7 +80,6 @@ defineProps<Props>();
67
80
  cursor: pointer;
68
81
  }
69
82
 
70
- /* Hover effect */
71
83
  .ns-entity-card:hover {
72
84
  background: rgba(255, 255, 255, 0.12);
73
85
  border-color: rgba(255, 255, 255, 0.25);
@@ -75,7 +87,6 @@ defineProps<Props>();
75
87
  transform: translateY(-4px);
76
88
  }
77
89
 
78
- /* Active state */
79
90
  .ns-entity-card:active {
80
91
  transform: translateY(-2px);
81
92
  }
@@ -88,7 +99,8 @@ defineProps<Props>();
88
99
  position: relative;
89
100
  width: 100%;
90
101
  height: 100%; /* Fill parent height (works for bento grid cells) */
91
- border-radius: var(--ns-border-radius-md);
102
+ border-radius: var(--ns-border-radius-sm);
103
+ overflow: hidden;
92
104
 
93
105
  /* Layout */
94
106
  display: flex;
@@ -0,0 +1,236 @@
1
+ <template>
2
+ <!-- Glass Container with Layout Variants -->
3
+ <motion.div
4
+ class="ns-entity-hero__glass-container"
5
+ :class="`ns-entity-hero--${variant}`"
6
+ :initial="{ opacity: 0, scale: 0.95 }"
7
+ :animate="{ opacity: 1, scale: 1 }"
8
+ :transition="{ duration: 0.8, delay: 0.2, ease: [0.19, 1, 0.22, 1] }"
9
+ >
10
+ <!-- Image Container -->
11
+ <div class="ns-entity-hero__image">
12
+ <!-- Cover image if available -->
13
+ <EntityImage
14
+ v-if="org.coverImageUrl"
15
+ :src="org.coverImageUrl"
16
+ :alt="`${org.name} cover image`"
17
+ />
18
+
19
+ <FlickeringGridBackground v-else :max-opacity="0.05" color="#FFFFFF" />
20
+
21
+ <!-- Title and subtitle overlay (always over image) -->
22
+ <div class="ns-entity-hero__image-content">
23
+ <h1 class="ns-entity-hero__image-title">{{ org.name }}</h1>
24
+
25
+ <p class="ns-entity-hero__sub-title" v-if="$slots['sub-title']">
26
+ <slot name="sub-title" />
27
+ </p>
28
+
29
+ <!-- Content slot for fullscreen variant (under title) -->
30
+ <div
31
+ v-if="variant === 'fullscreen' && $slots['content']"
32
+ class="ns-entity-hero__content-overlay"
33
+ >
34
+ <slot name="content" />
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <!-- Content Area (for non-fullscreen variants) -->
40
+ <div
41
+ v-if="variant !== 'fullscreen' && $slots['content']"
42
+ class="ns-entity-hero__content"
43
+ >
44
+ <slot name="content" />
45
+ </div>
46
+ </motion.div>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { motion } from "motion-v";
51
+ import FlickeringGridBackground from "../internal/components/FlickeringGridBackground.vue";
52
+ import EntityImage from "./EntityImage.vue";
53
+ import type { Organisation } from "../../types/entities";
54
+
55
+ interface Props {
56
+ /** Organization object */
57
+ org: Organisation;
58
+
59
+ /** Layout variant */
60
+ variant?: "left" | "right" | "fullscreen" | "horizontal";
61
+ }
62
+
63
+ withDefaults(defineProps<Props>(), {
64
+ variant: "left",
65
+ });
66
+ </script>
67
+
68
+ <style scoped>
69
+ /**
70
+ * Entity Hero Component Styles
71
+ * Glassy container with multiple layout variants
72
+ * Uses New Schools SDK tokens (--ns-*)
73
+ */
74
+
75
+ /* ============================================ */
76
+ /* GLASS CONTAINER - BASE */
77
+ /* ============================================ */
78
+
79
+ .ns-entity-hero__glass-container {
80
+ position: relative;
81
+ width: 100%;
82
+ background: rgba(255, 255, 255, 0.08);
83
+ border-radius: var(--ns-border-radius-md);
84
+ border: 1px solid rgba(255, 255, 255, 0.15);
85
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
86
+ overflow: hidden;
87
+
88
+ /* Mobile first: flex column (stack) */
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: var(--ns-spacing-md);
92
+ padding: var(--ns-spacing-md);
93
+ }
94
+
95
+ /* ============================================ */
96
+ /* VARIANT: FULLSCREEN */
97
+ /* ============================================ */
98
+
99
+ .ns-entity-hero--fullscreen {
100
+ height: calc(100dvh - var(--ns-spacing-2xl));
101
+ }
102
+
103
+ .ns-entity-hero--fullscreen .ns-entity-hero__image {
104
+ flex: 1;
105
+ height: 100%;
106
+ }
107
+
108
+ /* ============================================ */
109
+ /* VARIANT: LEFT (Default) */
110
+ /* ============================================ */
111
+
112
+ /* Mobile: stacked (default flex-column) */
113
+ .ns-entity-hero--left {
114
+ height: calc(100dvh - var(--ns-spacing-2xl));
115
+ }
116
+
117
+ /* Tablet+: Grid with image left */
118
+ @media (min-width: 768px) {
119
+ .ns-entity-hero--left {
120
+ display: grid;
121
+ grid-template-columns: 1.5fr 1fr;
122
+ gap: var(--ns-spacing-xl);
123
+ }
124
+ }
125
+
126
+ /* ============================================ */
127
+ /* VARIANT: RIGHT */
128
+ /* ============================================ */
129
+
130
+ /* Mobile: stacked (default flex-column) */
131
+ .ns-entity-hero--right {
132
+ height: calc(100dvh - var(--ns-spacing-2xl));
133
+ }
134
+
135
+ /* Tablet+: Grid with image right */
136
+ @media (min-width: 768px) {
137
+ .ns-entity-hero--right {
138
+ display: grid;
139
+ grid-template-columns: 1fr 1.5fr;
140
+ gap: var(--ns-spacing-xl);
141
+ }
142
+
143
+ .ns-entity-hero--right .ns-entity-hero__image {
144
+ order: 2;
145
+ }
146
+
147
+ .ns-entity-hero--right .ns-entity-hero__content {
148
+ order: 1;
149
+ }
150
+ }
151
+
152
+ /* ============================================ */
153
+ /* VARIANT: HORIZONTAL */
154
+ /* ============================================ */
155
+
156
+ .ns-entity-hero--horizontal {
157
+ min-height: calc(100dvh - var(--ns-spacing-2xl));
158
+ }
159
+
160
+ .ns-entity-hero--horizontal .ns-entity-hero__image {
161
+ height: clamp(40vh, 50vh, 60vh);
162
+ }
163
+
164
+ .ns-entity-hero--horizontal .ns-entity-hero__content {
165
+ flex: 1;
166
+ }
167
+
168
+ /* ============================================ */
169
+ /* IMAGE CONTAINER */
170
+ /* ============================================ */
171
+
172
+ .ns-entity-hero__image {
173
+ border-radius: var(--ns-border-radius-md);
174
+ overflow: hidden;
175
+ position: relative;
176
+ min-height: 50vh;
177
+
178
+ background: linear-gradient(
179
+ 180deg,
180
+ var(--ns-color-grey-900, #2d3748) 0%,
181
+ var(--ns-color-black, #1a202c) 100%
182
+ );
183
+
184
+ display: flex;
185
+ align-items: flex-end;
186
+ justify-content: flex-start;
187
+ }
188
+
189
+ /* Tablet+: auto height for grid layouts */
190
+ @media (min-width: 768px) {
191
+ .ns-entity-hero--left .ns-entity-hero__image,
192
+ .ns-entity-hero--right .ns-entity-hero__image {
193
+ min-height: auto;
194
+ }
195
+ }
196
+
197
+ /* ============================================ */
198
+ /* IMAGE CONTENT OVERLAY */
199
+ /* ============================================ */
200
+
201
+ .ns-entity-hero__image-content {
202
+ position: absolute;
203
+ bottom: 0;
204
+ left: 0;
205
+ right: 0;
206
+ padding: var(--ns-spacing-xl);
207
+ z-index: 3;
208
+ }
209
+
210
+ .ns-entity-hero__image-title {
211
+ margin: 0 0 var(--ns-spacing-sm) 0;
212
+ color: var(--ns-color-white);
213
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
214
+ }
215
+
216
+ .ns-entity-hero__sub-title {
217
+ margin: 0 0 var(--ns-spacing-md) 0;
218
+ color: var(--ns-color-white);
219
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
220
+ }
221
+
222
+ /* Content overlay for fullscreen variant (under title/subtitle) */
223
+ .ns-entity-hero__content-overlay {
224
+ margin-top: var(--ns-spacing-lg);
225
+ }
226
+
227
+ /* ============================================ */
228
+ /* CONTENT AREA (Non-fullscreen variants) */
229
+ /* ============================================ */
230
+
231
+ .ns-entity-hero__content {
232
+ display: flex;
233
+ flex-direction: column;
234
+ padding: var(--ns-spacing-md);
235
+ }
236
+ </style>
@@ -37,8 +37,8 @@ interface Props {
37
37
  }
38
38
 
39
39
  const props = withDefaults(defineProps<Props>(), {
40
- blurLayers: 5,
41
- blurIntensity: 0.2,
40
+ blurLayers: 4,
41
+ blurIntensity: 0.3,
42
42
  });
43
43
 
44
44
  const segmentSize = 1 / (props.blurLayers + 1);
@@ -70,18 +70,11 @@ const getBlurLayerStyle = (index: number) => {
70
70
  */
71
71
 
72
72
  .ns-entity-image {
73
- position: absolute;
74
- top: 0;
75
- left: 0;
76
73
  width: 100%;
77
74
  height: 100%;
78
- z-index: 1;
79
75
  }
80
76
 
81
77
  .ns-entity-image__picture {
82
- position: absolute;
83
- top: 0;
84
- left: 0;
85
78
  width: 100%;
86
79
  height: 100%;
87
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newschools/sdk",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "New Schools SDK - Multi-framework components and modules for integrating New Schools learning content",
5
5
  "type": "module",
6
6
  "main": "./nuxt/src/module.ts",
@@ -1,162 +0,0 @@
1
- <template>
2
- <!-- Glass Container with Grid Layout -->
3
- <motion.div
4
- class="ns-org-hero__glass-container"
5
- :initial="{ opacity: 0, scale: 0.95 }"
6
- :animate="{ opacity: 1, scale: 1 }"
7
- :transition="{ duration: 0.8, delay: 0.2, ease: [0.19, 1, 0.22, 1] }"
8
- >
9
- <!-- Image Container (Left) -->
10
- <div class="ns-org-hero__image">
11
- <FlickeringGridBackground :max-opacity="0.05" color="#FFFFFF" />
12
-
13
- <!-- Cover image if available -->
14
- <EntityImage :src="org.coverImageUrl" :alt="`${org.name} cover image`" />
15
-
16
- <!-- Organisation name overlay -->
17
- <div class="ns-org-hero__image-content">
18
- <h1 class="ns-org-hero__image-title">{{ org.name }}</h1>
19
-
20
- <p class="ns-org-hero__sub-title">
21
- <slot name="sub-title" v-if="$slots['sub-title']" />
22
- </p>
23
- </div>
24
- </div>
25
-
26
- <!-- Content Area (Right) -->
27
- <div class="ns-org-hero__content" v-if="$slots['content']">
28
- <slot name="content" />
29
- </div>
30
- </motion.div>
31
- </template>
32
-
33
- <script setup lang="ts">
34
- import { motion } from "motion-v";
35
- import FlickeringGridBackground from "../internal/components/FlickeringGridBackground.vue";
36
- import EntityImage from "./EntityImage.vue";
37
- import type { Organisation } from "../../types/entities";
38
-
39
- interface Props {
40
- /** Organization object */
41
- org: Organisation;
42
- }
43
-
44
- defineProps<Props>();
45
- </script>
46
-
47
- <style scoped>
48
- /**
49
- * Organization Hero Component Styles
50
- * Glassy container with grid layout
51
- * Uses New Schools SDK tokens (--ns-*)
52
- */
53
-
54
- /* ============================================ */
55
- /* GLASS CONTAINER WITH GRID LAYOUT */
56
- /* ============================================ */
57
-
58
- .ns-org-hero__glass-container {
59
- width: 100%;
60
- height: calc(100dvh - var(--ns-spacing-2xl));
61
- /* Glassmorphism effect */
62
- background: rgba(255, 255, 255, 0.08);
63
- backdrop-filter: blur(20px);
64
- border-radius: var(--ns-border-radius-md);
65
- border: 1px solid rgba(255, 255, 255, 0.15);
66
- box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
67
-
68
- /* Mobile first: flex column */
69
- display: flex;
70
- flex-direction: column;
71
- gap: var(--ns-spacing-md);
72
- padding: var(--ns-spacing-md);
73
- overflow: hidden;
74
- }
75
-
76
- /* Tablet and up: Grid layout */
77
- @media (min-width: 768px) {
78
- .ns-org-hero__glass-container {
79
- display: grid;
80
- grid-template-columns: 1.5fr 1fr;
81
- gap: var(--ns-spacing-xl);
82
- padding: var(--ns-spacing-lg);
83
- }
84
- }
85
-
86
- /* ============================================ */
87
- /* IMAGE CONTAINER (LEFT) */
88
- /* ============================================ */
89
-
90
- .ns-org-hero__image {
91
- /* Apple card style - rounded corners */
92
- border-radius: var(--ns-border-radius-md);
93
- overflow: hidden;
94
- position: relative;
95
-
96
- /* Mobile: ensure minimum height for empty state */
97
- min-height: 100%;
98
-
99
- background: linear-gradient(
100
- 180deg,
101
- var(--ns-color-grey-900, #2d3748) 0%,
102
- var(--ns-color-black, #1a202c) 100%
103
- );
104
-
105
- /* Layout */
106
- display: flex;
107
- align-items: center;
108
- justify-content: center;
109
- }
110
-
111
- /* Tablet and up: auto height (grid handles it) */
112
- @media (min-width: 768px) {
113
- .ns-org-hero__image {
114
- min-height: auto;
115
- flex: initial;
116
- }
117
- }
118
-
119
- .ns-org-hero__image--no-image {
120
- position: absolute;
121
- top: 50%;
122
- left: 50%;
123
- transform: translate(-50%, -50%);
124
- color: white;
125
- z-index: 2;
126
- }
127
-
128
- .ns-org-hero__image-content {
129
- position: absolute;
130
- bottom: 0;
131
- left: 0;
132
- right: 0;
133
- padding: var(--ns-spacing-xl);
134
- z-index: 3;
135
- }
136
-
137
- .ns-org-hero__image-title {
138
- margin: 0;
139
- text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
140
- }
141
-
142
- .ns-org-hero__sub-title,
143
- .ns-org-hero__image-title {
144
- color: var(--ns-color-white);
145
- color: var(--ns-color-white);
146
- }
147
-
148
- .ns-org-hero__sub-title {
149
- margin: 0;
150
- }
151
-
152
- /* ============================================ */
153
- /* CONTENT AREA (RIGHT) */
154
- /* ============================================ */
155
-
156
- .ns-org-hero__content {
157
- /* Content area for text, purchase form, etc. */
158
- display: flex;
159
- flex-direction: column;
160
- padding: var(--ns-spacing-md);
161
- }
162
- </style>