Files
helexa/helexa.ai/src/components/DirectionalIcon.tsx
rob thijssen 7a6f252fe0
All checks were successful
CI / Format (push) Successful in 40s
CI / CUDA type-check (push) Successful in 1m39s
CI / Clippy (push) Successful in 2m50s
CI / Test (push) Successful in 6m34s
CI / Build cortex SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
feat(F1): theming + 33-language i18n + usage-ordered language selector
Ports the reference site's visual + i18n foundation into helexa.ai and
adds the deliberate usage-ordered language picker.

- Ported from ~/git/helexa-ai/helexa.ai: src/layout (ThemeProvider/theme,
  localStorage + data-theme, light/dark), src/App.css (cyan/hot-pink accents,
  system fonts), src/i18n (index + languages + translation-priority +
  resources for 33 languages × common/home/chat), Footer, DirectionalIcon,
  public assets, and the check-i18n-* scripts.
- getLanguageOptionsByUsage() (in translation-priority.ts): orders the
  selector by the TRANSLATION_PRIORITY ranking (≈ native-speaker usage),
  deduping repeated entries and appending any unranked supported language —
  NOT alphabetical, the marketing-driven choice that foregrounds helexa's
  international grounding. RTL preserved.
- Header: usage-ordered language dropdown (autonym + secondary label in the
  current language), theme toggle, and new nav — `/` (chat), `/mission`,
  and a Login/Register auth cluster stubbed until F4. New nav keys
  (mission/login/register/account/logout) injected into all 33 common.json
  with English placeholders so key-parity holds.
- App composes ThemeProvider → BrowserRouter → Header + routes + Footer
  (placeholders for `/` and `/mission`); main.tsx loads i18n.

Validated: npm run lint, typecheck, build all green; npm run i18n:check
reports all keys consistent across the 33 languages. (Build emits a
chunk-size advisory — code-splitting is deferred to F6 polish.) Staged with
explicit paths; no node_modules/dist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F6o3ddqmYNh9kzdwq6eowh
2026-06-23 11:01:04 +03:00

128 lines
3.4 KiB
TypeScript

import React from "react";
import { useTranslation } from "react-i18next";
import { isRtlLanguage, type LanguageCode } from "../i18n/languages";
export type Direction = "forward" | "back";
/**
* DirectionalIcon
*
* Small helper component to render direction-aware icons that respect
* the current UI writing direction (LTR vs RTL).
*
* Usage example:
*
* <DirectionalIcon
* direction="forward"
* ltrIcon={FaArrowRight}
* rtlIcon={FaArrowLeft}
* />
*
* - `direction="forward"` means “toward the natural reading direction”
* (right in LTR, left in RTL).
* - `direction="back"` means the opposite (left in LTR, right in RTL).
*
* You can either:
* - pass explicit `ltrIcon` and `rtlIcon` React components, or
* - pass a single `icon` component and set `mirrorInRtl` to flip it
* horizontally when in RTL (via CSS transform).
*
* In most cases, using explicit LTR / RTL icons is clearer and avoids
* surprises with asymmetric icon shapes.
*/
export interface DirectionalIconProps {
/**
* Logical direction relative to reading order.
* - "forward": in the direction of the text flow
* - "back": opposite the direction of the text flow
*/
direction: Direction;
/**
* Icon component to use for LTR contexts (e.g. FaArrowRight).
*/
ltrIcon?: React.ComponentType<{ size?: number | string; className?: string }>;
/**
* Icon component to use for RTL contexts (e.g. FaArrowLeft).
*/
rtlIcon?: React.ComponentType<{ size?: number | string; className?: string }>;
/**
* Single base icon component. When provided together with
* `mirrorInRtl={true}`, it will be mirrored horizontally in RTL.
*/
icon?: React.ComponentType<{ size?: number | string; className?: string }>;
/**
* Whether to flip the `icon` horizontally in RTL.
* Ignored if both `ltrIcon` and `rtlIcon` are supplied.
*/
mirrorInRtl?: boolean;
/**
* Optional size forwarded to the rendered icon.
*/
size?: number | string;
/**
* Additional className to apply to the rendered icon.
*/
className?: string;
}
/**
* Determine if current language is RTL based on i18next language code.
*
* Delegates to the shared `isRtlLanguage` helper from i18n/languages.ts
* so that all RTL logic lives in one place.
*/
const isRtlLanguageCode = (code: string | undefined | null): boolean => {
if (!code) return false;
const lang = code.split("-")[0].toLowerCase() as LanguageCode;
return isRtlLanguage(lang);
};
const DirectionalIcon: React.FC<DirectionalIconProps> = ({
direction,
ltrIcon: LtrIcon,
rtlIcon: RtlIcon,
icon: BaseIcon,
mirrorInRtl = false,
size,
className,
}) => {
const { i18n } = useTranslation();
const isRtl = isRtlLanguageCode(i18n.language);
// If explicit LTR/RTL icons are provided, prefer those.
if (LtrIcon && RtlIcon) {
const IconComponent =
(direction === "forward" && !isRtl) || (direction === "back" && isRtl)
? LtrIcon
: RtlIcon;
return <IconComponent size={size} className={className} />;
}
// Fallback: single base icon, optionally mirrored in RTL.
if (!BaseIcon) {
return null;
}
const shouldMirror =
mirrorInRtl &&
((direction === "forward" && isRtl) || (direction === "back" && !isRtl));
const combinedClassName = [
className,
shouldMirror ? "diricon-mirror-rtl" : null,
]
.filter(Boolean)
.join(" ");
return <BaseIcon size={size} className={combinedClassName} />;
};
export default DirectionalIcon;