Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/long-suns-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'spectacle': minor
---

Utilize `Kbar` to allow users to quickly search and use the current commands Spectacle supports within presentations. Fixes #1115
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
strict-peer-dependencies=false
prefer-workspace-packages=true
1 change: 1 addition & 0 deletions packages/spectacle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"broadcastchannel-polyfill": "^1.0.0",
"dedent": "^0.7.0",
"history": "^4.9.0",
"kbar": "0.1.0-beta.36",
"mdast-builder": "^1.1.1",
"mdast-zone": "^4.0.0",
"merge-anything": "^3.0.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
KEYBOARD_SHORTCUTS_IDS,
SpectacleMode,
SPECTACLE_MODES
} from '../../utils/constants';
import useModes from '../../hooks/use-modes';

/**
* Kbar default actions, those that do not depend on dynamic logic, can be added here.
* To register actions dynamically use 'useRegisterActions' and make sure the action
* is registed within the KBarProvider.
* @see https://kbar.vercel.app/docs/concepts/actions
* Kbar action shortcuts dont seem to support all keybindings. If you need to utilize
* keybindings that are not supported you'll have to implement the keybinding seperately.
* @see useMousetrap
* To display keybindings that are not supported in the Kbar results, please use
* KEYBOARD_SHORTCUTS instead of Kbar actions 'shortcut' property.
* @see CommandBarResults getShortcutKeys
*/

const spectacleModeDisplay = {
[SPECTACLE_MODES.DEFAULT_MODE]: 'Default Mode',
[SPECTACLE_MODES.PRESENTER_MODE]: 'Presenter Mode',
[SPECTACLE_MODES.OVERVIEW_MODE]: 'Overview Mode',
[SPECTACLE_MODES.PRINT_MODE]: 'Print Mode',
[SPECTACLE_MODES.EXPORT_MODE]: 'Export Mode'
};

const getName = (currentMode: string, mode: SpectacleMode) => {
const defaultMode = SPECTACLE_MODES.DEFAULT_MODE;

return currentMode === mode
? `← Back to ${spectacleModeDisplay[defaultMode]}`
: spectacleModeDisplay[mode];
};

const useCommandBarActions = () => {
const { toggleMode, getCurrentMode } = useModes();
const currentMode = getCurrentMode();
return [
{
id: KEYBOARD_SHORTCUTS_IDS.PRESENTER_MODE,
name: getName(currentMode, SPECTACLE_MODES.PRESENTER_MODE),
keywords: 'presenter',
perform: () => toggleMode({ newMode: SPECTACLE_MODES.PRESENTER_MODE }),
section: 'Mode'
},
{
id: KEYBOARD_SHORTCUTS_IDS.OVERVIEW_MODE,
name: getName(currentMode, SPECTACLE_MODES.OVERVIEW_MODE),
keywords: 'overview',
perform: () => toggleMode({ newMode: SPECTACLE_MODES.OVERVIEW_MODE }),
section: 'Mode'
},
{
id: KEYBOARD_SHORTCUTS_IDS.PRINT_MODE,
name: getName(currentMode, SPECTACLE_MODES.PRINT_MODE),
keywords: 'export',
perform: () => toggleMode({ newMode: SPECTACLE_MODES.PRINT_MODE }),
section: 'Mode'
},
{
id: KEYBOARD_SHORTCUTS_IDS.EXPORT_MODE,
name: getName(currentMode, SPECTACLE_MODES.EXPORT_MODE),
keywords: 'export',
perform: () => toggleMode({ newMode: SPECTACLE_MODES.EXPORT_MODE }),
section: 'Mode'
}
];
};

export default useCommandBarActions;
20 changes: 20 additions & 0 deletions packages/spectacle/src/components/command-bar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ReactNode } from 'react';
import { KBarProvider } from 'kbar';
import useCommandBarActions from './command-bar-actions';
import CommandBarSearch from './search';

const CommandBar = ({ children }: CommandBarProps): JSX.Element => {
const actions = useCommandBarActions();
return (
<KBarProvider actions={actions}>
<CommandBarSearch />
{children}
</KBarProvider>
);
};

export type CommandBarProps = {
children: ReactNode;
};

export default CommandBar;
90 changes: 90 additions & 0 deletions packages/spectacle/src/components/command-bar/results/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import styled from 'styled-components';
import { ActionImpl, KBarResults, useMatches } from 'kbar';
import { prettifyShortcut } from '../../../utils/platform-keys';
import {
KeyboardShortcutTypes,
KEYBOARD_SHORTCUTS,
SYSTEM_FONT
} from '../../../utils/constants';
import { Text } from '../../typography';

type RenderParams = {
item: ActionImpl | string;
active: boolean;
};

function getShortcutKeys({ id, shortcut = [] }: ActionImpl): string[] {
if (id in KEYBOARD_SHORTCUTS && !shortcut?.length) {
const _id = id as KeyboardShortcutTypes;
return prettifyShortcut(KEYBOARD_SHORTCUTS[_id]);
}
return prettifyShortcut(shortcut);
}

const ResultCommand = styled.div<Partial<RenderParams>>`
display: flex;
justify-content: space-between;
align-items: center;
background-color: ${(p) => (p.active ? 'lightsteelblue' : 'transparent')};
padding: 0.5rem 1rem;
cursor: pointer;
height: 30px;
`;

const ResultSectionHeader = styled(Text)`
background-color: white;
color: gray;
margin: 0 2rem;
padding: 0.5rem 0;
font-size: small;
font-weight: bold;
font-family: ${SYSTEM_FONT};
`;

