@khanacademy/wonder-blocks-icon 5.3.23 → 6.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @khanacademy/wonder-blocks-icon
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - a1f5a09: `PhosphorIcon` now automatically mirrors directional icons in RTL via a central allowlist, so call sites must stop swapping icons on `isRtl` (or pass `mirrorInRtl` to override per call site) to avoid double-flipping.
8
+
3
9
  ## 5.3.23
4
10
 
5
11
  ### Patch Changes
@@ -28,6 +28,13 @@ import { IconSize, PhosphorIconAsset } from "../types";
28
28
  * - medium: 2.4rem (24px)
29
29
  * - large: 4.8rem (48px)
30
30
  * - xlarge: 9.6rem (96px)
31
+ *
32
+ * ## Right-to-left
33
+ *
34
+ * Directional glyphs (arrows, carets, and similar) are mirrored automatically
35
+ * in RTL via a central allowlist. Pass the LTR-facing icon — do not swap icons
36
+ * based on `isRtl`. Use `mirrorInRtl` to override the allowlist per call site.
37
+ * See the PhosphorIcon RTL Mirroring docs for the list.
31
38
  */
32
39
  export declare const PhosphorIcon: React.ForwardRefExoticComponent<Pick<AriaProps, "aria-hidden" | "aria-label" | "role"> & {
33
40
  /**
@@ -70,4 +77,14 @@ export declare const PhosphorIcon: React.ForwardRefExoticComponent<Pick<AriaProp
70
77
  * - `string`: an import referencing an arbitrary SVG file.
71
78
  */
72
79
  icon: PhosphorIconAsset | string;
80
+ /**
81
+ * Whether to mirror this icon's glyph in right-to-left content, overriding
82
+ * the directional allowlist.
83
+ *
84
+ * - `true`: mirror even if the icon is not allowlisted (e.g. a custom
85
+ * directional SVG).
86
+ * - `false`: do not mirror even if the icon is allowlisted.
87
+ * - omitted: use the allowlist.
88
+ */
89
+ mirrorInRtl?: boolean;
73
90
  } & React.RefAttributes<HTMLSpanElement>>;
package/dist/es/index.js CHANGED
@@ -6,7 +6,11 @@ import { sizing } from '@khanacademy/wonder-blocks-tokens';
6
6
 
7
7
  function getSize$1(size){switch(size){case"small":default:{return styles$1.small}case"medium":{return styles$1.medium}case"large":{return styles$1.large}case"xlarge":{return styles$1.xlarge}}}const StyledDiv=addStyle("div");const Icon=React.forwardRef((props,ref)=>{const{size="small",id,testId,style,children,...otherProps}=props;const childrenElement=React.cloneElement(children,{style:{width:"100%",height:"100%"}});return jsx(StyledDiv,{style:[getSize$1(size),style],id:id,"data-testid":testId,ref:ref,...otherProps,children:childrenElement})});const styles$1=StyleSheet.create({small:{width:sizing.size_160,height:sizing.size_160},medium:{width:sizing.size_240,height:sizing.size_240},large:{width:sizing.size_480,height:sizing.size_480},xlarge:{width:sizing.size_960,height:sizing.size_960}});
8
8
 
