@tuturuuu/ui 0.30.0 → 0.30.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.30.1](https://github.com/tutur3u/platform/compare/ui-v0.30.0...ui-v0.30.1) (2026-08-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **forms:** repair the landing demo, the brand lockup and two analytics labels ([3f16968](https://github.com/tutur3u/platform/commit/3f16968f5802ebbd518ee27025c61a759839fd4e))
9
+ * **forms:** repair the landing demo, the brand lockup and two analytics labels ([#5164](https://github.com/tutur3u/platform/issues/5164)) ([56d7f62](https://github.com/tutur3u/platform/commit/56d7f62325a8ec0caa5bb3ccd76b050dd66b1e53))
10
+
3
11
  ## [0.30.0](https://github.com/tutur3u/platform/compare/ui-v0.29.2...ui-v0.30.0) (2026-08-26)
4
12
 
5
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuturuuu/ui",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -93,7 +93,7 @@
93
93
  "@tuturuuu/icons": "0.1.0",
94
94
  "@tuturuuu/internal-api": "0.33.0",
95
95
  "@tuturuuu/supabase": "0.5.1",
96
- "@tuturuuu/utils": "0.26.0",
96
+ "@tuturuuu/utils": "0.27.0",
97
97
  "@types/debug": "^4.1.13",
98
98
  "browser-image-compression": "^2.0.2",
99
99
  "class-variance-authority": "^0.7.1",
@@ -313,6 +313,10 @@
313
313
  "types": "./src/components/ui/custom/system-language-wrapper.tsx",
314
314
  "import": "./src/components/ui/custom/system-language-wrapper.tsx"
315
315
  },
316
+ "./custom/tuturuuu-wordmark": {
317
+ "types": "./src/components/ui/custom/tuturuuu-wordmark.tsx",
318
+ "import": "./src/components/ui/custom/tuturuuu-wordmark.tsx"
319
+ },
316
320
  "./custom/tuturuuu-logo": {
317
321
  "types": "./src/components/ui/custom/tuturuuu-logo.tsx",
318
322
  "import": "./src/components/ui/custom/tuturuuu-logo.tsx"
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { toProductName } from '../tuturuuu-wordmark';
3
+
4
+ describe('toProductName', () => {
5
+ it('strips the company name from a full app name', () => {
6
+ expect(toProductName('Tuturuuu Forms')).toBe('Forms');
7
+ expect(toProductName('Tuturuuu Calendar')).toBe('Calendar');
8
+ });
9
+
10
+ it('is case-insensitive and tolerates extra spacing', () => {
11
+ expect(toProductName('TUTURUUU Forms')).toBe('Forms');
12
+ });
13
+
14
+ it('leaves a bare product name alone', () => {
15
+ expect(toProductName('Forms')).toBe('Forms');
16
+ });
17
+
18
+ it('returns the name unchanged when stripping would empty it', () => {
19
+ // Otherwise the lockup renders with a blank second line.
20
+ expect(toProductName('Tuturuuu')).toBe('Tuturuuu');
21
+ });
22
+ });
@@ -0,0 +1,72 @@
1
+ import { cn } from '@tuturuuu/utils/format';
2
+ import { TuturuuLogo } from './tuturuuu-logo';
3
+
4
+ /**
5
+ * The Tuturuuu product lockup: mark, then "Tuturuuu" over the product name.
6
+ *
7
+ * Two lines rather than one string. "Tuturuuu Forms" set on a single line reads
8
+ * as one long product name; stacking it says what it is — a Tuturuuu product
9
+ * called Forms — and keeps the company name consistent across products while
10
+ * only the second line changes.
11
+ *
12
+ * The logo deliberately does not take a local `src`. Satellites are served from
13
+ * their own domains and do not carry `/media`, so a local path 404s there; the
14
+ * component's default is the absolute URL for exactly that reason.
15
+ */
16
+ export function TuturuuuWordmark({
17
+ className,
18
+ product,
19
+ size = 'md',
20
+ }: {
21
+ className?: string;
22
+ /** The second line — the product name, without "Tuturuuu" in front of it. */
23
+ product: string;
24
+ size?: 'sm' | 'md';
25
+ }) {
26
+ const dimension = size === 'sm' ? 28 : 32;
27
+
28
+ return (
29
+ <span className={cn('flex items-center gap-2.5', className)}>
30
+ <TuturuuLogo
31
+ alt=""
32
+ aria-hidden
33
+ className={cn('object-contain', size === 'sm' ? 'h-7 w-7' : 'h-8 w-8')}
34
+ height={dimension}
35
+ width={dimension}
36
+ />
37
+ <span className="flex flex-col text-left leading-none">
38
+ <span
39
+ className={cn(
40
+ 'uppercase tracking-[0.24em] opacity-70',
41
+ size === 'sm' ? 'text-[9px]' : 'text-[10px]'
42
+ )}
43
+ >
44
+ Tuturuuu
45
+ </span>
46
+ <span
47
+ className={cn(
48
+ 'font-display font-semibold uppercase tracking-[0.08em]',
49
+ size === 'sm' ? 'text-sm' : 'text-lg'
50
+ )}
51
+ >
52
+ {product}
53
+ </span>
54
+ </span>
55
+ </span>
56
+ );
57
+ }
58
+
59
+ /**
60
+ * The product line of a full app name: "Tuturuuu Forms" -> "Forms".
61
+ *
62
+ * Exists so the nav and the footer cannot disagree. They derive the same label
63
+ * from the same prop, and two copies of one regex is exactly the kind of
64
+ * duplication that drifts when only one of them is updated.
65
+ *
66
+ * A name that is only "Tuturuuu" is returned unchanged rather than reduced to
67
+ * an empty string, which would render a lockup with a blank second line.
68
+ */
69
+ export function toProductName(appName: string): string {
70
+ const stripped = appName.replace(/^Tuturuuu\s+/i, '').trim();
71
+ return stripped || appName;
72
+ }
@@ -1,6 +1,6 @@
1
1
  import { cn } from '@tuturuuu/utils/format';
2
2
  import type { ReactNode } from 'react';
3
- import { TuturuuLogo } from '../custom/tuturuuu-logo';
3
+ import { TuturuuuWordmark, toProductName } from '../custom/tuturuuu-wordmark';
4
4
 
5
5
  export interface MarketingFooterLink {
6
6
  href: string;
@@ -16,6 +16,8 @@ export interface MarketingFooterColumn {
16
16
 
17
17
  interface MarketingFooterProps {
18
18
  appName: string;
19
+ /** Product line of the lockup; defaults to `appName` minus "Tuturuuu ". */
20
+ productName?: string;
19
21
  /** One-line positioning statement under the wordmark. */
20
22
  tagline: string;
21
23
  columns: MarketingFooterColumn[];
@@ -29,6 +31,7 @@ interface MarketingFooterProps {
29
31
  /** Shared satellite marketing footer: wordmark column plus link columns. */
30
32
  export function MarketingFooter({
31
33
  appName,
34
+ productName,
32
35
  tagline,
33
36
  columns,
34
37
  legal,
@@ -44,12 +47,10 @@ export function MarketingFooter({
44
47
  >
45
48
  <div className="mx-auto grid w-full max-w-6xl gap-12 lg:grid-cols-[1.5fr_repeat(3,1fr)]">
46
49
  <div className="max-w-sm">
47
- <div className="flex items-center gap-2.5">
48
- <TuturuuLogo className="h-7 w-7" height={28} width={28} />
49
- <span className="font-display font-semibold text-[0.95rem] tracking-[-0.01em]">
50
- {appName}
51
- </span>
52
- </div>
50
+ <TuturuuuWordmark
51
+ product={productName ?? toProductName(appName)}
52
+ size="sm"
53
+ />
53
54
  <p className="mt-4 text-foreground/50 text-sm leading-relaxed">
54
55
  {tagline}
55
56
  </p>
@@ -3,7 +3,7 @@
3
3
  import { Menu, X } from '@tuturuuu/icons';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import { type ReactNode, useEffect, useState } from 'react';
6
- import { TuturuuLogo } from '../custom/tuturuuu-logo';
6
+ import { TuturuuuWordmark, toProductName } from '../custom/tuturuuu-wordmark';
7
7
 
8
8
  export interface MarketingNavLink {
9
9
  /** In-page anchor (`#pricing`) or absolute URL. */
@@ -12,8 +12,17 @@ export interface MarketingNavLink {
12
12
  }
13
13
 
14
14
  interface MarketingNavProps {
15
- /** App name shown next to the logo, e.g. "Forms". */
15
+ /**
16
+ * The full app name, e.g. "Tuturuuu Forms". Used for the accessible landmark
17
+ * label, and as the fallback source for `productName`.
18
+ */
16
19
  appName: string;
20
+ /**
21
+ * The product line of the lockup — "Forms", not "Tuturuuu Forms". Defaults
22
+ * to `appName` with a leading "Tuturuuu " stripped, so callers that already
23
+ * pass the full name keep working.
24
+ */
25
+ productName?: string;
17
26
  /** Where the wordmark links back to. Defaults to the landing root. */
18
27
  homeHref?: string;
19
28
  links: MarketingNavLink[];
@@ -38,6 +47,7 @@ interface MarketingNavProps {
38
47
  */
39
48
  export function MarketingNav({
40
49
  appName,
50
+ productName,
41
51
  homeHref = '/',
42
52
  links,
43
53
  action,
@@ -79,13 +89,13 @@ export function MarketingNav({
79
89
  className="mx-auto flex h-16 w-full max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8"
80
90
  >
81
91
  <a
82
- className="flex shrink-0 items-center gap-2.5 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
92
+ className="flex shrink-0 items-center rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
83
93
  href={homeHref}
84
94
  >
85
- <TuturuuLogo className="h-7 w-7" height={28} width={28} />
86
- <span className="font-display font-semibold text-[0.95rem] tracking-[-0.01em]">
87
- {appName}
88
- </span>
95
+ <TuturuuuWordmark
96
+ product={productName ?? toProductName(appName)}
97
+ size="sm"
98
+ />
89
99
  </a>
90
100
 
91
101
  <div className="hidden items-center gap-1 md:flex">