const ResultShortcut = styled.span`
display: flex;
gap: 5px;
`;

const ResultShortcutKey = styled.kbd`
display: flex;
justify-content: center;
align-items: center;
background-color: #eee;
border-radius: 5px;
border: 1px solid #b4b4b4;
padding: 5px 10px;
min-width: 20px;
height: 25px;
white-space: nowrap;
font-family: ${SYSTEM_FONT};
`;

function onRender({ item, active }: RenderParams) {
if (typeof item === 'string') {
return <ResultSectionHeader>{item}</ResultSectionHeader>;
} else {
return (
<ResultCommand active={active}>
<Text fontFamily={SYSTEM_FONT}>{item.name}</Text>
<ResultShortcut>
{getShortcutKeys(item).map(
(key) =>
key && (
<ResultShortcutKey key={`${item.id}-${key}`}>
{key}
</ResultShortcutKey>
)
)}
</ResultShortcut>
</ResultCommand>
);
}
}

const CommandBarResults = () => {
const { results } = useMatches();
return <KBarResults items={results} onRender={onRender} />;
};

export default CommandBarResults;
36 changes: 36 additions & 0 deletions packages/spectacle/src/components/command-bar/search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import styled from 'styled-components';
import { KBarPortal, KBarPositioner, KBarAnimator, KBarSearch } from 'kbar';
import CommandBarResults from '../results';

const KBarSearchStyled = styled(KBarSearch)`
padding: 12px 16px;
font-size: 16px;
width: 100%;
box-sizing: border-box;
outline: none;
border: none;
`;

const KBarAnimatorStyled = styled(KBarAnimator)`
max-width: 600px;
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: rgb(0 0 0 / 50%) 0px 16px 70px;
`;

const CommandBarSearch = () => {
return (
<KBarPortal>
<KBarPositioner>
<KBarAnimatorStyled>
<KBarSearchStyled />
<CommandBarResults />
</KBarAnimatorStyled>
</KBarPositioner>
</KBarPortal>
);
};

export default CommandBarSearch;
35 changes: 35 additions & 0 deletions packages/spectacle/src/components/deck/deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { defaultTransition, SlideTransition } from '../transitions';
import { SwipeEventData } from 'react-swipeable';
import { MarkdownComponentMap } from '../../utils/mdx-component-mapper';
import TemplateWrapper from '../template-wrapper';
import { useRegisterActions } from 'kbar';
import { KEYBOARD_SHORTCUTS_IDS } from '../../utils/constants';

export type DeckContextType = {
deckId: string | number;
Expand Down Expand Up @@ -66,6 +68,7 @@ export type DeckContextType = {
};
skipTo(options: { slideIndex: number; stepIndex: number }): void;
stepForward(): void;
stepBackward(): void;
advanceSlide(): void;
regressSlide(): void;
commitTransition(newView?: { stepIndex: number }): void;
Expand Down Expand Up @@ -217,6 +220,37 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
]
);

useRegisterActions(
!disableInteractivity
? [
{
id: KEYBOARD_SHORTCUTS_IDS.NEXT_SLIDE,
name: 'Next Slide',
keywords: 'next',
perform: () => stepForward(),
section: 'Slide'
},
{
id: KEYBOARD_SHORTCUTS_IDS.PREVIOUS_SLIDE,
name: 'Previous Slide',
keywords: 'previous',
perform: () => stepBackward(),
section: 'Slide'
},
{
id: 'Restart Presentation',
name: 'Restart Presentation',
keywords: 'restart',
perform: () =>
skipTo({
slideIndex: 0,
stepIndex: 0
}),
section: 'Slide'
}
]
: []
);
useMousetrap(
disableInteractivity
? {}
Expand Down Expand Up @@ -441,6 +475,7 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
},
skipTo,
stepForward,
stepBackward,
advanceSlide,
regressSlide,
commitTransition,
Expand Down
18 changes: 10 additions & 8 deletions packages/spectacle/src/components/deck/default-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useMousetrap from '../../hooks/use-mousetrap';
import {
KEYBOARD_SHORTCUTS,
SPECTACLE_MODES,
SpectacleMode
ToggleModeParams
} from '../../utils/constants';

/**
Expand Down Expand Up @@ -51,7 +51,9 @@ const DefaultDeck = (props: DefaultDeckProps): JSX.Element => {
stepIndex: 0
}),
[KEYBOARD_SHORTCUTS.SELECT_SLIDE_OVERVIEW_MODE]: (e) =>
toggleMode(e, SPECTACLE_MODES.DEFAULT_MODE)
toggleMode({
newMode: SPECTACLE_MODES.DEFAULT_MODE
})
}
: {},
[]
Expand All @@ -62,7 +64,11 @@ const DefaultDeck = (props: DefaultDeckProps): JSX.Element => {
>(
(e, slideIndex) => {
if (overviewMode) {
toggleMode(e, SPECTACLE_MODES.DEFAULT_MODE, +slideIndex);
toggleMode({
e,
newMode: SPECTACLE_MODES.DEFAULT_MODE,
senderSlideIndex: +slideIndex
});
}
},
[overviewMode, toggleMode]
Expand Down Expand Up @@ -98,11 +104,7 @@ const DefaultDeck = (props: DefaultDeckProps): JSX.Element => {
export default DefaultDeck;

type DefaultDeckProps = DeckProps & {
toggleMode(
e: unknown,
newMode: SpectacleMode,
senderSlideIndex?: number
): void;
toggleMode(args: ToggleModeParams): void;
overviewMode?: boolean;
printMode?: boolean;
exportMode?: boolean;
Expand Down
Loading