From 6cb901f1cf877b724e9a00015d8a67acaa263a71 Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Mon, 18 Mar 2024 07:26:37 -0700 Subject: [PATCH 1/4] docs: add health footer link (#3141) --- .storybook/manager-head.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html index 66cddd140a..3e6a277d8f 100644 --- a/.storybook/manager-head.html +++ b/.storybook/manager-head.html @@ -4,6 +4,8 @@ From 056b15ec9960160b5035ef9c8ae805ac6d3f78ec Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Thu, 21 Mar 2024 09:29:32 -0700 Subject: [PATCH 2/4] docs: fix dynamic group story accessibility concerns (#3139) Co-authored-by: Nickii Miaro --- .../peoplePicker.properties.stories.js | 189 ++++++------------ 1 file changed, 65 insertions(+), 124 deletions(-) diff --git a/stories/components/peoplePicker/peoplePicker.properties.stories.js b/stories/components/peoplePicker/peoplePicker.properties.stories.js index 0697c6575a..4fd1b9724b 100644 --- a/stories/components/peoplePicker/peoplePicker.properties.stories.js +++ b/stories/components/peoplePicker/peoplePicker.properties.stories.js @@ -80,134 +80,75 @@ export const disableSuggestions = () => html` `; export const dynamicGroupId = () => html` - -
-

Pick a group:

-
- -
    -
    -

    People chosen:

    -
    -
    - - + +groupChooser.addEventListener('change', function(e) { + const selection = e.target.value; + if (selection !== -1) { + setGroupValue(selection); + } +}); + `; export const pickPeopleAndGroups = () => html` From 1e695aabb704563f0f511a5f014ed881dc3132c2 Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Thu, 21 Mar 2024 19:55:52 +0300 Subject: [PATCH 3/4] feat: add a useIsSignedIn custom hook for react (#3093) Add signed in state core functionality Add a hook to check the signed in state Add documentation of custom hooks on read me Add tests for isSignedIn Update the react-contoso sample to use the mgt-react hook Add sample stories for HTML and React usages --- packages/mgt-element/src/index.ts | 1 + .../mgt-element/src/utils/isSignedIn.tests.ts | 22 +++++++++++++ packages/mgt-element/src/utils/isSignedIn.ts | 19 ++++++++++++ packages/mgt-react/readme.md | 18 +++++++++++ packages/mgt-react/src/hooks/index.ts | 8 +++++ packages/mgt-react/src/hooks/useIsSignedIn.ts | 31 +++++++++++++++++++ packages/mgt-react/src/index.ts | 1 + samples/react-contoso/src/Layout.tsx | 2 +- .../react-contoso/src/components/Header.tsx | 12 ++----- .../react-contoso/src/hooks/useIsSignedIn.ts | 23 -------------- ...ral.stories.js => general.html.stories.js} | 20 ++++++++++-- stories/samples/general.react.stories.js | 28 +++++++++++++++++ 12 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 packages/mgt-element/src/utils/isSignedIn.tests.ts create mode 100644 packages/mgt-element/src/utils/isSignedIn.ts create mode 100644 packages/mgt-react/src/hooks/index.ts create mode 100644 packages/mgt-react/src/hooks/useIsSignedIn.ts delete mode 100644 samples/react-contoso/src/hooks/useIsSignedIn.ts rename stories/samples/{general.stories.js => general.html.stories.js} (92%) create mode 100644 stories/samples/general.react.stories.js diff --git a/packages/mgt-element/src/index.ts b/packages/mgt-element/src/index.ts index f1369fc92a..1899faeaec 100644 --- a/packages/mgt-element/src/index.ts +++ b/packages/mgt-element/src/index.ts @@ -39,6 +39,7 @@ export * from './utils/LocalizationHelper'; export * from './utils/mgtHtml'; export * from './utils/CustomElement'; export * from './utils/registerComponent'; +export * from './utils/isSignedIn'; export { PACKAGE_VERSION } from './utils/version'; diff --git a/packages/mgt-element/src/utils/isSignedIn.tests.ts b/packages/mgt-element/src/utils/isSignedIn.tests.ts new file mode 100644 index 0000000000..56e87cf57b --- /dev/null +++ b/packages/mgt-element/src/utils/isSignedIn.tests.ts @@ -0,0 +1,22 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { expect } from '@open-wc/testing'; +import { MockProvider } from '../mock/MockProvider'; +import { ProviderState } from '../providers/IProvider'; +import { Providers } from '../providers/Providers'; +import { isSignedIn } from './isSignedIn'; + +describe('signedInState', () => { + it('should change', () => { + /* eslint-disable @typescript-eslint/no-unused-expressions */ + Providers.globalProvider = new MockProvider(false); + expect(isSignedIn()).to.be.false; + Providers.globalProvider.setState(ProviderState.SignedIn); + expect(isSignedIn()).to.be.true; + }); +}); diff --git a/packages/mgt-element/src/utils/isSignedIn.ts b/packages/mgt-element/src/utils/isSignedIn.ts new file mode 100644 index 0000000000..d60f0a8db7 --- /dev/null +++ b/packages/mgt-element/src/utils/isSignedIn.ts @@ -0,0 +1,19 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { ProviderState } from '../providers/IProvider'; +import { Providers } from '../providers/Providers'; + +/** + * Checks the provider state if it's in signed in. + * + * @returns true if signed in, otherwise false. + */ +export const isSignedIn = (): boolean => { + const provider = Providers.globalProvider; + return provider && provider.state === ProviderState.SignedIn; +}; diff --git a/packages/mgt-react/readme.md b/packages/mgt-react/readme.md index 136a193ae0..9b16445f17 100644 --- a/packages/mgt-react/readme.md +++ b/packages/mgt-react/readme.md @@ -93,6 +93,24 @@ const App = (props) => { The `template` prop allows you to specify which template to overwrite. In this case, the `MyEvent` component will be repeated for every event, and the `event` object will be passed as part of the `dataContext` prop. +## Custom hooks + +`mgt-react` exposes some custom hooks that you can use in your app: + +### `useIsSignedIn` + +You can use this hook to check the signed in state: + +```tsx +import { Agenda, useIsSignedIn } from '@microsoft/mgt-react'; + +const App = (props) => { + const [isSignedIn] = useIsSignedIn(); + + return {isSignedIn && } +} +``` + ## Why If you've used web components in React, you know that proper interop between web components and React components requires a bit of extra work. diff --git a/packages/mgt-react/src/hooks/index.ts b/packages/mgt-react/src/hooks/index.ts new file mode 100644 index 0000000000..e9ac8bc12c --- /dev/null +++ b/packages/mgt-react/src/hooks/index.ts @@ -0,0 +1,8 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +export * from './useIsSignedIn'; diff --git a/packages/mgt-react/src/hooks/useIsSignedIn.ts b/packages/mgt-react/src/hooks/useIsSignedIn.ts new file mode 100644 index 0000000000..1bba8ad0f8 --- /dev/null +++ b/packages/mgt-react/src/hooks/useIsSignedIn.ts @@ -0,0 +1,31 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { isSignedIn, Providers } from '@microsoft/mgt-element'; +import { useState, useEffect } from 'react'; + +/** + * Hook to check if a user is signed in. + * + * @returns true if a user is signed on, otherwise false. + */ +export const useIsSignedIn = (): [boolean] => { + const [signedIn, setIsSignedIn] = useState(false); + useEffect(() => { + const updateState = () => { + setIsSignedIn(isSignedIn()); + }; + + Providers.onProviderUpdated(updateState); + updateState(); + + return () => { + Providers.removeProviderUpdatedListener(updateState); + }; + }, [setIsSignedIn]); + return [signedIn]; +}; diff --git a/packages/mgt-react/src/index.ts b/packages/mgt-react/src/index.ts index cccdb613a1..48c9dcf290 100644 --- a/packages/mgt-react/src/index.ts +++ b/packages/mgt-react/src/index.ts @@ -8,5 +8,6 @@ export * from './Mgt'; export * from './MgtTemplateProps'; export * from './generated/react'; +export * from './hooks'; export * from '@microsoft/mgt-components/dist/es6/exports'; export * from '@microsoft/mgt-element'; diff --git a/samples/react-contoso/src/Layout.tsx b/samples/react-contoso/src/Layout.tsx index 3c667e0cd8..98d7f77f0b 100644 --- a/samples/react-contoso/src/Layout.tsx +++ b/samples/react-contoso/src/Layout.tsx @@ -2,7 +2,7 @@ import React, { Suspense, lazy } from 'react'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { Header } from './components/Header'; import { SideNavigation } from './components/SideNavigation'; -import { useIsSignedIn } from './hooks/useIsSignedIn'; +import { useIsSignedIn } from '@microsoft/mgt-react'; import { NavigationItem } from './models/NavigationItem'; import { getNavigation } from './services/Navigation'; import { FluentProvider, makeStyles, mergeClasses, shorthands } from '@fluentui/react-components'; diff --git a/samples/react-contoso/src/components/Header.tsx b/samples/react-contoso/src/components/Header.tsx index c2594992dc..6bc2916468 100644 --- a/samples/react-contoso/src/components/Header.tsx +++ b/samples/react-contoso/src/components/Header.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; -import { Login, SearchBox } from '@microsoft/mgt-react'; +import { Login, SearchBox, useIsSignedIn } from '@microsoft/mgt-react'; import { PACKAGE_VERSION } from '@microsoft/mgt-element'; -import { InfoButton } from '@fluentui/react-components/unstable'; import { SimpleLogin } from './SimpleLogin'; -import { useIsSignedIn } from '../hooks/useIsSignedIn'; import { useNavigate } from 'react-router-dom'; import { ThemeSwitcher } from './ThemeSwitcher'; import { useAppContext } from '../AppContext'; -import { Label, makeStyles, mergeClasses, shorthands, tokens } from '@fluentui/react-components'; +import { Label, makeStyles, mergeClasses, shorthands, tokens, InfoLabel } from '@fluentui/react-components'; import { GridDotsRegular } from '@fluentui/react-icons'; import { useLocation } from 'react-router-dom'; @@ -149,11 +147,7 @@ const HeaderComponent: React.FunctionComponent = () => {
    - Using the Graph Toolkit v{PACKAGE_VERSION}} - /> + Using the Graph Toolkit v{PACKAGE_VERSION}} />
    diff --git a/samples/react-contoso/src/hooks/useIsSignedIn.ts b/samples/react-contoso/src/hooks/useIsSignedIn.ts deleted file mode 100644 index 8cb7709bcc..0000000000 --- a/samples/react-contoso/src/hooks/useIsSignedIn.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Providers, ProvidersChangedState, ProviderState } from '@microsoft/mgt-element'; - -export function useIsSignedIn(): [boolean] { - const [isSignedIn, setIsSignedIn] = useState(false); - - useEffect(() => { - const updateState = (stateEvent: ProvidersChangedState) => { - if (stateEvent === ProvidersChangedState.ProviderStateChanged) { - setIsSignedIn(Providers.globalProvider.state === ProviderState.SignedIn); - } - }; - - setIsSignedIn(Providers.globalProvider.state === ProviderState.SignedIn); - Providers.onProviderUpdated(updateState); - - return () => { - Providers.removeProviderUpdatedListener(updateState); - }; - }, []); - - return [isSignedIn]; -} diff --git a/stories/samples/general.stories.js b/stories/samples/general.html.stories.js similarity index 92% rename from stories/samples/general.stories.js rename to stories/samples/general.html.stories.js index 92acf72c05..c3a4bcce43 100644 --- a/stories/samples/general.stories.js +++ b/stories/samples/general.html.stories.js @@ -9,7 +9,7 @@ import { html } from 'lit'; import { withCodeEditor } from '../../.storybook/addons/codeEditorAddon/codeAddon'; export default { - title: 'Samples / General', + title: 'Samples / General / HTML', decorators: [withCodeEditor], parameters: { viewMode: 'story' @@ -89,7 +89,7 @@ export const Localization = () => html` `; -export const cache = () => html` +export const Cache = () => html` Clear Cache
    `; -export const theme = () => html` +export const Theme = () => html`

    This demonstrates how to set the theme globally without using a theme toggle and customize styling within specific scopes

    Please refer to the JS and CSS tabs in the editor for implentation details

    @@ -223,3 +223,17 @@ body { } `; + +export const IsSignedIn = () => html` +

    This demonstrates how to use the isSignedIn utility function in JavaScript. If you're signed in, mgt-person is rendered below.

    +
    + +`; diff --git a/stories/samples/general.react.stories.js b/stories/samples/general.react.stories.js new file mode 100644 index 0000000000..101f337d70 --- /dev/null +++ b/stories/samples/general.react.stories.js @@ -0,0 +1,28 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { html } from 'lit'; +import { withCodeEditor } from '../../.storybook/addons/codeEditorAddon/codeAddon'; + +export default { + title: 'Samples / General / React', + component: 'person-card', + decorators: [withCodeEditor] +}; + +export const Hooks = () => html` +

    This demonstrates how to use the useIsSignedIn hook in React.

    + + + import { PersonCard, useIsSignedIn } from '@microsoft/mgt-react'; + const [isSignedIn] = useIsSignedIn(); + + export default () => ( + isSignedIn ? : null + ); + +`; From 81826d967daad4bf60cc9e7018265c52445a3aeb Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Wed, 27 Mar 2024 10:56:26 -0700 Subject: [PATCH 4/4] fix(a11y): fix forced colors for file upload button (#3114) --- .../mgt-file-upload/mgt-file-upload.scss | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/packages/mgt-components/src/components/mgt-file-list/mgt-file-upload/mgt-file-upload.scss b/packages/mgt-components/src/components/mgt-file-list/mgt-file-upload/mgt-file-upload.scss index 403a8e98cf..5763d9e025 100644 --- a/packages/mgt-components/src/components/mgt-file-list/mgt-file-upload/mgt-file-upload.scss +++ b/packages/mgt-components/src/components/mgt-file-list/mgt-file-upload/mgt-file-upload.scss @@ -9,9 +9,11 @@ @import '../../../styles/shared-sass-variables'; @import './mgt-file-upload.theme'; - $file-upload-button-border: var(--file-upload-button-border, none); -$file-upload-dialog-replace-button-border: var(--file-upload-dialog-replace-button-border, 1px solid var(--neutral-foreground-rest)); +$file-upload-dialog-replace-button-border: var( + --file-upload-dialog-replace-button-border, + 1px solid var(--neutral-foreground-rest) +); $file-upload-dialog-keep-both-button-border: var(--file-upload-dialog-keep-both-button-border, none); $file-upload-dialog-border: var(--file-upload-dialog-border, 1px solid var(--neutral-fill-rest)); $file-upload-dialog-width: var(--file-upload-dialog-width, auto); @@ -234,9 +236,58 @@ $file-upload-border-drag: var(--file-upload-border-drag, 1px dashed #0078d4); } } -[dir="rtl"] { - :host .file-upload-status{ +[dir='rtl'] { + :host .file-upload-status { left: 0; right: 28px; } } + +@media (forced-colors: active) { + :host { + fluent-button { + .upload-icon { + path { + fill: highlighttext; + } + } + + &.file-upload-button { + &::part(control) { + border-width: 1px; + border-style: solid; + border-color: buttontext; + background: highlight; + + &:hover { + background: highlighttext; + border-color: highlight; + } + } + + .upload-text { + color: highlighttext; + } + } + + &:hover { + .upload-icon { + path { + fill: highlight; + } + } + + &.file-upload-button { + &::part(control) { + border-color: highlight; + background: highlighttext; + } + + .upload-text { + color: highlight; + } + } + } + } + } +}