9
- const StyledSpan=addStyle("span");const PhosphorIcon=React.forwardRef(function PhosphorIcon(props,ref){const{color="currentColor",icon,size="small",style,testId,className,role,...sharedProps}=props;const sizeStyles=getSize(size);const classNames=`${className??""}`;return jsx(StyledSpan,{...sharedProps,className:classNames,style:[styles.svg,sizeStyles,{backgroundColor:color,maskImage:`url(${icon})`},style],"data-testid":testId,ref:ref,role:role??sharedProps["aria-label"]?"img":undefined})});function getSize(size){switch(size){case"small":default:return styles.small;case"medium":return styles.medium;case"large":return styles.large;case"xlarge":return styles.xlarge}}const styles=StyleSheet.create({svg:{display:"inline-block",verticalAlign:"text-bottom",flexShrink:0,flexGrow:0,maskSize:"100%",maskRepeat:"no-repeat",maskPosition:"center"},small:{width:sizing.size_160,height:sizing.size_160},medium:{width:sizing.size_240,height:sizing.size_240},large:{width:sizing.size_480,height:sizing.size_480},xlarge:{width:sizing.size_960,height:sizing.size_960}});PhosphorIcon.displayName="PhosphorIcon";
9
+ const MIRRORED_ICON_NAMES=new Set(["arrow-arc-left","arrow-arc-right","arrow-bend-double-up-left","arrow-bend-double-up-right","arrow-bend-down-left","arrow-bend-down-right","arrow-bend-left-down","arrow-bend-left-up","arrow-bend-right-down","arrow-bend-right-up","arrow-bend-up-left","arrow-bend-up-right","arrow-circle-down-left","arrow-circle-down-right","arrow-circle-left","arrow-circle-right","arrow-circle-up-left","arrow-circle-up-right","arrow-down-left","arrow-down-right","arrow-elbow-down-left","arrow-elbow-down-right","arrow-elbow-left","arrow-elbow-left-down","arrow-elbow-left-up","arrow-elbow-right","arrow-elbow-right-down","arrow-elbow-right-up","arrow-elbow-up-left","arrow-elbow-up-right","arrow-fat-left","arrow-fat-line-left","arrow-fat-line-right","arrow-fat-lines-left","arrow-fat-lines-right","arrow-fat-right","arrow-left","arrow-line-down-left","arrow-line-down-right","arrow-line-left","arrow-line-right","arrow-line-up-left","arrow-line-up-right","arrow-right","arrow-square-down-left","arrow-square-down-right","arrow-square-left","arrow-square-right","arrow-u-down-left","arrow-u-down-right","arrow-u-left-down","arrow-u-left-up","arrow-u-right-down","arrow-u-right-up","arrow-u-up-left","arrow-u-up-right","arrow-up-left","caret-circle-double-left","caret-circle-double-right","caret-circle-left","caret-circle-right","caret-double-left","caret-double-right","caret-left","caret-line-left","caret-line-right","caret-right","paper-plane","paper-plane-right","paper-plane-tilt","sign-in","sign-out","text-indent","text-outdent"]);
10
+
11
+ const WEIGHT_SUFFIXES=["-bold","-fill","-thin","-light","-duotone"];function getPhosphorIconName(icon){if(!icon||icon.startsWith("data:")){return undefined}const path=icon.split(/[?#]/)[0];const basename=path.substring(path.lastIndexOf("/")+1);if(!basename.toLowerCase().endsWith(".svg")){return undefined}const withoutExtension=basename.slice(0,-".svg".length);for(const suffix of WEIGHT_SUFFIXES){if(withoutExtension.endsWith(suffix)){return withoutExtension.slice(0,-suffix.length)}}return withoutExtension}function shouldMirrorIconInRtl(icon){const name=getPhosphorIconName(icon);if(!name){return false}const segments=name.split("-");for(let i=0;i<segments.length;i++){if(MIRRORED_ICON_NAMES.has(segments.slice(i).join("-"))){return true}}for(let end=1;end<segments.length;end++){if(MIRRORED_ICON_NAMES.has(segments.slice(0,end).join("-"))){return true}}return false}function resolveMirrorInRtl(icon,mirrorInRtl){return mirrorInRtl??shouldMirrorIconInRtl(icon)}
12
+
13
+ const StyledSpan=addStyle("span");const PhosphorIcon=React.forwardRef(function PhosphorIcon(props,ref){const{color="currentColor",icon,size="small",style,testId,className,role,mirrorInRtl,...sharedProps}=props;const sizeStyles=getSize(size);const classNames=`${className??""}`;const shouldMirror=resolveMirrorInRtl(icon,mirrorInRtl);return jsx(StyledSpan,{...sharedProps,className:classNames,style:[styles.svg,sizeStyles,{backgroundColor:color,maskImage:`url(${icon})`},shouldMirror&&styles.mirroredInRtl,style],"data-testid":testId,ref:ref,role:role??sharedProps["aria-label"]?"img":undefined})});function getSize(size){switch(size){case"small":default:return styles.small;case"medium":return styles.medium;case"large":return styles.large;case"xlarge":return styles.xlarge}}const styles=StyleSheet.create({svg:{display:"inline-block",verticalAlign:"text-bottom",flexShrink:0,flexGrow:0,maskSize:"100%",maskRepeat:"no-repeat",maskPosition:"center"},mirroredInRtl:{[":dir(rtl)"]:{transform:"scaleX(-1)"}},small:{width:sizing.size_160,height:sizing.size_160},medium:{width:sizing.size_240,height:sizing.size_240},large:{width:sizing.size_480,height:sizing.size_480},xlarge:{width:sizing.size_960,height:sizing.size_960}});PhosphorIcon.displayName="PhosphorIcon";
10
14
 
11
15
  function useImageRoleAttributes(props){const{"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy}=props;const presentationOnlyAttributes={"aria-hidden":true};const iconMeaningAttributes={"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,role:"img"};const attributes=ariaLabel||ariaLabelledBy?iconMeaningAttributes:presentationOnlyAttributes;return attributes}
12
16
 
package/dist/index.js CHANGED
@@ -30,7 +30,11 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
30
30
 
31
31
  function getSize$1(size){switch(size){case"small":default:{return styles$1.small}case"medium":{return styles$1.medium}case"large":{return styles$1.large}case"xlarge":{return styles$1.xlarge}}}const StyledDiv=wonderBlocksCore.addStyle("div");const Icon=React__namespace.forwardRef((props,ref)=>{const{size="small",id,testId,style,children,...otherProps}=props;const childrenElement=React__namespace.cloneElement(children,{style:{width:"100%",height:"100%"}});return jsxRuntime.jsx(StyledDiv,{style:[getSize$1(size),style],id:id,"data-testid":testId,ref:ref,...otherProps,children:childrenElement})});const styles$1=aphrodite.StyleSheet.create({small:{width:wonderBlocksTokens.sizing.size_160,height:wonderBlocksTokens.sizing.size_160},medium:{width:wonderBlocksTokens.sizing.size_240,height:wonderBlocksTokens.sizing.size_240},large:{width:wonderBlocksTokens.sizing.size_480,height:wonderBlocksTokens.sizing.size_480},xlarge:{width:wonderBlocksTokens.sizing.size_960,height:wonderBlocksTokens.sizing.size_960}});
32
32
 
33
- const StyledSpan=wonderBlocksCore.addStyle("span");const PhosphorIcon=React__namespace.forwardRef(function PhosphorIcon(props,ref){const{color="currentColor",icon,size="small",style,testId,className,role,...sharedProps}=props;const sizeStyles=getSize(size);const classNames=`${className??""}`;return jsxRuntime.jsx(StyledSpan,{...sharedProps,className:classNames,style:[styles.svg,sizeStyles,{backgroundColor:color,maskImage:`url(${icon})`},style],"data-testid":testId,ref:ref,role:role??sharedProps["aria-label"]?"img":undefined})});function getSize(size){switch(size){case"small":default:return styles.small;case"medium":return styles.medium;case"large":return styles.large;case"xlarge":return styles.xlarge}}const styles=aphrodite.StyleSheet.create({svg:{display:"inline-block",verticalAlign:"text-bottom",flexShrink:0,flexGrow:0,maskSize:"100%",maskRepeat:"no-repeat",maskPosition:"center"},small:{width:wonderBlocksTokens.sizing.size_160,height:wonderBlocksTokens.sizing.size_160},medium:{width:wonderBlocksTokens.sizing.size_240,height:wonderBlocksTokens.sizing.size_240},large:{width:wonderBlocksTokens.sizing.size_480,height:wonderBlocksTokens.sizing.size_480},xlarge:{width:wonderBlocksTokens.sizing.size_960,height:wonderBlocksTokens.sizing.size_960}});PhosphorIcon.displayName="PhosphorIcon";
33
+ const MIRRORED_ICON_NAMES=new Set(["arrow-arc-left","arrow-arc-right","arrow-bend-double-up-left","arrow-bend-double-up-right","arrow-bend-down-left","arrow-bend-down-right","arrow-bend-left-down","arrow-bend-left-up","arrow-bend-right-down","arrow-bend-right-up","arrow-bend-up-left","arrow-bend-up-right","arrow-circle-down-left","arrow-circle-down-right","arrow-circle-left","arrow-circle-right","arrow-circle-up-left","arrow-circle-up-right","arrow-down-left","arrow-down-right","arrow-elbow-down-left","arrow-elbow-down-right","arrow-elbow-left","arrow-elbow-left-down","arrow-elbow-left-up","arrow-elbow-right","arrow-elbow-right-down","arrow-elbow-right-up","arrow-elbow-up-left","arrow-elbow-up-right","arrow-fat-left","arrow-fat-line-left","arrow-fat-line-right","arrow-fat-lines-left","arrow-fat-lines-right","arrow-fat-right","arrow-left","arrow-line-down-left","arrow-line-down-right","arrow-line-left","arrow-line-right","arrow-line-up-left","arrow-line-up-right","arrow-right","arrow-square-down-left","arrow-square-down-right","arrow-square-left","arrow-square-right","arrow-u-down-left","arrow-u-down-right","arrow-u-left-down","arrow-u-left-up","arrow-u-right-down","arrow-u-right-up","arrow-u-up-left","arrow-u-up-right","arrow-up-left","caret-circle-double-left","caret-circle-double-right","caret-circle-left","caret-circle-right","caret-double-left","caret-double-right","caret-left","caret-line-left","caret-line-right","caret-right","paper-plane","paper-plane-right","paper-plane-tilt","sign-in","sign-out","text-indent","text-outdent"]);
34
+
35
+ const WEIGHT_SUFFIXES=["-bold","-fill","-thin","-light","-duotone"];function getPhosphorIconName(icon){if(!icon||icon.startsWith("data:")){return undefined}const path=icon.split(/[?#]/)[0];const basename=path.substring(path.lastIndexOf("/")+1);if(!basename.toLowerCase().endsWith(".svg")){return undefined}const withoutExtension=basename.slice(0,-".svg".length);for(const suffix of WEIGHT_SUFFIXES){if(withoutExtension.endsWith(suffix)){return withoutExtension.slice(0,-suffix.length)}}return withoutExtension}function shouldMirrorIconInRtl(icon){const name=getPhosphorIconName(icon);if(!name){return false}const segments=name.split("-");for(let i=0;i<segments.length;i++){if(MIRRORED_ICON_NAMES.has(segments.slice(i).join("-"))){return true}}for(let end=1;end<segments.length;end++){if(MIRRORED_ICON_NAMES.has(segments.slice(0,end).join("-"))){return true}}return false}function resolveMirrorInRtl(icon,mirrorInRtl){return mirrorInRtl??shouldMirrorIconInRtl(icon)}
36
+
37
+ const StyledSpan=wonderBlocksCore.addStyle("span");const PhosphorIcon=React__namespace.forwardRef(function PhosphorIcon(props,ref){const{color="currentColor",icon,size="small",style,testId,className,role,mirrorInRtl,...sharedProps}=props;const sizeStyles=getSize(size);const classNames=`${className??""}`;const shouldMirror=resolveMirrorInRtl(icon,mirrorInRtl);return jsxRuntime.jsx(StyledSpan,{...sharedProps,className:classNames,style:[styles.svg,sizeStyles,{backgroundColor:color,maskImage:`url(${icon})`},shouldMirror&&styles.mirroredInRtl,style],"data-testid":testId,ref:ref,role:role??sharedProps["aria-label"]?"img":undefined})});function getSize(size){switch(size){case"small":default:return styles.small;case"medium":return styles.medium;case"large":return styles.large;case"xlarge":return styles.xlarge}}const styles=aphrodite.StyleSheet.create({svg:{display:"inline-block",verticalAlign:"text-bottom",flexShrink:0,flexGrow:0,maskSize:"100%",maskRepeat:"no-repeat",maskPosition:"center"},mirroredInRtl:{[":dir(rtl)"]:{transform:"scaleX(-1)"}},small:{width:wonderBlocksTokens.sizing.size_160,height:wonderBlocksTokens.sizing.size_160},medium:{width:wonderBlocksTokens.sizing.size_240,height:wonderBlocksTokens.sizing.size_240},large:{width:wonderBlocksTokens.sizing.size_480,height:wonderBlocksTokens.sizing.size_480},xlarge:{width:wonderBlocksTokens.sizing.size_960,height:wonderBlocksTokens.sizing.size_960}});PhosphorIcon.displayName="PhosphorIcon";
34
38
 
35
39
  function useImageRoleAttributes(props){const{"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy}=props;const presentationOnlyAttributes={"aria-hidden":true};const iconMeaningAttributes={"aria-label":ariaLabel,"aria-labelledby":ariaLabelledBy,role:"img"};const attributes=ariaLabel||ariaLabelledBy?iconMeaningAttributes:presentationOnlyAttributes;return attributes}
36
40
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Extracts the Phosphor icon name from a resolved asset reference.
3
+ *
4
+ * The `icon` prop is whatever the bundler produced for the SVG import, so the
5
+ * shape varies by environment:
6
+ * - Vite dev server: `/node_modules/.../assets/regular/arrow-right.svg`
7
+ * - Vite production / Storybook build: `/assets/arrow-right-[hash].svg`
8
+ * - rspack `asset/resource`: `/images/[hash]-arrow-right.svg`
9
+ * - Jest transform: `arrow-right.svg`
10
+ *
11
+ * All of these end in `<name>[-weight].svg`, optionally behind a content hash
12
+ * and a directory prefix, so the name is recovered from the final path
13
+ * segment. Returns `undefined` when no name can be recovered — most notably
14
+ * for `data:` URIs, which carry no file name at all.
15
+ */
16
+ export declare function getPhosphorIconName(icon: string): string | undefined;
17
+ /**
18
+ * Whether the given icon asset should be mirrored in right-to-left content.
19
+ *
20
+ * Bundlers insert a content hash next to the icon name:
21
+ * - rspack `asset/resource`: `[hash]-[name][ext]` (hash prefix)
22
+ * - Vite production builds: `[name]-[hash][ext]` (hash suffix)
23
+ *
24
+ * We match every `-`-delimited prefix and suffix of the basename against the
25
+ * allowlist so both forms resolve (e.g. `arrow-right` from
26
+ * `a1b2c3d4-arrow-right` or `arrow-right-a1b2c3d4`). Returns `false` when the
27
+ * icon cannot be identified.
28
+ */
29
+ export declare function shouldMirrorIconInRtl(icon: string): boolean;
30
+ /**
31
+ * Resolves whether an icon is mirrored in RTL, honoring the `mirrorInRtl`
32
+ * override.
33
+ *
34
+ * `mirrorInRtl` wins in both directions when set: `true` opts a custom
35
+ * directional SVG in, `false` opts an allowlisted icon out. Omitting it falls
36
+ * back to the allowlist.
37
+ */
38
+ export declare function resolveMirrorInRtl(icon: string, mirrorInRtl?: boolean): boolean;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Icon names mirrored in RTL by `PhosphorIcon`. Single source of truth —
3
+ * add or remove names in a Wonder Blocks PR rather than special-casing a call
4
+ * site. `mirrorInRtl` exists for the exceptions. See the PhosphorIcon RTL docs
5
+ * for deliberate exclusions (media, launch, trends, physical text-align, etc.).
6
+ *
7
+ * Names are the Phosphor asset names, without the weight suffix — browse them
8
+ * at https://phosphoricons.com/. All weights of a name share one entry, so
9
+ * `caret-right` covers `caret-right-bold`, `caret-right-fill`, and so on.
10
+ */
11
+ export declare const MIRRORED_ICON_NAMES: ReadonlySet<string>;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "5.3.23",
6
+ "version": "6.0.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },