@olundot/ui 0.20.0-next.1 → 0.20.0-next.3

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,31 @@
1
1
  # @olundot/ui
2
2
 
3
+ ## 0.20.0-next.3
4
+
5
+ ### Minor Changes
6
+
7
+ - b7176ad: `OlunLogo`가 `@olundot/tokens/brand`의 정본 데이터만 그린다. 모양이 바뀐다.
8
+
9
+ - 워드마크: Pretendard 800 글자(`<span>`) 대신 Montserrat 700 윤곽 SVG(자간 +0.03em). 접근 가능한 이름은 `role="img"` + `aria-label`(`OLUN`, 워드마크만이면 `OLUN.`)이다.
10
+ - MASTER(기본형): SVG 하나로 그리고 `symbolSize` 하나로 통째로 확대·축소한다. 간격 12px(`--space-3`) 대신 대문자 높이 H 기준 고정 비율(링 바깥 지름 1.24H, 궤도 점 끝 → O 0.55H)을 쓴다. 높이는 잉크에 맞춰 예전 심볼 상자보다 낮다(기본 `symbolSize` 28에서 약 17px).
11
+ - `fontSize`: 워드마크만 쓸 때(`showSymbol={false}`)의 글자 크기로만 쓴다. MASTER에서는 무시한다.
12
+ - 워드마크만 쓸 때 마침표 색이 `--accent`에서 `--accent-pop`으로 바뀐다(궤도 점과 같은 색 표식 `olun-accent`).
13
+ - `OlunSymbol`: 모양과 크기는 그대로다.
14
+
15
+ ### Patch Changes
16
+
17
+ - 15f638e: 겹친 오버레이를 한 동작으로 함께 닫아도 포커스가 `<body>`로 떨어지지 않고 바깥 오버레이를 연 요소로 돌아간다. 바깥 오버레이가 먼저 사라지는 동안 안쪽 오버레이가 닫히는 애니메이션 중이어도 해당한다. Trigger가 있는 경우와 닫히는 동안 포커스가 다른 요소로 옮겨 간 경우는 그대로다.
18
+ - Updated dependencies [b7176ad]
19
+ - @olundot/tokens@0.18.0-next.1
20
+
21
+ ## 0.20.0-next.2
22
+
23
+ ### Patch Changes
24
+
25
+ - 48c5a4d: `Tabs` `line`의 선택 표시가 다른 탭을 고를 때 그 탭으로 미끄러진다(`--dur-base`, `--ease-standard`). 처음 그릴 때와 크기가 바뀔 때는 바로 놓이고, 동작 줄이기 설정에서는 움직이지 않는다. 가로와 세로 모두 해당하며 `contained`는 그대로다.
26
+ - 목록 안에 `data-slot="tabs-indicator"` 요소(`aria-hidden`) 하나가 생긴다. 하이드레이션 전에는 기존 `::after` 밑줄이 보이고, 강조선이 자리를 잡으면 숨는다.
27
+ - 선택된 탭 라벨이 굵어져도 탭 폭이 바뀌지 않는다. 라벨에 굵기별 숨은 사본을 두어 폭을 미리 잡는다. 탭을 바꿔도 다른 탭이 밀리지 않는다.
28
+
3
29
  ## 0.20.0-next.1
4
30
 
