@netlify/spark-ui 1.9.1 → 1.10.0
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/dist/assets/AskNetlify.css +1 -1
- package/dist/chunks/{ChatInterface.BD7rlv4H.js → ChatInterface.EQAPs01R.js} +411 -392
- package/dist/components/preact/AskNetlify/AskNetlify.js +1 -1
- package/dist/components/preact/AskNetlify/components/ChatInterface.js +1 -1
- package/dist/utils/fetch-svg.d.ts +11 -0
- package/dist/utils/kebab.d.ts +17 -0
- package/dist/utils/types.d.ts +13 -0
- package/package.json +1 -1
- package/packages/components/Blockquote.astro +63 -10
- package/packages/components/TestimonialSlider.astro +110 -0
- package/packages/components/preact/AskNetlify/AskNetlify.css +2 -15
- package/packages/styles/animations.css +26 -0
- package/packages/styles/tokens.css +12 -0
- package/packages/utils/fetch-svg.ts +28 -0
- package/packages/utils/kebab.ts +22 -0
- package/packages/utils/types.ts +14 -0
- package/packages/components/Testimonial.astro +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { u as t } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
|
|
2
2
|
import { A as a } from "../../../chunks/hooks.module.DGkdD5n9.js";
|
|
3
|
-
import { C as s, K as i } from "../../../chunks/ChatInterface.
|
|
3
|
+
import { C as s, K as i } from "../../../chunks/ChatInterface.EQAPs01R.js";
|
|
4
4
|
import { A as c } from "../../../chunks/analytics.Ch1vuuOl.js";
|
|
5
5
|
import '../../../assets/AskNetlify.css';function k(o) {
|
|
6
6
|
const e = a(new c());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
|
|
2
2
|
import "../../../../chunks/hooks.module.DGkdD5n9.js";
|
|
3
|
-
import { C as f } from "../../../../chunks/ChatInterface.
|
|
3
|
+
import { C as f } from "../../../../chunks/ChatInterface.EQAPs01R.js";
|
|
4
4
|
import "./QuestionList/questions.js";
|
|
5
5
|
import "./QuestionList/QuestionList.js";
|
|
6
6
|
import "./BackButton.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches an SVG from a remote URL and strips any `fill` attributes
|
|
3
|
+
* to allow styling the SVG via CSS (e.g., using `currentColor` or masks).
|
|
4
|
+
*
|
|
5
|
+
* If the fetch fails or the response is not OK, logs the error and returns an empty string.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} url - The URL of the SVG to fetch.
|
|
8
|
+
* @returns {Promise<string>} The cleaned SVG markup with `fill` attributes removed,
|
|
9
|
+
* or an empty string if the fetch fails.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fetchSvg(url: string): Promise<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a given string to kebab-case.
|
|
3
|
+
*
|
|
4
|
+
* - Converts camelCase to kebab-case.
|
|
5
|
+
* - Replaces spaces and underscores with dashes.
|
|
6
|
+
* - Removes all non-alphanumeric characters except dashes.
|
|
7
|
+
* - Converts the entire string to lowercase.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} str - The input string to convert.
|
|
10
|
+
* @returns {string} The kebab-cased version of the input string.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* kebabCase('HelloWorld') // 'hello-world'
|
|
14
|
+
* kebabCase('some_text Here') // 'some-text-here'
|
|
15
|
+
* kebabCase('already-kebab-case') // 'already-kebab-case'
|
|
16
|
+
*/
|
|
17
|
+
export declare const kebabCase: (str: string) => string;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -12,3 +12,16 @@ export interface ImageProps {
|
|
|
12
12
|
width: string;
|
|
13
13
|
height: string;
|
|
14
14
|
}
|
|
15
|
+
export interface Testimonial {
|
|
16
|
+
quote?: string;
|
|
17
|
+
author?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
company?: string;
|
|
20
|
+
logoSrc?: string;
|
|
21
|
+
avatarSrc?: string;
|
|
22
|
+
cta?: {
|
|
23
|
+
id?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -3,10 +3,20 @@ interface Props {
|
|
|
3
3
|
quote?: string;
|
|
4
4
|
author?: string;
|
|
5
5
|
title?: string;
|
|
6
|
-
|
|
6
|
+
avatarSrc?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
variant?: 'default' | 'simple' | 'testimonial';
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
quote,
|
|
13
|
+
variant = 'default',
|
|
14
|
+
author,
|
|
15
|
+
title,
|
|
16
|
+
avatarSrc,
|
|
17
|
+
class: className,
|
|
18
|
+
...rest
|
|
19
|
+
} = Astro.props as Props;
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* If quote is added via <slot />, trim surrounding whitespace so that quotation marks properly wrap inline with the text string.
|
|
@@ -25,8 +35,11 @@ if (Astro.slots.has('default')) {
|
|
|
25
35
|
{
|
|
26
36
|
author || title ? (
|
|
27
37
|
<figcaption>
|
|
28
|
-
{
|
|
29
|
-
|
|
38
|
+
{avatarSrc && <img src={avatarSrc} alt={`Avatar of ${author}`} />}
|
|
39
|
+
<div class="n-blockquote-author-info">
|
|
40
|
+
{author && <span class="n-blockquote-author">{author}</span>}
|
|
41
|
+
{title && <span class="n-blockquote-title">{title}</span>}
|
|
42
|
+
</div>
|
|
30
43
|
</figcaption>
|
|
31
44
|
) : null
|
|
32
45
|
}
|
|
@@ -60,13 +73,15 @@ if (Astro.slots.has('default')) {
|
|
|
60
73
|
|
|
61
74
|
blockquote::before {
|
|
62
75
|
content: open-quote;
|
|
76
|
+
color: var(--blockquote-accent-color);
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
blockquote::after {
|
|
66
80
|
content: close-quote;
|
|
81
|
+
color: var(--blockquote-accent-color);
|
|
67
82
|
}
|
|
68
83
|
|
|
69
|
-
|
|
84
|
+
.n-blockquote-author-info {
|
|
70
85
|
display: flex;
|
|
71
86
|
gap: 0.2em;
|
|
72
87
|
font-size: var(--font-size-text-xs);
|
|
@@ -75,29 +90,67 @@ if (Astro.slots.has('default')) {
|
|
|
75
90
|
|
|
76
91
|
/* Variants */
|
|
77
92
|
[data-blockquote-variant='default'] {
|
|
78
|
-
|
|
93
|
+
.n-blockquote-author-info {
|
|
79
94
|
flex-direction: column;
|
|
80
95
|
font-weight: var(--font-weight-regular);
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
|
|
98
|
+
.n-blockquote-author-info > * {
|
|
84
99
|
display: block;
|
|
85
100
|
font-family: var(--font-family-mono);
|
|
86
101
|
text-transform: uppercase;
|
|
87
102
|
}
|
|
88
103
|
|
|
89
|
-
|
|
104
|
+
.n-blockquote-author-info > *:last-child:not(:only-child) {
|
|
90
105
|
color: var(--color-text-weak);
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
[data-blockquote-variant='simple'] {
|
|
95
|
-
|
|
110
|
+
.n-blockquote-author-info > * {
|
|
96
111
|
font-size: var(--font-size-text-sm);
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
|
|
114
|
+
.n-blockquote-author-info > *:not(:last-child)::after {
|
|
100
115
|
content: ',';
|
|
101
116
|
}
|
|
102
117
|
}
|
|
118
|
+
|
|
119
|
+
[data-blockquote-variant='testimonial'] {
|
|
120
|
+
border-inline-start: none;
|
|
121
|
+
|
|
122
|
+
font-size: var(--font-size-display-lg);
|
|
123
|
+
font-weight: var(--font-weight-bold);
|
|
124
|
+
line-height: var(--line-height-heading);
|
|
125
|
+
|
|
126
|
+
.n-blockquote-author-info {
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
font-weight: var(--font-weight-regular);
|
|
129
|
+
font-family: var(--font-family-mono);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
figcaption {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: var(--_gap);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
figcaption img {
|
|
139
|
+
aspect-ratio: 1 / 1;
|
|
140
|
+
object-fit: cover;
|
|
141
|
+
width: 3.125rem;
|
|
142
|
+
height: auto;
|
|
143
|
+
clip-path: circle();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.n-blockquote-author-info {
|
|
147
|
+
font-size: var(--font-size-text-sm);
|
|
148
|
+
font-weight: var(--font-weight-regular);
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.n-blockquote-title {
|
|
153
|
+
color: var(--color-text-weak);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
103
156
|
</style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { fetchSvg } from '../utils/fetch-svg.ts';
|
|
3
|
+
import { kebabCase } from '../utils/kebab';
|
|
4
|
+
import Blockquote from './Blockquote.astro';
|
|
5
|
+
import Button from './Button.astro';
|
|
6
|
+
import { Testimonial } from '../utils/types';
|
|
7
|
+
|
|
8
|
+
import Tabs from '@netlify/spark-ui/components/Tabs.astro';
|
|
9
|
+
import Tab from '@netlify/spark-ui/components/Tab.astro';
|
|
10
|
+
import TabList from '@netlify/spark-ui/components/TabList.astro';
|
|
11
|
+
import TabPanel from '@netlify/spark-ui/components/TabPanel.astro';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
items?: Testimonial[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { items } = Astro.props as Props;
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<Tabs class="n-testimonial-slider">
|
|
21
|
+
<TabList class="n-testimonial-logos n-cluster">
|
|
22
|
+
{
|
|
23
|
+
items?.map(({ company, logoSrc }, index) => (
|
|
24
|
+
<Tab
|
|
25
|
+
class="n-logo-btn"
|
|
26
|
+
data-id={company && kebabCase(company)}
|
|
27
|
+
isSelected={index === 0}
|
|
28
|
+
aria-label={`Show testimonial from ${company}`}
|
|
29
|
+
>
|
|
30
|
+
{logoSrc && <slot set:html={fetchSvg(logoSrc)} />}
|
|
31
|
+
</Tab>
|
|
32
|
+
))
|
|
33
|
+
}
|
|
34
|
+
</TabList>
|
|
35
|
+
{
|
|
36
|
+
items?.map((testimonial, index) => (
|
|
37
|
+
<TabPanel>
|
|
38
|
+
<div class="n-testimonial-quote-body n-flow">
|
|
39
|
+
<Blockquote
|
|
40
|
+
variant="testimonial"
|
|
41
|
+
aria-hidden={index !== 0}
|
|
42
|
+
class={`n-testimonial-quote n-fadeInUp ${index === 0 ? 'is-active' : ''}`}
|
|
43
|
+
quote={testimonial.quote}
|
|
44
|
+
author={testimonial.author}
|
|
45
|
+
title={testimonial.title}
|
|
46
|
+
avatarSrc={testimonial.avatarSrc}
|
|
47
|
+
/>
|
|
48
|
+
{testimonial.cta && (
|
|
49
|
+
<Button
|
|
50
|
+
id={`cta-${testimonial.cta.id}`}
|
|
51
|
+
href={testimonial.cta.url}
|
|
52
|
+
class="n-testimonial-cta n-fadeIn"
|
|
53
|
+
>
|
|
54
|
+
{testimonial.cta.label}
|
|
55
|
+
</Button>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
</TabPanel>
|
|
59
|
+
))
|
|
60
|
+
}
|
|
61
|
+
</Tabs>
|
|
62
|
+
|
|
63
|
+
<style>
|
|
64
|
+
@import url('../styles/animations.css');
|
|
65
|
+
|
|
66
|
+
.n-testimonial-slider {
|
|
67
|
+
--_spacing: var(--space-xl);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.n-testimonial-quote-body {
|
|
71
|
+
--n-flow-space: var(--_spacing);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.n-testimonial-logos {
|
|
75
|
+
--n-cluster-column-gap: var(--space-l);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[role='tablist'] {
|
|
79
|
+
margin-block-end: var(--_spacing);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.n-testimonial-cta {
|
|
83
|
+
width: fit-content;
|
|
84
|
+
animation-delay: 200ms;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.n-logo-btn {
|
|
88
|
+
background: none;
|
|
89
|
+
border: none;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
display: flex;
|
|
92
|
+
place-items: center;
|
|
93
|
+
outline-offset: 0.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.n-logo-btn :global(svg) {
|
|
97
|
+
width: 6.25rem;
|
|
98
|
+
height: auto;
|
|
99
|
+
fill: var(--testimonial-logo-inactive-color);
|
|
100
|
+
transition: fill 300ms ease-out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.n-logo-btn:hover :global(svg) {
|
|
104
|
+
fill: var(--testimonial-logo-hover-color);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.n-logo-btn[aria-selected='true'] :global(svg) {
|
|
108
|
+
fill: var(--blockquote-accent-color);
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import url('../../../styles/animations.css');
|
|
2
|
+
|
|
1
3
|
/* ----------------------------- */
|
|
2
4
|
/* Layout Containers */
|
|
3
5
|
/* ----------------------------- */
|
|
@@ -242,21 +244,6 @@
|
|
|
242
244
|
/* ----------------------------- */
|
|
243
245
|
/* Animations and Keyframes */
|
|
244
246
|
/* ----------------------------- */
|
|
245
|
-
@keyframes fadeIn {
|
|
246
|
-
0% {
|
|
247
|
-
opacity: 0;
|
|
248
|
-
}
|
|
249
|
-
100% {
|
|
250
|
-
opacity: 1;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
@keyframes fadeInUp {
|
|
255
|
-
to {
|
|
256
|
-
opacity: 1;
|
|
257
|
-
transform: translateY(0);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
247
|
|
|
261
248
|
@keyframes askEyeBlink {
|
|
262
249
|
0%,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.n-fadeIn {
|
|
2
|
+
opacity: 0;
|
|
3
|
+
animation: fadeIn var(--animation-duration, 300ms) ease-out forwards;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.n-fadeInUp {
|
|
7
|
+
opacity: 0;
|
|
8
|
+
transform: translateY(var(--animation-transformY, 5px));
|
|
9
|
+
animation: fadeInUp var(--animation-duration, 300ms) ease-out forwards;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes fadeIn {
|
|
13
|
+
0% {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
}
|
|
16
|
+
100% {
|
|
17
|
+
opacity: 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes fadeInUp {
|
|
22
|
+
to {
|
|
23
|
+
opacity: 1;
|
|
24
|
+
transform: translateY(0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
--badge-bg-color: var(--neutral-100);
|
|
8
8
|
--badge-border-color: var(--neutral-300);
|
|
9
9
|
--badge-text-color: var(--neutral-500);
|
|
10
|
+
--blockquote-accent-color: var(--blue-700);
|
|
10
11
|
--blue-000: color(srgb 0.9294117647058824 0.9568627450980393 1);
|
|
11
12
|
--blue-100: color(srgb 0.803921568627451 0.8862745098039215 1);
|
|
12
13
|
--blue-200: color(srgb 0.7098039215686275 0.8235294117647058 0.984313725490196);
|
|
@@ -196,6 +197,8 @@
|
|
|
196
197
|
--teal-700: color(srgb 0.00392156862745098 0.4117647058823529 0.40784313725490196);
|
|
197
198
|
--teal-800: color(srgb 0.00392156862745098 0.2823529411764706 0.2784313725490196);
|
|
198
199
|
--teal-900: color(srgb 0.047058823529411764 0.16470588235294117 0.16470588235294117);
|
|
200
|
+
--testimonial-logo-hover-color: var(--neutral-500);
|
|
201
|
+
--testimonial-logo-inactive-color: var(--neutral-400);
|
|
199
202
|
--title-text-color: color(srgb 0.32941176470588235 0.35294117647058826 0.3803921568627451 / 0.7490196078431373);
|
|
200
203
|
}
|
|
201
204
|
|
|
@@ -205,6 +208,7 @@
|
|
|
205
208
|
--badge-bg-color: var(--neutral-700);
|
|
206
209
|
--badge-border-color: var(--neutral-600);
|
|
207
210
|
--badge-text-color: var(--neutral-100);
|
|
211
|
+
--blockquote-accent-color: var(--teal-200);
|
|
208
212
|
--button-outline-bg-color: color(srgb 0 0 0 / 0);
|
|
209
213
|
--button-outline-bg-color-hover: color(srgb 0.6509803921568628 1 0.9921568627450981 / 0.09803921568627451);
|
|
210
214
|
--button-outline-stroke-color: var(--teal-200);
|
|
@@ -277,6 +281,8 @@
|
|
|
277
281
|
--tabbed-content-border-color: var(--color-icon);
|
|
278
282
|
--tabbed-content-code-bg-color: var(--neutral-900);
|
|
279
283
|
--tabbed-content-tab-text-color-active: var(--color-icon);
|
|
284
|
+
--testimonial-logo-hover-color: var(--neutral-300);
|
|
285
|
+
--testimonial-logo-inactive-color: var(--neutral-400);
|
|
280
286
|
--title-text-color: color(srgb 0.8352941176470589 1 0.996078431372549 / 0.7490196078431373);
|
|
281
287
|
}
|
|
282
288
|
}
|
|
@@ -286,6 +292,7 @@
|
|
|
286
292
|
--badge-bg-color: var(--neutral-700);
|
|
287
293
|
--badge-border-color: var(--neutral-600);
|
|
288
294
|
--badge-text-color: var(--neutral-100);
|
|
295
|
+
--blockquote-accent-color: var(--teal-200);
|
|
289
296
|
--button-outline-bg-color: color(srgb 0 0 0 / 0);
|
|
290
297
|
--button-outline-bg-color-hover: color(srgb 0.6509803921568628 1 0.9921568627450981 / 0.09803921568627451);
|
|
291
298
|
--button-outline-stroke-color: var(--teal-200);
|
|
@@ -358,6 +365,8 @@
|
|
|
358
365
|
--tabbed-content-border-color: var(--color-icon);
|
|
359
366
|
--tabbed-content-code-bg-color: var(--neutral-900);
|
|
360
367
|
--tabbed-content-tab-text-color-active: var(--color-icon);
|
|
368
|
+
--testimonial-logo-hover-color: var(--neutral-300);
|
|
369
|
+
--testimonial-logo-inactive-color: var(--neutral-400);
|
|
361
370
|
--title-text-color: color(srgb 0.8352941176470589 1 0.996078431372549 / 0.7490196078431373);
|
|
362
371
|
}
|
|
363
372
|
|
|
@@ -366,6 +375,7 @@
|
|
|
366
375
|
--badge-bg-color: var(--neutral-100);
|
|
367
376
|
--badge-border-color: var(--neutral-300);
|
|
368
377
|
--badge-text-color: var(--neutral-500);
|
|
378
|
+
--blockquote-accent-color: var(--blue-700);
|
|
369
379
|
--button-outline-bg-color: color(srgb 0 0 0 / 0);
|
|
370
380
|
--button-outline-bg-color-hover: color(srgb 0.19607843137254902 0.9019607843137255 0.8862745098039215 / 0.2);
|
|
371
381
|
--button-outline-stroke-color: var(--neutral-800);
|
|
@@ -437,6 +447,8 @@
|
|
|
437
447
|
--tabbed-content-border-color: var(--color-icon);
|
|
438
448
|
--tabbed-content-code-bg-color: var(--neutral-900);
|
|
439
449
|
--tabbed-content-tab-text-color-active: var(--color-icon);
|
|
450
|
+
--testimonial-logo-hover-color: var(--neutral-500);
|
|
451
|
+
--testimonial-logo-inactive-color: var(--neutral-400);
|
|
440
452
|
--title-text-color: color(srgb 0.32941176470588235 0.35294117647058826 0.3803921568627451 / 0.7490196078431373);
|
|
441
453
|
}
|
|
442
454
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches an SVG from a remote URL and strips any `fill` attributes
|
|
3
|
+
* to allow styling the SVG via CSS (e.g., using `currentColor` or masks).
|
|
4
|
+
*
|
|
5
|
+
* If the fetch fails or the response is not OK, logs the error and returns an empty string.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} url - The URL of the SVG to fetch.
|
|
8
|
+
* @returns {Promise<string>} The cleaned SVG markup with `fill` attributes removed,
|
|
9
|
+
* or an empty string if the fetch fails.
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchSvg(url: string): Promise<string> {
|
|
12
|
+
function stripFillAttributes(svg: string): string {
|
|
13
|
+
return svg.replace(/fill\s*=\s*["'][^"']*["']/gi, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(url);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
console.error(`[fetchSvg] Failed to fetch: ${url}`);
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
const raw = await res.text();
|
|
23
|
+
return stripFillAttributes(raw);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`[fetchSvg] Error fetching ${url}:`, err);
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a given string to kebab-case.
|
|
3
|
+
*
|
|
4
|
+
* - Converts camelCase to kebab-case.
|
|
5
|
+
* - Replaces spaces and underscores with dashes.
|
|
6
|
+
* - Removes all non-alphanumeric characters except dashes.
|
|
7
|
+
* - Converts the entire string to lowercase.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} str - The input string to convert.
|
|
10
|
+
* @returns {string} The kebab-cased version of the input string.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* kebabCase('HelloWorld') // 'hello-world'
|
|
14
|
+
* kebabCase('some_text Here') // 'some-text-here'
|
|
15
|
+
* kebabCase('already-kebab-case') // 'already-kebab-case'
|
|
16
|
+
*/
|
|
17
|
+
export const kebabCase = (str: string) =>
|
|
18
|
+
str
|
|
19
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase → kebab-case
|
|
20
|
+
.replace(/[\s_]+/g, '-') // spaces & underscores → dashes
|
|
21
|
+
.replace(/[^a-zA-Z0-9-]/g, '') // remove non-alphanumeric except dash
|
|
22
|
+
.toLowerCase();
|
package/packages/utils/types.ts
CHANGED
|
@@ -14,3 +14,17 @@ export interface ImageProps {
|
|
|
14
14
|
width: string;
|
|
15
15
|
height: string;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
export interface Testimonial {
|
|
19
|
+
quote?: string;
|
|
20
|
+
author?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
company?: string;
|
|
23
|
+
logoSrc?: string;
|
|
24
|
+
avatarSrc?: string;
|
|
25
|
+
cta?: {
|
|
26
|
+
id?: string;
|
|
27
|
+
url?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
File without changes
|