@umodeapporg/ui 0.20.0 → 0.21.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.
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface IPumpkinGlyphProps {
|
|
2
|
+
/** Same unit as `MaterialSymbol`: pixels at the default root size, rendered in rem. */
|
|
3
|
+
size: number;
|
|
4
|
+
/** Mirrors the FILL axis of the icon font: solid while its option is the active one. */
|
|
5
|
+
filled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The icon font has no pumpkin, so the Halloween option draws its own. It is a single-ink
|
|
9
|
+
* glyph on the 24-unit grid of the font and takes `currentColor`, which keeps it in step with
|
|
10
|
+
* the other positions of the knob: outlined at rest, solid with a carved face while active.
|
|
11
|
+
*/
|
|
12
|
+
declare const PumpkinGlyph: React.FC<IPumpkinGlyphProps>;
|
|
13
|
+
export default PumpkinGlyph;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
const STEM_PATH = "M11 6.8c0-1.9.7-3.3 2.2-4.3l1.1 1.3c-.9.7-1.3 1.7-1.3 3z";
|
|
3
|
+
const BODY_PATH = "M3 14a9 7.5 0 1 0 18 0a9 7.5 0 1 0-18 0z";
|
|
4
|
+
const FACE_PATH = "M7.2 13.2h3l-1.5-2.8zM13.8 13.2h3l-1.5-2.8zM7 15.6l2 1 1.5-1 1.5 1 1.5-1 1.5 1 2-1c-.8 2.6-2.7 4-5 4s-4.2-1.4-5-4z";
|
|
5
|
+
const PumpkinGlyph = ({ size, filled = false }) => {
|
|
6
|
+
const sizeInRem = `${size / 16}rem`;
|
|
7
|
+
return /* @__PURE__ */ jsxs(
|
|
8
|
+
"svg",
|
|
9
|
+
{
|
|
10
|
+
viewBox: "0 0 24 24",
|
|
11
|
+
"aria-hidden": true,
|
|
12
|
+
focusable: "false",
|
|
13
|
+
fill: "currentColor",
|
|
14
|
+
className: "shrink-0",
|
|
15
|
+
style: { width: sizeInRem, height: sizeInRem },
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ jsx("path", { d: STEM_PATH }),
|
|
18
|
+
filled ? /* @__PURE__ */ jsx("path", { d: `${BODY_PATH}${FACE_PATH}`, fillRule: "evenodd" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19
|
+
/* @__PURE__ */ jsx("path", { d: BODY_PATH, fill: "none", stroke: "currentColor", strokeWidth: 1.5 }),
|
|
20
|
+
/* @__PURE__ */ jsx("path", { d: FACE_PATH })
|
|
21
|
+
] })
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
PumpkinGlyph as default
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=PumpkinGlyph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PumpkinGlyph.js","sources":["../../../src/components/Shell/PumpkinGlyph.tsx"],"sourcesContent":["interface IPumpkinGlyphProps {\n /** Same unit as `MaterialSymbol`: pixels at the default root size, rendered in rem. */\n size: number;\n /** Mirrors the FILL axis of the icon font: solid while its option is the active one. */\n filled?: boolean;\n}\n\nconst STEM_PATH = 'M11 6.8c0-1.9.7-3.3 2.2-4.3l1.1 1.3c-.9.7-1.3 1.7-1.3 3z';\nconst BODY_PATH = 'M3 14a9 7.5 0 1 0 18 0a9 7.5 0 1 0-18 0z';\nconst FACE_PATH =\n 'M7.2 13.2h3l-1.5-2.8zM13.8 13.2h3l-1.5-2.8zM7 15.6l2 1 1.5-1 1.5 1 1.5-1 1.5 1 2-1c-.8 2.6-2.7 4-5 4s-4.2-1.4-5-4z';\n\n/**\n * The icon font has no pumpkin, so the Halloween option draws its own. It is a single-ink\n * glyph on the 24-unit grid of the font and takes `currentColor`, which keeps it in step with\n * the other positions of the knob: outlined at rest, solid with a carved face while active.\n */\nconst PumpkinGlyph: React.FC<IPumpkinGlyphProps> = ({ size, filled = false }) => {\n const sizeInRem = `${size / 16}rem`;\n\n return (\n <svg\n viewBox=\"0 0 24 24\"\n aria-hidden\n focusable=\"false\"\n fill=\"currentColor\"\n className=\"shrink-0\"\n style={{ width: sizeInRem, height: sizeInRem }}\n >\n <path d={STEM_PATH} />\n {filled ? (\n <path d={`${BODY_PATH}${FACE_PATH}`} fillRule=\"evenodd\" />\n ) : (\n <>\n <path d={BODY_PATH} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} />\n <path d={FACE_PATH} />\n </>\n )}\n </svg>\n );\n};\n\nexport default PumpkinGlyph;\n"],"names":[],"mappings":";AAOA,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,YACJ;AAOF,MAAM,eAA6C,CAAC,EAAE,MAAM,SAAS,YAAY;AAC/E,QAAM,YAAY,GAAG,OAAO,EAAE;AAE9B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,eAAW;AAAA,MACX,WAAU;AAAA,MACV,MAAK;AAAA,MACL,WAAU;AAAA,MACV,OAAO,EAAE,OAAO,WAAW,QAAQ,UAAA;AAAA,MAEnC,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,GAAG,UAAA,CAAW;AAAA,QACnB,SACC,oBAAC,QAAA,EAAK,GAAG,GAAG,SAAS,GAAG,SAAS,IAAI,UAAS,UAAA,CAAU,IAExD,qBAAA,UAAA,EACE,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,GAAG,WAAW,MAAK,QAAO,QAAO,gBAAe,aAAa,IAAA,CAAK;AAAA,UACxE,oBAAC,QAAA,EAAK,GAAG,UAAA,CAAW;AAAA,QAAA,EAAA,CACtB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;"}
|
|
@@ -5,12 +5,18 @@ import ControlledButton from "../Form/ControlledButton.js";
|
|
|
5
5
|
import ControlledTooltip from "../UI/ControlledTooltip.js";
|
|
6
6
|
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
7
7
|
import { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from "./headerControlStyles.js";
|
|
8
|
+
import PumpkinGlyph from "./PumpkinGlyph.js";
|
|
8
9
|
const OPTION_SIZE_REM = HEADER_CONTROL_SIZE_REM;
|
|
10
|
+
const OPTION_ICON_SIZE = 15;
|
|
11
|
+
const renderSymbol = (icon) => (isActive) => /* @__PURE__ */ jsx(MaterialSymbol, { icon, size: OPTION_ICON_SIZE, fill: isActive });
|
|
9
12
|
const OPTION_PRESENTATION = {
|
|
10
|
-
light: {
|
|
11
|
-
system: {
|
|
12
|
-
dark: {
|
|
13
|
-
halloween: {
|
|
13
|
+
light: { labelKey: "theme.light", renderIcon: renderSymbol("light_mode") },
|
|
14
|
+
system: { labelKey: "theme.system", renderIcon: renderSymbol("computer") },
|
|
15
|
+
dark: { labelKey: "theme.dark", renderIcon: renderSymbol("dark_mode") },
|
|
16
|
+
halloween: {
|
|
17
|
+
labelKey: "theme.halloween",
|
|
18
|
+
renderIcon: (isActive) => /* @__PURE__ */ jsx(PumpkinGlyph, { size: OPTION_ICON_SIZE, filled: isActive })
|
|
19
|
+
}
|
|
14
20
|
};
|
|
15
21
|
const DEFAULT_OPTIONS = ["light", "system", "dark"];
|
|
16
22
|
const ThemeToggle = ({
|
|
@@ -49,7 +55,7 @@ const ThemeToggle = ({
|
|
|
49
55
|
isActive ? "text-brand-emphasis" : "text-foreground-faint hover:text-foreground-muted"
|
|
50
56
|
),
|
|
51
57
|
style: { width: `${OPTION_SIZE_REM}rem`, height: `${OPTION_SIZE_REM}rem` },
|
|
52
|
-
children:
|
|
58
|
+
children: presentation.renderIcon(isActive)
|
|
53
59
|
}
|
|
54
60
|
) }, option);
|
|
55
61
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeToggle.js","sources":["../../../src/components/Shell/ThemeToggle.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { TUiThemeSkin } from '@/helpers/themeSkins';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\n\nconst OPTION_SIZE_REM = HEADER_CONTROL_SIZE_REM;\n\nexport type TUiThemePreference = 'light' | 'system' | 'dark';\n\n/** A skin is one more position of the knob: choosing it is choosing its scheme, re-coloured. */\nexport type TUiThemeToggleOption = TUiThemePreference | TUiThemeSkin;\n\
|
|
1
|
+
{"version":3,"file":"ThemeToggle.js","sources":["../../../src/components/Shell/ThemeToggle.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { TUiThemeSkin } from '@/helpers/themeSkins';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\nimport PumpkinGlyph from './PumpkinGlyph';\n\nconst OPTION_SIZE_REM = HEADER_CONTROL_SIZE_REM;\nconst OPTION_ICON_SIZE = 15;\n\nexport type TUiThemePreference = 'light' | 'system' | 'dark';\n\n/** A skin is one more position of the knob: choosing it is choosing its scheme, re-coloured. */\nexport type TUiThemeToggleOption = TUiThemePreference | TUiThemeSkin;\n\ninterface IOptionPresentation {\n labelKey: string;\n renderIcon: (isActive: boolean) => ReactNode;\n}\n\nconst renderSymbol = (icon: SymbolCodepoints) => (isActive: boolean) => (\n <MaterialSymbol icon={icon} size={OPTION_ICON_SIZE} fill={isActive} />\n);\n\nconst OPTION_PRESENTATION: Record<TUiThemeToggleOption, IOptionPresentation> = {\n light: { labelKey: 'theme.light', renderIcon: renderSymbol('light_mode') },\n system: { labelKey: 'theme.system', renderIcon: renderSymbol('computer') },\n dark: { labelKey: 'theme.dark', renderIcon: renderSymbol('dark_mode') },\n halloween: {\n labelKey: 'theme.halloween',\n renderIcon: (isActive) => <PumpkinGlyph size={OPTION_ICON_SIZE} filled={isActive} />,\n },\n};\n\nconst DEFAULT_OPTIONS: ReadonlyArray<TUiThemePreference> = ['light', 'system', 'dark'];\n\ninterface IThemeToggleProps<TOption extends TUiThemeToggleOption = TUiThemePreference> {\n preference: TOption;\n onChange: (preference: TOption) => void;\n /** Positions of the knob, in order. Defaults to light, system and dark; the host adds a\n skin for as long as it is on offer and drops it again when the season ends. */\n options?: ReadonlyArray<TOption>;\n}\n\n/**\n * The theme control: an explicit light/dark choice, `system` to follow the OS, and any skin\n * the host is offering. The host owns the preference (persistence + writing `data-theme` and\n * `data-theme-skin` on <html>); this renders the knob.\n */\nconst ThemeToggle = <TOption extends TUiThemeToggleOption = TUiThemePreference>({\n preference,\n onChange,\n options,\n}: IThemeToggleProps<TOption>) => {\n const { t } = useTranslation('ui');\n\n const requestedOptions = (\n Array.isArray(options) && options.length > 0 ? options : DEFAULT_OPTIONS\n ) as ReadonlyArray<TOption>;\n const visibleOptions = requestedOptions.filter((option) => Boolean(OPTION_PRESENTATION[option]));\n const activeIndex = visibleOptions.indexOf(preference);\n\n return (\n <div role=\"group\" aria-label={t('theme.toggle')} className={HEADER_CONTROL_SHELL_CLASS}>\n <div className=\"relative flex items-center\">\n {activeIndex >= 0 && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter\"\n style={{\n width: `${OPTION_SIZE_REM}rem`,\n height: `${OPTION_SIZE_REM}rem`,\n transform: `translateX(${activeIndex * OPTION_SIZE_REM}rem)`,\n }}\n />\n )}\n\n {visibleOptions.map((option) => {\n const isActive = option === preference;\n const presentation = OPTION_PRESENTATION[option];\n\n return (\n <ControlledTooltip key={option} content={t(presentation.labelKey)}>\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isActive}\n onClick={() => onChange(option)}\n className={classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full leading-none transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-faint hover:text-foreground-muted',\n )}\n style={{ width: `${OPTION_SIZE_REM}rem`, height: `${OPTION_SIZE_REM}rem` }}\n >\n {presentation.renderIcon(isActive)}\n </ControlledButton>\n </ControlledTooltip>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default ThemeToggle;\n"],"names":[],"mappings":";;;;;;;;AAYA,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAYzB,MAAM,eAAe,CAAC,SAA2B,CAAC,aAChD,oBAAC,gBAAA,EAAe,MAAY,MAAM,kBAAkB,MAAM,SAAA,CAAU;AAGtE,MAAM,sBAAyE;AAAA,EAC7E,OAAO,EAAE,UAAU,eAAe,YAAY,aAAa,YAAY,EAAA;AAAA,EACvE,QAAQ,EAAE,UAAU,gBAAgB,YAAY,aAAa,UAAU,EAAA;AAAA,EACvE,MAAM,EAAE,UAAU,cAAc,YAAY,aAAa,WAAW,EAAA;AAAA,EACpE,WAAW;AAAA,IACT,UAAU;AAAA,IACV,YAAY,CAAC,aAAa,oBAAC,gBAAa,MAAM,kBAAkB,QAAQ,SAAA,CAAU;AAAA,EAAA;AAEtF;AAEA,MAAM,kBAAqD,CAAC,SAAS,UAAU,MAAM;AAerF,MAAM,cAAc,CAA4D;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AACF,MAAkC;AAChC,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,mBACJ,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,IAAI,UAAU;AAE3D,QAAM,iBAAiB,iBAAiB,OAAO,CAAC,WAAW,QAAQ,oBAAoB,MAAM,CAAC,CAAC;AAC/F,QAAM,cAAc,eAAe,QAAQ,UAAU;AAErD,SACE,oBAAC,OAAA,EAAI,MAAK,SAAQ,cAAY,EAAE,cAAc,GAAG,WAAW,4BAC1D,UAAA,qBAAC,OAAA,EAAI,WAAU,8BACZ,UAAA;AAAA,IAAA,eAAe,KACd;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QACV,OAAO;AAAA,UACL,OAAO,GAAG,eAAe;AAAA,UACzB,QAAQ,GAAG,eAAe;AAAA,UAC1B,WAAW,cAAc,cAAc,eAAe;AAAA,QAAA;AAAA,MACxD;AAAA,IAAA;AAAA,IAIH,eAAe,IAAI,CAAC,WAAW;AAC9B,YAAM,WAAW,WAAW;AAC5B,YAAM,eAAe,oBAAoB,MAAM;AAE/C,iCACG,mBAAA,EAA+B,SAAS,EAAE,aAAa,QAAQ,GAC9D,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,gBAAc;AAAA,UACd,SAAS,MAAM,SAAS,MAAM;AAAA,UAC9B,WAAW;AAAA,YACT;AAAA,YACA,WAAW,wBAAwB;AAAA,UAAA;AAAA,UAErC,OAAO,EAAE,OAAO,GAAG,eAAe,OAAO,QAAQ,GAAG,eAAe,MAAA;AAAA,UAElE,UAAA,aAAa,WAAW,QAAQ;AAAA,QAAA;AAAA,MAAA,KAXb,MAaxB;AAAA,IAEJ,CAAC;AAAA,EAAA,EAAA,CACH,EAAA,CACF;AAEJ;"}
|