diff --git a/apps/public-docsite-v9/package.json b/apps/public-docsite-v9/package.json index f96a4e5c7c7526..a857d4db110ef9 100644 --- a/apps/public-docsite-v9/package.json +++ b/apps/public-docsite-v9/package.json @@ -27,6 +27,7 @@ "@fluentui/react-northstar": "*", "@fluentui/react-icons-northstar": "*", "@fluentui/react-components": "*", + "@fluentui/react-file-type-icons": "*", "@fluentui/react-storybook-addon": "*", "@fluentui/react-storybook-addon-export-to-sandbox": "*", "@fluentui/theme-designer": "*", diff --git a/apps/public-docsite/src/pages/Styles/FileTypeIconsPage/docs/web/FileTypeIconsImplementation.md b/apps/public-docsite/src/pages/Styles/FileTypeIconsPage/docs/web/FileTypeIconsImplementation.md index ed533cbd3d4adb..41744dc833bdf3 100644 --- a/apps/public-docsite/src/pages/Styles/FileTypeIconsPage/docs/web/FileTypeIconsImplementation.md +++ b/apps/public-docsite/src/pages/Styles/FileTypeIconsPage/docs/web/FileTypeIconsImplementation.md @@ -1,4 +1,4 @@ -The most durable and future-proof way to get the right icon for a file or item is to use the [react-file-type-icons package](https://github.com/microsoft/fluentui/tree/master/packages/react-file-type-icons) from Fluent UI (`@fluentui/react-file-type-icons` on npm). +The most durable and future-proof way to get the right icon for a file or item is to use the [react-file-type-icons package](https://github.com/microsoft/fluentui/tree/master/packages/react-components/react-file-type-icons/library) from Fluent UI (`@fluentui/react-file-type-icons` on npm). The following code shows you how to specify a file type icon by extension, item type, icon size, and image type using the `@fluentui/react-file-type-icons` package along with Fluent UI's `` component: @@ -19,8 +19,8 @@ When specifying `size`, stick to these default sizes so the images appear as int #### References -Here's a [simple demo](https://github.com/microsoft/fluentui/blob/master/packages/react-examples/src/react-experiments/FileTypeIcon/FileTypeIcon.Basic.Example.tsx) of how to use the [`@fluentui/react-file-type-icons` package](https://github.com/microsoft/fluentui/tree/master/packages/react-file-type-icons).  The code for the main method you'll use ([`getFileTypeIconProps`](https://github.com/microsoft/fluentui/blob/master/packages/react-file-type-icons/src/getFileTypeIconProps.ts)) is also well-commented. The icons are kept up-to-date on Fluent UI's CDN, so we recommend not copying or referencing these assets directly. +Here's a [simple demo](https://github.com/microsoft/fluentui/blob/master/packages/react-examples/src/react-experiments/FileTypeIcon/FileTypeIcon.Basic.Example.tsx) of how to use the [`@fluentui/react-file-type-icons` package](https://github.com/microsoft/fluentui/tree/master/packages/react-components/react-file-type-icons/library).  The code for the main method you'll use ([`getFileTypeIconProps`](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.ts)) is also well-commented. The icons are kept up-to-date on Fluent UI's CDN, so we recommend not copying or referencing these assets directly. -#### Map icons to extensions - [TypeScript mapping file](https://github.com/microsoft/fluentui/blob/master/packages/react-file-type-icons/src/FileTypeIconMap.ts) +#### Map icons to extensions - [TypeScript mapping file](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/FileTypeIconMap.ts) -This file maps file extensions to the right PNG or SVG icon. The mapping minimizes the number of required icons while maximizing the number of files that get a non-generic icon. Support for non-file system objects which may not have file extensions can be added in [getFileTypeIconProps.ts](https://github.com/microsoft/fluentui/blob/master/packages/react-file-type-icons/src/getFileTypeIconProps.ts) and [FileIconType.ts](https://github.com/microsoft/fluentui/blob/master/packages/react-file-type-icons/src/FileIconType.ts). +This file maps file extensions to the right PNG or SVG icon. The mapping minimizes the number of required icons while maximizing the number of files that get a non-generic icon. Support for non-file system objects which may not have file extensions can be added in [getFileTypeIconProps.ts](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.ts) and [FileIconType.ts](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/FileIconType.ts). diff --git a/apps/vr-tests-react-components/package.json b/apps/vr-tests-react-components/package.json index 947b718394ca52..55d61b964d1c96 100644 --- a/apps/vr-tests-react-components/package.json +++ b/apps/vr-tests-react-components/package.json @@ -75,6 +75,7 @@ "@fluentui/react-teaching-popover": "*", "@fluentui/react-tag-picker": "*", "@fluentui/react-carousel": "*", + "@fluentui/react-file-type-icons": "*", "@fluentui/react-list": "*", "@fluentui/react-color-picker": "*", "@fluentui/react-nav": "*" diff --git a/apps/vr-tests-react-components/src/stories/FileTypeIcon/FileTypeIcon.stories.tsx b/apps/vr-tests-react-components/src/stories/FileTypeIcon/FileTypeIcon.stories.tsx new file mode 100644 index 00000000000000..0e8da90751431c --- /dev/null +++ b/apps/vr-tests-react-components/src/stories/FileTypeIcon/FileTypeIcon.stories.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import type { Meta } from '@storybook/react-webpack5'; +import { Steps } from 'storywright'; +import type { StoryParameters } from 'storywright'; +import { FileIconType, FileTypeIcon } from '@fluentui/react-file-type-icons'; +import { DARK_MODE, getStoryVariant, HIGH_CONTRAST, TestWrapperDecorator } from '../../utilities'; + +export default { + title: 'FileTypeIcon', + component: FileTypeIcon, + decorators: [TestWrapperDecorator], + parameters: { + storyWright: { + steps: new Steps().snapshot('default', { cropTo: '.testWrapper' }).end(), + }, + } satisfies StoryParameters, +} satisfies Meta; + +export const Default = () => ( +
+ + + + + +
+); +Default.storyName = 'default'; + +export const DefaultHighContrast = getStoryVariant(Default, HIGH_CONTRAST); + +export const DefaultDarkMode = getStoryVariant(Default, DARK_MODE); diff --git a/change/@fluentui-babel-preset-storybook-full-source-443e9121-6ba8-4f1e-a657-36bae8b67e43.json b/change/@fluentui-babel-preset-storybook-full-source-443e9121-6ba8-4f1e-a657-36bae8b67e43.json new file mode 100644 index 00000000000000..ccc39508faa977 --- /dev/null +++ b/change/@fluentui-babel-preset-storybook-full-source-443e9121-6ba8-4f1e-a657-36bae8b67e43.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Minor refactor of modifyImports/fullsource helpers carried on this branch from an earlier dev session; no published runtime changes.", + "packageName": "@fluentui/babel-preset-storybook-full-source", + "email": "caperez@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-file-type-icons-8ed04cc5-3dd7-452c-a5a2-c18c9ca7638a.json b/change/@fluentui-react-file-type-icons-8ed04cc5-3dd7-452c-a5a2-c18c9ca7638a.json new file mode 100644 index 00000000000000..b9e0b351509272 --- /dev/null +++ b/change/@fluentui-react-file-type-icons-8ed04cc5-3dd7-452c-a5a2-c18c9ca7638a.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Move @fluentui/react-file-type-icons under react-components and ship as 9.0.0 (same npm name, major bump - v8 consumers must opt in). Adds the FileTypeIcon v9 component while preserving the v8 utility surface (initializeFileTypeIcons, getFileTypeIconProps, getFileTypeIconAsHTMLString, getFileTypeIconAsUrl, FileIconType, FileTypeIconMap). Behavior notes: initializeFileTypeIcons now honors its options.disableWarnings flag (warns on duplicate icon registration unless suppressed) but overwrites existing entries rather than skipping them; getFileTypeIconSuffix is SSR-safe and falls back to a 1x device pixel ratio when window is absent instead of throwing; icons are still written to globalThis.__globalSettings__.icons with the same record shape, preserving interop with v8 @fluentui/react ; src/version.ts is removed (no setVersion call), so the v8 duplicate-package warning will not fire if 8.x and 9.x copies are bundled together.", + "packageName": "@fluentui/react-file-type-icons", + "email": "caperez@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-headless-components-preview-782ee96a-e8c3-404e-94c9-923f0faca775.json b/change/@fluentui-react-headless-components-preview-782ee96a-e8c3-404e-94c9-923f0faca775.json new file mode 100644 index 00000000000000..3e08fd46b602f2 --- /dev/null +++ b/change/@fluentui-react-headless-components-preview-782ee96a-e8c3-404e-94c9-923f0faca775.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "This branch showed lint problems when merged an unrelated commit, submitting with linter fix to ensure the GH Actions pass.", + "packageName": "@fluentui/react-headless-components-preview", + "email": "caperez@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-shared-contexts-filetypeicon-stylehook.json b/change/@fluentui-react-shared-contexts-filetypeicon-stylehook.json new file mode 100644 index 00000000000000..8be957cce5f187 --- /dev/null +++ b/change/@fluentui-react-shared-contexts-filetypeicon-stylehook.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Add FileTypeIcon custom style hook key", + "packageName": "@fluentui/react-shared-contexts", + "email": "caperez@microsoft.com", + "dependentChangeType": "patch" +} \ No newline at end of file diff --git a/nx.json b/nx.json index ec7986898dad7f..86ef40b5bcd349 100644 --- a/nx.json +++ b/nx.json @@ -187,8 +187,7 @@ "packages/utilities/**", "packages/react-icons-mdl2/**", "packages/react-monaco-editor/**", - "packages/react-docsite-components/**", - "packages/react-file-type-icons/**" + "packages/react-docsite-components/**" ] } ], diff --git a/packages/react-components/babel-preset-storybook-full-source/src/modifyImports.ts b/packages/react-components/babel-preset-storybook-full-source/src/modifyImports.ts index 03f8348eb73726..3006ceb5da934e 100644 --- a/packages/react-components/babel-preset-storybook-full-source/src/modifyImports.ts +++ b/packages/react-components/babel-preset-storybook-full-source/src/modifyImports.ts @@ -17,7 +17,7 @@ export const PLUGIN_NAME = 'storybook-stories-modifyImports'; */ export function modifyImportsPlugin(babel: typeof Babel, options: BabelPluginOptions): Babel.PluginObj { const { types: t } = babel; - const { importMappings } = options; + const importMappings = options.importMappings ?? {}; const cssModulesEnabled = Boolean(options.cssModules); return { diff --git a/packages/react-components/react-file-type-icons/library/.swcrc b/packages/react-components/react-file-type-icons/library/.swcrc new file mode 100644 index 00000000000000..b4ffa86dee3067 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/.swcrc @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "exclude": [ + "/testing", + "/**/*.cy.ts", + "/**/*.cy.tsx", + "/**/*.spec.ts", + "/**/*.spec.tsx", + "/**/*.test.ts", + "/**/*.test.tsx" + ], + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true, + "decorators": false, + "dynamicImport": false + }, + "externalHelpers": true, + "transform": { + "react": { + "runtime": "classic", + "useSpread": true + } + }, + "target": "es2019" + }, + "minify": false, + "sourceMaps": true +} diff --git a/packages/react-file-type-icons/CHANGELOG.json b/packages/react-components/react-file-type-icons/library/CHANGELOG.json similarity index 100% rename from packages/react-file-type-icons/CHANGELOG.json rename to packages/react-components/react-file-type-icons/library/CHANGELOG.json diff --git a/packages/react-file-type-icons/CHANGELOG.md b/packages/react-components/react-file-type-icons/library/CHANGELOG.md similarity index 100% rename from packages/react-file-type-icons/CHANGELOG.md rename to packages/react-components/react-file-type-icons/library/CHANGELOG.md diff --git a/packages/react-file-type-icons/LICENSE b/packages/react-components/react-file-type-icons/library/LICENSE similarity index 100% rename from packages/react-file-type-icons/LICENSE rename to packages/react-components/react-file-type-icons/library/LICENSE diff --git a/packages/react-components/react-file-type-icons/library/README.md b/packages/react-components/react-file-type-icons/library/README.md new file mode 100644 index 00000000000000..6a091608d769b3 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/README.md @@ -0,0 +1,51 @@ +# @fluentui/react-file-type-icons + +**File type icons for [Fluent UI React](https://react.fluentui.dev/)** + +This package includes a collection of icons to represent file types. + +## Getting started + +If you are using the v8 `Icon` component or composing with icon-name utilities, you can make all file type icons available by calling the `initializeFileTypeIcons` function from the `@fluentui/react-file-type-icons` package: + +```tsx +import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons'; + +// Register icons and load assets from the default Microsoft Fluent CDN: +initializeFileTypeIcons(); + +// Or register icons and load assets from a different CDN or folder path: +initializeFileTypeIcons('https://my.cdn.com/path/to/icons/'); +``` + +**NOTE:** Proceed carefully if you override the default CDN location, whose contents may not match the registered file type icons and supported extensions. Do not use the `item-types-fluent` icon set that was previously uploaded to the Fluent CDN; it's deprecated. + +## Usage in code + +If you are using Fluent UI React, you can use the `Icon` component and pass in the corresponding icon properties to render a given icon. + +```tsx +import { Icon } from '@fluentui/react/lib/Icon'; +import { getFileTypeIconProps } from '@fluentui/react-file-type-icons'; + +; +``` + +## FileTypeIcon component + +`@fluentui/react-file-type-icons` is a Fluent UI React v9 package under the `react-components` package model. It also keeps the existing utility APIs for scenarios that still compose file icons manually. + +This package also exports a `FileTypeIcon` React component that can be used directly without separately composing icon props or calling `initializeFileTypeIcons`. + +```tsx +import { FileTypeIcon, FileIconType } from '@fluentui/react-file-type-icons'; + +; +; +``` + +`FileTypeIcon` resolves icons through the same shared extension/type logic as `getFileTypeIconProps` to keep utility and component-based usage in sync. + +## Notes + +See [GitHub](https://github.com/microsoft/fluentui) for more details on the Fluent UI React project and packages within. diff --git a/packages/react-components/react-file-type-icons/library/config/api-extractor.json b/packages/react-components/react-file-type-icons/library/config/api-extractor.json new file mode 100644 index 00000000000000..8d482156d10d53 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/config/api-extractor.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "@fluentui/scripts-api-extractor/api-extractor.common.v-next.json", + "mainEntryPointFilePath": "/../../../../../../dist/out-tsc/types/packages/react-components//library/src/index.d.ts" +} diff --git a/packages/react-components/react-file-type-icons/library/config/tests.js b/packages/react-components/react-file-type-icons/library/config/tests.js new file mode 100644 index 00000000000000..2e211ae9e21420 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/config/tests.js @@ -0,0 +1 @@ +/** Jest test setup file. */ diff --git a/packages/react-file-type-icons/eslint.config.js b/packages/react-components/react-file-type-icons/library/eslint.config.js similarity index 100% rename from packages/react-file-type-icons/eslint.config.js rename to packages/react-components/react-file-type-icons/library/eslint.config.js diff --git a/packages/react-components/react-file-type-icons/library/etc/react-file-type-icons.api.md b/packages/react-components/react-file-type-icons/library/etc/react-file-type-icons.api.md new file mode 100644 index 00000000000000..54d63e17c27518 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/etc/react-file-type-icons.api.md @@ -0,0 +1,148 @@ +## API Report File for "@fluentui/react-file-type-icons" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { ComponentProps } from '@fluentui/react-utilities'; +import type { ComponentState } from '@fluentui/react-utilities'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import type { JSXElement } from '@fluentui/react-utilities'; +import * as React_2 from 'react'; +import type { Slot } from '@fluentui/react-utilities'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +// @public +export enum FileIconType { + // (undocumented) + album = 21,// Start at 1 so it will evaluate as "truthy" + // (undocumented) + campaign = 23, + // (undocumented) + desktopFolder = 9, + // (undocumented) + docset = 1, + // (undocumented) + documentsFolder = 10, + // (undocumented) + folder = 2, + // (undocumented) + form = 14, + // (undocumented) + genericFile = 3, + // (undocumented) + linkedFolder = 12, + // (undocumented) + list = 13, + // (undocumented) + listForm = 22, + // (undocumented) + listItem = 4, + // (undocumented) + loopworkspace = 17, + // (undocumented) + multiple = 6, + // (undocumented) + news = 8, + // (undocumented) + picturesFolder = 11, + // (undocumented) + planner = 18, + // (undocumented) + playlist = 16, + // (undocumented) + portfolio = 20, + // (undocumented) + sharedFolder = 5, + // (undocumented) + shortcutsdefaultfolder = 24, + // (undocumented) + stream = 7, + // (undocumented) + sway = 15, + // (undocumented) + todoItem = 19 +} + +// @public +export const FileTypeIcon: ForwardRefComponent; + +// @public (undocumented) +export const fileTypeIconClassNames: SlotClassNames; + +// @public +export const FileTypeIconMap: { + [key: string]: { + extensions?: string[]; + types?: FileIconType[]; + }; +}; + +// @public (undocumented) +export type FileTypeIconProps = Omit, 'src'> & IFileTypeIconOptions & { + baseUrl?: string; +}; + +// @public (undocumented) +export type FileTypeIconSize = 16 | 20 | 24 | 32 | 40 | 48 | 64 | 96; + +// @public (undocumented) +export type FileTypeIconSlots = { + root: Slot<'img'>; +}; + +// @public (undocumented) +export type FileTypeIconState = ComponentState & Required> & { + baseUrl?: string; + iconName: string; + src?: string; + extension?: string; + type?: IFileTypeIconOptions['type']; +}; + +// @public +export function getFileTypeIconAsHTMLString(options: IFileTypeIconOptions, baseUrl?: string): string | undefined; + +// @public +export function getFileTypeIconAsUrl(options: IFileTypeIconOptions, baseUrl?: string): string | undefined; + +// @public +export function getFileTypeIconProps(options: IFileTypeIconOptions): { + iconName: string; + 'aria-label'?: string; +}; + +// @public (undocumented) +export interface IFileTypeIconOptions { + extension?: string; + imageFileType?: ImageFileType; + size?: FileTypeIconSize; + type?: FileIconTypeInput; +} + +// @public (undocumented) +export interface IIconOptions { + // (undocumented) + disableWarnings: boolean; + // (undocumented) + warnOnMissingIcons?: boolean; +} + +// @public (undocumented) +export type ImageFileType = 'svg' | 'png'; + +// @public (undocumented) +export function initializeFileTypeIcons(baseUrl?: string, options?: Partial): void; + +// @public +export const renderFileTypeIcon_unstable: (state: FileTypeIconState) => JSXElement; + +// @public +export const useFileTypeIcon_unstable: (props: FileTypeIconProps, ref: React_2.Ref) => FileTypeIconState; + +// @public (undocumented) +export const useFileTypeIconStyles_unstable: (state: FileTypeIconState) => FileTypeIconState; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/react-components/react-file-type-icons/library/jest.config.js b/packages/react-components/react-file-type-icons/library/jest.config.js new file mode 100644 index 00000000000000..ac25f140dcf9bf --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/jest.config.js @@ -0,0 +1,25 @@ +// @ts-check +/* eslint-disable */ + +const { readFileSync } = require('node:fs'); +const { join } = require('node:path'); + +const { exclude: _, ...swcJestConfig } = JSON.parse(readFileSync(join(__dirname, '.swcrc'), 'utf-8')); + +if (swcJestConfig.swcrc === undefined) { + swcJestConfig.swcrc = false; +} + +/** + * @type {import('@jest/types').Config.InitialOptions} + */ +module.exports = { + displayName: 'react-file-type-icons', + preset: '../../../../jest.preset.js', + transform: { + '^.+\\.tsx?$': ['@swc/jest', swcJestConfig], + }, + coverageDirectory: './coverage', + setupFilesAfterEnv: ['./config/tests.js'], + snapshotSerializers: ['@griffel/jest-serializer'], +}; diff --git a/packages/react-components/react-file-type-icons/library/package.json b/packages/react-components/react-file-type-icons/library/package.json new file mode 100644 index 00000000000000..7e84e85f432093 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/package.json @@ -0,0 +1,48 @@ +{ + "name": "@fluentui/react-file-type-icons", + "version": "9.0.0", + "description": "Fluent UI React FileTypeIcon component and file type icon utilities.", + "main": "lib-commonjs/index.js", + "module": "lib/index.js", + "typings": "./dist/index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/fluentui" + }, + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.4.2", + "@fluentui/react-shared-contexts": "^9.26.2", + "@fluentui/react-utilities": "^9.26.3", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + }, + "beachball": { + "disallowedChangeTypes": [ + "major", + "prerelease" + ] + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": "./lib-commonjs/index.js", + "import": "./lib/index.js", + "require": "./lib-commonjs/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "*.md", + "dist/*.d.ts", + "lib", + "lib-commonjs" + ] +} diff --git a/packages/react-components/react-file-type-icons/library/project.json b/packages/react-components/react-file-type-icons/library/project.json new file mode 100644 index 00000000000000..5a18eb305dda56 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/project.json @@ -0,0 +1,8 @@ +{ + "name": "react-file-type-icons", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/react-components/react-file-type-icons/library/src", + "tags": ["vNext", "platform:web"], + "implicitDependencies": [] +} diff --git a/packages/react-file-type-icons/src/FileIconType.test.ts b/packages/react-components/react-file-type-icons/library/src/FileIconType.test.ts similarity index 100% rename from packages/react-file-type-icons/src/FileIconType.test.ts rename to packages/react-components/react-file-type-icons/library/src/FileIconType.test.ts diff --git a/packages/react-file-type-icons/src/FileIconType.ts b/packages/react-components/react-file-type-icons/library/src/FileIconType.ts similarity index 100% rename from packages/react-file-type-icons/src/FileIconType.ts rename to packages/react-components/react-file-type-icons/library/src/FileIconType.ts diff --git a/packages/react-components/react-file-type-icons/library/src/FileTypeIcon.ts b/packages/react-components/react-file-type-icons/library/src/FileTypeIcon.ts new file mode 100644 index 00000000000000..c21d15d335c8b9 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/FileTypeIcon.ts @@ -0,0 +1,13 @@ +export type { + FileTypeIconProps, + FileTypeIconSlots, + FileTypeIconState, + ImageFileType as FileTypeIconImageFileType, +} from './components/FileTypeIcon/index'; +export { + FileTypeIcon, + fileTypeIconClassNames, + renderFileTypeIcon_unstable, + useFileTypeIcon_unstable, + useFileTypeIconStyles_unstable, +} from './components/FileTypeIcon/index'; diff --git a/packages/react-file-type-icons/src/FileTypeIconMap.ts b/packages/react-components/react-file-type-icons/library/src/FileTypeIconMap.ts similarity index 86% rename from packages/react-file-type-icons/src/FileTypeIconMap.ts rename to packages/react-components/react-file-type-icons/library/src/FileTypeIconMap.ts index fa9cfbf59f7ffc..b50d2af037b6ce 100644 --- a/packages/react-file-type-icons/src/FileTypeIconMap.ts +++ b/packages/react-components/react-file-type-icons/library/src/FileTypeIconMap.ts @@ -1,9 +1,11 @@ +import { FileIconType } from './FileIconType'; + /** * Enumeration of icon file names, and what extensions they map to. * Please keep items alphabetical. Items without extensions may require specific logic in the code to map. * Always use getFileTypeIconProps to get the most up-to-date icon at the right pixel density. */ -export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { +export const FileTypeIconMap: { [key: string]: { extensions?: string[]; types?: FileIconType[] } } = { accdb: { extensions: ['accdb', 'mdb'], }, @@ -13,7 +15,9 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { archive: { extensions: ['7z', 'ace', 'arc', 'arj', 'dmg', 'gz', 'iso', 'lzh', 'pkg', 'rar', 'sit', 'tgz', 'tar', 'z'], }, - album: {}, + album: { + types: [FileIconType.album], + }, audio: { extensions: [ 'aif', @@ -270,13 +274,21 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { csv: { extensions: ['csv'], }, - companyfolder: {}, + companyfolder: { + types: [FileIconType.shortcutsdefaultfolder], + }, designer: { extensions: ['design'], }, - desktopfolder: {}, - docset: {}, - documentsfolder: {}, + desktopfolder: { + types: [FileIconType.desktopFolder], + }, + docset: { + types: [FileIconType.docset], + }, + documentsfolder: { + types: [FileIconType.documentsFolder], + }, docx: { extensions: ['doc', 'docm', 'docx', 'docb'], }, @@ -290,12 +302,18 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { extensions: ['application', 'appref-ms', 'apk', 'app', 'appx', 'exe', 'ipa', 'msi', 'xap'], }, favoritesfolder: {}, - folder: {}, + folder: { + types: [FileIconType.folder], + }, font: { extensions: ['ttf', 'otf', 'woff'], }, - form: {}, - genericfile: {}, + form: { + types: [FileIconType.form], + }, + genericfile: { + types: [FileIconType.genericFile], + }, html: { extensions: ['htm', 'html', 'mht', 'mhtml'], }, @@ -308,17 +326,27 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { link: { extensions: ['lnk', 'link', 'url', 'website', 'webloc'], }, - linkedfolder: {}, - listform: {}, - listitem: {}, + linkedfolder: { + types: [FileIconType.linkedFolder], + }, + listform: { + types: [FileIconType.listForm], + }, + listitem: { + types: [FileIconType.listItem], + }, loop: { extensions: ['fluid', 'loop', 'note'], }, - loopworkspace: {}, + loopworkspace: { + types: [FileIconType.loopworkspace], + }, officescript: { extensions: ['osts'], }, - splist: {}, + splist: { + types: [FileIconType.list], + }, mcworld: { extensions: ['mcworld'], }, @@ -364,7 +392,9 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { mpt: { extensions: ['mpt'], }, - multiple: {}, + multiple: { + types: [FileIconType.multiple], + }, one: { // This is a partial OneNote page or section export. Not whole notebooks, see "onetoc" extensions: ['one', 'onepart'], @@ -424,9 +454,15 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { ], }, photo360: {}, - picturesfolder: {}, - planner: {}, - portfolio: {}, + picturesfolder: { + types: [FileIconType.picturesFolder], + }, + planner: { + types: [FileIconType.planner], + }, + portfolio: { + types: [FileIconType.portfolio], + }, potx: { extensions: ['pot', 'potm', 'potx'], }, @@ -448,17 +484,27 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { spo: { extensions: ['aspx'], }, - spocampaign: {}, - sponews: {}, + spocampaign: { + types: [FileIconType.campaign], + }, + sponews: { + types: [FileIconType.news], + }, spreadsheet: { extensions: ['odc', 'ods', 'gsheet', 'numbers', 'tsv'], }, rtf: { extensions: ['epub', 'gdoc', 'odt', 'rtf', 'wri', 'pages'], }, - sharedfolder: {}, - playlist: {}, - sway: {}, + sharedfolder: { + types: [FileIconType.sharedFolder], + }, + playlist: { + types: [FileIconType.playlist], + }, + sway: { + types: [FileIconType.sway], + }, sysfile: { extensions: [ 'bak', @@ -492,7 +538,9 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { 'xll', ], }, - todoitem: {}, + todoitem: { + types: [FileIconType.todoItem], + }, txt: { extensions: ['dif', 'diff', 'readme', 'out', 'plist', 'properties', 'text', 'txt'], }, @@ -525,6 +573,7 @@ export const FileTypeIconMap: { [key: string]: { extensions?: string[] } } = { ], }, video: { + types: [FileIconType.stream], extensions: [ '3g2', '3gp', diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.test.tsx b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.test.tsx new file mode 100644 index 00000000000000..d8c1e754c110ad --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.test.tsx @@ -0,0 +1,101 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { CustomStyleHooksProvider_unstable } from '@fluentui/react-shared-contexts'; +import { getFileTypeIconAsUrl } from '../../getFileTypeIconAsUrl'; +import { getFileTypeIconProps } from '../../getFileTypeIconProps'; +import { FileIconType } from '../../FileIconType'; +import { FileTypeIcon } from './FileTypeIcon'; +import type { FileTypeIconState } from './FileTypeIcon.types'; +import { fileTypeIconClassNames } from './useFileTypeIconStyles.styles'; + +describe('FileTypeIcon', () => { + beforeAll(() => { + Object.defineProperty(window, 'devicePixelRatio', { + value: 1, + configurable: true, + }); + }); + + it('renders as an image element', () => { + const { getByRole } = render(); + expect(getByRole('img')).toBeTruthy(); + }); + + it('applies the component static class name', () => { + const { getByRole } = render(); + expect(getByRole('img').className.indexOf(fileTypeIconClassNames.root) >= 0).toBe(true); + }); + + it('uses shared resolver output for icon name and URL', () => { + const options = { extension: 'pptx' as const, size: 24 as const, imageFileType: 'svg' as const }; + const expectedIcon = getFileTypeIconProps(options); + const expectedUrl = getFileTypeIconAsUrl(options); + + const { getByRole } = render(); + const img = getByRole('img'); + + expect(img.getAttribute('data-icon-name')).toBe(expectedIcon.iconName); + expect(img.getAttribute('src')).toBe(expectedUrl); + expect(img.getAttribute('aria-label')).toBe(expectedIcon['aria-label']); + expect(img.getAttribute('width')).toBe('24'); + expect(img.getAttribute('height')).toBe('24'); + }); + + it('supports resolving by FileIconType and custom baseUrl', () => { + const options = { + type: FileIconType.folder, + size: 32 as const, + imageFileType: 'png' as const, + }; + + const expectedUrl = getFileTypeIconAsUrl(options, 'https://example.com/assets/item-types/'); + + const { getByRole } = render( + , + ); + + expect(getByRole('img').getAttribute('src')).toBe(expectedUrl); + }); + + it('supports v9-style invocation for non-extension campaign type mapping', () => { + const { getByRole } = render(); + expect(getByRole('img').getAttribute('data-icon-name')).toContain('spocampaign24_svg'); + }); + + it('falls back to genericfile icon for unknown extension', () => { + const { getByRole } = render(); + const iconName = getByRole('img').getAttribute('data-icon-name') || ''; + expect(iconName.slice(0, 'genericfile'.length)).toBe('genericfile'); + }); + + it('forwards refs to the root img element', () => { + const ref = React.createRef(); + render(); + + expect(ref.current?.tagName).toBe('IMG'); + }); + + it('supports v9 custom style hooks', () => { + const fileTypeIconStylesHook = jest.fn((state: unknown) => { + const fileTypeIconState = state as FileTypeIconState; + (fileTypeIconState.root as Record)['data-custom-style-hook'] = 'true'; + }); + const customStyleHooks = { + ['useFileTypeIconStyles_unstable']: fileTypeIconStylesHook, + }; + + const { getByRole } = render( + + + , + ); + + expect(fileTypeIconStylesHook).toHaveBeenCalledTimes(1); + expect(getByRole('img').getAttribute('data-custom-style-hook')).toBe('true'); + }); +}); diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.tsx b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.tsx new file mode 100644 index 00000000000000..8d38c78003c04e --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.tsx @@ -0,0 +1,24 @@ +'use client'; + +import * as React from 'react'; +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import type { FileTypeIconProps } from './FileTypeIcon.types'; +import { renderFileTypeIcon_unstable } from './renderFileTypeIcon_unstable'; +import { useFileTypeIcon_unstable } from './useFileTypeIcon_unstable'; +import { useFileTypeIconStyles_unstable } from './useFileTypeIconStyles.styles'; + +/** + * FileTypeIcon renders a file type icon as an image resolved from the file extension or type. + */ +export const FileTypeIcon: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useFileTypeIcon_unstable(props, ref); + + useFileTypeIconStyles_unstable(state); + + useCustomStyleHook_unstable('useFileTypeIconStyles_unstable')(state); + + return renderFileTypeIcon_unstable(state); +}); + +FileTypeIcon.displayName = 'FileTypeIcon'; diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.types.ts b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.types.ts new file mode 100644 index 00000000000000..329ca8e1583714 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/FileTypeIcon.types.ts @@ -0,0 +1,26 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { ImageFileType, IFileTypeIconOptions } from '../../getFileTypeIconProps'; + +export type FileTypeIconSlots = { + root: Slot<'img'>; +}; + +export type FileTypeIconProps = Omit, 'src'> & + IFileTypeIconOptions & { + /** + * Optional base URL used to resolve image URLs. + * @default DEFAULT_BASE_URL + */ + baseUrl?: string; + }; + +export type FileTypeIconState = ComponentState & + Required> & { + baseUrl?: string; + iconName: string; + src?: string; + extension?: string; + type?: IFileTypeIconOptions['type']; + }; + +export type { ImageFileType }; diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/index.ts b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/index.ts new file mode 100644 index 00000000000000..eede2598095f66 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/index.ts @@ -0,0 +1,5 @@ +export type { FileTypeIconProps, FileTypeIconSlots, FileTypeIconState, ImageFileType } from './FileTypeIcon.types'; +export { FileTypeIcon } from './FileTypeIcon'; +export { renderFileTypeIcon_unstable } from './renderFileTypeIcon_unstable'; +export { useFileTypeIcon_unstable } from './useFileTypeIcon_unstable'; +export { fileTypeIconClassNames, useFileTypeIconStyles_unstable } from './useFileTypeIconStyles.styles'; diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/renderFileTypeIcon_unstable.tsx b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/renderFileTypeIcon_unstable.tsx new file mode 100644 index 00000000000000..d962d302bc5338 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/renderFileTypeIcon_unstable.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { assertSlots, getSlots } from '@fluentui/react-utilities'; +import type { JSXElement } from '@fluentui/react-utilities'; +import type { FileTypeIconSlots, FileTypeIconState } from './FileTypeIcon.types'; + +/** + * Render the final JSX of FileTypeIcon. + */ +export const renderFileTypeIcon_unstable = (state: FileTypeIconState): JSXElement => { + assertSlots(state); + // eslint-disable-next-line @typescript-eslint/no-deprecated + const { slots, slotProps } = getSlots(state); + + return React.createElement(slots.root, slotProps.root); +}; diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIconStyles.styles.ts b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIconStyles.styles.ts new file mode 100644 index 00000000000000..ad27a426ba4bfe --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIconStyles.styles.ts @@ -0,0 +1,24 @@ +'use client'; + +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { FileTypeIconSlots, FileTypeIconState } from './FileTypeIcon.types'; + +export const fileTypeIconClassNames: SlotClassNames = { + root: 'fui-FileTypeIcon', +}; + +const useStyles = makeStyles({ + root: { + display: 'inline-block', + verticalAlign: 'middle', + }, +}); + +export const useFileTypeIconStyles_unstable = (state: FileTypeIconState): FileTypeIconState => { + const styles = useStyles(); + + state.root.className = mergeClasses(fileTypeIconClassNames.root, styles.root, state.root.className); + + return state; +}; diff --git a/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIcon_unstable.ts b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIcon_unstable.ts new file mode 100644 index 00000000000000..5cad9efca9c319 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/FileTypeIcon/useFileTypeIcon_unstable.ts @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; +import { resolveFileTypeIconUrl } from '../../fileTypeIconUrl'; +import { DEFAULT_ICON_SIZE } from '../../getFileTypeIconProps'; +import type { FileTypeIconProps, FileTypeIconState } from './FileTypeIcon.types'; + +/** + * Given user props, returns state and render function for a FileTypeIcon. + */ +export const useFileTypeIcon_unstable = ( + props: FileTypeIconProps, + ref: React.Ref, +): FileTypeIconState => { + const { + baseUrl, + extension, + type, + size = DEFAULT_ICON_SIZE, + imageFileType = 'svg', + 'aria-label': ariaLabel, + ...rootProps + } = props; + + const { + src, + iconName, + ariaLabel: resolvedAriaLabel, + } = resolveFileTypeIconUrl({ extension, type, size, imageFileType }, baseUrl); + + return { + size, + imageFileType, + baseUrl, + iconName, + src, + extension, + type, + components: { + root: 'img', + }, + root: slot.always( + getIntrinsicElementProps('img', { + ref, + width: size, + height: size, + ...rootProps, + src, + 'aria-label': ariaLabel ?? resolvedAriaLabel, + 'data-icon-name': iconName, + }), + { elementType: 'img' }, + ), + }; +}; diff --git a/packages/react-components/react-file-type-icons/library/src/components/index.ts b/packages/react-components/react-file-type-icons/library/src/components/index.ts new file mode 100644 index 00000000000000..be4c861ba9f71c --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/components/index.ts @@ -0,0 +1 @@ +export * from './FileTypeIcon/index'; diff --git a/packages/react-components/react-file-type-icons/library/src/fileTypeIconUrl.ts b/packages/react-components/react-file-type-icons/library/src/fileTypeIconUrl.ts new file mode 100644 index 00000000000000..a79cd7a361e37b --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/fileTypeIconUrl.ts @@ -0,0 +1,36 @@ +import { + DEFAULT_ICON_SIZE, + getDevicePixelRatioVariant, + getFileTypeIconNameFromExtensionOrType, +} from './getFileTypeIconProps'; +import type { IFileTypeIconOptions } from './getFileTypeIconProps'; + +const FLUENT_CDN_BASE_URL = 'https://res.cdn.office.net/files/fabric-cdn-prod_20260506.001'; + +export const DEFAULT_BASE_URL = `${FLUENT_CDN_BASE_URL}/assets/item-types/`; +export const ICON_SIZES: number[] = [16, 20, 24, 32, 40, 48, 64, 96]; + +export type FileTypeIconUrlResolution = { + src: string; + iconName: string; + ariaLabel?: string; + usesPixelRatioDirectory: boolean; +}; + +export function resolveFileTypeIconUrl( + options: IFileTypeIconOptions, + baseUrl: string = DEFAULT_BASE_URL, +): FileTypeIconUrlResolution { + const { extension, size = DEFAULT_ICON_SIZE, type, imageFileType } = options; + const baseIconName = getFileTypeIconNameFromExtensionOrType(extension, type); + const { dprDir, ext } = getDevicePixelRatioVariant(size, imageFileType); + + const src = dprDir ? `${baseUrl}${size}${dprDir}/${baseIconName}.${ext}` : `${baseUrl}${size}/${baseIconName}.${ext}`; + + return { + src, + iconName: baseIconName + size + dprDir + '_' + ext, + ariaLabel: extension, + usesPixelRatioDirectory: dprDir !== '', + }; +} diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.test.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.test.ts similarity index 98% rename from packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.test.ts rename to packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.test.ts index 19b2efa9c1db8a..2df402628f1c39 100644 --- a/packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.test.ts +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.test.ts @@ -1,4 +1,4 @@ -import { ICON_SIZES, DEFAULT_BASE_URL } from './initializeFileTypeIcons'; +import { DEFAULT_BASE_URL, ICON_SIZES } from './fileTypeIconUrl'; import { DEFAULT_ICON_SIZE } from './getFileTypeIconProps'; import { getFileTypeIconAsHTMLString } from './getFileTypeIconAsHTMLString'; import type { FileTypeIconSize } from './getFileTypeIconProps'; diff --git a/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.ts new file mode 100644 index 00000000000000..722470bfabb246 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsHTMLString.ts @@ -0,0 +1,21 @@ +import { DEFAULT_BASE_URL, resolveFileTypeIconUrl } from './fileTypeIconUrl'; +import type { IFileTypeIconOptions } from './getFileTypeIconProps'; + +/** + * Given the `fileTypeIconOptions`, this function returns the DOM element for the `FileTypeIcon` + * as an HTML string. Similar to `getFileTypeIconProps`, this also accepts the same type of object + * but rather than returning the `iconName`, this returns the entire DOM element as a string. + * @param options - Options used to resolve the file type icon HTML string. + * @param baseUrl - optionally provide a custom CDN base url to fetch icons from + */ +export function getFileTypeIconAsHTMLString( + options: IFileTypeIconOptions, + baseUrl: string = DEFAULT_BASE_URL, +): string | undefined { + const resolution = resolveFileTypeIconUrl(options, baseUrl); + + if (resolution.usesPixelRatioDirectory) { + return ``; + } + return ``; +} diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.test.ts similarity index 97% rename from packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts rename to packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.test.ts index e67f8c4e4fa9a9..6582fd3eaab774 100644 --- a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.test.ts @@ -1,4 +1,4 @@ -import { ICON_SIZES, DEFAULT_BASE_URL } from './initializeFileTypeIcons'; +import { DEFAULT_BASE_URL, ICON_SIZES } from './fileTypeIconUrl'; import { DEFAULT_ICON_SIZE } from './getFileTypeIconProps'; import type { FileTypeIconSize } from './getFileTypeIconProps'; import { getFileTypeIconAsUrl } from './getFileTypeIconAsUrl'; diff --git a/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.ts new file mode 100644 index 00000000000000..3cbcc94401c515 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconAsUrl.ts @@ -0,0 +1,16 @@ +import { DEFAULT_BASE_URL, resolveFileTypeIconUrl } from './fileTypeIconUrl'; +import type { IFileTypeIconOptions } from './getFileTypeIconProps'; + +/** + * Given the `fileTypeIconOptions`, this function returns the CDN-based URL for `FileTypeIcon`. + * Similar to `getFileTypeIconProps`, this also accepts the same type of object + * but rather than returning the `iconName`, this returns the raw URL. + * @param options - Options used to resolve the file type icon URL. + * @param baseUrl - optionally provide a custom CDN base url to fetch icons from + */ +export function getFileTypeIconAsUrl( + options: IFileTypeIconOptions, + baseUrl: string = DEFAULT_BASE_URL, +): string | undefined { + return resolveFileTypeIconUrl(options, baseUrl).src; +} diff --git a/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.test.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.test.ts new file mode 100644 index 00000000000000..71dd738300bcea --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.test.ts @@ -0,0 +1,41 @@ +import { FileIconType } from './FileIconType'; +import { FileTypeIconMap } from './FileTypeIconMap'; +import { getFileTypeIconNameFromExtensionOrType, getFileTypeIconProps } from './getFileTypeIconProps'; + +describe('getFileTypeIconNameFromExtensionOrType', () => { + it('returns an icon name in file type icon map for every FileIconType enum value', () => { + for (const key of Object.keys(FileIconType)) { + // Iterate through a TypeScript enum + const value = FileIconType[key as unknown as FileIconType]; + if (typeof value === 'number') { + expect(FileTypeIconMap).toHaveProperty(getFileTypeIconNameFromExtensionOrType(undefined, value)); + } + } + }); + + it('supports extension-based mapping with case-insensitive input', () => { + expect(getFileTypeIconNameFromExtensionOrType('DOCX', undefined)).toBe('docx'); + expect(getFileTypeIconNameFromExtensionOrType('.pptx', undefined)).toBe('pptx'); + expect(getFileTypeIconNameFromExtensionOrType('unknown-extension', undefined)).toBe('genericfile'); + }); + + it('supports non-extension mappings from FileIconType declarations in FileTypeIconMap', () => { + expect(getFileTypeIconNameFromExtensionOrType(undefined, FileIconType.folder)).toBe('folder'); + expect(getFileTypeIconNameFromExtensionOrType(undefined, FileIconType.list)).toBe('splist'); + expect(getFileTypeIconNameFromExtensionOrType(undefined, FileIconType.stream)).toBe('video'); + expect(getFileTypeIconNameFromExtensionOrType(undefined, FileIconType.campaign)).toBe('spocampaign'); + }); +}); + +describe('getFileTypeIconProps (v8-style invocation)', () => { + it('returns a usable iconName object for legacy utility callers', () => { + const result = getFileTypeIconProps({ extension: 'docx', size: 16, imageFileType: 'svg' }); + expect(result.iconName).toBe('docx16_svg'); + expect(result['aria-label']).toBe('docx'); + }); + + it('resolves non-extension icon types for legacy utility callers', () => { + const result = getFileTypeIconProps({ type: FileIconType.folder, size: 20, imageFileType: 'png' }); + expect(result.iconName).toContain('folder20'); + }); +}); diff --git a/packages/react-file-type-icons/src/getFileTypeIconProps.ts b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.ts similarity index 50% rename from packages/react-file-type-icons/src/getFileTypeIconProps.ts rename to packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.ts index 295036d48cb207..57b61da1f6f530 100644 --- a/packages/react-file-type-icons/src/getFileTypeIconProps.ts +++ b/packages/react-components/react-file-type-icons/library/src/getFileTypeIconProps.ts @@ -1,33 +1,11 @@ +import { canUseDOM } from '@fluentui/react-utilities'; import { FileTypeIconMap } from './FileTypeIconMap'; -import { FileIconType } from './FileIconType'; -import type { FileIconTypeInput } from './FileIconType'; +import type { FileIconType, FileIconTypeInput } from './FileIconType'; let _extensionToIconName: { [key: string]: string }; +let _typeToIconName: { [key: number]: string }; const GENERIC_FILE = 'genericfile'; -const FOLDER = 'folder'; -const SHARED_FOLDER = 'sharedfolder'; -const DOCSET_FOLDER = 'docset'; -const LIST_ITEM = 'listitem'; -const LIST = 'splist'; -const MULTIPLE_ITEMS = 'multiple'; -const NEWS = 'sponews'; -const STREAM = 'video'; -const DESKTOP_FOLDER = 'desktopfolder'; -const DOCUMENTS_FOLDER = 'documentsfolder'; -const PICTURES_FOLDER = 'picturesfolder'; -const LINKED_FOLDER = 'linkedfolder'; -const FORM = 'form'; -const SWAY = 'sway'; -const PLAYLIST = 'playlist'; -const LOOP_WORKSPACE = 'loopworkspace'; -const TODOITEM = 'todoitem'; -const PLANNER = 'planner'; -const PORTFOLIO = 'portfolio'; -const ALBUM = 'album'; -const LIST_FORM = 'listform'; -const CAMPAIGN = 'spocampaign'; -const SHORTCUTS_DEFAULT_FOLDER = 'companyfolder'; export const DEFAULT_ICON_SIZE: FileTypeIconSize = 16; export type FileTypeIconSize = 16 | 20 | 24 | 32 | 40 | 48 | 64 | 96; @@ -63,7 +41,7 @@ export interface IFileTypeIconOptions { * It accounts for different device pixel ratios. For example, * `getFileTypeIconProps({ extension: 'doc', size: 16, imageFileType: 'png' })` * will return `{ iconName: 'docx16_2x_png' }` if the `devicePixelRatio` is 2. - * @param options + * @param options - Options used to resolve the file type icon props. */ export function getFileTypeIconProps(options: IFileTypeIconOptions): { iconName: string; 'aria-label'?: string } { // First, obtain the base name of the icon using the extension or type. @@ -71,7 +49,7 @@ export function getFileTypeIconProps(options: IFileTypeIconOptions): { iconName: const { extension, type, size, imageFileType } = options; iconBaseName = getFileTypeIconNameFromExtensionOrType(extension, type); - // Next, obtain the suffix using the icon size, user's device pixel ration, and + // Next, obtain the suffix using the icon size, user's device pixel ratio, and // preference for svg or png const _size: FileTypeIconSize = size || DEFAULT_ICON_SIZE; const suffix: string = getFileTypeIconSuffix(_size, imageFileType); @@ -83,7 +61,6 @@ export function getFileTypeIconNameFromExtensionOrType( extension: string | undefined, type: FileIconType | undefined, ): string { - let iconBaseName: string | undefined; if (extension) { if (!_extensionToIconName) { _extensionToIconName = {}; @@ -105,111 +82,65 @@ export function getFileTypeIconNameFromExtensionOrType( extension = extension.replace('.', '').toLowerCase(); return _extensionToIconName[extension] || GENERIC_FILE; } else if (type) { - switch (type) { - case FileIconType.docset: - iconBaseName = DOCSET_FOLDER; - break; - case FileIconType.folder: - iconBaseName = FOLDER; - break; - case FileIconType.listItem: - iconBaseName = LIST_ITEM; - break; - case FileIconType.sharedFolder: - iconBaseName = SHARED_FOLDER; - break; - case FileIconType.stream: - iconBaseName = STREAM; - break; - case FileIconType.multiple: - iconBaseName = MULTIPLE_ITEMS; - break; - case FileIconType.news: - iconBaseName = NEWS; - break; - case FileIconType.desktopFolder: - iconBaseName = DESKTOP_FOLDER; - break; - case FileIconType.documentsFolder: - iconBaseName = DOCUMENTS_FOLDER; - break; - case FileIconType.picturesFolder: - iconBaseName = PICTURES_FOLDER; - break; - case FileIconType.linkedFolder: - iconBaseName = LINKED_FOLDER; - break; - case FileIconType.list: - iconBaseName = LIST; - break; - case FileIconType.form: - iconBaseName = FORM; - break; - case FileIconType.sway: - iconBaseName = SWAY; - break; - case FileIconType.playlist: - iconBaseName = PLAYLIST; - break; - case FileIconType.loopworkspace: - iconBaseName = LOOP_WORKSPACE; - break; - case FileIconType.planner: - iconBaseName = PLANNER; - break; - case FileIconType.todoItem: - iconBaseName = TODOITEM; - break; - case FileIconType.portfolio: - iconBaseName = PORTFOLIO; - break; - case FileIconType.album: - iconBaseName = ALBUM; - break; - case FileIconType.listForm: - iconBaseName = LIST_FORM; - break; - case FileIconType.campaign: - iconBaseName = CAMPAIGN; - break; - case FileIconType.shortcutsdefaultfolder: - iconBaseName = SHORTCUTS_DEFAULT_FOLDER; - break; + if (!_typeToIconName) { + _typeToIconName = {}; + + for (const iconName in FileTypeIconMap) { + if (FileTypeIconMap.hasOwnProperty(iconName)) { + const types = FileTypeIconMap[iconName].types; + + if (types) { + for (let i = 0; i < types.length; i++) { + _typeToIconName[types[i]] = iconName; + } + } + } + } } + + return _typeToIconName[type] || GENERIC_FILE; } - return iconBaseName || GENERIC_FILE; + + return GENERIC_FILE; +} + +export function getFileTypeIconSuffix(size: FileTypeIconSize, imageFileType: ImageFileType = 'svg'): string { + const { dprDir, ext } = getDevicePixelRatioVariant(size, imageFileType); + return size + dprDir + '_' + ext; } -export function getFileTypeIconSuffix( +/** + * Internal helper that returns the device-pixel-ratio directory suffix and image extension to use + * for a given icon size + format. Centralizes the DPR branching so URL construction and the + * `getFileTypeIconSuffix` string output stay in lock-step. + */ +export function getDevicePixelRatioVariant( size: FileTypeIconSize, imageFileType: ImageFileType = 'svg', - win?: Window, -): string { - // eslint-disable-next-line no-restricted-globals - win ??= window; - const devicePixelRatio: number = win.devicePixelRatio; - let devicePixelRatioSuffix = ''; // Default is 1x +): { dprDir: '' | '_1.5x' | '_2x' | '_3x' | '_4x'; ext: ImageFileType } { + const devicePixelRatio: number = canUseDOM() ? globalThis.window.devicePixelRatio : 1; + let dprDir: '' | '_1.5x' | '_2x' | '_3x' | '_4x' = ''; // SVGs scale well, so you can generally use the default image. // 1.5x is a special case where SVGs need a different image. if (imageFileType === 'svg' && devicePixelRatio > 1 && devicePixelRatio <= 1.5) { // Currently missing 1.5x SVGs at size 20, snap to 1x for now if (size !== 20) { - devicePixelRatioSuffix = '_1.5x'; + dprDir = '_1.5x'; } } else if (imageFileType === 'png') { // To look good, PNGs should use a different image for higher device pixel ratios if (devicePixelRatio > 1 && devicePixelRatio <= 1.5) { // Currently missing 1.5x icons for size 20, snap to 2x for now - devicePixelRatioSuffix = size === 20 ? '_2x' : '_1.5x'; + dprDir = size === 20 ? '_2x' : '_1.5x'; } else if (devicePixelRatio > 1.5 && devicePixelRatio <= 2) { - devicePixelRatioSuffix = '_2x'; + dprDir = '_2x'; } else if (devicePixelRatio > 2 && devicePixelRatio <= 3) { - devicePixelRatioSuffix = '_3x'; + dprDir = '_3x'; } else if (devicePixelRatio > 3) { - devicePixelRatioSuffix = '_4x'; + dprDir = '_4x'; } } - return size + devicePixelRatioSuffix + '_' + imageFileType; + return { dprDir, ext: imageFileType }; } diff --git a/packages/react-file-type-icons/src/index.ts b/packages/react-components/react-file-type-icons/library/src/index.ts similarity index 57% rename from packages/react-file-type-icons/src/index.ts rename to packages/react-components/react-file-type-icons/library/src/index.ts index 6cf4a8febb97fa..59398c561be06e 100644 --- a/packages/react-file-type-icons/src/index.ts +++ b/packages/react-components/react-file-type-icons/library/src/index.ts @@ -1,4 +1,13 @@ export { initializeFileTypeIcons } from './initializeFileTypeIcons'; +export type { IIconOptions } from './initializeFileTypeIcons'; + +export { FileTypeIcon } from './FileTypeIcon'; +export { + fileTypeIconClassNames, + renderFileTypeIcon_unstable, + useFileTypeIconStyles_unstable, + useFileTypeIcon_unstable, +} from './FileTypeIcon'; export { getFileTypeIconProps } from './getFileTypeIconProps'; @@ -10,6 +19,5 @@ export { getFileTypeIconAsHTMLString } from './getFileTypeIconAsHTMLString'; export { getFileTypeIconAsUrl } from './getFileTypeIconAsUrl'; -import './version'; - export type { FileTypeIconSize, IFileTypeIconOptions, ImageFileType } from './getFileTypeIconProps'; +export type { FileTypeIconProps, FileTypeIconSlots, FileTypeIconState } from './FileTypeIcon'; diff --git a/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.test.tsx b/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.test.tsx new file mode 100644 index 00000000000000..7bd3febef29add --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.test.tsx @@ -0,0 +1,123 @@ +import * as React from 'react'; +import { DEFAULT_BASE_URL, ICON_SIZES, initializeFileTypeIcons } from './initializeFileTypeIcons'; + +type RegisteredIcon = { + code: React.ReactElement>; + subset: { + mergeImageProps: boolean; + }; +}; + +type IconRecords = Record; + +type GlobalWithSettings = typeof globalThis & { + __globalSettings__?: { + icons?: IconRecords; + }; +}; + +const expectImageIcon = (icon: unknown, props: React.ImgHTMLAttributes) => { + const element = icon as React.ReactElement>; + + expect(element.type).toBe('img'); + expect(element.props).toMatchObject(props); +}; + +describe('initializeFileTypeIcons', () => { + const globalWithSettings = globalThis as GlobalWithSettings; + + beforeEach(() => { + delete globalWithSettings.__globalSettings__; + }); + + const getRegisteredIcons = () => globalWithSettings.__globalSettings__?.icons as IconRecords; + + const getRegisteredIcon = (iconName: string) => getRegisteredIcons()[iconName.toLowerCase()] as RegisteredIcon; + + it('registers file type icons using the default CDN base URL', () => { + initializeFileTypeIcons(); + + ICON_SIZES.forEach(size => { + expect(getRegisteredIcon(`docx${size}_svg`)).toBeDefined(); + }); + + expect(getRegisteredIcon('docx16_svg').subset.mergeImageProps).toBe(true); + expectImageIcon(getRegisteredIcon('docx16_svg').code, { + src: `${DEFAULT_BASE_URL}16/docx.svg`, + height: 16, + width: 16, + alt: '', + }); + expectImageIcon(getRegisteredIcon('docx16_png').code, { + src: `${DEFAULT_BASE_URL}16/docx.png`, + height: 16, + width: 16, + alt: '', + }); + expectImageIcon(getRegisteredIcon('docx16_1.5x_svg').code, { + src: `${DEFAULT_BASE_URL}16_1.5x/docx.svg`, + height: 16, + width: 16, + alt: '', + }); + expectImageIcon(getRegisteredIcon('docx16_2x_png').code, { + src: `${DEFAULT_BASE_URL}16_2x/docx.png`, + height: 16, + width: 16, + alt: '', + }); + }); + + it('registers file type icons using a custom base URL', () => { + const baseUrl = 'https://example.com/assets/item-types/'; + + initializeFileTypeIcons(baseUrl); + + expectImageIcon(getRegisteredIcon('docx16_svg').code, { + src: `${baseUrl}16/docx.svg`, + height: 16, + width: 16, + alt: '', + }); + }); + + it('redefines file type icons when initialized with a different base URL', () => { + const baseUrl = 'https://example.com/assets/item-types/'; + initializeFileTypeIcons(); + initializeFileTypeIcons(baseUrl, { disableWarnings: true }); + + expectImageIcon(getRegisteredIcon('docx16_svg').code, { + src: `${baseUrl}16/docx.svg`, + height: 16, + width: 16, + alt: '', + }); + }); + + it('keeps v8-compatible image prop merging metadata', () => { + initializeFileTypeIcons(); + + const { subset } = getRegisteredIcon('docx16_svg'); + + expect(subset).toEqual({ mergeImageProps: true }); + }); + + it('warns when icons are re-registered unless disableWarnings is set', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + + try { + initializeFileTypeIcons(); + expect(warnSpy).not.toHaveBeenCalled(); + + initializeFileTypeIcons(); + expect(warnSpy).toHaveBeenCalled(); + expect(warnSpy.mock.calls[0][0]).toMatch(/was re-registered/); + + warnSpy.mockClear(); + initializeFileTypeIcons(undefined, { disableWarnings: true }); + expect(warnSpy).not.toHaveBeenCalled(); + } finally { + warnSpy.mockRestore(); + } + }); +}); diff --git a/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.tsx b/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.tsx new file mode 100644 index 00000000000000..61ae9c8fa6d266 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/src/initializeFileTypeIcons.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import { canUseDOM } from '@fluentui/react-utilities'; +import { FileTypeIconMap } from './FileTypeIconMap'; +import { DEFAULT_BASE_URL, ICON_SIZES } from './fileTypeIconUrl'; + +export { DEFAULT_BASE_URL, ICON_SIZES } from './fileTypeIconUrl'; + +export interface IIconOptions { + disableWarnings: boolean; + warnOnMissingIcons?: boolean; +} + +type IconRecords = Record & { + __options: IIconOptions; +}; + +const GLOBAL_SETTINGS_PROP_NAME = '__globalSettings__'; +const ICON_SETTING_NAME = 'icons'; +const fileTypeIconSubset = { mergeImageProps: true }; + +// Each entry produces a `_` key for every icon name. +// SVGs only ship at 1x and 1.5x; PNGs ship the full DPR ladder. +const ICON_VARIANTS: ReadonlyArray<{ dir: string; exts: ReadonlyArray<'png' | 'svg'> }> = [ + { dir: '', exts: ['png', 'svg'] }, + { dir: '_1.5x', exts: ['png', 'svg'] }, + { dir: '_2x', exts: ['png'] }, + { dir: '_3x', exts: ['png'] }, + { dir: '_4x', exts: ['png'] }, +]; + +const moduleGlobalSettings: Record = {}; + +export function initializeFileTypeIcons(baseUrl: string = DEFAULT_BASE_URL, options?: Partial): void { + ICON_SIZES.forEach((size: number) => { + _initializeIcons(baseUrl, size, options); + }); +} + +function _initializeIcons(baseUrl: string, size: number, options?: Partial): void { + const iconTypes: string[] = Object.keys(FileTypeIconMap); + const fileTypeIcons: { [key: string]: React.ReactElement } = {}; + + iconTypes.forEach((type: string) => { + ICON_VARIANTS.forEach(({ dir, exts }) => { + exts.forEach(ext => { + const key = `${type}${size}${dir}_${ext}`; + const src = `${baseUrl}${size}${dir}/${type}.${ext}`; + fileTypeIcons[key] = createFileTypeIconImage(src, size); + }); + }); + }); + + registerIcons(fileTypeIcons, options); +} + +function createFileTypeIconImage(src: string, size: number): React.ReactElement { + return ; +} + +function registerIcons(icons: Record, options?: Partial): void { + const iconSettings = getIconSettings(); + + // Merge caller-provided options onto the persisted defaults so subsequent registrations + // observe the most recent preference (matches v8 @fluentui/style-utilities behavior). + if (options) { + iconSettings.__options = { ...iconSettings.__options, ...options }; + } + const { disableWarnings } = iconSettings.__options; + + for (const iconName in icons) { + if (Object.prototype.hasOwnProperty.call(icons, iconName)) { + const normalizedIconName = normalizeIconName(iconName); + if (iconSettings[normalizedIconName] && !disableWarnings) { + // eslint-disable-next-line no-console + console.warn(`Icon '${iconName}' was re-registered. Use 'disableWarnings' to suppress this warning.`); + } + iconSettings[normalizedIconName] = { + code: icons[iconName], + subset: fileTypeIconSubset, + }; + } + } +} + +function getIconSettings(): IconRecords { + const globalSettings = getGlobalSettings(); + const iconSettings = globalSettings[ICON_SETTING_NAME]; + + if (!iconSettings || typeof iconSettings !== 'object') { + globalSettings[ICON_SETTING_NAME] = { + __options: { + disableWarnings: false, + warnOnMissingIcons: true, + }, + }; + } + + return globalSettings[ICON_SETTING_NAME] as IconRecords; +} + +function getGlobalSettings(): Record { + if (!canUseDOM()) { + return moduleGlobalSettings; + } + + const globalScope = globalThis as typeof globalThis & { + [GLOBAL_SETTINGS_PROP_NAME]?: Record; + }; + + if (!globalScope[GLOBAL_SETTINGS_PROP_NAME]) { + globalScope[GLOBAL_SETTINGS_PROP_NAME] = {}; + } + + return globalScope[GLOBAL_SETTINGS_PROP_NAME]; +} + +function normalizeIconName(name: string): string { + return name.toLowerCase(); +} diff --git a/packages/react-components/react-file-type-icons/library/tsconfig.json b/packages/react-components/react-file-type-icons/library/tsconfig.json new file mode 100644 index 00000000000000..d9f469376bc84a --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2019", + "noEmit": true, + "isolatedModules": true, + "jsx": "react", + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "noUnusedLocals": true, + "preserveConstEnums": true, + "strict": true + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/react-components/react-file-type-icons/library/tsconfig.lib.json b/packages/react-components/react-file-type-icons/library/tsconfig.lib.json new file mode 100644 index 00000000000000..53066fdd11fff0 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "lib": ["ES2019", "dom"], + "declaration": true, + "declarationDir": "../../../../dist/out-tsc/types", + "outDir": "../../../../dist/out-tsc", + "inlineSources": true, + "types": ["static-assets", "environment"] + }, + "exclude": [ + "./src/testing/**", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.ts", + "**/*.stories.tsx" + ], + "include": ["./src/**/*.ts", "./src/**/*.tsx"] +} diff --git a/packages/react-components/react-file-type-icons/library/tsconfig.spec.json b/packages/react-components/react-file-type-icons/library/tsconfig.spec.json new file mode 100644 index 00000000000000..911456fe4b4d91 --- /dev/null +++ b/packages/react-components/react-file-type-icons/library/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist", + "types": ["jest", "node"] + }, + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.d.ts", + "./src/testing/**/*.ts", + "./src/testing/**/*.tsx" + ] +} diff --git a/packages/react-components/react-file-type-icons/stories/.storybook/main.js b/packages/react-components/react-file-type-icons/stories/.storybook/main.js new file mode 100644 index 00000000000000..d220b5ee9c9207 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/.storybook/main.js @@ -0,0 +1,23 @@ +const rootMain = require('../../../../../.storybook/main'); +const path = require('path'); + +module.exports = { + ...rootMain, + stories: [...rootMain.stories, '../src/**/*.mdx', '../src/**/index.stories.@(ts|tsx)'], + addons: [...rootMain.addons], + /** + * @param {import('webpack').Configuration} config + * @param {any} options + */ + webpackFinal: (config, options) => { + const localConfig = /** @type {import('webpack').Configuration} */ (rootMain.webpackFinal(config, options)); + + localConfig.resolve = localConfig.resolve || {}; + localConfig.resolve.alias = { + ...(localConfig.resolve.alias || {}), + '@fluentui/react-file-type-icons$': path.resolve(__dirname, '../../library/src/index.ts'), + }; + + return localConfig; + }, +}; diff --git a/packages/react-components/react-file-type-icons/stories/.storybook/preview.js b/packages/react-components/react-file-type-icons/stories/.storybook/preview.js new file mode 100644 index 00000000000000..6553edcf2879f1 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/.storybook/preview.js @@ -0,0 +1,10 @@ +import * as rootPreview from '../../../../../.storybook/preview'; + +export const decorators = [...rootPreview.decorators]; + +export const parameters = { + ...rootPreview.parameters, + exportToSandbox: { disabled: true }, +}; + +export const tags = ['autodocs']; diff --git a/packages/react-components/react-file-type-icons/stories/.storybook/tsconfig.json b/packages/react-components/react-file-type-icons/stories/.storybook/tsconfig.json new file mode 100644 index 00000000000000..03980896da25ed --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/.storybook/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "", + "allowJs": true, + "checkJs": true, + "forceConsistentCasingInFileNames": true, + "types": ["static-assets", "environment"] + }, + "include": ["*.js"] +} diff --git a/packages/react-components/react-file-type-icons/stories/README.md b/packages/react-components/react-file-type-icons/stories/README.md new file mode 100644 index 00000000000000..b9409ea529dffe --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/README.md @@ -0,0 +1,20 @@ +# @fluentui/react-file-type-icons-stories + +Storybook stories for packages/react-components/react-file-type-icons/stories + +## Usage + +To include within storybook specify stories globs: + +```js +module.exports = { + stories: [ + '../packages/react-components/react-file-type-icons/stories/src/**/*.stories.mdx', + '../packages/react-components/react-file-type-icons/stories/src/**/index.stories.@(ts|tsx)', + ], +}; +``` + +## API + +No public API available. diff --git a/packages/react-components/react-file-type-icons/stories/eslint.config.js b/packages/react-components/react-file-type-icons/stories/eslint.config.js new file mode 100644 index 00000000000000..2d5076ba7d3060 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/eslint.config.js @@ -0,0 +1,11 @@ +// @ts-check + +const { defineConfig } = require('eslint/config'); +const rootConfig = require('../../../../eslint.config.js'); + +module.exports = defineConfig([ + ...rootConfig, + { + rules: { 'import/no-extraneous-dependencies': 'off' }, + }, +]); diff --git a/packages/react-components/react-file-type-icons/stories/package.json b/packages/react-components/react-file-type-icons/stories/package.json new file mode 100644 index 00000000000000..9b73672168c8ee --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/package.json @@ -0,0 +1,5 @@ +{ + "name": "@fluentui/react-file-type-icons-stories", + "version": "0.0.0", + "private": true +} diff --git a/packages/react-components/react-file-type-icons/stories/project.json b/packages/react-components/react-file-type-icons/stories/project.json new file mode 100644 index 00000000000000..f29bb718962091 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/project.json @@ -0,0 +1,8 @@ +{ + "name": "react-file-type-icons-stories", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "implicitDependencies": ["react-file-type-icons"], + "sourceRoot": "packages/react-components/react-file-type-icons/stories/src", + "tags": ["vNext", "platform:web", "type:stories"] +} diff --git a/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconBestPractices.md b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconBestPractices.md new file mode 100644 index 00000000000000..9ec0d88ee5fccb --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconBestPractices.md @@ -0,0 +1,13 @@ +## Best practices + +### Do + +- Use `extension` for file types (e.g., `pdf`, `docx`, `xlsx`). The leading dot is optional and matching is case-insensitive. +- Use `type` for non-file entities like folders, shared folders, or list items. Pass a named member of the [`FileIconType`](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/FileIconType.ts) enum — never a raw number — so your code stays readable and unaffected by changes to the underlying numeric values. +- If you leave the extension/type blank, or it's unknown, this component will render the correct "generic file icon", so it's safe to pass user-supplied extensions directly. + +### Don't + +- Don't set both `extension` and `type` — `extension` wins and `type` is ignored. +- Don't pass `format="png"` unless you specifically need a bitmap. The default is SVG which looks sharp on any monitor's DPI. +- Don't set `baseUrl` outside offline or isolated scenarios — you lose the shared browser cache and risk icons drifting out of date. If you must, host the exact compatible `item-types` assets yourself and keep the directory structure and file naming aligned with the resolver's expectations (`//.`). diff --git a/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDefault.stories.tsx b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDefault.stories.tsx new file mode 100644 index 00000000000000..f0a0165681f2b4 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDefault.stories.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { FileIconType, FileTypeIcon } from '@fluentui/react-file-type-icons'; + +const columnStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'auto 1fr', + columnGap: 12, + rowGap: 8, + alignItems: 'center', +}; + +export const Default = (): React.ReactElement => ( +
+
+