5
31
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -2055,11 +2055,11 @@ declare function TreeView<T extends TreeNode>({ nodes, selectedId, selectionAria
2055
2055
  /**
2056
2056
  * OLUN 브랜드 로고.
2057
2057
  *
2058
- * - symbol: O 원형 테두리 + 우하단 accent-pop 작은 circle (32px 초과/이하 두 비율)
2059
- * - wordmark: "OLUN" + 마지막 "." 은 accent 색상
2060
- * - 조합: symbol과 함께 쓰면 wordmark의 "."를 뺀다. 궤도 점은 symbol이 맡는다(점 두 개 경쟁 방지).
2058
+ * - symbol: O 링 + 우하단 accent-pop 궤도 점 (32px 초과/이하 두 광학 비율)
2059
+ * - wordmark: Montserrat 700 윤곽 `OLUN` + 단독일 때 accent-pop 마침표
2060
+ * - MASTER: symbol과 함께 쓰면 wordmark의 "."를 뺀다. 궤도 점이 마침표를 맡는다(점 두 개 경쟁 방지).
2061
2061
  *
2062
- * 레퍼런스: apps/web/docs/wireframes/ref/shell-wireframe.html Topbar 브랜드 영역.
2062
+ * 기하는 `@olundot/tokens/brand`의 정본 데이터(scripts/brand/build-brand-svg.mjs가 생성)에서만 온다.
2063
2063
  */
2064
2064
  interface OlunSymbolProps {
2065
2065
  /** 심볼 크기 px (기본 28) */
@@ -2068,9 +2068,15 @@ interface OlunSymbolProps {
2068
2068
  }
2069
2069
  declare function OlunSymbol({ size, className }: OlunSymbolProps): react_jsx_runtime.JSX.Element;
2070
2070
  interface OlunLogoProps {
2071
- /** 심볼 크기 px (기본 28) */
2071
+ /**
2072
+ * MASTER 크기. 심볼 240 상자 기준 px (기본 28).
2073
+ * 워드마크와 간격은 고정 비율로 함께 커진다. 32px 이하는 아이콘 비율 심볼을 쓴다.
2074
+ */
2072
2075
  symbolSize?: number;
2073
- /** wordmark font-size px (기본 18) */
2076
+ /**
2077
+ * 워드마크만 쓸 때(`showSymbol={false}`) 글자 크기 px (기본 18).
2078
+ * MASTER에서는 쓰지 않는다. MASTER 크기는 `symbolSize` 하나로 정한다.
2079
+ */
2074
2080
  fontSize?: number;
2075
2081
  /** 심볼 표시 여부 (기본 true) */
2076
2082
  showSymbol?: boolean;
package/dist/index.js CHANGED
@@ -3020,12 +3020,12 @@ var tabsListVariants = cva8(
3020
3020
  {
3021
3021
  variant: "line",
3022
3022
  orientation: "horizontal",
3023
- className: "border-b border-[var(--border-subtle)] gap-[var(--space-6)] overflow-x-auto overflow-y-hidden"
3023
+ className: "group/tabs-list relative border-b border-[var(--border-subtle)] gap-[var(--space-6)] overflow-x-auto overflow-y-hidden"
3024
3024
  },
3025
3025
  {
3026
3026
  variant: "line",
3027
3027
  orientation: "vertical",
3028
- className: "border-r border-[var(--border-subtle)] gap-[var(--space-2)] pr-[var(--space-4)] max-sm:flex-row max-sm:gap-[var(--space-6)] max-sm:overflow-x-auto max-sm:overflow-y-hidden max-sm:border-r-0 max-sm:border-b max-sm:pr-0"
3028
+ className: "group/tabs-list relative border-r border-[var(--border-subtle)] gap-[var(--space-2)] pr-[var(--space-4)] max-sm:flex-row max-sm:gap-[var(--space-6)] max-sm:overflow-x-auto max-sm:overflow-y-hidden max-sm:border-r-0 max-sm:border-b max-sm:pr-0"
3029
3029
  },
3030
3030
  {
3031
3031
  variant: "contained",
@@ -3080,6 +3080,12 @@ var tabsTriggerVariants = cva8(
3080
3080
  orientation: "horizontal",
3081
3081
  className: "mx-[-2px] px-[2px] first:ml-0 last:mr-0 focus-visible:ring-0 focus-visible:inset-ring-2 focus-visible:inset-ring-[var(--focus-ring)]"
3082
3082
  },
3083
+ // Once the list's indicator has measured the active trigger, it replaces the static ::after
3084
+ // underline. Before hydration (or without the indicator) the ::after stays.
3085
+ {
3086
+ variant: "line",
3087
+ className: "group-data-[indicator=ready]/tabs-list:after:hidden"
3088
+ },
3083
3089
  {
3084
3090
  variant: "line",
3085
3091
  orientation: "vertical",
@@ -3092,6 +3098,35 @@ var tabsTriggerVariants = cva8(
3092
3098
  }
3093
3099
  }
3094
3100
  );
3101
+ var tabsIndicatorVariants = cva8(
3102
+ [
3103
+ "pointer-events-none absolute invisible bg-[var(--text-primary)]",
3104
+ // transition-property stays none (duration alone would animate every property, visibility included)
3105
+ // until data-motion turns on the per-orientation transition below.
3106
+ "transition-none duration-[var(--dur-base)] ease-[var(--ease-standard)]",
3107
+ "group-data-[indicator=ready]/tabs-list:visible"
3108
+ ].join(" "),
3109
+ {
3110
+ variants: {
3111
+ orientation: {
3112
+ horizontal: "bottom-[-1px] left-0 h-[2px] w-[var(--tabs-indicator-width)] [transform:translateX(var(--tabs-indicator-left))] motion-safe:data-[motion=on]:transition-[transform,width]",
3113
+ // Below `sm` the vertical list becomes a horizontal row, so the indicator follows the horizontal rule.
3114
+ vertical: [
3115
+ "max-sm:bottom-[-1px] max-sm:left-0 max-sm:h-[2px] max-sm:w-[var(--tabs-indicator-width)] max-sm:[transform:translateX(var(--tabs-indicator-left))] max-sm:motion-safe:data-[motion=on]:transition-[transform,width]",
3116
+ "sm:top-0 sm:right-[-1px] sm:h-[var(--tabs-indicator-height)] sm:w-[2px] sm:[transform:translateY(var(--tabs-indicator-top))] sm:motion-safe:data-[motion=on]:transition-[transform,height]"
3117
+ ].join(" ")
3118
+ }
3119
+ },
3120
+ defaultVariants: {
3121
+ orientation: "horizontal"
3122
+ }
3123
+ }
3124
+ );
3125
+ var tabsLabelReserveClassName = [
3126
+ "inline-flex flex-col",
3127
+ "before:invisible before:h-0 before:overflow-hidden before:font-medium before:content-[attr(data-label)]",
3128
+ "after:invisible after:h-0 after:overflow-hidden after:font-bold after:content-[attr(data-label)]"
3129
+ ].join(" ");
3095
3130
  var tabsBadgeVariants = cva8(
3096
3131
  [
3097
3132
  "inline-flex h-[18px] min-w-[18px] items-center justify-center",
@@ -3106,6 +3141,62 @@ var tabsBadgeVariants = cva8(
3106
3141
  "group-data-[state=active]:border-[var(--border-strong)]"
3107
3142
  ].join(" ")
3108
3143
  );
3144
+ function useTabsIndicator(enabled, activeKey, tabsKey, listRef, indicatorRef) {
3145
+ const placedRef = React17.useRef(null);
3146
+ const previousKeyRef = React17.useRef(null);
3147
+ const place = React17.useCallback(
3148
+ (animate) => {
3149
+ const list = listRef.current;
3150
+ const indicator = indicatorRef.current;
3151
+ if (!list || !indicator) return;
3152
+ const trigger = list.querySelector(':scope > [data-slot="tabs-trigger"][data-state="active"]');
3153
+ if (!trigger) {
3154
+ delete list.dataset.indicator;
3155
+ placedRef.current = null;
3156
+ return;
3157
+ }
3158
+ const { offsetLeft, offsetWidth, offsetTop, offsetHeight } = trigger;
3159
+ const placed = `${offsetLeft} ${offsetWidth} ${offsetTop} ${offsetHeight}`;
3160
+ if (placed === placedRef.current) return;
3161
+ placedRef.current = placed;
3162
+ if (animate && list.dataset.indicator === "ready") indicator.dataset.motion = "on";
3163
+ else delete indicator.dataset.motion;
3164
+ indicator.style.setProperty("--tabs-indicator-left", `${offsetLeft}px`);
3165
+ indicator.style.setProperty("--tabs-indicator-width", `${offsetWidth}px`);
3166
+ indicator.style.setProperty("--tabs-indicator-top", `${offsetTop}px`);
3167
+ indicator.style.setProperty("--tabs-indicator-height", `${offsetHeight}px`);
3168
+ list.dataset.indicator = "ready";
3169
+ },
3170
+ [listRef, indicatorRef]
3171
+ );
3172
+ React17.useLayoutEffect(() => {
3173
+ if (!enabled) {
3174
+ placedRef.current = null;
3175
+ previousKeyRef.current = null;
3176
+ return;
3177
+ }
3178
+ const previousKey = previousKeyRef.current;
3179
+ previousKeyRef.current = activeKey;
3180
+ place(previousKey !== null && previousKey !== activeKey);
3181
+ }, [enabled, activeKey, tabsKey, place]);
3182
+ React17.useEffect(() => {
3183
+ const list = listRef.current;
3184
+ if (!enabled || !list) return;
3185
+ let cancelled = false;
3186
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place(false));
3187
+ if (observer) {
3188
+ observer.observe(list);
3189
+ list.querySelectorAll(':scope > [data-slot="tabs-trigger"]').forEach((trigger) => observer.observe(trigger));
3190
+ }
3191
+ document.fonts?.ready.then(() => {
3192
+ if (!cancelled) place(false);
3193
+ });
3194
+ return () => {
3195
+ cancelled = true;
3196
+ observer?.disconnect();
3197
+ };
3198
+ }, [enabled, tabsKey, place, listRef]);
3199
+ }
3109
3200
  function formatTabBadge(value) {
3110
3201
  return value > 99 ? "99+" : String(value);
3111
3202
  }
@@ -3128,55 +3219,77 @@ var Tabs = React17.forwardRef(
3128
3219
  orientation = "horizontal",
3129
3220
  variant = "line",
3130
3221
  ...props
3131
- }, ref) => /* @__PURE__ */ jsxs17(
3132
- TabsPrimitive.Root,
3133
- {
3134
- ref,
3135
- "data-slot": "tabs",
3136
- value: activeKey,
3137
- onValueChange: onChange,
3138
- orientation,
3139
- className: cn(tabsRootVariants({ orientation }), className),
3140
- ...props,
3141
- children: [
3142
- /* @__PURE__ */ jsx18(
3143
- TabsPrimitive.List,
3144
- {
3145
- "data-slot": "tabs-list",
3146
- "data-variant": variant,
3147
- className: cn(tabsListVariants({ variant, orientation }), listClassName),
3148
- children: tabs.map((tab) => /* @__PURE__ */ jsxs17(
3149
- TabsPrimitive.Trigger,
3150
- {
3151
- id: getTabId(baseId, tab.key),
3152
- value: tab.key,
3153
- disabled: tab.disabled,
3154
- "aria-controls": getPanelId(baseId, tab.key),
3155
- "data-slot": "tabs-trigger",
3156
- "data-variant": variant,
3157
- className: cn("group", tabsTriggerVariants({ variant, orientation }), triggerClassName),
3158
- children: [
3159
- /* @__PURE__ */ jsx18("span", { children: tab.label }),
3160
- tab.badge !== void 0 && tab.badge > 0 ? /* @__PURE__ */ jsx18("span", { "aria-hidden": "true", "data-slot": "tabs-badge", className: tabsBadgeVariants(), children: formatTabBadge(tab.badge) }) : null,
3161
- tab.disabled ? /* @__PURE__ */ jsx18(
3162
- Lock,
3163
- {
3164
- "aria-hidden": "true",
3165
- "data-slot": "tabs-disabled-icon",
3166
- className: "size-[14px] shrink-0 text-text-disabled",
3167
- strokeWidth: 2
3222
+ }, ref) => {
3223
+ const listRef = React17.useRef(null);
3224
+ const indicatorRef = React17.useRef(null);
3225
+ const isLine = variant === "line";
3226
+ const tabsKey = tabs.map((tab) => tab.key).join("\0");
3227
+ useTabsIndicator(isLine, activeKey, tabsKey, listRef, indicatorRef);
3228
+ return /* @__PURE__ */ jsxs17(
3229
+ TabsPrimitive.Root,
3230
+ {
3231
+ ref,
3232
+ "data-slot": "tabs",
3233
+ value: activeKey,
3234
+ onValueChange: onChange,
3235
+ orientation,
3236
+ className: cn(tabsRootVariants({ orientation }), className),
3237
+ ...props,
3238
+ children: [
3239
+ /* @__PURE__ */ jsxs17(
3240
+ TabsPrimitive.List,
3241
+ {
3242
+ ref: listRef,
3243
+ "data-slot": "tabs-list",
3244
+ "data-variant": variant,
3245
+ className: cn(tabsListVariants({ variant, orientation }), listClassName),
3246
+ children: [
3247
+ tabs.map((tab) => /* @__PURE__ */ jsxs17(
3248
+ TabsPrimitive.Trigger,
3249
+ {
3250
+ id: getTabId(baseId, tab.key),
3251
+ value: tab.key,
3252
+ disabled: tab.disabled,
3253
+ "aria-controls": getPanelId(baseId, tab.key),
3254
+ "data-slot": "tabs-trigger",
3255
+ "data-variant": variant,
3256
+ className: cn("group", tabsTriggerVariants({ variant, orientation }), triggerClassName),
3257
+ children: [
3258
+ isLine ? /* @__PURE__ */ jsx18("span", { "data-label": tab.label, className: tabsLabelReserveClassName, children: tab.label }) : /* @__PURE__ */ jsx18("span", { children: tab.label }),
3259
+ tab.badge !== void 0 && tab.badge > 0 ? /* @__PURE__ */ jsx18("span", { "aria-hidden": "true", "data-slot": "tabs-badge", className: tabsBadgeVariants(), children: formatTabBadge(tab.badge) }) : null,
3260
+ tab.disabled ? /* @__PURE__ */ jsx18(
3261
+ Lock,
3262
+ {
3263
+ "aria-hidden": "true",
3264
+ "data-slot": "tabs-disabled-icon",
3265
+ className: "size-[14px] shrink-0 text-text-disabled",
3266
+ strokeWidth: 2
3267
+ }
3268
+ ) : null
3269
+ ]
3270
+ },
3271
+ tab.key
3272
+ )),
3273
+ isLine ? /* @__PURE__ */ jsx18(
3274
+ "span",
3275
+ {
3276
+ ref: indicatorRef,
3277
+ "aria-hidden": "true",
3278
+ "data-slot": "tabs-indicator",
3279
+ className: tabsIndicatorVariants({ orientation }),
3280
+ onTransitionEnd: (event) => {
3281
+ if (event.target === event.currentTarget) delete event.currentTarget.dataset.motion;
3168
3282
  }
3169
- ) : null
3170
- ]
3171
- },
3172
- tab.key
3173
- ))
3174
- }
3175
- ),
3176
- children
3177
- ]
3178
- }
3179
- )
3283
+ }
3284
+ ) : null
3285
+ ]
3286
+ }
3287
+ ),
3288
+ children
3289
+ ]
3290
+ }
3291
+ );
3292
+ }
3180
3293
  );
3181
3294
  Tabs.displayName = "Tabs";
3182
3295
  var TabsContent = React17.forwardRef(({ className, tabKey, baseId, ...props }, ref) => /* @__PURE__ */ jsx18(
@@ -3559,6 +3672,7 @@ function useOverlayAction({
3559
3672
 
3560
3673
  // src/lib/use-return-focus.ts
3561
3674
  import * as React21 from "react";
3675
+ var closingOverlay = '[role="dialog"][data-state="closed"], [role="alertdialog"][data-state="closed"]';
3562
3676
  function useReturnFocus() {
3563
3677
  const returnTo = React21.useRef(null);
3564
3678
  const onOpenAutoFocus = React21.useCallback((event) => {
@@ -3574,7 +3688,9 @@ function useReturnFocus() {
3574
3688
  returnTo.current = null;
3575
3689
  if (!target?.isConnected) return;
3576
3690
  const active = document.activeElement;
3577
- if (active instanceof HTMLElement && active !== document.body && active !== target) return;
3691
+ if (active instanceof HTMLElement && active !== document.body && active !== target && !active.closest(closingOverlay)) {
3692
+ return;
3693
+ }
3578
3694
  event.preventDefault();
3579
3695
  target.focus();
3580
3696
  }, []);
@@ -10456,36 +10572,46 @@ function TreeView({
10456
10572
  }
10457
10573
 
10458
10574
  // src/components/branding/olun-logo.tsx
10459
- import { jsx as jsx64, jsxs as jsxs50 } from "react/jsx-runtime";
10460
- var SYMBOL_LARGE = { stroke: 24, dotX: 184, dotY: 180, dotR: 13 };
10461
- var SYMBOL_SMALL = { stroke: 18, dotX: 185, dotY: 175, dotR: 22 };
10575
+ import { OLUN_MASTER, OLUN_SYMBOL, OLUN_WORDMARK } from "@olundot/tokens/brand";
10576
+ import { Fragment as Fragment13, jsx as jsx64, jsxs as jsxs50 } from "react/jsx-runtime";
10577
+ var ACCENT_FILL = "var(--accent-pop)";
10578
+ var INK = { color: "var(--text-primary)" };
10579
+ var INK_TIGHT = { ...INK, display: "block", overflow: "visible" };
10580
+ function symbolGeometry(size) {
10581
+ return size > OLUN_SYMBOL.smallMax ? OLUN_SYMBOL.large : OLUN_SYMBOL.small;
10582
+ }
10583
+ function SymbolCircles({ size }) {
10584
+ const { ring } = OLUN_SYMBOL;
10585
+ const g = symbolGeometry(size);
10586
+ return /* @__PURE__ */ jsxs50(Fragment13, { children: [
10587
+ /* @__PURE__ */ jsx64(
10588
+ "circle",
10589
+ {
10590
+ className: "olun-logo__ring",
10591
+ cx: ring.cx,
10592
+ cy: ring.cy,
10593
+ r: ring.r,
10594
+ stroke: "currentColor",
10595
+ strokeWidth: g.stroke,
10596
+ fill: "none"
10597
+ }
10598
+ ),
10599
+ /* @__PURE__ */ jsx64("circle", { className: "olun-accent olun-logo__dot", cx: g.dot.cx, cy: g.dot.cy, r: g.dot.r, fill: ACCENT_FILL })
10600
+ ] });
10601
+ }
10462
10602
  function OlunSymbol({ size = 28, className }) {
10463
- const g = size > 32 ? SYMBOL_LARGE : SYMBOL_SMALL;
10464
- return /* @__PURE__ */ jsxs50(
10603
+ return /* @__PURE__ */ jsx64(
10465
10604
  "svg",
10466
10605
  {
10467
10606
  width: size,
10468
10607
  height: size,
10469
- viewBox: "0 0 240 240",
10608
+ viewBox: `0 0 ${OLUN_SYMBOL.box} ${OLUN_SYMBOL.box}`,
10470
10609
  fill: "none",
10471
10610
  xmlns: "http://www.w3.org/2000/svg",
10472
10611
  className,
10612
+ style: INK,
10473
10613
  "aria-hidden": "true",
10474
- children: [
10475
- /* @__PURE__ */ jsx64(
10476
- "circle",
10477
- {
10478
- cx: "108",
10479
- cy: "120",
10480
- r: "60",
10481
- stroke: "currentColor",
10482
- strokeWidth: g.stroke,
10483
- fill: "none",
10484
- style: { color: "var(--text-primary)" }
10485
- }
10486
- ),
10487
- /* @__PURE__ */ jsx64("circle", { cx: g.dotX, cy: g.dotY, r: g.dotR, fill: "var(--accent-pop)" })
10488
- ]
10614
+ children: /* @__PURE__ */ jsx64(SymbolCircles, { size })
10489
10615
  }
10490
10616
  );
10491
10617
  }
@@ -10495,33 +10621,61 @@ function OlunLogo({
10495
10621
  showSymbol = true,
10496
10622
  className
10497
10623
  }) {
10624
+ if (!showSymbol) {
10625
+ const { full } = OLUN_WORDMARK;
10626
+ const height2 = fontSize * full.height / OLUN_WORDMARK.unitsPerEm;
10627
+ return /* @__PURE__ */ jsxs50(
10628
+ "svg",
10629
+ {
10630
+ width: height2 * full.width / full.height,
10631
+ height: height2,
10632
+ viewBox: full.viewBox,
10633
+ fill: "none",
10634
+ xmlns: "http://www.w3.org/2000/svg",
10635
+ className,
10636
+ style: INK_TIGHT,
10637
+ role: "img",
10638
+ "aria-label": "OLUN.",
10639
+ children: [
10640
+ /* @__PURE__ */ jsx64("path", { className: "olun-logo__wordmark", fill: "currentColor", d: full.letters }),
10641
+ /* @__PURE__ */ jsx64(
10642
+ "circle",
10643
+ {
10644
+ className: "olun-accent olun-logo__period",
10645
+ cx: full.period.cx,
10646
+ cy: full.period.cy,
10647
+ r: full.period.r,
10648
+ fill: ACCENT_FILL
10649
+ }
10650
+ )
10651
+ ]
10652
+ }
10653
+ );
10654
+ }
10655
+ const layout = symbolSize > OLUN_SYMBOL.smallMax ? OLUN_MASTER.large : OLUN_MASTER.small;
10656
+ const height = symbolSize * layout.height / (OLUN_SYMBOL.box * layout.symbol.scale);
10498
10657
  return /* @__PURE__ */ jsxs50(
10499
- "div",
10658
+ "svg",
10500
10659
  {
10660
+ width: height * layout.width / layout.height,
10661
+ height,
10662
+ viewBox: layout.viewBox,
10663
+ fill: "none",
10664
+ xmlns: "http://www.w3.org/2000/svg",
10501
10665
  className,
10502
- style: {
10503
- display: "flex",
10504
- alignItems: "center",
10505
- gap: "var(--space-3)"
10506
- },
10666
+ style: INK_TIGHT,
10667
+ role: "img",
10668
+ "aria-label": "OLUN",
10507
10669
  children: [
10508
- showSymbol && /* @__PURE__ */ jsx64(OlunSymbol, { size: symbolSize }),
10509
- /* @__PURE__ */ jsxs50(
10510
- "span",
10670
+ /* @__PURE__ */ jsx64(
10671
+ "g",
10511
10672
  {
10512
- style: {
10513
- fontFamily: "var(--font-display)",
10514
- fontWeight: 800,
10515
- fontSize: `${fontSize}px`,
10516
- letterSpacing: "-0.02em",
10517
- color: "var(--text-primary)"
10518
- },
10519
- children: [
10520
- "OLUN",
10521
- !showSymbol && /* @__PURE__ */ jsx64("span", { style: { color: "var(--accent)" }, children: "." })
10522
- ]
10673
+ className: "olun-logo__symbol",
10674
+ transform: `translate(${layout.symbol.x} ${layout.symbol.y}) scale(${layout.symbol.scale})`,
10675
+ children: /* @__PURE__ */ jsx64(SymbolCircles, { size: symbolSize })
10523
10676
  }
10524
- )
10677
+ ),
10678
+ /* @__PURE__ */ jsx64("path", { className: "olun-logo__wordmark", fill: "currentColor", d: layout.letters })
10525
10679
  ]
10526
10680
  }
10527
10681
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olundot/ui",
3
- "version": "0.20.0-next.1",
3
+ "version": "0.20.0-next.3",
4
4
  "description": "OLUN. shared React UI components built on OLUN design tokens",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -44,7 +44,7 @@
44
44
  "lucide-react": "^1.8.0",
45
45
  "radix-ui": "^1.4.3",
46
46
  "tailwind-merge": "^3.5.0",
47
- "@olundot/tokens": "^0.18.0-next.0"
47
+ "@olundot/tokens": "^0.18.0-next.1"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": ">=18",
@@ -62,6 +62,7 @@
62
62
  "@types/node": "^24.10.0",
63
63
  "@types/react": "^19.2.14",
64
64
  "@types/react-dom": "^19.2.3",
65
+ "esbuild": "^0.27.7",
65
66
  "jsdom": "^30.1.1",
66
67
  "react": "^19.2.6",
67
68
  "react-dom": "^19.2.6",