Extensions and types

+
+ + Microsoft Word document (16px, default) + + Microsoft Excel spreadsheet (20px) + + Microsoft PowerPoint presentation (24px) + + Folder (FileIconType.folder) + + Shared folder (FileIconType.sharedFolder) + + List (FileIconType.list) +
+
+
+

Sizes and formats

+
+ + 16px SVG + + 24px SVG + + 32px PNG + + 48px PNG +
+

Fallback

+
+ + Unknown or empty extension falls back to the generic file icon +
+
+
+); + +Default.parameters = { + docs: { + description: { + story: + 'Overview of extension- and type-based usage alongside size and format variations. Prefer SVG for crisp rendering; use PNG when bitmap assets are required.', + }, + }, +}; + +Default.storyName = 'Default'; diff --git a/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDescription.md b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDescription.md new file mode 100644 index 00000000000000..faf8ff4090816d --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconDescription.md @@ -0,0 +1 @@ +`FileTypeIcon` renders the correct item-type icon for a file or content object based on its file extension or `FileIconType`. diff --git a/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconV8UtilityInterop.stories.tsx b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconV8UtilityInterop.stories.tsx new file mode 100644 index 00000000000000..27a02ebc7cb7b9 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/FileTypeIconV8UtilityInterop.stories.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { getFileTypeIconAsUrl, getFileTypeIconProps } from '@fluentui/react-file-type-icons'; + +export const V8UtilityInterop = (): React.ReactElement => { + const iconProps = getFileTypeIconProps({ extension: 'docx', size: 16, imageFileType: 'svg' }); + const iconUrl = getFileTypeIconAsUrl({ extension: 'docx', size: 16, imageFileType: 'svg' }); + + return ( +
+
+

+ {`getFileTypeIconProps({ extension: 'docx', size: 16, imageFileType: 'svg' })`} returns: +

+
{JSON.stringify(iconProps, null, 2)}
+
+
+

+ {`getFileTypeIconAsUrl({ extension: 'docx', size: 16, imageFileType: 'svg' })`} returns: +

+
url: {iconUrl}
+
+
+

Use with {``} tag:

+
+ + <img> rendered from getFileTypeIconAsUrl +
+
+
+ ); +}; + +V8UtilityInterop.parameters = { + docs: { + description: { + story: [ + 'Utility functions retained from `@fluentui/react-file-type-icons` v8 share the same resolver as the v9 `FileTypeIcon` component, so an extension or `FileIconType` always maps to the same asset whichever entry point you use. They exist to keep existing call sites compiling during a v8 → v9 migration.', + '', + '- `getFileTypeIconAsUrl` — resolved asset URL. Use for ``, CSS `background-image`, or non-React surfaces.', + '- `getFileTypeIconAsHTMLString` — `` HTML string. Use for server-rendered or string-templating contexts.', + '- `getFileTypeIconProps` — `{ iconName }` for callers that still pipe icon names through their own renderer.', + '', + 'Also exported for source-compat only: `initializeFileTypeIcons` (registers assets with the legacy icon registry — only needed if you still render via the v8 `` component), `FileIconType` enum, and `FileTypeIconMap`.', + '', + 'For new code, prefer ``; reach for the utilities only when you need a raw URL/HTML string or are migrating an existing call site incrementally.', + ].join('\n'), + }, + }, +}; + +V8UtilityInterop.storyName = 'v8 Compatibility'; diff --git a/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/index.stories.tsx b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/index.stories.tsx new file mode 100644 index 00000000000000..3b4200a137bfca --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/FileTypeIcon/index.stories.tsx @@ -0,0 +1,86 @@ +import { FileIconType, FileTypeIcon } from '@fluentui/react-file-type-icons'; +import type { Meta } from '@storybook/react-webpack5'; + +import descriptionMd from './FileTypeIconDescription.md'; +import bestPracticesMd from './FileTypeIconBestPractices.md'; + +export { Default } from './FileTypeIconDefault.stories'; +export { V8UtilityInterop } from './FileTypeIconV8UtilityInterop.stories'; + +const meta = { + title: 'Components/FileTypeIcon', + component: FileTypeIcon, + argTypes: { + extension: { + control: { type: 'text' }, + description: 'File extension to resolve, with or without leading dot.', + }, + type: { + control: { type: 'select' }, + options: ['none', 'folder', 'sharedFolder', 'list', 'genericFile'], + mapping: { + none: undefined, + folder: FileIconType.folder, + sharedFolder: FileIconType.sharedFolder, + list: FileIconType.list, + genericFile: FileIconType.genericFile, + }, + description: + 'Special icon type for non-file entities (folder, list, etc.). Pass a [`FileIconType`](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-file-type-icons/library/src/FileIconType.ts) value to render the icon for items without a file extension.', + table: { + type: { + summary: 'FileIconType', + detail: [ + 'docset = 1', + 'folder = 2', + 'genericFile = 3', + 'listItem = 4', + 'sharedFolder = 5', + 'multiple = 6', + 'stream = 7', + 'news = 8', + 'desktopFolder = 9', + 'documentsFolder = 10', + 'picturesFolder = 11', + 'linkedFolder = 12', + 'list = 13', + 'form = 14', + 'sway = 15', + 'playlist = 16', + 'loopworkspace = 17', + 'planner = 18', + 'todoItem = 19', + 'portfolio = 20', + 'album = 21', + 'listForm = 22', + 'campaign = 23', + 'shortcutsdefaultfolder = 24', + ].join('\n'), + }, + }, + }, + size: { + control: { type: 'select' }, + options: [16, 20, 24, 32, 40, 48, 64, 96], + description: 'Icon size in pixels.', + }, + imageFileType: { + control: { type: 'radio' }, + options: ['svg', 'png'], + description: 'Asset format to request from the icon set.', + }, + baseUrl: { + control: { type: 'text' }, + description: 'Optional custom CDN base URL for icon assets.', + }, + }, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +} satisfies Meta; + +export default meta; diff --git a/packages/react-components/react-file-type-icons/stories/src/index.ts b/packages/react-components/react-file-type-icons/stories/src/index.ts new file mode 100644 index 00000000000000..cb0ff5c3b541f6 --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/react-components/react-file-type-icons/stories/tsconfig.json b/packages/react-components/react-file-type-icons/stories/tsconfig.json new file mode 100644 index 00000000000000..e86a49bf63773c --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2019", + "noEmit": true, + "isolatedModules": true, + "importHelpers": true, + "jsx": "react", + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "preserveConstEnums": true + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./.storybook/tsconfig.json" + } + ] +} diff --git a/packages/react-components/react-file-type-icons/stories/tsconfig.lib.json b/packages/react-components/react-file-type-icons/stories/tsconfig.lib.json new file mode 100644 index 00000000000000..b48b90fe605e4d --- /dev/null +++ b/packages/react-components/react-file-type-icons/stories/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["ES2019", "dom"], + "outDir": "../../../../dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "inlineSources": true, + "types": ["static-assets", "environment"] + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"] +} diff --git a/packages/react-components/react-shared-contexts/library/etc/react-shared-contexts.api.md b/packages/react-components/react-shared-contexts/library/etc/react-shared-contexts.api.md index 32323a2d71971f..6443cf2b761338 100644 --- a/packages/react-components/react-shared-contexts/library/etc/react-shared-contexts.api.md +++ b/packages/react-components/react-shared-contexts/library/etc/react-shared-contexts.api.md @@ -97,6 +97,7 @@ export const CustomStyleHooksContext_unstable: React_2.Context {

Size 16 docset icon as .png

-

Size 20 folder icon as .svg

- +

Size 20 shared folder icon as .svg

+

Size 40 genericfile icon as .png

Size 48 listitem icon as .svg

-

Size 64 sharedfolder icon as .png

- +

Size 64 folder icon as .png

+

Size 64 linkedfolder icon as .png

diff --git a/packages/react-file-type-icons/.npmignore b/packages/react-file-type-icons/.npmignore deleted file mode 100644 index 5333ec287d27a2..00000000000000 --- a/packages/react-file-type-icons/.npmignore +++ /dev/null @@ -1,39 +0,0 @@ -*.api.json -*.config.js -*.log -*.nuspec -*.test.* -*.yml -.editorconfig -.eslintrc* -.eslintcache -.gitattributes -.gitignore -.vscode -coverage -dist/storybook -dist/*.stats.html -dist/*.stats.json -dist/demo -fabric-test* -gulpfile.js -images -index.html -jsconfig.json -node_modules -results -src/**/* -!src/**/*.types.ts -temp -tsconfig.json -tsd.json -tslint.json -typings -visualtests -project.json - -# exclude gitignore patterns explicitly -!lib -!lib-commonjs -!lib-amd -!dist \ No newline at end of file diff --git a/packages/react-file-type-icons/README.md b/packages/react-file-type-icons/README.md deleted file mode 100644 index 306739ffaf7035..00000000000000 --- a/packages/react-file-type-icons/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# @fluentui/react-file-type-icons - -**File type icons for [Fluent UI React](https://developer.microsoft.com/en-us/fluentui)** -([formerly Office UI Fabric React](https://developer.microsoft.com/en-us/office/blogs/ui-fabric-is-evolving-into-fluent-ui/)) - -This package includes a collection of icons to represent file types. - -## Getting started - -If you are using Fluent UI React components, you can make all file type icons available by calling the `initializeFileTypeIcons` function from the `@fluentui/react-file-type-icons` package: - -```tsx -import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons'; - -// Register icons and pull the fonts from the default Microsoft Fluent CDN: -initializeFileTypeIcons(); - -// Or register icons and pull the fonts from a different CDN or folder path: -initializeFileTypeIcons('https://my.cdn.com/path/to/icons/'); -``` - -**NOTE:** Proceed carefully if you override the default CDN location, whose contents may not match the registered file type icons and supported extensions. Do not use the `item-types-fluent` icon set that was previously uploaded to the Fluent CDN; it's deprecated. - -## Usage in code - -If you are using Fluent UI React, you can use the `Icon` component and pass in the corresponding icon properties to render a given icon. - -```tsx -import { Icon } from '@fluentui/react/lib/Icon'; -import { getFileTypeIconProps } from '@fluentui/react-file-type-icons'; - -; -``` - -## Notes - -See [GitHub](https://github.com/microsoft/fluentui) for more details on the Fluent UI React project and packages within. diff --git a/packages/react-file-type-icons/jest.config.js b/packages/react-file-type-icons/jest.config.js deleted file mode 100644 index c0d56a899958b3..00000000000000 --- a/packages/react-file-type-icons/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const { createV8Config: createConfig } = require('@fluentui/scripts-jest'); - -module.exports = createConfig(); diff --git a/packages/react-file-type-icons/just.config.ts b/packages/react-file-type-icons/just.config.ts deleted file mode 100644 index b10db31a6aca51..00000000000000 --- a/packages/react-file-type-icons/just.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { preset } from '@fluentui/scripts-tasks'; - -preset(); diff --git a/packages/react-file-type-icons/package.json b/packages/react-file-type-icons/package.json deleted file mode 100644 index 80ea694a241b60..00000000000000 --- a/packages/react-file-type-icons/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@fluentui/react-file-type-icons", - "version": "8.18.0", - "description": "Fluent UI React file type icon set.", - "main": "lib-commonjs/index.js", - "module": "lib/index.js", - "sideEffects": [ - "lib/version.js" - ], - "typings": "lib/index.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/fluentui" - }, - "license": "MIT", - "scripts": { - "build": "just-scripts build", - "clean": "just-scripts clean" - }, - "dependencies": { - "@fluentui/set-version": "^8.2.24", - "@fluentui/style-utilities": "^8.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@types/react": ">=16.8.0 <20.0.0", - "react": ">=16.8.0 <20.0.0" - }, - "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "require": "./lib-commonjs/index.js" - } - } -} diff --git a/packages/react-file-type-icons/project.json b/packages/react-file-type-icons/project.json deleted file mode 100644 index 49d600378aadde..00000000000000 --- a/packages/react-file-type-icons/project.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "react-file-type-icons", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "sourceRoot": "packages/react-file-type-icons/src", - "tags": ["v8"], - "implicitDependencies": [], - "targets": { - "test": { - "dependsOn": ["^build"] - } - } -} diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.ts b/packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.ts deleted file mode 100644 index ce9ddd23ac081c..00000000000000 --- a/packages/react-file-type-icons/src/getFileTypeIconAsHTMLString.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { DEFAULT_BASE_URL } from './initializeFileTypeIcons'; -import { - getFileTypeIconNameFromExtensionOrType, - getFileTypeIconSuffix, - DEFAULT_ICON_SIZE, -} from './getFileTypeIconProps'; -import type { IFileTypeIconOptions } from './getFileTypeIconProps'; - -/** - * Given the `fileTypeIconOptions`, this function returns the DOM element for the `FileTypeIcon` - * as an HTML string. Similar to `getFileTypeIconProps`, this also accepts the same type of object - * but rather than returning the `iconName`, this returns the entire DOM element as a string. - * @param options - * @param baseUrl - optionally provide a custom CDN base url to fetch icons from - */ -export function getFileTypeIconAsHTMLString( - options: IFileTypeIconOptions, - baseUrl: string = DEFAULT_BASE_URL, -): string | undefined { - const { extension, size = DEFAULT_ICON_SIZE, type, imageFileType } = options; - const baseIconName = getFileTypeIconNameFromExtensionOrType(extension, type); // eg: docx - const baseSuffix = getFileTypeIconSuffix(size, imageFileType); // eg: 96_3x_svg or 96_png - const suffixArray = baseSuffix.split('_'); // eg: ['96', '3x', 'svg'] - - let src: string | undefined; - if (suffixArray.length === 3) { - /** suffix is of type 96_3x_svg - it has a pixel ratio > 1*/ - src = `${baseUrl}${size}_${suffixArray[1]}/${baseIconName}.${suffixArray[2]}`; - return ``; - } else if (suffixArray.length === 2) { - /** suffix is of type 96_svg - it has a pixel ratio of 1*/ - src = `${baseUrl}${size}/${baseIconName}.${suffixArray[1]}`; - return ``; - } -} diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts b/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts deleted file mode 100644 index 73b7ae28ca07a9..00000000000000 --- a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DEFAULT_BASE_URL } from './initializeFileTypeIcons'; -import { - getFileTypeIconNameFromExtensionOrType, - getFileTypeIconSuffix, - DEFAULT_ICON_SIZE, -} from './getFileTypeIconProps'; -import type { IFileTypeIconOptions } from './getFileTypeIconProps'; - -/** - * Given the `fileTypeIconOptions`, this function returns the CDN-based URL for `FileTypeIcon`. - * Similar to `getFileTypeIconProps`, this also accepts the same type of object - * but rather than returning the `iconName`, this returns the raw URL. - * @param options - * @param baseUrl - optionally provide a custom CDN base url to fetch icons from - */ -export function getFileTypeIconAsUrl( - options: IFileTypeIconOptions, - baseUrl: string = DEFAULT_BASE_URL, -): string | undefined { - const { extension, size = DEFAULT_ICON_SIZE, type, imageFileType } = options; - const baseIconName = getFileTypeIconNameFromExtensionOrType(extension, type); // eg: docx - const baseSuffix = getFileTypeIconSuffix(size, imageFileType); // eg: 96_3x_svg or 96_png - const suffixArray = baseSuffix.split('_'); // eg: ['96', '3x', 'svg'] - - if (suffixArray.length === 3) { - /** suffix is of type 96_3x_svg - it has a pixel ratio > 1*/ - return `${baseUrl}${size}_${suffixArray[1]}/${baseIconName}.${suffixArray[2]}`; - } else if (suffixArray.length === 2) { - /** suffix is of type 96_svg - it has a pixel ratio of 1*/ - return `${baseUrl}${size}/${baseIconName}.${suffixArray[1]}`; - } -} diff --git a/packages/react-file-type-icons/src/getFileTypeIconProps.test.ts b/packages/react-file-type-icons/src/getFileTypeIconProps.test.ts deleted file mode 100644 index c7dccd4a441428..00000000000000 --- a/packages/react-file-type-icons/src/getFileTypeIconProps.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FileIconType } from './FileIconType'; -import { FileTypeIconMap } from './FileTypeIconMap'; -import { getFileTypeIconNameFromExtensionOrType } from './getFileTypeIconProps'; - -describe('return valid icon name', () => { - it('returns an icon name in file type icon map', () => { - for (const key of Object.keys(FileIconType)) { - // Iterate through a TypeScript enum - const value = FileIconType[key as unknown as FileIconType]; - if (typeof value === 'number') { - expect(FileTypeIconMap).toHaveProperty(getFileTypeIconNameFromExtensionOrType(undefined, value)); - } - } - }); -}); diff --git a/packages/react-file-type-icons/src/initializeFileTypeIcons.tsx b/packages/react-file-type-icons/src/initializeFileTypeIcons.tsx deleted file mode 100644 index cfee9890512ae1..00000000000000 --- a/packages/react-file-type-icons/src/initializeFileTypeIcons.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react'; -import { registerIcons, FLUENT_CDN_BASE_URL } from '@fluentui/style-utilities'; -import { FileTypeIconMap } from './FileTypeIconMap'; -import type { IIconOptions } from '@fluentui/style-utilities'; -import type { JSXElement } from '@fluentui/utilities'; - -const PNG_SUFFIX = '_png'; -const SVG_SUFFIX = '_svg'; - -export const DEFAULT_BASE_URL = `${FLUENT_CDN_BASE_URL}/assets/item-types/`; -export const ICON_SIZES: number[] = [16, 20, 24, 32, 40, 48, 64, 96]; - -export function initializeFileTypeIcons(baseUrl: string = DEFAULT_BASE_URL, options?: Partial): void { - ICON_SIZES.forEach((size: number) => { - _initializeIcons(baseUrl, size, options); - }); -} - -function _initializeIcons(baseUrl: string, size: number, options?: Partial): void { - const iconTypes: string[] = Object.keys(FileTypeIconMap); - const fileTypeIcons: { [key: string]: JSXElement } = {}; - - iconTypes.forEach((type: string) => { - const baseUrlSizeType = baseUrl + size + '/' + type; - fileTypeIcons[type + size + PNG_SUFFIX] = ; - fileTypeIcons[type + size + SVG_SUFFIX] = ; - - // For high resolution screens, register additional versions - // Apply height=100% and width=100% to force image to fit into containing element - - // SVGs scale well, so you can generally use the default image. - // 1.5x is a special case where both SVGs and PNGs need a different image. - - fileTypeIcons[type + size + '_1.5x' + PNG_SUFFIX] = ( - - ); - fileTypeIcons[type + size + '_1.5x' + SVG_SUFFIX] = ( - - ); - - fileTypeIcons[type + size + '_2x' + PNG_SUFFIX] = ( - - ); - fileTypeIcons[type + size + '_3x' + PNG_SUFFIX] = ( - - ); - fileTypeIcons[type + size + '_4x' + PNG_SUFFIX] = ( - - ); - }); - - registerIcons( - { - fontFace: {}, - style: { - width: size, - height: size, - overflow: 'hidden', - }, - icons: fileTypeIcons, - mergeImageProps: true, - }, - options, - ); -} diff --git a/packages/react-file-type-icons/src/version.ts b/packages/react-file-type-icons/src/version.ts deleted file mode 100644 index 0e76ed76fff720..00000000000000 --- a/packages/react-file-type-icons/src/version.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Do not modify this file; it is generated as part of publish. -// The checked in version is a placeholder only and will not be updated. -import { setVersion } from '@fluentui/set-version'; -setVersion('@fluentui/react-file-type-icons', '0.0.0'); diff --git a/packages/react-file-type-icons/tsconfig.json b/packages/react-file-type-icons/tsconfig.json deleted file mode 100644 index 178315f897be51..00000000000000 --- a/packages/react-file-type-icons/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "lib": ["es5", "dom", "ES2015.Iterable", "ES2015.Symbol.WellKnown"], - "jsx": "react", - "declaration": true, - "sourceMap": true, - "experimentalDecorators": true, - "importHelpers": true, - "strict": true, - "moduleResolution": "node", - "preserveConstEnums": true, - "typeRoots": ["../../node_modules/@types", "../../typings"], - "types": ["jest", "custom-global"], - "isolatedModules": true - }, - "include": ["src"] -} diff --git a/scripts/beachball/src/config.test.ts b/scripts/beachball/src/config.test.ts index 8372976931bd86..24950ecc2688b2 100644 --- a/scripts/beachball/src/config.test.ts +++ b/scripts/beachball/src/config.test.ts @@ -65,7 +65,6 @@ describe(`beachball configs`, () => { 'packages/react-docsite-components', 'packages/react-examples', 'packages/react-experiments', - 'packages/react-file-type-icons', 'packages/react-focus', 'packages/react-hooks', 'packages/scheme-utilities', diff --git a/tsconfig.base.all.json b/tsconfig.base.all.json index 8d5d5cf60d08e6..2402453e7a4424 100644 --- a/tsconfig.base.all.json +++ b/tsconfig.base.all.json @@ -26,7 +26,7 @@ "@fluentui/react-date-time": ["packages/react-date-time/src/index.ts"], "@fluentui/react-experiments": ["packages/react-experiments/src/index.ts"], "@fluentui/react-experiments/lib/*": ["packages/react-experiments/src/*"], - "@fluentui/react-file-type-icons": ["packages/react-file-type-icons/src/index.ts"], + "@fluentui/react-file-type-icons": ["packages/react-components/react-file-type-icons/library/src/index.ts"], "@fluentui/react-focus": ["packages/react-focus/src/index.ts"], "@fluentui/react-hooks": ["packages/react-hooks/src/index.ts"], "@fluentui/scheme-utilities": ["packages/scheme-utilities/src/index.ts"], @@ -121,6 +121,9 @@ "@fluentui/react-drawer-stories": ["packages/react-components/react-drawer/stories/src/index.ts"], "@fluentui/react-field": ["packages/react-components/react-field/library/src/index.ts"], "@fluentui/react-field-stories": ["packages/react-components/react-field/stories/src/index.ts"], + "@fluentui/react-file-type-icons-stories": [ + "packages/react-components/react-file-type-icons/stories/src/index.ts" + ], "@fluentui/react-focus-management": ["packages/react-focus-management/src/index.ts"], "@fluentui/react-headless-components-preview/*": [ "packages/react-components/react-headless-components-preview/library/src/*.ts" diff --git a/tsconfig.base.json b/tsconfig.base.json index 58d5e4845e447d..2b5ce33b375e70 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -76,6 +76,10 @@ "@fluentui/react-drawer-stories": ["packages/react-components/react-drawer/stories/src/index.ts"], "@fluentui/react-field": ["packages/react-components/react-field/library/src/index.ts"], "@fluentui/react-field-stories": ["packages/react-components/react-field/stories/src/index.ts"], + "@fluentui/react-file-type-icons": ["packages/react-components/react-file-type-icons/library/src/index.ts"], + "@fluentui/react-file-type-icons-stories": [ + "packages/react-components/react-file-type-icons/stories/src/index.ts" + ], "@fluentui/react-focus-management": ["packages/react-focus-management/src/index.ts"], "@fluentui/react-headless-components-preview-stories": [ "packages/react-components/react-headless-components-preview/stories/src/index.ts" diff --git a/tsconfig.base.v8.json b/tsconfig.base.v8.json index 16cf17379d5982..b0e166a5e3cf37 100644 --- a/tsconfig.base.v8.json +++ b/tsconfig.base.v8.json @@ -27,7 +27,7 @@ "@fluentui/react-date-time": ["packages/react-date-time/src/index.ts"], "@fluentui/react-experiments": ["packages/react-experiments/src/index.ts"], "@fluentui/react-experiments/lib/*": ["packages/react-experiments/src/*"], - "@fluentui/react-file-type-icons": ["packages/react-file-type-icons/src/index.ts"], + "@fluentui/react-file-type-icons": ["packages/react-components/react-file-type-icons/library/src/index.ts"], "@fluentui/react-focus": ["packages/react-focus/src/index.ts"], "@fluentui/react-hooks": ["packages/react-hooks/src/index.ts"], "@fluentui/scheme-utilities": ["packages/scheme-utilities/src/index.ts"],